diff --git a/rhino/aliases/cmd/section.py b/rhino/aliases/cmd/section.py
index ea594bb..d607572 100644
--- a/rhino/aliases/cmd/section.py
+++ b/rhino/aliases/cmd/section.py
@@ -20,7 +20,7 @@ else:
"cutAtLine": True, "namePrefix": "S",
}
try:
- import rhinopanel
+ import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc)
d = (ps or {}).get("defaults", {})
defaults["depthBack"] = float(d.get("schnittDepthBack", 8.0))
diff --git a/rhino/aliases/cmd/smart_join.py b/rhino/aliases/cmd/smart_join.py
index 0f5b027..4d2cb60 100644
--- a/rhino/aliases/cmd/smart_join.py
+++ b/rhino/aliases/cmd/smart_join.py
@@ -392,7 +392,7 @@ def _run():
# gestaltung fuer Fill-Re-Apply
_g = None
try:
- import gestaltung as _gmod; _g = _gmod
+ import styles as _gmod; _g = _gmod
except Exception as iex:
print("[SMART-JOIN] gestaltung import:", iex)
diff --git a/rhino/aliases/cmd/smart_split.py b/rhino/aliases/cmd/smart_split.py
index 851cbb9..1615b04 100644
--- a/rhino/aliases/cmd/smart_split.py
+++ b/rhino/aliases/cmd/smart_split.py
@@ -233,7 +233,7 @@ def _run():
_replicate_hatch(doc, nobj, hatch_props)
else:
try:
- import gestaltung as _gmod
+ import styles as _gmod
for nid in new_ids:
nobj = doc.Objects.FindId(nid)
if nobj is not None:
diff --git a/rhino/ausschnitte.py b/rhino/ausschnitte.py
index 6efe458..5be4d02 100644
--- a/rhino/ausschnitte.py
+++ b/rhino/ausschnitte.py
@@ -556,7 +556,7 @@ class AusschnittBridge(panel_base.BaseBridge):
kombi = (snap.get("layerCombination") or "").strip()
if kombi:
try:
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel.apply_layer_preset_by_name(doc, kombi)
except Exception as ex:
print("[AUSSCHNITTE] kombi-apply '{}':".format(kombi), ex)
@@ -565,7 +565,7 @@ class AusschnittBridge(panel_base.BaseBridge):
_apply_layers_global(doc, snap.get("layers", []))
# Eigene Sichtbarkeit → active_comb_name clearen
try:
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel.set_active_comb_name(doc, None)
rhinopanel._notify_oberleiste_combs()
except Exception: pass
@@ -785,7 +785,7 @@ class AusschnittBridge(panel_base.BaseBridge):
# Listen fuer Dropdowns
display_modes = []
try:
- import oberleiste
+ import toolbar as oberleiste
display_modes = oberleiste._list_display_modes()
except Exception as ex:
print("[AUSSCHNITTE] display_modes:", ex)
@@ -797,7 +797,7 @@ class AusschnittBridge(panel_base.BaseBridge):
print("[AUSSCHNITTE] overrides_presets:", ex)
layer_kombis = []
try:
- import rhinopanel
+ import layers_panel as rhinopanel
layer_kombis = rhinopanel.list_layer_preset_names(d)
except Exception as ex:
print("[AUSSCHNITTE] layer_kombis:", ex)
diff --git a/rhino/dimensionen.py b/rhino/dimensions.py
similarity index 100%
rename from rhino/dimensionen.py
rename to rhino/dimensions.py
diff --git a/rhino/elemente.py b/rhino/elemente.py
index 989ca20..76e975f 100644
--- a/rhino/elemente.py
+++ b/rhino/elemente.py
@@ -771,7 +771,7 @@ def _find_ebene_sublayer_name(doc, keywords, default_code, default_name,
print("[ELEMENTE] build_layers nach auto-add:", ex)
# Ebenen-Manager UI mit-informieren via broadcast_state
try:
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception as 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")
zlist = json.loads(z_raw) if z_raw else []
if zlist: layer_builder.build_layers(doc, zlist, ebenen)
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception as 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")
zlist = json.loads(z_raw) if z_raw else []
if zlist: layer_builder.build_layers(doc, zlist, ebenen)
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception as 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("textures", {})
try:
- import rhinopanel
+ import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) if doc else None
if isinstance(ps, dict):
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
konfiguriert (auch keine Defaults greifbar), leere Liste."""
try:
- import rhinopanel
+ import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) if doc else None
if isinstance(ps, dict):
return list(ps.get("wand_styles", []) or [])
@@ -13337,7 +13337,7 @@ class ElementeBridge(panel_base.BaseBridge):
# Projekt-Adresse als Vorschlag fuer die Adress-Suche
project_address = ""
try:
- import rhinopanel
+ import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) if doc else None
if isinstance(ps, dict):
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 []
if zlist:
layer_builder.build_layers(doc, zlist, ebenen)
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception as 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
# ebenen_bridge_ref + zeichnungsebenen_bridge_ref
try:
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception as ex:
self._push_log("broadcast_state: {}".format(ex))
@@ -14660,7 +14660,7 @@ class ElementeBridge(panel_base.BaseBridge):
if zlist:
import layer_builder
layer_builder.build_layers(doc, zlist, ebenen)
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception as 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:
print("[ELEMENTE] build_layers nach Plangrafik-Migration:", ex)
try:
- import rhinopanel as _rp
+ import layers_panel as _rp
_rp._broadcast_state(doc)
except Exception: pass
@@ -16789,7 +16789,7 @@ def _migrate_layer_caps_once(doc):
except Exception as ex:
print("[ELEMENTE] build_layers nach CAPS-Migration:", ex)
try:
- import rhinopanel as _rp
+ import layers_panel as _rp
_rp._broadcast_state(doc)
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
# Panel sonst nicht neu gerendert.
try:
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception: pass
except Exception as ex:
diff --git a/rhino/rhinopanel.py b/rhino/layers_panel.py
similarity index 99%
rename from rhino/rhinopanel.py
rename to rhino/layers_panel.py
index 89d9aa8..e6b591a 100644
--- a/rhino/rhinopanel.py
+++ b/rhino/layers_panel.py
@@ -2204,7 +2204,7 @@ class EbenenBridge(panel_base.BaseBridge):
# geaendert hat (nicht bei reinen Name/Farb-Aenderungen, die das
# Settings-Dialog auch triggern koennte).
try:
- import gestaltung
+ import styles as gestaltung
if fill_changed:
gestaltung.refresh_layer_fills(doc)
else:
diff --git a/rhino/library.py b/rhino/library.py
index 284cbd4..862bcc1 100644
--- a/rhino/library.py
+++ b/rhino/library.py
@@ -664,7 +664,7 @@ def import_material(doc, item):
"uvScaleM", "textures"):
if k in data: new_mat[k] = data[k]
# Lazy-Import um Zyklen zu vermeiden
- import rhinopanel
+ import layers_panel as rhinopanel
settings = rhinopanel.load_project_settings(doc)
mats = list(settings.get("materials", []))
for m in mats:
diff --git a/rhino/panel_base.py b/rhino/panel_base.py
index c1c8a50..e8a4e94 100644
--- a/rhino/panel_base.py
+++ b/rhino/panel_base.py
@@ -20,6 +20,21 @@ import scriptcontext as sc
_HERE = os.path.dirname(os.path.abspath(__file__))
_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 ------------------------------------------------
@@ -317,7 +332,8 @@ def load_inline(wv, mode, params=None):
_INLINE_TEMPLATE = (sig, tmpl)
# 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:
try:
parts.append('window.PANEL_PARAMS=' + json.dumps(params, ensure_ascii=False) + ';')
diff --git a/rhino/schnitt_grips.py b/rhino/schnitt_grips.py
index 14ea00e..ec3b683 100644
--- a/rhino/schnitt_grips.py
+++ b/rhino/schnitt_grips.py
@@ -155,7 +155,7 @@ def _update_linePts(doc, schnitt_id, new_p1, new_p2):
# Panel-Broadcast (linePts haben sich geaendert, Ebenen-Panel will
# ggf. mit-rendern)
try:
- import rhinopanel
+ import layers_panel as rhinopanel
rhinopanel._broadcast_state(doc)
except Exception: pass
try: doc.Views.Redraw()
diff --git a/rhino/schnitte.py b/rhino/schnitte.py
index e08c2cd..4f61370 100644
--- a/rhino/schnitte.py
+++ b/rhino/schnitte.py
@@ -609,7 +609,7 @@ def pick_schnitt_interactive(doc, defaults=None):
# Project-Defaults als 2-stufiges Fallback (defaults > project > hardcoded)
proj_d = {}
try:
- import rhinopanel
+ import layers_panel as rhinopanel
ps = rhinopanel.load_project_settings(doc) or {}
proj_d = ps.get("defaults", {}) or {}
except Exception: pass
diff --git a/rhino/startup.py b/rhino/startup.py
index 8698772..e9be639 100644
--- a/rhino/startup.py
+++ b/rhino/startup.py
@@ -44,13 +44,13 @@ _UI_FILE = os.path.join(_HERE, "DOSSIERUI.rhw")
# Muss synchron sein mit launcher/modules.json. Wenn neue Module dazukommen,
# beide Stellen pflegen.
_MODULE_TO_PY = {
- "ebenen": "rhinopanel",
- "oberleiste": "oberleiste",
+ "ebenen": "layers_panel",
+ "oberleiste": "toolbar",
"ausschnitte": "ausschnitte",
- "gestaltung": "gestaltung",
- "werkzeuge": "werkzeuge",
+ "gestaltung": "styles",
+ "werkzeuge": "tools",
"overrides": "overrides_panel",
- "dimensionen": "dimensionen",
+ "dimensionen": "dimensions",
"layouts": "layouts",
"elemente": "elemente",
}
@@ -209,7 +209,7 @@ def _check_doc_unit(doc):
return
except Exception: pass
try:
- import rhinopanel
+ import layers_panel as rhinopanel
target_unit_str = rhinopanel.get_project_unit(doc)
target_unit_enum = rhinopanel.get_project_unit_enum(doc)
except Exception as ex:
diff --git a/rhino/gestaltung.py b/rhino/styles.py
similarity index 100%
rename from rhino/gestaltung.py
rename to rhino/styles.py
diff --git a/rhino/oberleiste.py b/rhino/toolbar.py
similarity index 97%
rename from rhino/oberleiste.py
rename to rhino/toolbar.py
index f437cf1..8407245 100644
--- a/rhino/oberleiste.py
+++ b/rhino/toolbar.py
@@ -22,7 +22,7 @@ if _HERE not in sys.path:
import panel_base
import massstab
import overrides
-import rhinopanel
+import layers_panel as rhinopanel
PANEL_GUID_STR = "7e1f6a5b-8e2f-4f3c-d5e6-f70819203b51"
OVERRIDES_PANEL_GUID_STR = "8f2a7b6c-9f3a-4f4d-e6f7-08192a3c4d62"
@@ -1849,13 +1849,17 @@ class OberleisteBridge(panel_base.BaseBridge):
# --- Settings + Window-Layout -----------------------------------
elif t == "OPEN_SETTINGS":
- # Primaerweg: Dossier-Launcher (Tauri-App) oeffnen, dort lebt das
- # echte Settings-UI. Wenn der Launcher nicht installiert ist,
- # faellt es auf den Eto-Dialog zurueck.
- if not _launch_dossier_app():
- open_settings_dialog()
+ _open_dossier_settings_panel()
elif t == "GET_SETTINGS":
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":
name = (p.get("name") or "").strip()
if name: _apply_window_layout(name)
@@ -2195,6 +2199,53 @@ class OberleisteBridge(panel_base.BaseBridge):
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 --------------------------------------------------------
def _install_listeners(bridge):
diff --git a/rhino/werkzeuge.py b/rhino/tools.py
similarity index 100%
rename from rhino/werkzeuge.py
rename to rhino/tools.py
diff --git a/src/App.jsx b/src/App.jsx
index bb35abd..47b965f 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useState, useEffect, useMemo } from 'react'
-import EbenenManager from './components/EbenenManager'
+import EbenenManager from './components/LayerManager'
import {
applyAll, setActiveEbene,
onMessage, notifyReady, applyVisibility,
diff --git a/src/KameraApp.jsx b/src/CameraApp.jsx
similarity index 100%
rename from src/KameraApp.jsx
rename to src/CameraApp.jsx
diff --git a/src/DimensionenApp.jsx b/src/DimensionsApp.jsx
similarity index 100%
rename from src/DimensionenApp.jsx
rename to src/DimensionsApp.jsx
diff --git a/src/DossierSettingsApp.jsx b/src/DossierSettingsApp.jsx
new file mode 100644
index 0000000..d90774f
--- /dev/null
+++ b/src/DossierSettingsApp.jsx
@@ -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 (
+
+ {/* Header */}
+
+
+ {label('Dossier-Einstellungen', 'Dossier Settings')}
+
+ {saved && (
+
+
+ {label('Gespeichert', 'Saved')}
+
+ )}
+
+
+ {/* Body */}
+
+
+ {/* Language */}
+
+
+ {LANGS.map(l => (
+
+ ))}
+
+
+ {label('Gilt für alle Panels — Rhino neu laden um alle anzuwenden.', 'Applies to all panels — reload Rhino to apply everywhere.')}
+
+
+
+ {/* Launcher status */}
+
+
+
+
+ {launcherOk === true
+ ? label('Launcher verbunden', 'Launcher connected')
+ : launcherOk === false
+ ? label('Launcher nicht gefunden', 'Launcher not found')
+ : label('Unbekannt', 'Unknown')}
+
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+ )
+}
+
+function Section({ label, icon, children }) {
+ return (
+
+
+ {icon && }
+ {label}
+
+ {children}
+
+ )
+}
diff --git a/src/ZeichnungsebenenApp.jsx b/src/DrawingLevelsApp.jsx
similarity index 98%
rename from src/ZeichnungsebenenApp.jsx
rename to src/DrawingLevelsApp.jsx
index b6aabfd..878d53c 100644
--- a/src/ZeichnungsebenenApp.jsx
+++ b/src/DrawingLevelsApp.jsx
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useState, useEffect, useMemo } from 'react'
-import GeschossManager from './components/GeschossManager'
+import GeschossManager from './components/FloorManager'
import {
applyAll, setActiveZeichnungsebene,
onMessage, notifyReady, applyVisibility,
diff --git a/src/ElementePropertiesApp.jsx b/src/ElementPropertiesApp.jsx
similarity index 100%
rename from src/ElementePropertiesApp.jsx
rename to src/ElementPropertiesApp.jsx
diff --git a/src/ElementeUebersichtApp.jsx b/src/ElementsOverviewApp.jsx
similarity index 100%
rename from src/ElementeUebersichtApp.jsx
rename to src/ElementsOverviewApp.jsx
diff --git a/src/GeschossDialogApp.jsx b/src/FloorDialogApp.jsx
similarity index 96%
rename from src/GeschossDialogApp.jsx
rename to src/FloorDialogApp.jsx
index dc8c44c..70f8e6a 100644
--- a/src/GeschossDialogApp.jsx
+++ b/src/FloorDialogApp.jsx
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useEffect } from 'react'
-import GeschossDialog from './components/GeschossDialog'
+import GeschossDialog from './components/FloorDialog'
import { notifyReady, send as bridgeSend } from './lib/rhinoBridge'
// recalcOkff direkt hier — gleiche Logik wie in ZeichnungsebenenApp.jsx,
diff --git a/src/GeschossSettingsApp.jsx b/src/FloorSettingsApp.jsx
similarity index 92%
rename from src/GeschossSettingsApp.jsx
rename to src/FloorSettingsApp.jsx
index 6f739dc..bbebb8b 100644
--- a/src/GeschossSettingsApp.jsx
+++ b/src/FloorSettingsApp.jsx
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useEffect } from 'react'
-import GeschossSettingsDialog from './components/GeschossSettingsDialog'
+import GeschossSettingsDialog from './components/FloorSettingsDialog'
import { notifyReady, send as bridgeSend } from './lib/rhinoBridge'
export default function GeschossSettingsApp() {
diff --git a/src/LayerCombinationsApp.jsx b/src/LayerCombinationsApp.jsx
index 0e8b2db..6121cb2 100644
--- a/src/LayerCombinationsApp.jsx
+++ b/src/LayerCombinationsApp.jsx
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useState, useEffect } from 'react'
-import AusschnittLayerDialog from './components/AusschnittLayerDialog'
+import AusschnittLayerDialog from './components/ViewportLayerDialog'
import { onMessage, notifyReady } from './lib/rhinoBridge'
function send(type, payload) {
diff --git a/src/EbenenSettingsApp.jsx b/src/LayerSettingsApp.jsx
similarity index 98%
rename from src/EbenenSettingsApp.jsx
rename to src/LayerSettingsApp.jsx
index 5ceaf5e..6bb7393 100644
--- a/src/EbenenSettingsApp.jsx
+++ b/src/LayerSettingsApp.jsx
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
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'
export default function EbenenSettingsApp() {
diff --git a/src/LayoutsApp.jsx b/src/LayoutsApp.jsx
index 130a658..469a87a 100644
--- a/src/LayoutsApp.jsx
+++ b/src/LayoutsApp.jsx
@@ -3,6 +3,7 @@
import { useEffect, useState, useRef } from 'react'
import Icon from './components/Icon'
import ContextMenu from './components/ContextMenu'
+import { t } from './i18n/index.js'
import { BarButton, BarCombo, BAR_H } from './components/BarControls'
import {
onMessage, notifyReady,
@@ -181,69 +182,66 @@ export default function LayoutsApp() {
// Kontextmenue-Items pro Layout
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) },
- { label: 'Umbenennen', icon: 'edit',
+ { label: t('common.rename'), icon: 'edit',
onClick: () => setRenamingId(l.id) },
{ divider: true },
- { label: 'Als PDF exportieren', icon: 'picture_as_pdf',
+ { label: t('layouts.export_pdf'), icon: 'picture_as_pdf',
onClick: () => exportPdf(l.id, 300) },
- { label: 'Papierformat ändern', icon: 'aspect_ratio',
+ { label: t('layouts.change_paper'), icon: 'aspect_ratio',
onClick: () => openLayoutDialog('edit', {
id: l.id, name: l.name, width: l.widthMm, height: l.heightMm,
}) },
{ divider: true },
...(folders.length > 0 ? [
...folders.map(f => ({
- label: `Verschieben → ${f}`,
+ label: t('layouts.move_to', { folder: f }),
icon: 'folder',
disabled: l.folder === f,
onClick: () => setLayoutFolder(l.id, f),
})),
- { label: 'Aus Ordner entfernen',
+ { label: t('layouts.remove_from_folder'),
icon: 'folder_off',
disabled: !l.folder,
onClick: () => setLayoutFolder(l.id, '') },
{ divider: true },
] : []),
- { label: 'Löschen', icon: 'delete', danger: true,
+ { label: t('common.delete'), icon: 'delete', danger: true,
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 items = grouped[folderName] || []
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',
onClick: () => toggleFolderCollapse(folderName) },
{ divider: true },
- { label: 'Alle ankreuzen / abwählen',
+ { label: t('layouts.check_all'),
icon: 'check_box',
onClick: () => checkAllInFolder(items) },
- { label: `Ordner als PDF (${items.length})`,
+ { label: t('layouts.folder_pdf', { count: items.length }),
icon: 'picture_as_pdf',
disabled: items.length === 0,
onClick: () => handleExportFolder(folderName) },
{ divider: true },
- { label: 'Ordner umbenennen',
+ { label: t('common.rename_folder'),
icon: 'edit',
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) {
- // Atomar via Server-Side waere besser; simpler: alle Layouts
- // umhaengen + alten loeschen + neuen anlegen.
addLayoutFolder(next.trim())
items.forEach(l => setLayoutFolder(l.id, next.trim()))
removeLayoutFolder(folderName)
}
} },
- { label: 'Ordner loeschen',
+ { label: t('common.delete_folder'),
icon: 'folder_off', danger: true,
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)
} },
]
diff --git a/src/MassstabApp.jsx b/src/ScaleApp.jsx
similarity index 100%
rename from src/MassstabApp.jsx
rename to src/ScaleApp.jsx
diff --git a/src/GestaltungApp.jsx b/src/StylesApp.jsx
similarity index 100%
rename from src/GestaltungApp.jsx
rename to src/StylesApp.jsx
diff --git a/src/OberleisteApp.jsx b/src/ToolbarApp.jsx
similarity index 100%
rename from src/OberleisteApp.jsx
rename to src/ToolbarApp.jsx
diff --git a/src/WerkzeugeApp.jsx b/src/ToolsApp.jsx
similarity index 100%
rename from src/WerkzeugeApp.jsx
rename to src/ToolsApp.jsx
diff --git a/src/MasseSettingsApp.jsx b/src/UnitsSettingsApp.jsx
similarity index 100%
rename from src/MasseSettingsApp.jsx
rename to src/UnitsSettingsApp.jsx
diff --git a/src/AusschnittSettingsApp.jsx b/src/ViewportSettingsApp.jsx
similarity index 100%
rename from src/AusschnittSettingsApp.jsx
rename to src/ViewportSettingsApp.jsx
diff --git a/src/AusschnitteApp.jsx b/src/ViewportsApp.jsx
similarity index 93%
rename from src/AusschnitteApp.jsx
rename to src/ViewportsApp.jsx
index b1fc7a4..3ac45bc 100644
--- a/src/AusschnitteApp.jsx
+++ b/src/ViewportsApp.jsx
@@ -3,6 +3,7 @@
import { useState, useEffect, useMemo } from 'react'
import Icon from './components/Icon'
import ContextMenu from './components/ContextMenu'
+import { t } from './i18n/index.js'
import {
onMessage, notifyReady,
listAusschnitte, saveAusschnitt, updateAusschnitt,
@@ -287,25 +288,25 @@ export default function AusschnitteApp() {
}
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())
}
const ctxItems = (id) => [
- { label: 'Wiederherstellen', icon: 'restore', onClick: () => restoreAusschnitt(id) },
- { label: 'Auf Detail anwenden', icon: 'crop_landscape', onClick: () => applyAusschnittToDetail(id) },
+ { label: t('viewports.restore'), icon: 'restore', onClick: () => restoreAusschnitt(id) },
+ { label: t('viewports.apply_to_detail'),icon: 'crop_landscape', onClick: () => applyAusschnittToDetail(id) },
{ divider: true },
- { label: 'Ausschnittseinstellungen…', icon: 'tune', onClick: () => openAusschnittSettings(id) },
+ { label: t('viewports.settings'), icon: 'tune', onClick: () => openAusschnittSettings(id) },
{ divider: true },
- { label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicateAusschnitt(id) },
- { label: 'Aktualisieren', icon: 'sync', onClick: () => updateAusschnitt(id) },
+ { label: t('common.duplicate'), icon: 'content_copy', onClick: () => duplicateAusschnitt(id) },
+ { label: t('viewports.update'), icon: 'sync', onClick: () => updateAusschnitt(id) },
{ 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) => [
- { label: 'Ordner umbenennen', icon: 'edit', onClick: () => {
- const newName = window.prompt('Neuer Ordnername:', folderName)
+ { label: t('common.rename_folder'), icon: 'edit', onClick: () => {
+ const newName = window.prompt(t('common.new_folder_name'), folderName)
if (newName && newName.trim() && newName !== folderName) {
snaps.filter(s => s.folder === folderName).forEach(s => setAusschnittFolder(s.id, newName.trim()))
addAusschnittFolder(newName.trim())
@@ -313,8 +314,8 @@ export default function AusschnitteApp() {
}
}},
{ divider: true },
- { label: 'Ordner löschen', icon: 'folder_off', danger: true, onClick: () => {
- if (window.confirm(`Ordner "${folderName}" löschen? Ausschnitte werden zur Wurzel verschoben.`)) {
+ { label: t('common.delete_folder'), icon: 'folder_off', danger: true, onClick: () => {
+ if (window.confirm(`${t('common.delete_folder')} "${folderName}"? ${t('viewports.delete_folder_confirm')}`)) {
removeAusschnittFolder(folderName)
}
}},
diff --git a/src/components/ConfirmDeleteEbene.jsx b/src/components/ConfirmDeleteLayer.jsx
similarity index 100%
rename from src/components/ConfirmDeleteEbene.jsx
rename to src/components/ConfirmDeleteLayer.jsx
diff --git a/src/components/GeschossDialog.jsx b/src/components/FloorDialog.jsx
similarity index 100%
rename from src/components/GeschossDialog.jsx
rename to src/components/FloorDialog.jsx
diff --git a/src/components/GeschossManager.jsx b/src/components/FloorManager.jsx
similarity index 96%
rename from src/components/GeschossManager.jsx
rename to src/components/FloorManager.jsx
index f9a4f69..8871790 100644
--- a/src/components/GeschossManager.jsx
+++ b/src/components/FloorManager.jsx
@@ -3,6 +3,7 @@
import { useState } from 'react'
import Icon from './Icon'
import ContextMenu from './ContextMenu'
+import { t } from '../i18n/index.js'
import { BarCombo, BarButton } from './BarControls'
import { openGeschossSettings, openGeschossDialog, createSchnitt } from '../lib/rhinoBridge'
@@ -150,12 +151,12 @@ function ZeichnungsebeneRow({
)
}
-const MODES = [
- { value: 'all_force', label: 'Alle anzeigen' },
- { value: 'all', label: 'Ausgewählte' },
- { value: 'active', label: 'Nur aktive' },
- { value: 'grey', label: 'Andere grau' },
- { value: 'grey_locked', label: 'Andere grau & gesperrt' },
+const MODES = () => [
+ { value: 'all_force', label: t('layers.show_all_mode') },
+ { value: 'all', label: t('layers.selected_mode') },
+ { value: 'active', label: t('layers.active_only_mode') },
+ { value: 'grey', label: t('layers.others_gray') },
+ { value: 'grey_locked', label: t('layers.others_gray_locked') },
]
export default function GeschossManager({
@@ -443,11 +444,11 @@ export default function GeschossManager({
const z = zeichnungsebenen.find(x => x.id === id)
if (!z) return []
return [
- { label: 'Einstellungen…', icon: 'settings', onClick: () => openGeschossSettings(z) },
+ { label: t('common.settings') + '…', icon: 'settings', onClick: () => openGeschossSettings(z) },
{ divider: true },
- { label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicate(id) },
+ { label: t('common.duplicate'), icon: 'content_copy', onClick: () => duplicate(id) },
{ divider: true },
- { label: 'Löschen', icon: 'delete', danger: true,
+ { label: t('common.delete'), icon: 'delete', danger: true,
disabled: zeichnungsebenen.length <= 1,
onClick: () => remove(id) },
]
@@ -461,22 +462,22 @@ export default function GeschossManager({
background: 'var(--bg-section)',
borderBottom: '1px solid var(--border-light)',
}}>
- Sichtbarkeit
+ {t('layers.visibility_mode')}
openGeschossDialog(zeichnungsebenen)}
gearIcon="settings"
- gearTitle="Einstellungen"
+ gearTitle={t('common.settings')}
onSecond={openAddMenu}
secondIcon="add"
- secondTitle="Hinzufuegen: Geschoss / Schnitt / Zeichnung"
+ secondTitle={t('floors.add')}
>
- {MODES.map(m => (
+ {MODES().map(m => (
))}
@@ -501,8 +502,7 @@ export default function GeschossManager({
if (mode === 'active' || mode === 'all_force') onModeChange('all')
}}
title={zeichnungsebenen.every(z => z.visible !== false)
- ? 'Alle Zeichnungsebenen ausblenden'
- : 'Alle Zeichnungsebenen einblenden'}
+ ? t('floors.hide_all') : t('floors.show_all')}
style={{ width: 16, height: 16,
opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }}
>
@@ -519,8 +519,7 @@ export default function GeschossManager({
onChange(zeichnungsebenen.map(z => ({ ...z, locked: !anyLocked })))
}}
title={zeichnungsebenen.every(z => z.locked === true)
- ? 'Alle Zeichnungsebenen entsperren'
- : 'Alle Zeichnungsebenen sperren'}
+ ? t('floors.unlock_all') : t('floors.lock_all')}
style={{ width: 14, height: 14 }}
>
[
+ { value: 'all_force', label: t('layers.show_all_mode') },
+ { value: 'all', label: t('layers.selected_mode') },
+ { value: 'active', label: t('layers.active_only_mode') },
+ { value: 'grey', label: t('layers.others_gray') },
+ { 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]
@@ -532,20 +533,20 @@ export default function EbenenManager({
}
const ctxItems = (code) => [
- { label: 'Ebeneneinstellungen…', icon: 'settings', onClick: () => {
+ { label: t('layers.settings'), icon: 'settings', onClick: () => {
const target = _findInTree(ebenen, code)
if (target) openEbenenSettings(target, hatchPatterns)
} },
{ divider: true },
- { label: 'Sub-Ebene hinzufügen…', icon: 'add', onClick: () => addChild(code) },
- { label: 'Selektion hierher übertragen', icon: 'move_down', onClick: () => moveSelectionToEbene(code) },
+ { label: t('layers.add_sub'), icon: 'add', onClick: () => addChild(code) },
+ { label: t('layers.move_selection'), icon: 'move_down', onClick: () => moveSelectionToEbene(code) },
{ divider: true },
- { label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicateEbene(code) },
- { label: 'Eigenschaften kopieren', icon: 'colorize', onClick: () => copyProps(code) },
- { label: 'Eigenschaften einfügen', icon: 'format_paint', onClick: () => pasteProps(code), disabled: !clipboard },
+ { label: t('common.duplicate'), icon: 'content_copy', onClick: () => duplicateEbene(code) },
+ { label: t('layers.copy_props'), icon: 'colorize', onClick: () => copyProps(code) },
+ { label: t('layers.paste_props'), icon: 'format_paint', onClick: () => pasteProps(code), disabled: !clipboard },
{ divider: true },
- { label: 'Löschen', icon: 'delete', onClick: () => handleDelete(code), danger: true,
- disabled: ebenen.length <= 1 },
+ { label: t('common.delete'), icon: 'delete', onClick: () => handleDelete(code), danger: true,
+ disabled: ebenen.length <= 1 },
]
return (
@@ -556,7 +557,7 @@ export default function EbenenManager({
background: 'var(--bg-section)',
borderBottom: '1px solid var(--border-light)',
}}>
- Ebenenkombination
+ {t('layers.combination')}
-
+
{layerCombinations.map(n => (
))}
-
+
{activeKombi && (
-
+
)}
-
+
@@ -607,19 +608,19 @@ export default function EbenenManager({
background: 'var(--bg-section)',
borderBottom: '1px solid var(--border-light)',
}}>
- Sichtbarkeit
+ {t('layers.visibility_mode')}
- {MODES.map(m => )}
+ {MODES().map(m => )}
@@ -642,7 +643,7 @@ export default function EbenenManager({
if (mode === 'active' || mode === 'all_force') onModeChange('all')
}}
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,
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)
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 }}
>
e.locked === true) ? 'lock' : 'lock_open'} size={11} />
diff --git a/src/components/EbenenSettingsDialog.jsx b/src/components/LayerSettingsDialog.jsx
similarity index 100%
rename from src/components/EbenenSettingsDialog.jsx
rename to src/components/LayerSettingsDialog.jsx
diff --git a/src/components/AusschnittLayerDialog.jsx b/src/components/ViewportLayerDialog.jsx
similarity index 100%
rename from src/components/AusschnittLayerDialog.jsx
rename to src/components/ViewportLayerDialog.jsx
diff --git a/src/i18n/de.json b/src/i18n/de.json
new file mode 100644
index 0000000..24ac16e
--- /dev/null
+++ b/src/i18n/de.json
@@ -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"
+}
diff --git a/src/i18n/en.json b/src/i18n/en.json
new file mode 100644
index 0000000..b0de66b
--- /dev/null
+++ b/src/i18n/en.json
@@ -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"
+}
diff --git a/src/i18n/index.js b/src/i18n/index.js
new file mode 100644
index 0000000..2ef8c1d
--- /dev/null
+++ b/src/i18n/index.js
@@ -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() }
diff --git a/src/main.jsx b/src/main.jsx
index 4356daa..d9fb4b9 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -3,63 +3,71 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
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 ZeichnungsebenenApp from './ZeichnungsebenenApp.jsx'
-import GeschossSettingsApp from './GeschossSettingsApp.jsx'
+import DrawingLevelsApp from './DrawingLevelsApp.jsx'
+import FloorSettingsApp from './FloorSettingsApp.jsx'
+import FloorDialogApp from './FloorDialogApp.jsx'
import ProjectSettingsApp from './ProjectSettingsApp.jsx'
+import DossierSettingsApp from './DossierSettingsApp.jsx'
import LibraryApp from './LibraryApp.jsx'
import SymbolPickerApp from './SymbolPickerApp.jsx'
-import EbenenSettingsApp from './EbenenSettingsApp.jsx'
-import GeschossDialogApp from './GeschossDialogApp.jsx'
+import LayerSettingsApp from './LayerSettingsApp.jsx'
import LayerCombinationsApp from './LayerCombinationsApp.jsx'
-import AusschnittSettingsApp from './AusschnittSettingsApp.jsx'
+import ViewportSettingsApp from './ViewportSettingsApp.jsx'
import LayoutDialogApp from './LayoutDialogApp.jsx'
import SwisstopoApp from './SwisstopoApp.jsx'
import OsmApp from './OsmApp.jsx'
-import KameraApp from './KameraApp.jsx'
-import MasseSettingsApp from './MasseSettingsApp.jsx'
+import CameraApp from './CameraApp.jsx'
+import UnitsSettingsApp from './UnitsSettingsApp.jsx'
import AboutApp from './AboutApp.jsx'
import TextEditorApp from './TextEditorApp.jsx'
-import GestaltungApp from './GestaltungApp.jsx'
-import AusschnitteApp from './AusschnitteApp.jsx'
-import MassstabApp from './MassstabApp.jsx'
-import WerkzeugeApp from './WerkzeugeApp.jsx'
-import OberleisteApp from './OberleisteApp.jsx'
+import StylesApp from './StylesApp.jsx'
+import ViewportsApp from './ViewportsApp.jsx'
+import ScaleApp from './ScaleApp.jsx'
+import ToolsApp from './ToolsApp.jsx'
+import ToolbarApp from './ToolbarApp.jsx'
import OverridesApp from './OverridesApp.jsx'
-import DimensionenApp from './DimensionenApp.jsx'
+import DimensionsApp from './DimensionsApp.jsx'
import LayoutsApp from './LayoutsApp.jsx'
import ElementeApp from './ElementeApp.jsx'
-import ElementeUebersichtApp from './ElementeUebersichtApp.jsx'
-import ElementePropertiesApp from './ElementePropertiesApp.jsx'
+import ElementsOverviewApp from './ElementsOverviewApp.jsx'
+import ElementPropertiesApp from './ElementPropertiesApp.jsx'
const mode = (typeof window !== 'undefined' && window.PANEL_MODE) || 'ebenen'
-const RootApp = mode === 'gestaltung' ? GestaltungApp
- : mode === 'ausschnitte' ? AusschnitteApp
- : mode === 'massstab' ? MassstabApp
- : mode === 'werkzeuge' ? WerkzeugeApp
- : mode === 'oberleiste' ? OberleisteApp
+const RootApp = mode === 'gestaltung' ? StylesApp
+ : mode === 'ausschnitte' ? ViewportsApp
+ : mode === 'massstab' ? ScaleApp
+ : mode === 'werkzeuge' ? ToolsApp
+ : mode === 'oberleiste' ? ToolbarApp
: mode === 'overrides' ? OverridesApp
- : mode === 'dimensionen' ? DimensionenApp
+ : mode === 'dimensionen' ? DimensionsApp
: mode === 'layouts' ? LayoutsApp
: mode === 'elemente' ? ElementeApp
- : mode === 'zeichnungsebenen' ? ZeichnungsebenenApp
- : mode === 'geschoss_settings' ? GeschossSettingsApp
+ : mode === 'zeichnungsebenen' ? DrawingLevelsApp
+ : mode === 'geschoss_settings' ? FloorSettingsApp
: mode === 'project_settings' ? ProjectSettingsApp
+ : mode === 'dossier_settings' ? DossierSettingsApp
: mode === 'library' ? LibraryApp
: mode === 'symbol_picker' ? SymbolPickerApp
- : mode === 'ebenen_settings' ? EbenenSettingsApp
- : mode === 'geschoss_dialog' ? GeschossDialogApp
+ : mode === 'ebenen_settings' ? LayerSettingsApp
+ : mode === 'geschoss_dialog' ? FloorDialogApp
: mode === 'layer_combinations' ? LayerCombinationsApp
- : mode === 'ausschnitt_settings' ? AusschnittSettingsApp
+ : mode === 'ausschnitt_settings' ? ViewportSettingsApp
: mode === 'layout_dialog' ? LayoutDialogApp
: mode === 'swisstopo' ? SwisstopoApp
: mode === 'osm' ? OsmApp
- : mode === 'kamera' ? KameraApp
- : mode === 'masse_settings' ? MasseSettingsApp
+ : mode === 'kamera' ? CameraApp
+ : mode === 'masse_settings' ? UnitsSettingsApp
: mode === 'about' ? AboutApp
: mode === 'text_editor' ? TextEditorApp
- : mode === 'elemente_uebersicht' ? ElementeUebersichtApp
- : mode === 'elemente_properties' ? ElementePropertiesApp
+ : mode === 'elemente_uebersicht' ? ElementsOverviewApp
+ : mode === 'elemente_properties' ? ElementPropertiesApp
: App
document.addEventListener('contextmenu', e => e.preventDefault(), true)