i18n DE/EN + DossierSettings panel + English file renames

i18n:
- src/i18n/de.json + en.json: 200+ keys covering all main panels
- src/i18n/index.js: t(key, vars) reads window.DOSSIER_LANG
- panel_base.py: injects window.DOSSIER_LANG from dossier_settings.json
- EbenenManager, GeschossManager, AusschnitteApp, LayoutsApp: all
  context menus and main labels use t()

DossierSettings panel:
- DossierSettingsApp.jsx: language toggle (DE/EN pill) + launcher status
- toolbar.py: OPEN_SETTINGS opens new Rhino-hosted satellite window,
  SAVE_LANG writes lang to dossier_settings.json + reloads all panels

File renames (JSX → English):
- ZeichnungsebenenApp → DrawingLevelsApp
- GeschossManager/Dialog/Settings → Floor*
- AusschnitteApp/Settings → Viewports*
- EbenenManager/Settings → Layer*
- GestaltungApp → StylesApp, OberleisteApp → ToolbarApp
- WerkzeugeApp → ToolsApp, DimensionenApp → DimensionsApp
- MassstabApp → ScaleApp, KameraApp → CameraApp
- MasseSettingsApp → UnitsSettingsApp
- ConfirmDeleteEbene → ConfirmDeleteLayer
- AusschnittLayerDialog → ViewportLayerDialog

