From 375487c10c72fccd670052a918203efadd6f6ec5 Mon Sep 17 00:00:00 2001 From: karim Date: Sat, 6 Jun 2026 11:09:33 +0200 Subject: [PATCH] i18n DE/EN + DossierSettings panel + English file renames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- rhino/aliases/cmd/section.py | 2 +- rhino/aliases/cmd/smart_join.py | 2 +- rhino/aliases/cmd/smart_split.py | 2 +- rhino/ausschnitte.py | 8 +- rhino/{dimensionen.py => dimensions.py} | 0 rhino/elemente.py | 24 +- rhino/{rhinopanel.py => layers_panel.py} | 2 +- rhino/library.py | 2 +- rhino/panel_base.py | 18 +- rhino/schnitt_grips.py | 2 +- rhino/schnitte.py | 2 +- rhino/startup.py | 12 +- rhino/{gestaltung.py => styles.py} | 0 rhino/{oberleiste.py => toolbar.py} | 63 +++- rhino/{werkzeuge.py => tools.py} | 0 src/App.jsx | 2 +- src/{KameraApp.jsx => CameraApp.jsx} | 0 src/{DimensionenApp.jsx => DimensionsApp.jsx} | 0 src/DossierSettingsApp.jsx | 178 +++++++++++ ...ungsebenenApp.jsx => DrawingLevelsApp.jsx} | 2 +- ...ertiesApp.jsx => ElementPropertiesApp.jsx} | 0 ...ersichtApp.jsx => ElementsOverviewApp.jsx} | 0 ...schossDialogApp.jsx => FloorDialogApp.jsx} | 2 +- ...ssSettingsApp.jsx => FloorSettingsApp.jsx} | 2 +- src/LayerCombinationsApp.jsx | 2 +- ...enSettingsApp.jsx => LayerSettingsApp.jsx} | 2 +- src/LayoutsApp.jsx | 34 +- src/{MassstabApp.jsx => ScaleApp.jsx} | 0 src/{GestaltungApp.jsx => StylesApp.jsx} | 0 src/{OberleisteApp.jsx => ToolbarApp.jsx} | 0 src/{WerkzeugeApp.jsx => ToolsApp.jsx} | 0 ...seSettingsApp.jsx => UnitsSettingsApp.jsx} | 0 ...ettingsApp.jsx => ViewportSettingsApp.jsx} | 0 src/{AusschnitteApp.jsx => ViewportsApp.jsx} | 23 +- ...DeleteEbene.jsx => ConfirmDeleteLayer.jsx} | 0 .../{GeschossDialog.jsx => FloorDialog.jsx} | 0 .../{GeschossManager.jsx => FloorManager.jsx} | 35 +-- ...ingsDialog.jsx => FloorSettingsDialog.jsx} | 0 .../{EbenenManager.jsx => LayerManager.jsx} | 59 ++-- ...ingsDialog.jsx => LayerSettingsDialog.jsx} | 0 ...ayerDialog.jsx => ViewportLayerDialog.jsx} | 0 src/i18n/de.json | 290 ++++++++++++++++++ src/i18n/en.json | 290 ++++++++++++++++++ src/i18n/index.js | 18 ++ src/main.jsx | 68 ++-- 45 files changed, 998 insertions(+), 148 deletions(-) rename rhino/{dimensionen.py => dimensions.py} (100%) rename rhino/{rhinopanel.py => layers_panel.py} (99%) rename rhino/{gestaltung.py => styles.py} (100%) rename rhino/{oberleiste.py => toolbar.py} (97%) rename rhino/{werkzeuge.py => tools.py} (100%) rename src/{KameraApp.jsx => CameraApp.jsx} (100%) rename src/{DimensionenApp.jsx => DimensionsApp.jsx} (100%) create mode 100644 src/DossierSettingsApp.jsx rename src/{ZeichnungsebenenApp.jsx => DrawingLevelsApp.jsx} (98%) rename src/{ElementePropertiesApp.jsx => ElementPropertiesApp.jsx} (100%) rename src/{ElementeUebersichtApp.jsx => ElementsOverviewApp.jsx} (100%) rename src/{GeschossDialogApp.jsx => FloorDialogApp.jsx} (96%) rename src/{GeschossSettingsApp.jsx => FloorSettingsApp.jsx} (92%) rename src/{EbenenSettingsApp.jsx => LayerSettingsApp.jsx} (98%) rename src/{MassstabApp.jsx => ScaleApp.jsx} (100%) rename src/{GestaltungApp.jsx => StylesApp.jsx} (100%) rename src/{OberleisteApp.jsx => ToolbarApp.jsx} (100%) rename src/{WerkzeugeApp.jsx => ToolsApp.jsx} (100%) rename src/{MasseSettingsApp.jsx => UnitsSettingsApp.jsx} (100%) rename src/{AusschnittSettingsApp.jsx => ViewportSettingsApp.jsx} (100%) rename src/{AusschnitteApp.jsx => ViewportsApp.jsx} (93%) rename src/components/{ConfirmDeleteEbene.jsx => ConfirmDeleteLayer.jsx} (100%) rename src/components/{GeschossDialog.jsx => FloorDialog.jsx} (100%) rename src/components/{GeschossManager.jsx => FloorManager.jsx} (96%) rename src/components/{GeschossSettingsDialog.jsx => FloorSettingsDialog.jsx} (100%) rename src/components/{EbenenManager.jsx => LayerManager.jsx} (92%) rename src/components/{EbenenSettingsDialog.jsx => LayerSettingsDialog.jsx} (100%) rename src/components/{AusschnittLayerDialog.jsx => ViewportLayerDialog.jsx} (100%) create mode 100644 src/i18n/de.json create mode 100644 src/i18n/en.json create mode 100644 src/i18n/index.js 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)