Python module renames:
- rhinopanel.py → layers_panel.py
- oberleiste.py → toolbar.py
- gestaltung.py → styles.py
- werkzeuge.py → tools.py
- dimensionen.py → dimensions.py
- startup.py _MODULE_TO_PY updated, all cross-imports fixed
This commit is contained in:
2026-06-06 11:09:33 +02:00
parent 92b4baa285
commit 375487c10c
45 changed files with 998 additions and 148 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ else:
"cutAtLine": True, "namePrefix": "S", "cutAtLine": True, "namePrefix": "S",
} }
try: try:
import rhinopanel import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) ps = rhinopanel.load_project_settings(doc)
d = (ps or {}).get("defaults", {}) d = (ps or {}).get("defaults", {})
defaults["depthBack"] = float(d.get("schnittDepthBack", 8.0)) defaults["depthBack"] = float(d.get("schnittDepthBack", 8.0))
+1 -1
View File
@@ -392,7 +392,7 @@ def _run():
# gestaltung fuer Fill-Re-Apply # gestaltung fuer Fill-Re-Apply
_g = None _g = None
try: try:
import gestaltung as _gmod; _g = _gmod import styles as _gmod; _g = _gmod
except Exception as iex: except Exception as iex:
print("[SMART-JOIN] gestaltung import:", iex) print("[SMART-JOIN] gestaltung import:", iex)
+1 -1
View File
@@ -233,7 +233,7 @@ def _run():
_replicate_hatch(doc, nobj, hatch_props) _replicate_hatch(doc, nobj, hatch_props)
else: else:
try: try:
import gestaltung as _gmod import styles as _gmod
for nid in new_ids: for nid in new_ids:
nobj = doc.Objects.FindId(nid) nobj = doc.Objects.FindId(nid)
if nobj is not None: if nobj is not None:
+4 -4
View File
@@ -556,7 +556,7 @@ class AusschnittBridge(panel_base.BaseBridge):
kombi = (snap.get("layerCombination") or "").strip() kombi = (snap.get("layerCombination") or "").strip()
if kombi: if kombi:
try: try:
import rhinopanel import layers_panel as rhinopanel
rhinopanel.apply_layer_preset_by_name(doc, kombi) rhinopanel.apply_layer_preset_by_name(doc, kombi)
except Exception as ex: except Exception as ex:
print("[AUSSCHNITTE] kombi-apply '{}':".format(kombi), ex) print("[AUSSCHNITTE] kombi-apply '{}':".format(kombi), ex)
@@ -565,7 +565,7 @@ class AusschnittBridge(panel_base.BaseBridge):
_apply_layers_global(doc, snap.get("layers", [])) _apply_layers_global(doc, snap.get("layers", []))
# Eigene Sichtbarkeit → active_comb_name clearen # Eigene Sichtbarkeit → active_comb_name clearen
try: try:
import rhinopanel import layers_panel as rhinopanel
rhinopanel.set_active_comb_name(doc, None) rhinopanel.set_active_comb_name(doc, None)
rhinopanel._notify_oberleiste_combs() rhinopanel._notify_oberleiste_combs()
except Exception: pass except Exception: pass
@@ -785,7 +785,7 @@ class AusschnittBridge(panel_base.BaseBridge):
# Listen fuer Dropdowns # Listen fuer Dropdowns
display_modes = [] display_modes = []
try: try:
import oberleiste import toolbar as oberleiste
display_modes = oberleiste._list_display_modes() display_modes = oberleiste._list_display_modes()
except Exception as ex: except Exception as ex:
print("[AUSSCHNITTE] display_modes:", ex) print("[AUSSCHNITTE] display_modes:", ex)
@@ -797,7 +797,7 @@ class AusschnittBridge(panel_base.BaseBridge):
print("[AUSSCHNITTE] overrides_presets:", ex) print("[AUSSCHNITTE] overrides_presets:", ex)
layer_kombis = [] layer_kombis = []
try: try:
import rhinopanel import layers_panel as rhinopanel
layer_kombis = rhinopanel.list_layer_preset_names(d) layer_kombis = rhinopanel.list_layer_preset_names(d)
except Exception as ex: except Exception as ex:
print("[AUSSCHNITTE] layer_kombis:", ex) print("[AUSSCHNITTE] layer_kombis:", ex)
+12 -12
View File
@@ -771,7 +771,7 @@ def _find_ebene_sublayer_name(doc, keywords, default_code, default_name,
print("[ELEMENTE] build_layers nach auto-add:", ex) print("[ELEMENTE] build_layers nach auto-add:", ex)
# Ebenen-Manager UI mit-informieren via broadcast_state # Ebenen-Manager UI mit-informieren via broadcast_state
try: try:
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception as ex: except Exception as ex:
print("[ELEMENTE] broadcast_state:", ex) print("[ELEMENTE] broadcast_state:", ex)
@@ -866,7 +866,7 @@ def _ensure_referenz_child_in_doc(doc, parent_code, parent_keywords,
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen") z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
zlist = json.loads(z_raw) if z_raw else [] zlist = json.loads(z_raw) if z_raw else []
if zlist: layer_builder.build_layers(doc, zlist, ebenen) if zlist: layer_builder.build_layers(doc, zlist, ebenen)
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception as ex: except Exception as ex:
print("[ELEMENTE] _ensure_referenz_child:", ex) print("[ELEMENTE] _ensure_referenz_child:", ex)
@@ -953,7 +953,7 @@ def _ensure_oeff_ebenen_in_doc(doc):
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen") z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
zlist = json.loads(z_raw) if z_raw else [] zlist = json.loads(z_raw) if z_raw else []
if zlist: layer_builder.build_layers(doc, zlist, ebenen) if zlist: layer_builder.build_layers(doc, zlist, ebenen)
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception as ex: except Exception as ex:
print("[ELEMENTE] _ensure_oeff_ebenen_in_doc build:", ex) print("[ELEMENTE] _ensure_oeff_ebenen_in_doc build:", ex)
@@ -3641,7 +3641,7 @@ def _get_all_materials(doc):
merged[n].setdefault("uvScaleM", 1.0) merged[n].setdefault("uvScaleM", 1.0)
merged[n].setdefault("textures", {}) merged[n].setdefault("textures", {})
try: try:
import rhinopanel import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) if doc else None ps = rhinopanel.load_project_settings(doc) if doc else None
if isinstance(ps, dict): if isinstance(ps, dict):
for m in ps.get("materials", []): for m in ps.get("materials", []):
@@ -3657,7 +3657,7 @@ def _get_all_wand_styles(doc):
"""Liefert alle Wand-Stile aus den Project-Settings. Wenn keine """Liefert alle Wand-Stile aus den Project-Settings. Wenn keine
konfiguriert (auch keine Defaults greifbar), leere Liste.""" konfiguriert (auch keine Defaults greifbar), leere Liste."""
try: try:
import rhinopanel import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) if doc else None ps = rhinopanel.load_project_settings(doc) if doc else None
if isinstance(ps, dict): if isinstance(ps, dict):
return list(ps.get("wand_styles", []) or []) return list(ps.get("wand_styles", []) or [])
@@ -13337,7 +13337,7 @@ class ElementeBridge(panel_base.BaseBridge):
# Projekt-Adresse als Vorschlag fuer die Adress-Suche # Projekt-Adresse als Vorschlag fuer die Adress-Suche
project_address = "" project_address = ""
try: try:
import rhinopanel import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) if doc else None ps = rhinopanel.load_project_settings(doc) if doc else None
if isinstance(ps, dict): if isinstance(ps, dict):
project_address = (ps.get("project", {}) or {}).get("address") or "" project_address = (ps.get("project", {}) or {}).get("address") or ""
@@ -14223,7 +14223,7 @@ class ElementeBridge(panel_base.BaseBridge):
zlist = json.loads(z_raw) if z_raw else [] zlist = json.loads(z_raw) if z_raw else []
if zlist: if zlist:
layer_builder.build_layers(doc, zlist, ebenen) layer_builder.build_layers(doc, zlist, ebenen)
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception as ex: except Exception as ex:
self._push_log(" swisstopo-ebenen build: {}".format(ex)) self._push_log(" swisstopo-ebenen build: {}".format(ex))
@@ -14317,7 +14317,7 @@ class ElementeBridge(panel_base.BaseBridge):
# UI informieren — broadcast_state schickt STATE_SYNC an # UI informieren — broadcast_state schickt STATE_SYNC an
# ebenen_bridge_ref + zeichnungsebenen_bridge_ref # ebenen_bridge_ref + zeichnungsebenen_bridge_ref
try: try:
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception as ex: except Exception as ex:
self._push_log("broadcast_state: {}".format(ex)) self._push_log("broadcast_state: {}".format(ex))
@@ -14660,7 +14660,7 @@ class ElementeBridge(panel_base.BaseBridge):
if zlist: if zlist:
import layer_builder import layer_builder
layer_builder.build_layers(doc, zlist, ebenen) layer_builder.build_layers(doc, zlist, ebenen)
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception as ex: except Exception as ex:
self._push_log("osm-ebenen build: {}".format(ex)) self._push_log("osm-ebenen build: {}".format(ex))
@@ -16679,7 +16679,7 @@ def _migrate_plangrafik_60_to_80_once(doc):
except Exception as ex: except Exception as ex:
print("[ELEMENTE] build_layers nach Plangrafik-Migration:", ex) print("[ELEMENTE] build_layers nach Plangrafik-Migration:", ex)
try: try:
import rhinopanel as _rp import layers_panel as _rp
_rp._broadcast_state(doc) _rp._broadcast_state(doc)
except Exception: pass except Exception: pass
@@ -16789,7 +16789,7 @@ def _migrate_layer_caps_once(doc):
except Exception as ex: except Exception as ex:
print("[ELEMENTE] build_layers nach CAPS-Migration:", ex) print("[ELEMENTE] build_layers nach CAPS-Migration:", ex)
try: try:
import rhinopanel as _rp import layers_panel as _rp
_rp._broadcast_state(doc) _rp._broadcast_state(doc)
except Exception: pass except Exception: pass
@@ -16874,7 +16874,7 @@ def _migrate_referenz_layer_once(doc):
# nicht neu angelegt hat (z.B. weil er schon existiert) wird das # nicht neu angelegt hat (z.B. weil er schon existiert) wird das
# Panel sonst nicht neu gerendert. # Panel sonst nicht neu gerendert.
try: try:
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception: pass except Exception: pass
except Exception as ex: except Exception as ex:
@@ -2204,7 +2204,7 @@ class EbenenBridge(panel_base.BaseBridge):
# geaendert hat (nicht bei reinen Name/Farb-Aenderungen, die das # geaendert hat (nicht bei reinen Name/Farb-Aenderungen, die das
# Settings-Dialog auch triggern koennte). # Settings-Dialog auch triggern koennte).
try: try:
import gestaltung import styles as gestaltung
if fill_changed: if fill_changed:
gestaltung.refresh_layer_fills(doc) gestaltung.refresh_layer_fills(doc)
else: else:
+1 -1
View File
@@ -664,7 +664,7 @@ def import_material(doc, item):
"uvScaleM", "textures"): "uvScaleM", "textures"):
if k in data: new_mat[k] = data[k] if k in data: new_mat[k] = data[k]
# Lazy-Import um Zyklen zu vermeiden # Lazy-Import um Zyklen zu vermeiden
import rhinopanel import layers_panel as rhinopanel
settings = rhinopanel.load_project_settings(doc) settings = rhinopanel.load_project_settings(doc)
mats = list(settings.get("materials", [])) mats = list(settings.get("materials", []))
for m in mats: for m in mats:
+17 -1
View File
@@ -20,6 +20,21 @@ import scriptcontext as sc
_HERE = os.path.dirname(os.path.abspath(__file__)) _HERE = os.path.dirname(os.path.abspath(__file__))
_DIST = os.path.join(_HERE, "..", "dist", "index.html") _DIST = os.path.join(_HERE, "..", "dist", "index.html")
_SETTINGS_PATH = os.path.expanduser(
"~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json")
def _read_lang():
"""Liest die UI-Sprache aus dossier_settings.json. Default: 'de'."""
try:
if os.path.isfile(_SETTINGS_PATH):
with open(_SETTINGS_PATH, "rb") as f:
d = json.loads(f.read().decode("utf-8"))
lang = d.get("lang", "de")
return lang if lang in ("de", "en") else "de"
except Exception:
pass
return "de"
# --- Timing-Instrumentierung ------------------------------------------------ # --- Timing-Instrumentierung ------------------------------------------------
@@ -317,7 +332,8 @@ def load_inline(wv, mode, params=None):
_INLINE_TEMPLATE = (sig, tmpl) _INLINE_TEMPLATE = (sig, tmpl)
# Per-Mount: nur das Mode-Script-Snippet bauen # Per-Mount: nur das Mode-Script-Snippet bauen
parts = ['window.PANEL_MODE="{}";'.format(mode)] parts = ['window.PANEL_MODE="{}";'.format(mode),
'window.DOSSIER_LANG="{}";'.format(_read_lang())]
if params is not None: if params is not None:
try: try:
parts.append('window.PANEL_PARAMS=' + json.dumps(params, ensure_ascii=False) + ';') parts.append('window.PANEL_PARAMS=' + json.dumps(params, ensure_ascii=False) + ';')
+1 -1
View File
@@ -155,7 +155,7 @@ def _update_linePts(doc, schnitt_id, new_p1, new_p2):
# Panel-Broadcast (linePts haben sich geaendert, Ebenen-Panel will # Panel-Broadcast (linePts haben sich geaendert, Ebenen-Panel will
# ggf. mit-rendern) # ggf. mit-rendern)
try: try:
import rhinopanel import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc) rhinopanel._broadcast_state(doc)
except Exception: pass except Exception: pass
try: doc.Views.Redraw() try: doc.Views.Redraw()
+1 -1
View File
@@ -609,7 +609,7 @@ def pick_schnitt_interactive(doc, defaults=None):
# Project-Defaults als 2-stufiges Fallback (defaults > project > hardcoded) # Project-Defaults als 2-stufiges Fallback (defaults > project > hardcoded)
proj_d = {} proj_d = {}
try: try:
import rhinopanel import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) or {} ps = rhinopanel.load_project_settings(doc) or {}
proj_d = ps.get("defaults", {}) or {} proj_d = ps.get("defaults", {}) or {}
except Exception: pass except Exception: pass
+6 -6
View File
@@ -44,13 +44,13 @@ _UI_FILE = os.path.join(_HERE, "DOSSIERUI.rhw")
# Muss synchron sein mit launcher/modules.json. Wenn neue Module dazukommen, # Muss synchron sein mit launcher/modules.json. Wenn neue Module dazukommen,
# beide Stellen pflegen. # beide Stellen pflegen.
_MODULE_TO_PY = { _MODULE_TO_PY = {
"ebenen": "rhinopanel", "ebenen": "layers_panel",
"oberleiste": "oberleiste", "oberleiste": "toolbar",
"ausschnitte": "ausschnitte", "ausschnitte": "ausschnitte",
"gestaltung": "gestaltung", "gestaltung": "styles",
"werkzeuge": "werkzeuge", "werkzeuge": "tools",
"overrides": "overrides_panel", "overrides": "overrides_panel",
"dimensionen": "dimensionen", "dimensionen": "dimensions",
"layouts": "layouts", "layouts": "layouts",
"elemente": "elemente", "elemente": "elemente",
} }
@@ -209,7 +209,7 @@ def _check_doc_unit(doc):
return return
except Exception: pass except Exception: pass
try: try:
import rhinopanel import layers_panel as rhinopanel
target_unit_str = rhinopanel.get_project_unit(doc) target_unit_str = rhinopanel.get_project_unit(doc)
target_unit_enum = rhinopanel.get_project_unit_enum(doc) target_unit_enum = rhinopanel.get_project_unit_enum(doc)
except Exception as ex: except Exception as ex:
+57 -6
View File
@@ -22,7 +22,7 @@ if _HERE not in sys.path:
import panel_base import panel_base
import massstab import massstab
import overrides import overrides
import rhinopanel import layers_panel as rhinopanel
PANEL_GUID_STR = "7e1f6a5b-8e2f-4f3c-d5e6-f70819203b51" PANEL_GUID_STR = "7e1f6a5b-8e2f-4f3c-d5e6-f70819203b51"
OVERRIDES_PANEL_GUID_STR = "8f2a7b6c-9f3a-4f4d-e6f7-08192a3c4d62" OVERRIDES_PANEL_GUID_STR = "8f2a7b6c-9f3a-4f4d-e6f7-08192a3c4d62"
@@ -1849,13 +1849,17 @@ class OberleisteBridge(panel_base.BaseBridge):
# --- Settings + Window-Layout ----------------------------------- # --- Settings + Window-Layout -----------------------------------
elif t == "OPEN_SETTINGS": elif t == "OPEN_SETTINGS":
# Primaerweg: Dossier-Launcher (Tauri-App) oeffnen, dort lebt das _open_dossier_settings_panel()
# echte Settings-UI. Wenn der Launcher nicht installiert ist,
# faellt es auf den Eto-Dialog zurueck.
if not _launch_dossier_app():
open_settings_dialog()
elif t == "GET_SETTINGS": elif t == "GET_SETTINGS":
self._send_settings_state() self._send_settings_state()
elif t == "SAVE_LANG":
lang = (p.get("lang") or "de")
if lang not in ("de", "en"):
lang = "de"
cfg = _settings_load()
cfg["lang"] = lang
_settings_save(cfg)
_reload_all_panel_langs()
elif t == "APPLY_LAYOUT": elif t == "APPLY_LAYOUT":
name = (p.get("name") or "").strip() name = (p.get("name") or "").strip()
if name: _apply_window_layout(name) if name: _apply_window_layout(name)
@@ -2195,6 +2199,53 @@ class OberleisteBridge(panel_base.BaseBridge):
self._send_state(force=False) self._send_state(force=False)
# --- Dossier-Settings-Panel -------------------------------------------------
def _open_dossier_settings_panel():
"""Oeffnet das Dossier-Settings-Fenster (Rhino-hosted Eto-Satellite)."""
existing = sc.sticky.get("_dossier_settings_form")
if existing is not None:
try:
existing.BringToFront()
return
except Exception:
sc.sticky["_dossier_settings_form"] = None
cfg = _settings_load()
launcher_ok = os.path.isfile(_LAUNCHER_PATH)
try:
import panel_base
form = panel_base.open_satellite_window(
"dossier_settings",
params={
"lang": cfg.get("lang", "de"),
"launcherOk": launcher_ok,
},
title="Dossier-Einstellungen",
size=(340, 320),
)
sc.sticky["_dossier_settings_form"] = form
except Exception as ex:
print("[OBERLEISTE] open_dossier_settings_panel:", ex)
def _reload_all_panel_langs():
"""Laedt alle registrierten Panel-WebViews neu (Sprache geaendert)."""
try:
import panel_base
for key, val in list(sc.sticky.items()):
if (key.endswith("_bridge") or key.endswith("_bridge_ref")) \
and hasattr(val, "_wv") and val._wv is not None \
and hasattr(val, "mode"):
try:
panel_base.load_inline(val._wv, val.mode)
except Exception as ex:
print("[OBERLEISTE] reload_lang ({}): {}".format(key, ex))
except Exception as ex:
print("[OBERLEISTE] _reload_all_panel_langs:", ex)
# --- Listener-Hookup -------------------------------------------------------- # --- Listener-Hookup --------------------------------------------------------
def _install_listeners(bridge): def _install_listeners(bridge):
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano // Copyright (C) 2026 Karim Gabriele Varano
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect, useMemo } from 'react'
import EbenenManager from './components/EbenenManager' import EbenenManager from './components/LayerManager'
import { import {
applyAll, setActiveEbene, applyAll, setActiveEbene,
onMessage, notifyReady, applyVisibility, onMessage, notifyReady, applyVisibility,
+178
View File
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useState, useEffect } from 'react'
import { notifyReady, send as bridgeSend, onMessage } from './lib/rhinoBridge'
import Icon from './components/Icon'
const LANGS = [
{ id: 'de', label: 'Deutsch' },
{ id: 'en', label: 'English' },
]
export default function DossierSettingsApp() {
const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {}
const [lang, setLang] = useState(initial.lang || 'de')
const [launcherOk, setLauncherOk] = useState(initial.launcherOk ?? null)
const [saved, setSaved] = useState(false)
useEffect(() => {
notifyReady()
onMessage('SETTINGS', (p) => {
if (p.lang) setLang(p.lang)
if (p.launcherOk != null) setLauncherOk(p.launcherOk)
})
}, [])
function handleLang(id) {
setLang(id)
bridgeSend('SAVE_LANG', { lang: id })
setSaved(true)
setTimeout(() => setSaved(false), 1800)
}
const label = (de, en) => lang === 'en' ? en : de
return (
<div style={{
height: '100vh',
display: 'flex',
flexDirection: 'column',
background: 'var(--bg-base)',
color: 'var(--text-primary)',
fontFamily: 'var(--font)',
fontSize: 12,
overflow: 'hidden',
}}>
{/* Header */}
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '14px 16px 10px',
borderBottom: '1px solid var(--border-light)',
flexShrink: 0,
}}>
<span style={{ fontWeight: 600, fontSize: 13, letterSpacing: '0.01em' }}>
{label('Dossier-Einstellungen', 'Dossier Settings')}
</span>
{saved && (
<span style={{
fontSize: 10,
color: 'var(--accent)',
display: 'flex',
alignItems: 'center',
gap: 4,
}}>
<Icon name="check_circle" size={13} />
{label('Gespeichert', 'Saved')}
</span>
)}
</div>
{/* Body */}
<div style={{ flex: 1, overflowY: 'auto', padding: '16px' }}>
{/* Language */}
<Section label={label('Sprache', 'Language')} icon="language">
<div style={{ display: 'flex', gap: 6 }}>
{LANGS.map(l => (
<button
key={l.id}
onClick={() => handleLang(l.id)}
style={{
flex: 1,
padding: '7px 0',
borderRadius: 999,
border: lang === l.id
? '1.5px solid var(--accent)'
: '1px solid var(--border)',
background: lang === l.id ? 'var(--accent-dim)' : 'var(--bg-item)',
color: lang === l.id ? 'var(--accent)' : 'var(--text-secondary)',
fontFamily: 'var(--font)',
fontSize: 11,
fontWeight: lang === l.id ? 600 : 400,
cursor: 'pointer',
transition: 'all 120ms ease',
letterSpacing: '0.02em',
}}
>
{l.label}
</button>
))}
</div>
<p style={{ marginTop: 8, fontSize: 10, color: 'var(--text-muted)', lineHeight: 1.4 }}>
{label('Gilt für alle Panels — Rhino neu laden um alle anzuwenden.', 'Applies to all panels — reload Rhino to apply everywhere.')}
</p>
</Section>
{/* Launcher status */}
<Section label={label('Launcher', 'Launcher')} icon="hub">
<div style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 10px',
borderRadius: 8,
background: 'var(--bg-item)',
border: '1px solid var(--border-light)',
}}>
<div style={{
width: 7, height: 7, borderRadius: '50%', flexShrink: 0,
background: launcherOk === true
? 'var(--accent)'
: launcherOk === false
? 'var(--danger)'
: 'var(--text-muted)',
}} />
<span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>
{launcherOk === true
? label('Launcher verbunden', 'Launcher connected')
: launcherOk === false
? label('Launcher nicht gefunden', 'Launcher not found')
: label('Unbekannt', 'Unknown')}
</span>
</div>
</Section>
</div>
{/* Footer */}
<div style={{
padding: '10px 16px',
borderTop: '1px solid var(--border-light)',
display: 'flex',
justifyContent: 'flex-end',
flexShrink: 0,
}}>
<button
className="btn-outlined"
onClick={() => bridgeSend('CANCEL', {})}
>
{label('Schließen', 'Close')}
</button>
</div>
</div>
)
}
function Section({ label, icon, children }) {
return (
<div style={{ marginBottom: 20 }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: 6,
marginBottom: 10,
color: 'var(--text-muted)',
fontSize: 10,
fontWeight: 600,
letterSpacing: '0.08em',
textTransform: 'uppercase',
}}>
{icon && <Icon name={icon} size={13} />}
{label}
</div>
{children}
</div>
)
}
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano // Copyright (C) 2026 Karim Gabriele Varano
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect, useMemo } from 'react'
import GeschossManager from './components/GeschossManager' import GeschossManager from './components/FloorManager'
import { import {
applyAll, setActiveZeichnungsebene, applyAll, setActiveZeichnungsebene,
onMessage, notifyReady, applyVisibility, onMessage, notifyReady, applyVisibility,
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano // Copyright (C) 2026 Karim Gabriele Varano
import { useEffect } from 'react' import { useEffect } from 'react'
import GeschossDialog from './components/GeschossDialog' import GeschossDialog from './components/FloorDialog'
import { notifyReady, send as bridgeSend } from './lib/rhinoBridge' import { notifyReady, send as bridgeSend } from './lib/rhinoBridge'
// recalcOkff direkt hier gleiche Logik wie in ZeichnungsebenenApp.jsx, // recalcOkff direkt hier gleiche Logik wie in ZeichnungsebenenApp.jsx,
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano // Copyright (C) 2026 Karim Gabriele Varano
import { useEffect } from 'react' import { useEffect } from 'react'
import GeschossSettingsDialog from './components/GeschossSettingsDialog' import GeschossSettingsDialog from './components/FloorSettingsDialog'
import { notifyReady, send as bridgeSend } from './lib/rhinoBridge' import { notifyReady, send as bridgeSend } from './lib/rhinoBridge'
export default function GeschossSettingsApp() { export default function GeschossSettingsApp() {
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano // Copyright (C) 2026 Karim Gabriele Varano
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import AusschnittLayerDialog from './components/AusschnittLayerDialog' import AusschnittLayerDialog from './components/ViewportLayerDialog'
import { onMessage, notifyReady } from './lib/rhinoBridge' import { onMessage, notifyReady } from './lib/rhinoBridge'
function send(type, payload) { function send(type, payload) {
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano // Copyright (C) 2026 Karim Gabriele Varano
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef } from 'react'
import EbenenSettingsDialog from './components/EbenenSettingsDialog' import EbenenSettingsDialog from './components/LayerSettingsDialog'
import { notifyReady, onMessage, send as bridgeSend } from './lib/rhinoBridge' import { notifyReady, onMessage, send as bridgeSend } from './lib/rhinoBridge'
export default function EbenenSettingsApp() { export default function EbenenSettingsApp() {
+16 -18
View File
@@ -3,6 +3,7 @@
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef } from 'react'
import Icon from './components/Icon' import Icon from './components/Icon'
import ContextMenu from './components/ContextMenu' import ContextMenu from './components/ContextMenu'
import { t } from './i18n/index.js'
import { BarButton, BarCombo, BAR_H } from './components/BarControls' import { BarButton, BarCombo, BAR_H } from './components/BarControls'
import { import {
onMessage, notifyReady, onMessage, notifyReady,
@@ -181,69 +182,66 @@ export default function LayoutsApp() {
// Kontextmenue-Items pro Layout // Kontextmenue-Items pro Layout
const layoutCtxItems = (l) => [ const layoutCtxItems = (l) => [
{ label: 'In Rhino oeffnen', icon: 'open_in_new', { label: t('layouts.open_in_rhino'), icon: 'open_in_new',
onClick: () => activateLayout(l.id) }, onClick: () => activateLayout(l.id) },
{ label: 'Umbenennen', icon: 'edit', { label: t('common.rename'), icon: 'edit',
onClick: () => setRenamingId(l.id) }, onClick: () => setRenamingId(l.id) },
{ divider: true }, { divider: true },
{ label: 'Als PDF exportieren', icon: 'picture_as_pdf', { label: t('layouts.export_pdf'), icon: 'picture_as_pdf',
onClick: () => exportPdf(l.id, 300) }, onClick: () => exportPdf(l.id, 300) },
{ label: 'Papierformat ändern', icon: 'aspect_ratio', { label: t('layouts.change_paper'), icon: 'aspect_ratio',
onClick: () => openLayoutDialog('edit', { onClick: () => openLayoutDialog('edit', {
id: l.id, name: l.name, width: l.widthMm, height: l.heightMm, id: l.id, name: l.name, width: l.widthMm, height: l.heightMm,
}) }, }) },
{ divider: true }, { divider: true },
...(folders.length > 0 ? [ ...(folders.length > 0 ? [
...folders.map(f => ({ ...folders.map(f => ({
label: `Verschieben → ${f}`, label: t('layouts.move_to', { folder: f }),
icon: 'folder', icon: 'folder',
disabled: l.folder === f, disabled: l.folder === f,
onClick: () => setLayoutFolder(l.id, f), onClick: () => setLayoutFolder(l.id, f),
})), })),
{ label: 'Aus Ordner entfernen', { label: t('layouts.remove_from_folder'),
icon: 'folder_off', icon: 'folder_off',
disabled: !l.folder, disabled: !l.folder,
onClick: () => setLayoutFolder(l.id, '') }, onClick: () => setLayoutFolder(l.id, '') },
{ divider: true }, { divider: true },
] : []), ] : []),
{ label: 'Löschen', icon: 'delete', danger: true, { label: t('common.delete'), icon: 'delete', danger: true,
onClick: () => { onClick: () => {
if (window.confirm(`Layout "${l.name}" löschen?`)) deleteLayout(l.id) if (window.confirm(`${t('common.delete')} "${l.name}"?`)) deleteLayout(l.id)
} }, } },
] ]
// Kontextmenue-Items pro Ordner
const folderCtxItems = (folderName) => { const folderCtxItems = (folderName) => {
const items = grouped[folderName] || [] const items = grouped[folderName] || []
return [ return [
{ label: collapsedFolders.has(folderName) ? 'Aufklappen' : 'Einklappen', { label: collapsedFolders.has(folderName) ? t('common.expand') : t('common.collapse'),
icon: collapsedFolders.has(folderName) ? 'expand_more' : 'expand_less', icon: collapsedFolders.has(folderName) ? 'expand_more' : 'expand_less',
onClick: () => toggleFolderCollapse(folderName) }, onClick: () => toggleFolderCollapse(folderName) },
{ divider: true }, { divider: true },
{ label: 'Alle ankreuzen / abwählen', { label: t('layouts.check_all'),
icon: 'check_box', icon: 'check_box',
onClick: () => checkAllInFolder(items) }, onClick: () => checkAllInFolder(items) },
{ label: `Ordner als PDF (${items.length})`, { label: t('layouts.folder_pdf', { count: items.length }),
icon: 'picture_as_pdf', icon: 'picture_as_pdf',
disabled: items.length === 0, disabled: items.length === 0,
onClick: () => handleExportFolder(folderName) }, onClick: () => handleExportFolder(folderName) },
{ divider: true }, { divider: true },
{ label: 'Ordner umbenennen', { label: t('common.rename_folder'),
icon: 'edit', icon: 'edit',
onClick: () => { onClick: () => {
const next = window.prompt('Neuer Ordner-Name:', folderName) const next = window.prompt(t('layouts.new_folder_name'), folderName)
if (next && next.trim() && next !== folderName) { if (next && next.trim() && next !== folderName) {
// Atomar via Server-Side waere besser; simpler: alle Layouts
// umhaengen + alten loeschen + neuen anlegen.
addLayoutFolder(next.trim()) addLayoutFolder(next.trim())
items.forEach(l => setLayoutFolder(l.id, next.trim())) items.forEach(l => setLayoutFolder(l.id, next.trim()))
removeLayoutFolder(folderName) removeLayoutFolder(folderName)
} }
} }, } },
{ label: 'Ordner loeschen', { label: t('common.delete_folder'),
icon: 'folder_off', danger: true, icon: 'folder_off', danger: true,
onClick: () => { onClick: () => {
if (window.confirm(`Ordner "${folderName}" loeschen? Layouts werden zur Wurzel verschoben.`)) if (window.confirm(`${t('common.delete_folder')} "${folderName}"? ${t('layouts.delete_folder_confirm')}`))
removeLayoutFolder(folderName) removeLayoutFolder(folderName)
} }, } },
] ]
+12 -11
View File
@@ -3,6 +3,7 @@
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect, useMemo } from 'react'
import Icon from './components/Icon' import Icon from './components/Icon'
import ContextMenu from './components/ContextMenu' import ContextMenu from './components/ContextMenu'
import { t } from './i18n/index.js'
import { import {
onMessage, notifyReady, onMessage, notifyReady,
listAusschnitte, saveAusschnitt, updateAusschnitt, listAusschnitte, saveAusschnitt, updateAusschnitt,
@@ -287,25 +288,25 @@ export default function AusschnitteApp() {
} }
const handleAddFolder = () => { const handleAddFolder = () => {
const name = window.prompt('Name für neuen Ordner:') const name = window.prompt(t('viewports.new_folder_name'))
if (name && name.trim()) addAusschnittFolder(name.trim()) if (name && name.trim()) addAusschnittFolder(name.trim())
} }
const ctxItems = (id) => [ const ctxItems = (id) => [
{ label: 'Wiederherstellen', icon: 'restore', onClick: () => restoreAusschnitt(id) }, { label: t('viewports.restore'), icon: 'restore', onClick: () => restoreAusschnitt(id) },
{ label: 'Auf Detail anwenden', icon: 'crop_landscape', onClick: () => applyAusschnittToDetail(id) }, { label: t('viewports.apply_to_detail'),icon: 'crop_landscape', onClick: () => applyAusschnittToDetail(id) },
{ divider: true }, { divider: true },
{ label: 'Ausschnittseinstellungen…', icon: 'tune', onClick: () => openAusschnittSettings(id) }, { label: t('viewports.settings'), icon: 'tune', onClick: () => openAusschnittSettings(id) },
{ divider: true }, { divider: true },
{ label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicateAusschnitt(id) }, { label: t('common.duplicate'), icon: 'content_copy', onClick: () => duplicateAusschnitt(id) },
{ label: 'Aktualisieren', icon: 'sync', onClick: () => updateAusschnitt(id) }, { label: t('viewports.update'), icon: 'sync', onClick: () => updateAusschnitt(id) },
{ divider: true }, { divider: true },
{ label: 'Löschen', icon: 'delete', danger: true, onClick: () => deleteAusschnitt(id) }, { label: t('common.delete'), icon: 'delete', danger: true, onClick: () => deleteAusschnitt(id) },
] ]
const folderCtxItems = (folderName) => [ const folderCtxItems = (folderName) => [
{ label: 'Ordner umbenennen', icon: 'edit', onClick: () => { { label: t('common.rename_folder'), icon: 'edit', onClick: () => {
const newName = window.prompt('Neuer Ordnername:', folderName) const newName = window.prompt(t('common.new_folder_name'), folderName)
if (newName && newName.trim() && newName !== folderName) { if (newName && newName.trim() && newName !== folderName) {
snaps.filter(s => s.folder === folderName).forEach(s => setAusschnittFolder(s.id, newName.trim())) snaps.filter(s => s.folder === folderName).forEach(s => setAusschnittFolder(s.id, newName.trim()))
addAusschnittFolder(newName.trim()) addAusschnittFolder(newName.trim())
@@ -313,8 +314,8 @@ export default function AusschnitteApp() {
} }
}}, }},
{ divider: true }, { divider: true },
{ label: 'Ordner löschen', icon: 'folder_off', danger: true, onClick: () => { { label: t('common.delete_folder'), icon: 'folder_off', danger: true, onClick: () => {
if (window.confirm(`Ordner "${folderName}" löschen? Ausschnitte werden zur Wurzel verschoben.`)) { if (window.confirm(`${t('common.delete_folder')} "${folderName}"? ${t('viewports.delete_folder_confirm')}`)) {
removeAusschnittFolder(folderName) removeAusschnittFolder(folderName)
} }
}}, }},
@@ -3,6 +3,7 @@
import { useState } from 'react' import { useState } from 'react'
import Icon from './Icon' import Icon from './Icon'
import ContextMenu from './ContextMenu' import ContextMenu from './ContextMenu'
import { t } from '../i18n/index.js'
import { BarCombo, BarButton } from './BarControls' import { BarCombo, BarButton } from './BarControls'
import { openGeschossSettings, openGeschossDialog, createSchnitt } from '../lib/rhinoBridge' import { openGeschossSettings, openGeschossDialog, createSchnitt } from '../lib/rhinoBridge'
@@ -150,12 +151,12 @@ function ZeichnungsebeneRow({
) )
} }
const MODES = [ const MODES = () => [
{ value: 'all_force', label: 'Alle anzeigen' }, { value: 'all_force', label: t('layers.show_all_mode') },
{ value: 'all', label: 'Ausgewählte' }, { value: 'all', label: t('layers.selected_mode') },
{ value: 'active', label: 'Nur aktive' }, { value: 'active', label: t('layers.active_only_mode') },
{ value: 'grey', label: 'Andere grau' }, { value: 'grey', label: t('layers.others_gray') },
{ value: 'grey_locked', label: 'Andere grau & gesperrt' }, { value: 'grey_locked', label: t('layers.others_gray_locked') },
] ]
export default function GeschossManager({ export default function GeschossManager({
@@ -443,11 +444,11 @@ export default function GeschossManager({
const z = zeichnungsebenen.find(x => x.id === id) const z = zeichnungsebenen.find(x => x.id === id)
if (!z) return [] if (!z) return []
return [ return [
{ label: 'Einstellungen…', icon: 'settings', onClick: () => openGeschossSettings(z) }, { label: t('common.settings') + '…', icon: 'settings', onClick: () => openGeschossSettings(z) },
{ divider: true }, { divider: true },
{ label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicate(id) }, { label: t('common.duplicate'), icon: 'content_copy', onClick: () => duplicate(id) },
{ divider: true }, { divider: true },
{ label: 'Löschen', icon: 'delete', danger: true, { label: t('common.delete'), icon: 'delete', danger: true,
disabled: zeichnungsebenen.length <= 1, disabled: zeichnungsebenen.length <= 1,
onClick: () => remove(id) }, onClick: () => remove(id) },
] ]
@@ -461,22 +462,22 @@ export default function GeschossManager({
background: 'var(--bg-section)', background: 'var(--bg-section)',
borderBottom: '1px solid var(--border-light)', borderBottom: '1px solid var(--border-light)',
}}> }}>
<span className="label-xs">Sichtbarkeit</span> <span className="label-xs">{t('layers.visibility_mode')}</span>
<div style={{ display: 'flex', width: '100%' }}> <div style={{ display: 'flex', width: '100%' }}>
<BarCombo <BarCombo
stretch stretch
icon="visibility" icon="visibility"
value={mode} value={mode}
onChange={onModeChange} onChange={onModeChange}
title="Sichtbarkeits-Modus" title={t('layers.visibility_mode')}
onGear={() => openGeschossDialog(zeichnungsebenen)} onGear={() => openGeschossDialog(zeichnungsebenen)}
gearIcon="settings" gearIcon="settings"
gearTitle="Einstellungen" gearTitle={t('common.settings')}
onSecond={openAddMenu} onSecond={openAddMenu}
secondIcon="add" secondIcon="add"
secondTitle="Hinzufuegen: Geschoss / Schnitt / Zeichnung" secondTitle={t('floors.add')}
> >
{MODES.map(m => ( {MODES().map(m => (
<option key={m.value} value={m.value}>{m.label}</option> <option key={m.value} value={m.value}>{m.label}</option>
))} ))}
</BarCombo> </BarCombo>
@@ -501,8 +502,7 @@ export default function GeschossManager({
if (mode === 'active' || mode === 'all_force') onModeChange('all') if (mode === 'active' || mode === 'all_force') onModeChange('all')
}} }}
title={zeichnungsebenen.every(z => z.visible !== false) title={zeichnungsebenen.every(z => z.visible !== false)
? 'Alle Zeichnungsebenen ausblenden' ? t('floors.hide_all') : t('floors.show_all')}
: 'Alle Zeichnungsebenen einblenden'}
style={{ width: 16, height: 16, style={{ width: 16, height: 16,
opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }} opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }}
> >
@@ -519,8 +519,7 @@ export default function GeschossManager({
onChange(zeichnungsebenen.map(z => ({ ...z, locked: !anyLocked }))) onChange(zeichnungsebenen.map(z => ({ ...z, locked: !anyLocked })))
}} }}
title={zeichnungsebenen.every(z => z.locked === true) title={zeichnungsebenen.every(z => z.locked === true)
? 'Alle Zeichnungsebenen entsperren' ? t('floors.unlock_all') : t('floors.lock_all')}
: 'Alle Zeichnungsebenen sperren'}
style={{ width: 14, height: 14 }} style={{ width: 14, height: 14 }}
> >
<Icon <Icon
@@ -2,19 +2,20 @@
// Copyright (C) 2026 Karim Gabriele Varano // Copyright (C) 2026 Karim Gabriele Varano
import { useState, useRef, useMemo, useEffect } from 'react' import { useState, useRef, useMemo, useEffect } from 'react'
import Icon from './Icon' import Icon from './Icon'
import ConfirmDeleteEbene from './ConfirmDeleteEbene' import ConfirmDeleteEbene from './ConfirmDeleteLayer'
import ContextMenu from './ContextMenu' import ContextMenu from './ContextMenu'
import { BarCombo, BarButton } from './BarControls' import { BarCombo, BarButton } from './BarControls'
import { setLayerStyle, deleteEbene, moveSelectionToEbene, openEbenenSettings, import { setLayerStyle, deleteEbene, moveSelectionToEbene, openEbenenSettings,
pickLayerCombination, saveLayerCombination, deleteLayerCombination, pickLayerCombination, saveLayerCombination, deleteLayerCombination,
openLayerCombinationsDialog } from '../lib/rhinoBridge' openLayerCombinationsDialog } from '../lib/rhinoBridge'
import { t } from '../i18n/index.js'
const MODES = [ const MODES = () => [
{ value: 'all_force', label: 'Alle anzeigen' }, { value: 'all_force', label: t('layers.show_all_mode') },
{ value: 'all', label: 'Ausgewählte' }, { value: 'all', label: t('layers.selected_mode') },
{ value: 'active', label: 'Nur aktive' }, { value: 'active', label: t('layers.active_only_mode') },
{ value: 'grey', label: 'Andere grau' }, { value: 'grey', label: t('layers.others_gray') },
{ value: 'grey_locked', label: 'Andere grau & gesperrt' }, { value: 'grey_locked', label: t('layers.others_gray_locked') },
] ]
const LW_PRESETS = [0.02, 0.10, 0.13, 0.18, 0.25, 0.35, 0.50, 0.70, 1.00] const LW_PRESETS = [0.02, 0.10, 0.13, 0.18, 0.25, 0.35, 0.50, 0.70, 1.00]
@@ -532,19 +533,19 @@ export default function EbenenManager({
} }
const ctxItems = (code) => [ const ctxItems = (code) => [
{ label: 'Ebeneneinstellungen…', icon: 'settings', onClick: () => { { label: t('layers.settings'), icon: 'settings', onClick: () => {
const target = _findInTree(ebenen, code) const target = _findInTree(ebenen, code)
if (target) openEbenenSettings(target, hatchPatterns) if (target) openEbenenSettings(target, hatchPatterns)
} }, } },
{ divider: true }, { divider: true },
{ label: 'Sub-Ebene hinzufügen…', icon: 'add', onClick: () => addChild(code) }, { label: t('layers.add_sub'), icon: 'add', onClick: () => addChild(code) },
{ label: 'Selektion hierher übertragen', icon: 'move_down', onClick: () => moveSelectionToEbene(code) }, { label: t('layers.move_selection'), icon: 'move_down', onClick: () => moveSelectionToEbene(code) },
{ divider: true }, { divider: true },
{ label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicateEbene(code) }, { label: t('common.duplicate'), icon: 'content_copy', onClick: () => duplicateEbene(code) },
{ label: 'Eigenschaften kopieren', icon: 'colorize', onClick: () => copyProps(code) }, { label: t('layers.copy_props'), icon: 'colorize', onClick: () => copyProps(code) },
{ label: 'Eigenschaften einfügen', icon: 'format_paint', onClick: () => pasteProps(code), disabled: !clipboard }, { label: t('layers.paste_props'), icon: 'format_paint', onClick: () => pasteProps(code), disabled: !clipboard },
{ divider: true }, { divider: true },
{ label: 'Löschen', icon: 'delete', onClick: () => handleDelete(code), danger: true, { label: t('common.delete'), icon: 'delete', onClick: () => handleDelete(code), danger: true,
disabled: ebenen.length <= 1 }, disabled: ebenen.length <= 1 },
] ]
@@ -556,7 +557,7 @@ export default function EbenenManager({
background: 'var(--bg-section)', background: 'var(--bg-section)',
borderBottom: '1px solid var(--border-light)', borderBottom: '1px solid var(--border-light)',
}}> }}>
<span className="label-xs">Ebenenkombination</span> <span className="label-xs">{t('layers.combination')}</span>
<div style={{ display: 'flex', width: '100%' }}> <div style={{ display: 'flex', width: '100%' }}>
<BarCombo <BarCombo
stretch stretch
@@ -582,21 +583,21 @@ export default function EbenenManager({
pickLayerCombination(v === '__none__' ? null : v) pickLayerCombination(v === '__none__' ? null : v)
}} }}
title={activeKombi title={activeKombi
? `Aktive Kombi: ${activeKombi}` ? `${t('layers.combination')}: ${activeKombi}`
: 'Keine Kombination — manuelle Sichtbarkeit'} : t('layers.no_combination')}
onGear={openLayerCombinationsDialog} onGear={openLayerCombinationsDialog}
gearTitle="Ebenenkombinationen bearbeiten" gearTitle={t('layers.edit_combinations')}
> >
<option value="__none__"> Eigene </option> <option value="__none__">{t('layers.custom')}</option>
{layerCombinations.map(n => ( {layerCombinations.map(n => (
<option key={n} value={n}>{n}</option> <option key={n} value={n}>{n}</option>
))} ))}
<option disabled></option> <option disabled></option>
<option value="__save__">+ Aktuelle speichern</option> <option value="__save__">{t('layers.save_current')}</option>
{activeKombi && ( {activeKombi && (
<option value="__delete__">🗑 Aktuelle löschen</option> <option value="__delete__">🗑 {t('layers.delete_current')}</option>
)} )}
<option value="__configure__">Bearbeiten</option> <option value="__configure__">{t('common.edit')}</option>
</BarCombo> </BarCombo>
</div> </div>
</div> </div>
@@ -607,19 +608,19 @@ export default function EbenenManager({
background: 'var(--bg-section)', background: 'var(--bg-section)',
borderBottom: '1px solid var(--border-light)', borderBottom: '1px solid var(--border-light)',
}}> }}>
<span className="label-xs">Sichtbarkeit</span> <span className="label-xs">{t('layers.visibility_mode')}</span>
<div style={{ display: 'flex', width: '100%' }}> <div style={{ display: 'flex', width: '100%' }}>
<BarCombo <BarCombo
stretch stretch
icon="visibility" icon="visibility"
value={mode} value={mode}
onChange={onModeChange} onChange={onModeChange}
title="Sichtbarkeits-Modus" title={t('layers.visibility_mode')}
onSecond={addNew} onSecond={addNew}
secondIcon="add" secondIcon="add"
secondTitle="Ebene hinzufügen" secondTitle={t('layers.add')}
> >
{MODES.map(m => <option key={m.value} value={m.value}>{m.label}</option>)} {MODES().map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
</BarCombo> </BarCombo>
</div> </div>
</div> </div>
@@ -642,7 +643,7 @@ export default function EbenenManager({
if (mode === 'active' || mode === 'all_force') onModeChange('all') if (mode === 'active' || mode === 'all_force') onModeChange('all')
}} }}
title={ebenen.every(e => e.visible !== false) title={ebenen.every(e => e.visible !== false)
? 'Alle Ebenen ausblenden' : 'Alle Ebenen einblenden'} ? t('layers.hide_all') : t('layers.show_all')}
style={{ width: 16, height: 16, style={{ width: 16, height: 16,
opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }} opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }}
> >
@@ -658,7 +659,7 @@ export default function EbenenManager({
const anyLocked = ebenen.some(e => e.locked === true) const anyLocked = ebenen.some(e => e.locked === true)
onChange(ebenen.map(e => ({ ...e, locked: !anyLocked }))) onChange(ebenen.map(e => ({ ...e, locked: !anyLocked })))
}} }}
title={ebenen.every(e => e.locked === true) ? 'Alle Ebenen entsperren' : 'Alle Ebenen sperren'} title={ebenen.every(e => e.locked === true) ? t('layers.unlock_all') : t('layers.lock_all')}
style={{ width: 14, height: 14 }} style={{ width: 14, height: 14 }}
> >
<Icon name={ebenen.every(e => e.locked === true) ? 'lock' : 'lock_open'} size={11} /> <Icon name={ebenen.every(e => e.locked === true) ? 'lock' : 'lock_open'} size={11} />
+290
View File
@@ -0,0 +1,290 @@
{
"common.save": "Speichern",
"common.cancel": "Abbrechen",
"common.delete": "Löschen",
"common.duplicate": "Duplizieren",
"common.rename": "Umbenennen",
"common.settings": "Einstellungen",
"common.edit": "Bearbeiten",
"common.add": "Hinzufügen",
"common.expand": "Aufklappen",
"common.collapse": "Einklappen",
"common.show": "Einblenden",
"common.hide": "Ausblenden",
"common.lock": "Sperren",
"common.unlock": "Entsperren",
"common.refresh": "Aktualisieren",
"common.close": "Schließen",
"common.apply": "Anwenden",
"common.new_folder": "Neuer Ordner",
"common.rename_folder": "Ordner umbenennen",
"common.delete_folder": "Ordner löschen",
"common.new_folder_name": "Neuer Ordnername:",
"lang.de": "Deutsch",
"lang.en": "English",
"lang.label": "Sprache",
"settings.title": "Dossier-Einstellungen",
"settings.language": "Sprache",
"settings.language_hint": "Gilt für alle Panels",
"settings.launcher_sync": "Launcher-Einstellungen",
"settings.launcher_available": "Launcher verbunden",
"settings.launcher_missing": "Launcher nicht gefunden",
"settings.window_layout": "Fenster-Layout",
"settings.apply_layout": "Layout automatisch anwenden",
"settings.saved": "Gespeichert",
"layers.title": "Ebenen",
"layers.settings": "Ebeneneinstellungen…",
"layers.add_sub": "Sub-Ebene hinzufügen…",
"layers.move_selection": "Selektion hierher übertragen",
"layers.copy_props": "Eigenschaften kopieren",
"layers.paste_props": "Eigenschaften einfügen",
"layers.add": "Ebene hinzufügen",
"layers.hide_all": "Alle Ebenen ausblenden",
"layers.show_all": "Alle Ebenen einblenden",
"layers.lock_all": "Alle Ebenen sperren",
"layers.unlock_all": "Alle Ebenen entsperren",
"layers.visibility_mode": "Sichtbarkeits-Modus",
"layers.show_all_mode": "Alle anzeigen",
"layers.selected_mode": "Ausgewählte",
"layers.active_only_mode": "Nur aktive",
"layers.others_gray": "Andere grau",
"layers.others_gray_locked": "Andere grau & gesperrt",
"layers.combination": "Ebenenkombination",
"layers.no_combination": "Keine Kombination — manuelle Sichtbarkeit",
"layers.edit_combinations": "Ebenenkombinationen bearbeiten",
"layers.save_current": "+ Aktuelle speichern…",
"layers.delete_current": "Aktuelle löschen",
"layers.custom": "— Eigene —",
"layers.dblclick_edit": "Doppelklick zum Bearbeiten",
"layers.dblclick_rename": "Doppelklick zum Umbenennen",
"floors.title": "Zeichnungsebenen",
"floors.add": "Hinzufügen: Geschoss / Schnitt / Zeichnung",
"floors.floor": "Geschoss",
"floors.section": "Schnitt / Ansicht",
"floors.drawing": "Zeichnung",
"floors.new_floor": "Neues Geschoss",
"floors.position_relative": "Position relativ zu",
"floors.none": "— keins —",
"floors.insert_above": "Über dem Anker einfügen",
"floors.insert_below": "Unter dem Anker einfügen",
"floors.height": "Höhe (m)",
"floors.section_height": "Schnitt (m)",
"floors.section_height_hint": "Höhe der horizontalen Schnitt-Plane über OKFF",
"floors.hide_all": "Alle Zeichnungsebenen ausblenden",
"floors.show_all": "Alle Zeichnungsebenen einblenden",
"floors.lock_all": "Alle Zeichnungsebenen sperren",
"floors.unlock_all": "Alle Zeichnungsebenen entsperren",
"floors.clipping_off": "Clipping Plane ausschalten",
"floors.clipping_on": "Clipping Plane einschalten",
"floors.copy_suffix": "Kopie",
"floors.confirm_delete": "wirklich löschen?",
"viewports.title": "Ausschnitte",
"viewports.save": "Ausschnitt speichern",
"viewports.new_name": "Name für neuen Ausschnitt…",
"viewports.restore": "Wiederherstellen",
"viewports.apply_to_detail": "Auf Detail anwenden",
"viewports.settings": "Ausschnittseinstellungen…",
"viewports.update": "Aktualisieren",
"viewports.empty": "Noch keine Ausschnitte.",
"viewports.empty_hint": "Oben einen Namen eingeben und klicken.",
"viewports.empty_folder": "Leer — Ausschnitte hier ablegen.",
"viewports.drop_root": "Hier ablegen für Wurzel",
"viewports.folder_actions": "Ordner-Aktionen",
"viewports.new_folder_name": "Name für neuen Ordner:",
"viewports.delete_folder_confirm": "Ausschnitte werden zur Wurzel verschoben.",
"viewports.scale_hint": "Doppelklick um Maßstab einzutragen (z.B. 1:50)",
"viewports.footer_hint": "Drag & Drop auf Ordner-Card zum Verschieben · Doppelklick = bearbeiten · ⋮ für Aktionen",
"layouts.title": "Layouts",
"layouts.landscape": "Querformat",
"layouts.portrait": "Hochformat",
"layouts.untitled": "(unbenannt)",
"layouts.empty": "Noch keine Layouts.",
"layouts.empty_hint": "Unten klicken um ein neues Layout anzulegen.",
"layouts.empty_folder": "Leer — Layouts hier ablegen.",
"layouts.drop_remove": "Hier ablegen um aus Ordner zu entfernen",
"layouts.new_detail": "Neues Detail (zentriert auf Seite)",
"layouts.sync_all": "Alle Details mit ihren Ausschnitten neu synchronisieren",
"layouts.no_details": "Keine Details auf diesem Layout.",
"layouts.no_details_hint": "Oben klicken um eines hinzuzufügen.",
"layouts.no_section": "— kein Ausschnitt —",
"layouts.reapply_section": "Gebundenen Ausschnitt neu anwenden",
"layouts.new": "Neues Layout erstellen",
"layouts.open_in_rhino": "In Rhino öffnen",
"layouts.export_pdf": "Als PDF exportieren",
"layouts.export_selection_pdf": "Auswahl ({count}) als ein PDF exportieren",
"layouts.export_all_pdf": "Alle Layouts als ein PDF exportieren",
"layouts.select_first": "Erst Layouts ankreuzen",
"layouts.change_paper": "Papierformat ändern",
"layouts.move_to": "Verschieben → {folder}",
"layouts.remove_from_folder": "Aus Ordner entfernen",
"layouts.check_all": "Alle ankreuzen / abwählen",
"layouts.folder_pdf": "Ordner als PDF ({count})",
"layouts.check_for_pdf": "Für PDF-Export ankreuzen",
"layouts.delete_detail_confirm": "Detail löschen?",
"layouts.new_folder_name": "Neuer Ordner-Name:",
"layouts.delete_folder_confirm": "Layouts werden zur Wurzel verschoben.",
"overrides.title": "Overrides",
"overrides.layer_name": "Layer-Name",
"overrides.object_name": "Objekt-Name",
"overrides.contains": "enthält",
"overrides.starts_with": "beginnt mit",
"overrides.ends_with": "endet mit",
"overrides.remove_condition": "Diese Bedingung entfernen",
"overrides.logic_all": "Alle Bedingungen müssen zutreffen",
"overrides.logic_any": "Mindestens eine Bedingung muss zutreffen",
"overrides.add_condition": "Weitere Bedingung hinzufügen",
"overrides.color": "Farbe",
"overrides.lineweight": "Strichstärke",
"overrides.linetype": "Linientyp",
"overrides.hatch": "Schraffur",
"overrides.hatch_scale": "Schraffur-Skala",
"overrides.hatch_hint": "Hatch-Override modifiziert nur existierende Schraffuren.",
"overrides.conditions": "Bedingungen",
"overrides.overrides": "Überschreibungen",
"overrides.combinations": "Override-Kombinationen",
"overrides.no_combination": "— neu / keine —",
"overrides.save_as_combination": "Als Kombination speichern…",
"overrides.save_changes_to": "Änderungen in \"{name}\" speichern",
"overrides.save_current_as_new": "Aktuelle Regeln als neue Kombination speichern",
"overrides.delete_combination_confirm": "Kombination \"{name}\" dauerhaft löschen?",
"overrides.delete_combination": "Gewählte Kombination dauerhaft löschen",
"overrides.insert_empty": "Leere Regel oben einfügen",
"overrides.from_template": "+ Aus Vorlage…",
"overrides.insert_from_template": "Regel aus Vorlage einfügen",
"overrides.delete_template": "Vorlage löschen",
"overrides.additive_hint": "Regeln sind additiv. Bei Konflikt gewinnt die oberste.",
"overrides.empty": "Noch keine Regeln.",
"overrides.empty_hint": "Oben klicken um eine neue Regel zu erstellen.",
"overrides.activate": "Aktivieren",
"overrides.deactivate": "Deaktivieren",
"overrides.prio_up": "Prio höher (nach oben)",
"overrides.prio_down": "Prio tiefer (nach unten)",
"overrides.save_as_template": "Als Vorlage speichern…",
"overrides.template_name": "Name für Vorlage:",
"overrides.delete_rule_confirm": "Regel \"{name}\" löschen?",
"overrides.rule_active": "Regel aktiv",
"overrides.logic_label": "Logik:",
"overrides.key": "Key",
"overrides.value": "Wert",
"overrides.condition": "Bedingung",
"overrides.empty_value": "(leer)",
"toolbar.shortcuts_hint": "Shortcuts (Shift+Klick = Über Dossier)",
"toolbar.app_settings": "Dossier-Einstellungen",
"toolbar.project_settings": "Projekt-Einstellungen",
"toolbar.overrides_on": "Grafische Overrides aktiv — klick zum Ausschalten",
"toolbar.overrides_off": "Grafische Overrides ausgeschaltet",
"toolbar.open_overrides": "Overrides-Regel-Editor öffnen",
"toolbar.zoom_snap": "Zoom auf 1:{scale} snappen",
"toolbar.zoom_select_scale": "Erst einen Massstab wählen",
"toolbar.zoom_all": "Auf gesamten Inhalt zoomen",
"toolbar.zoom_selection": "Auf Selektion zoomen",
"toolbar.refs_show": "Referenzlinien einblenden",
"toolbar.refs_hide": "Referenzlinien ausblenden",
"toolbar.osnap_on": "Object-Snap an — Klick zum Ausschalten",
"toolbar.osnap_off": "Object-Snap aus — Klick zum Einschalten",
"toolbar.grid_toggle": "Konstruktions-Raster ein-/ausblenden",
"toolbar.print_view_on": "Print-View aktiv — klick zum Ausschalten",
"toolbar.print_view_off": "Strichstärken anzeigen (Print-View)",
"toolbar.set_scale": "Gesetzter Massstab",
"toolbar.live_scale": "Aktueller Live-Massstab",
"toolbar.no_scale": "Kein Massstab gesetzt",
"toolbar.perspective_no_scale": "Perspektive — kein Massstab",
"toolbar.scale_input": "Massstab eingeben",
"toolbar.font": "Schriftart",
"toolbar.text_height": "Texthöhe (m)",
"toolbar.bold": "Fett",
"toolbar.italic": "Kursiv",
"toolbar.insert_text": "Neuen Text einfügen",
"toolbar.measures_edit": "Masse bearbeiten / neues anlegen",
"toolbar.style_save": "+ Speichern…",
"toolbar.style_delete": "Aktiven löschen",
"toolbar.bring_front": "In den Vordergrund",
"toolbar.bring_forward": "Eine Stufe hoch",
"toolbar.send_backward": "Eine Stufe runter",
"toolbar.send_back": "In den Hintergrund",
"toolbar.camera_settings": "Kamera-Einstellungen",
"toolbar.detail_level_simple": "Einfach (1:100)",
"toolbar.detail_level_standard": "Standard (1:50)",
"toolbar.detail_level_detail": "Detail (1:20)",
"tools.drawing_2d": "2D Zeichnen",
"tools.editing_2d": "2D Editieren",
"tools.modeling_3d": "3D Modellieren",
"tools.selection": "Auswahl",
"tools.line": "Linie",
"tools.polyline": "Polylinie",
"tools.rectangle": "Rechteck",
"tools.circle": "Kreis",
"tools.arc": "Bogen",
"tools.spline": "Freie Kurve (Spline)",
"tools.text": "Text",
"tools.hatch": "Schraffur",
"tools.dimension": "Linearbemaßung",
"tools.move": "Verschieben",
"tools.copy": "Kopieren",
"tools.rotate": "Drehen",
"tools.scale": "Skalieren",
"tools.mirror": "Spiegeln",
"tools.offset": "Parallelversatz",
"tools.trim": "Stutzen",
"tools.extend": "Verlängern",
"tools.join": "Verbinden",
"tools.explode": "Auflösen",
"tools.fillet": "Verrunden",
"tools.array": "Polar-Array",
"tools.extrude": "Kurve zu 3D extrudieren",
"tools.box": "Quader",
"tools.bool_union": "Boolean-Vereinigung",
"tools.bool_diff": "Boolean-Differenz",
"tools.bool_intersect": "Boolean-Schnittmenge",
"tools.close_holes": "Planare Löcher schließen",
"tools.section_lines": "Schnittlinien erzeugen",
"tools.loft": "Loft",
"tools.select_tangent": "Tangentiale Kurvenkette wählen",
"tools.select_duplicates": "Doppelte Objekte wählen",
"tools.select_closed": "Geschlossene Kurven wählen",
"tools.invert_selection": "Auswahl invertieren",
"tools.select_all": "Alle auswählen",
"tools.deselect_all": "Auswahl aufheben",
"dimensions.no_selection": "Keine Selektion",
"dimensions.no_selection_hint": "In Rhino ein oder mehrere Objekte auswählen.",
"dimensions.world_coords": "Weltkoordinaten",
"dimensions.active_cplane": "Aktive Konstruktionsebene",
"dimensions.rotate_hint": "Selektion um Z-Achse der aktiven Plane drehen",
"dimensions.rotate_ccw": "90° gegen den Uhrzeigersinn",
"dimensions.rotate_cw": "90° im Uhrzeigersinn",
"measures.title": "Masse",
"measures.subtitle": "Globale Vorgaben für Raum-Rundung und Mass-Linien",
"measures.active": "Aktiv",
"measures.new": "Neues Mass anlegen",
"measures.delete_active": "Aktives Mass löschen",
"measures.name": "Name",
"measures.room_rounding": "Raum-Rundung",
"measures.decimal_places": "Mass-Dezimalstellen",
"measures.unit": "Mass-Einheit",
"measures.hint": "Änderungen werden sofort auf alle Räume angewendet.",
"measures.delete_confirm": "Mass \"{name}\" löschen?",
"measures.min_one": "Mindestens ein Mass muss erhalten bleiben.",
"measures.new_name": "Name für neues Mass:",
"measures.new_name_placeholder": "Name für neues Mass:",
"about.tagline": "Architektur-Studio für Rhino 8",
"about.part_of": "Teil von",
"about.author": "Autor",
"about.web": "Web",
"about.license": "Lizenz · Dual",
"about.license_open": "Frei nutzbar, modifizierbar und weitergebbar unter den Bedingungen der AGPL-3.0.",
"about.license_commercial": "Kommerzielle Lizenz",
"about.license_commercial_hint": "Für proprietäre Integrationen — Kontakt: karim@gabrielevarano.ch",
"about.made_in": "made in Switzerland"
}
+290
View File
@@ -0,0 +1,290 @@
{
"common.save": "Save",
"common.cancel": "Cancel",
"common.delete": "Delete",
"common.duplicate": "Duplicate",
"common.rename": "Rename",
"common.settings": "Settings",
"common.edit": "Edit",
"common.add": "Add",
"common.expand": "Expand",
"common.collapse": "Collapse",
"common.show": "Show",
"common.hide": "Hide",
"common.lock": "Lock",
"common.unlock": "Unlock",
"common.refresh": "Refresh",
"common.close": "Close",
"common.apply": "Apply",
"common.new_folder": "New Folder",
"common.rename_folder": "Rename Folder",
"common.delete_folder": "Delete Folder",
"common.new_folder_name": "New Folder Name:",
"lang.de": "Deutsch",
"lang.en": "English",
"lang.label": "Language",
"settings.title": "Dossier Settings",
"settings.language": "Language",
"settings.language_hint": "Applies to all panels",
"settings.launcher_sync": "Launcher Settings",
"settings.launcher_available": "Launcher connected",
"settings.launcher_missing": "Launcher not found",
"settings.window_layout": "Window Layout",
"settings.apply_layout": "Auto-apply layout",
"settings.saved": "Saved",
"layers.title": "Layers",
"layers.settings": "Layer Settings…",
"layers.add_sub": "Add Sub-Layer…",
"layers.move_selection": "Move Selection Here",
"layers.copy_props": "Copy Properties",
"layers.paste_props": "Paste Properties",
"layers.add": "Add Layer",
"layers.hide_all": "Hide All Layers",
"layers.show_all": "Show All Layers",
"layers.lock_all": "Lock All Layers",
"layers.unlock_all": "Unlock All Layers",
"layers.visibility_mode": "Visibility Mode",
"layers.show_all_mode": "Show All",
"layers.selected_mode": "Selected",
"layers.active_only_mode": "Active Only",
"layers.others_gray": "Others Gray",
"layers.others_gray_locked": "Others Gray & Locked",
"layers.combination": "Layer Combination",
"layers.no_combination": "No Combination — Manual Visibility",
"layers.edit_combinations": "Edit Layer Combinations",
"layers.save_current": "+ Save Current…",
"layers.delete_current": "Delete Current",
"layers.custom": "— Custom —",
"layers.dblclick_edit": "Double-click to Edit",
"layers.dblclick_rename": "Double-click to Rename",
"floors.title": "Drawing Levels",
"floors.add": "Add: Floor / Section / Drawing",
"floors.floor": "Floor",
"floors.section": "Section / View",
"floors.drawing": "Drawing",
"floors.new_floor": "New Floor",
"floors.position_relative": "Position Relative To",
"floors.none": "— none —",
"floors.insert_above": "Insert Above Anchor",
"floors.insert_below": "Insert Below Anchor",
"floors.height": "Height (m)",
"floors.section_height": "Section (m)",
"floors.section_height_hint": "Height of Section Plane Above OKFF",
"floors.hide_all": "Hide All Drawing Levels",
"floors.show_all": "Show All Drawing Levels",
"floors.lock_all": "Lock All Drawing Levels",
"floors.unlock_all": "Unlock All Drawing Levels",
"floors.clipping_off": "Turn Off Clipping Plane",
"floors.clipping_on": "Turn On Clipping Plane",
"floors.copy_suffix": "Copy",
"floors.confirm_delete": "Really delete?",
"viewports.title": "Viewports",
"viewports.save": "Save Viewport",
"viewports.new_name": "Name for New Viewport…",
"viewports.restore": "Restore",
"viewports.apply_to_detail": "Apply to Detail",
"viewports.settings": "Viewport Settings…",
"viewports.update": "Update",
"viewports.empty": "No Viewports Yet.",
"viewports.empty_hint": "Enter a name above and click.",
"viewports.empty_folder": "Empty — Drop viewports here.",
"viewports.drop_root": "Drop here for root",
"viewports.folder_actions": "Folder Actions",
"viewports.new_folder_name": "Name for New Folder:",
"viewports.delete_folder_confirm": "Viewports will be moved to root.",
"viewports.scale_hint": "Double-click to enter scale (e.g. 1:50)",
"viewports.footer_hint": "Drag & Drop onto folder card to move · Double-click = edit · ⋮ for actions",
"layouts.title": "Layouts",
"layouts.landscape": "Landscape",
"layouts.portrait": "Portrait",
"layouts.untitled": "(Untitled)",
"layouts.empty": "No Layouts Yet.",
"layouts.empty_hint": "Click below to create a new layout.",
"layouts.empty_folder": "Empty — Drop layouts here.",
"layouts.drop_remove": "Drop here to remove from folder",
"layouts.new_detail": "New Detail (Centered on Page)",
"layouts.sync_all": "Re-sync All Details with Their Viewports",
"layouts.no_details": "No Details on This Layout.",
"layouts.no_details_hint": "Click above to add one.",
"layouts.no_section": "— no viewport —",
"layouts.reapply_section": "Re-apply Bound Viewport",
"layouts.new": "Create New Layout",
"layouts.open_in_rhino": "Open in Rhino",
"layouts.export_pdf": "Export as PDF",
"layouts.export_selection_pdf": "Export Selection ({count}) as PDF",
"layouts.export_all_pdf": "Export All Layouts as PDF",
"layouts.select_first": "Select Layouts First",
"layouts.change_paper": "Change Paper Format",
"layouts.move_to": "Move → {folder}",
"layouts.remove_from_folder": "Remove from Folder",
"layouts.check_all": "Check All / Uncheck All",
"layouts.folder_pdf": "Folder as PDF ({count})",
"layouts.check_for_pdf": "Check for PDF Export",
"layouts.delete_detail_confirm": "Delete Detail?",
"layouts.new_folder_name": "New Folder Name:",
"layouts.delete_folder_confirm": "Layouts will be moved to root.",
"overrides.title": "Overrides",
"overrides.layer_name": "Layer Name",
"overrides.object_name": "Object Name",
"overrides.contains": "contains",
"overrides.starts_with": "starts with",
"overrides.ends_with": "ends with",
"overrides.remove_condition": "Remove This Condition",
"overrides.logic_all": "All Conditions Must Be Met",
"overrides.logic_any": "At Least One Condition Must Be Met",
"overrides.add_condition": "Add Another Condition",
"overrides.color": "Color",
"overrides.lineweight": "Line Weight",
"overrides.linetype": "Line Type",
"overrides.hatch": "Hatch Pattern",
"overrides.hatch_scale": "Hatch Scale",
"overrides.hatch_hint": "Hatch Override modifies only existing hatches.",
"overrides.conditions": "Conditions",
"overrides.overrides": "Overrides",
"overrides.combinations": "Override Combinations",
"overrides.no_combination": "— new / none —",
"overrides.save_as_combination": "Save as Combination…",
"overrides.save_changes_to": "Save Changes to \"{name}\"",
"overrides.save_current_as_new": "Save Current Rules as New Combination",
"overrides.delete_combination_confirm": "Permanently delete combination \"{name}\"?",
"overrides.delete_combination": "Permanently Delete Selected Combination",
"overrides.insert_empty": "Insert Empty Rule Above",
"overrides.from_template": "+ From Template…",
"overrides.insert_from_template": "Insert Rule from Template",
"overrides.delete_template": "Delete Template",
"overrides.additive_hint": "Rules are additive. In case of conflict, the topmost wins.",
"overrides.empty": "No Rules Yet.",
"overrides.empty_hint": "Click above to create a new rule.",
"overrides.activate": "Activate",
"overrides.deactivate": "Deactivate",
"overrides.prio_up": "Higher Priority (Up)",
"overrides.prio_down": "Lower Priority (Down)",
"overrides.save_as_template": "Save as Template…",
"overrides.template_name": "Name for Template:",
"overrides.delete_rule_confirm": "Delete rule \"{name}\"?",
"overrides.rule_active": "Rule Active",
"overrides.logic_label": "Logic:",
"overrides.key": "Key",
"overrides.value": "Value",
"overrides.condition": "Condition",
"overrides.empty_value": "(empty)",
"toolbar.shortcuts_hint": "Shortcuts (Shift+Click = About Dossier)",
"toolbar.app_settings": "Dossier Settings",
"toolbar.project_settings": "Project Settings",
"toolbar.overrides_on": "Graphic Overrides Active — click to turn off",
"toolbar.overrides_off": "Graphic Overrides Off",
"toolbar.open_overrides": "Open Overrides Editor",
"toolbar.zoom_snap": "Snap Zoom to 1:{scale}",
"toolbar.zoom_select_scale": "Select a Scale First",
"toolbar.zoom_all": "Zoom to All Content",
"toolbar.zoom_selection": "Zoom to Selection",
"toolbar.refs_show": "Show Reference Lines",
"toolbar.refs_hide": "Hide Reference Lines",
"toolbar.osnap_on": "Object Snap On — click to turn off",
"toolbar.osnap_off": "Object Snap Off — click to turn on",
"toolbar.grid_toggle": "Toggle Construction Grid",
"toolbar.print_view_on": "Print View Active — click to turn off",
"toolbar.print_view_off": "Show Line Weights (Print View)",
"toolbar.set_scale": "Set Scale",
"toolbar.live_scale": "Current Live Scale",
"toolbar.no_scale": "No Scale Set",
"toolbar.perspective_no_scale": "Perspective — No Scale",
"toolbar.scale_input": "Enter Scale",
"toolbar.font": "Font",
"toolbar.text_height": "Text Height (m)",
"toolbar.bold": "Bold",
"toolbar.italic": "Italic",
"toolbar.insert_text": "Insert New Text",
"toolbar.measures_edit": "Edit Measures / Create New",
"toolbar.style_save": "+ Save…",
"toolbar.style_delete": "Delete Active",
"toolbar.bring_front": "Bring to Front",
"toolbar.bring_forward": "Bring Forward",
"toolbar.send_backward": "Send Backward",
"toolbar.send_back": "Send to Back",
"toolbar.camera_settings": "Camera Settings",
"toolbar.detail_level_simple": "Simple (1:100)",
"toolbar.detail_level_standard": "Standard (1:50)",
"toolbar.detail_level_detail": "Detail (1:20)",
"tools.drawing_2d": "2D Drawing",
"tools.editing_2d": "2D Editing",
"tools.modeling_3d": "3D Modeling",
"tools.selection": "Selection",
"tools.line": "Line",
"tools.polyline": "Polyline",
"tools.rectangle": "Rectangle",
"tools.circle": "Circle",
"tools.arc": "Arc",
"tools.spline": "Free Curve (Spline)",
"tools.text": "Text",
"tools.hatch": "Hatch",
"tools.dimension": "Linear Dimension",
"tools.move": "Move",
"tools.copy": "Copy",
"tools.rotate": "Rotate",
"tools.scale": "Scale",
"tools.mirror": "Mirror",
"tools.offset": "Offset",
"tools.trim": "Trim",
"tools.extend": "Extend",
"tools.join": "Join",
"tools.explode": "Explode",
"tools.fillet": "Fillet",
"tools.array": "Polar Array",
"tools.extrude": "Extrude Curve to 3D",
"tools.box": "Box",
"tools.bool_union": "Boolean Union",
"tools.bool_diff": "Boolean Difference",
"tools.bool_intersect": "Boolean Intersection",
"tools.close_holes": "Close Planar Holes",
"tools.section_lines": "Create Section Lines",
"tools.loft": "Loft",
"tools.select_tangent": "Select Tangent Curve Chain",
"tools.select_duplicates": "Select Duplicate Objects",
"tools.select_closed": "Select Closed Curves",
"tools.invert_selection": "Invert Selection",
"tools.select_all": "Select All",
"tools.deselect_all": "Deselect All",
"dimensions.no_selection": "No Selection",
"dimensions.no_selection_hint": "Select one or more objects in Rhino.",
"dimensions.world_coords": "World Coordinates",
"dimensions.active_cplane": "Active Construction Plane",
"dimensions.rotate_hint": "Rotate selection around Z-axis of active plane",
"dimensions.rotate_ccw": "90° Counterclockwise",
"dimensions.rotate_cw": "90° Clockwise",
"measures.title": "Measures",
"measures.subtitle": "Global defaults for room rounding and measure lines",
"measures.active": "Active",
"measures.new": "Create New Measure",
"measures.delete_active": "Delete Active Measure",
"measures.name": "Name",
"measures.room_rounding": "Room Rounding",
"measures.decimal_places": "Decimal Places",
"measures.unit": "Unit",
"measures.hint": "Changes apply immediately to all rooms.",
"measures.delete_confirm": "Delete measure \"{name}\"?",
"measures.min_one": "At least one measure must remain.",
"measures.new_name": "Name for new measure:",
"measures.new_name_placeholder": "Name for new measure:",
"about.tagline": "Architecture Studio for Rhino 8",
"about.part_of": "Part of",
"about.author": "Author",
"about.web": "Web",
"about.license": "License · Dual",
"about.license_open": "Freely usable, modifiable and distributable under AGPL-3.0.",
"about.license_commercial": "Commercial License",
"about.license_commercial_hint": "For proprietary integrations — contact: karim@gabrielevarano.ch",
"about.made_in": "made in Switzerland"
}
+18
View File
@@ -0,0 +1,18 @@
import de from './de.json'
import en from './en.json'
const _langs = { de, en }
function _getLang() {
return (typeof window !== 'undefined' && window.DOSSIER_LANG) || 'de'
}
export function t(key, vars) {
const lang = _getLang()
const dict = _langs[lang] || _langs.de
const str = dict[key] ?? _langs.de[key] ?? key
if (!vars) return str
return str.replace(/\{(\w+)\}/g, (_, k) => (vars[k] ?? `{${k}}`))
}
export function getLang() { return _getLang() }
+38 -30
View File
@@ -3,63 +3,71 @@
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import './index.css' import './index.css'
// Set language before any component renders
if (typeof window !== 'undefined' && window.DOSSIER_LANG == null) {
window.DOSSIER_LANG = 'de'
}
import App from './App.jsx' import App from './App.jsx'
import ZeichnungsebenenApp from './ZeichnungsebenenApp.jsx' import DrawingLevelsApp from './DrawingLevelsApp.jsx'
import GeschossSettingsApp from './GeschossSettingsApp.jsx' import FloorSettingsApp from './FloorSettingsApp.jsx'
import FloorDialogApp from './FloorDialogApp.jsx'
import ProjectSettingsApp from './ProjectSettingsApp.jsx' import ProjectSettingsApp from './ProjectSettingsApp.jsx'
import DossierSettingsApp from './DossierSettingsApp.jsx'
import LibraryApp from './LibraryApp.jsx' import LibraryApp from './LibraryApp.jsx'
import SymbolPickerApp from './SymbolPickerApp.jsx' import SymbolPickerApp from './SymbolPickerApp.jsx'
import EbenenSettingsApp from './EbenenSettingsApp.jsx' import LayerSettingsApp from './LayerSettingsApp.jsx'
import GeschossDialogApp from './GeschossDialogApp.jsx'
import LayerCombinationsApp from './LayerCombinationsApp.jsx' import LayerCombinationsApp from './LayerCombinationsApp.jsx'
import AusschnittSettingsApp from './AusschnittSettingsApp.jsx' import ViewportSettingsApp from './ViewportSettingsApp.jsx'
import LayoutDialogApp from './LayoutDialogApp.jsx' import LayoutDialogApp from './LayoutDialogApp.jsx'
import SwisstopoApp from './SwisstopoApp.jsx' import SwisstopoApp from './SwisstopoApp.jsx'
import OsmApp from './OsmApp.jsx' import OsmApp from './OsmApp.jsx'
import KameraApp from './KameraApp.jsx' import CameraApp from './CameraApp.jsx'
import MasseSettingsApp from './MasseSettingsApp.jsx' import UnitsSettingsApp from './UnitsSettingsApp.jsx'
import AboutApp from './AboutApp.jsx' import AboutApp from './AboutApp.jsx'
import TextEditorApp from './TextEditorApp.jsx' import TextEditorApp from './TextEditorApp.jsx'
import GestaltungApp from './GestaltungApp.jsx' import StylesApp from './StylesApp.jsx'
import AusschnitteApp from './AusschnitteApp.jsx' import ViewportsApp from './ViewportsApp.jsx'
import MassstabApp from './MassstabApp.jsx' import ScaleApp from './ScaleApp.jsx'
import WerkzeugeApp from './WerkzeugeApp.jsx' import ToolsApp from './ToolsApp.jsx'
import OberleisteApp from './OberleisteApp.jsx' import ToolbarApp from './ToolbarApp.jsx'
import OverridesApp from './OverridesApp.jsx' import OverridesApp from './OverridesApp.jsx'
import DimensionenApp from './DimensionenApp.jsx' import DimensionsApp from './DimensionsApp.jsx'
import LayoutsApp from './LayoutsApp.jsx' import LayoutsApp from './LayoutsApp.jsx'
import ElementeApp from './ElementeApp.jsx' import ElementeApp from './ElementeApp.jsx'
import ElementeUebersichtApp from './ElementeUebersichtApp.jsx' import ElementsOverviewApp from './ElementsOverviewApp.jsx'
import ElementePropertiesApp from './ElementePropertiesApp.jsx' import ElementPropertiesApp from './ElementPropertiesApp.jsx'
const mode = (typeof window !== 'undefined' && window.PANEL_MODE) || 'ebenen' const mode = (typeof window !== 'undefined' && window.PANEL_MODE) || 'ebenen'
const RootApp = mode === 'gestaltung' ? GestaltungApp const RootApp = mode === 'gestaltung' ? StylesApp
: mode === 'ausschnitte' ? AusschnitteApp : mode === 'ausschnitte' ? ViewportsApp
: mode === 'massstab' ? MassstabApp : mode === 'massstab' ? ScaleApp
: mode === 'werkzeuge' ? WerkzeugeApp : mode === 'werkzeuge' ? ToolsApp
: mode === 'oberleiste' ? OberleisteApp : mode === 'oberleiste' ? ToolbarApp
: mode === 'overrides' ? OverridesApp : mode === 'overrides' ? OverridesApp
: mode === 'dimensionen' ? DimensionenApp : mode === 'dimensionen' ? DimensionsApp
: mode === 'layouts' ? LayoutsApp : mode === 'layouts' ? LayoutsApp
: mode === 'elemente' ? ElementeApp : mode === 'elemente' ? ElementeApp
: mode === 'zeichnungsebenen' ? ZeichnungsebenenApp : mode === 'zeichnungsebenen' ? DrawingLevelsApp
: mode === 'geschoss_settings' ? GeschossSettingsApp : mode === 'geschoss_settings' ? FloorSettingsApp
: mode === 'project_settings' ? ProjectSettingsApp : mode === 'project_settings' ? ProjectSettingsApp
: mode === 'dossier_settings' ? DossierSettingsApp
: mode === 'library' ? LibraryApp : mode === 'library' ? LibraryApp
: mode === 'symbol_picker' ? SymbolPickerApp : mode === 'symbol_picker' ? SymbolPickerApp
: mode === 'ebenen_settings' ? EbenenSettingsApp : mode === 'ebenen_settings' ? LayerSettingsApp
: mode === 'geschoss_dialog' ? GeschossDialogApp : mode === 'geschoss_dialog' ? FloorDialogApp
: mode === 'layer_combinations' ? LayerCombinationsApp : mode === 'layer_combinations' ? LayerCombinationsApp
: mode === 'ausschnitt_settings' ? AusschnittSettingsApp : mode === 'ausschnitt_settings' ? ViewportSettingsApp
: mode === 'layout_dialog' ? LayoutDialogApp : mode === 'layout_dialog' ? LayoutDialogApp
: mode === 'swisstopo' ? SwisstopoApp : mode === 'swisstopo' ? SwisstopoApp
: mode === 'osm' ? OsmApp : mode === 'osm' ? OsmApp
: mode === 'kamera' ? KameraApp : mode === 'kamera' ? CameraApp
: mode === 'masse_settings' ? MasseSettingsApp : mode === 'masse_settings' ? UnitsSettingsApp
: mode === 'about' ? AboutApp : mode === 'about' ? AboutApp
: mode === 'text_editor' ? TextEditorApp : mode === 'text_editor' ? TextEditorApp
: mode === 'elemente_uebersicht' ? ElementeUebersichtApp : mode === 'elemente_uebersicht' ? ElementsOverviewApp
: mode === 'elemente_properties' ? ElementePropertiesApp : mode === 'elemente_properties' ? ElementPropertiesApp
: App : App
document.addEventListener('contextmenu', e => e.preventDefault(), true) document.addEventListener('contextmenu', e => e.preventDefault(), true)