From e4fd7e0df66f44fb482e286a047fa130b54cf3e7 Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Wed, 22 Jul 2026 23:25:53 +0200 Subject: [PATCH] Translate remaining frontend --- docs/spec/04-ui-interaction-requirements.md | 6 ++ .../[circuitListId]/tree-edit/page.tsx | 2 +- .../[circuitListId]/tree/page.tsx | 8 +-- .../[projectId]/circuit-lists/page.tsx | 4 +- src/app/projects/[projectId]/page.tsx | 4 +- .../components/circuit-tree-editor.tsx | 14 +++- .../components/circuit-tree-preview.tsx | 70 +++++++++++-------- .../components/power-balance-workspace.tsx | 18 ++--- src/frontend/utils/api.ts | 2 +- src/frontend/utils/circuit-grid-model.ts | 2 +- tests/circuit-grid-model.test.ts | 2 +- 11 files changed, 79 insertions(+), 53 deletions(-) diff --git a/docs/spec/04-ui-interaction-requirements.md b/docs/spec/04-ui-interaction-requirements.md index 3c35b3e..1b41b30 100644 --- a/docs/spec/04-ui-interaction-requirements.md +++ b/docs/spec/04-ui-interaction-requirements.md @@ -16,6 +16,12 @@ The editor should support both: - low-click mouse interaction - drag-and-drop workflows +## Interface Language + +All user-facing labels, descriptions, messages and actions are displayed in German. + +Code, API fields and stored technical values keep their English domain names. Technical values are translated only for display so persisted data remains stable. + ## Cell Editing Cells are displayed as static text by default. diff --git a/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree-edit/page.tsx b/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree-edit/page.tsx index 9d3ffc8..1027ec6 100644 --- a/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree-edit/page.tsx +++ b/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree-edit/page.tsx @@ -25,7 +25,7 @@ export default function CircuitTreeEditPage() { Zum Projekt - Legacy-Editor + Bisherige Listenansicht diff --git a/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree/page.tsx b/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree/page.tsx index 45225f6..600cf69 100644 --- a/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree/page.tsx +++ b/src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree/page.tsx @@ -13,22 +13,22 @@ export default function CircuitTreePreviewPage() {
-

Circuit Tree Preview

+

Vorschau der Stromkreisstruktur

- Read-only preview of section blocks, circuits and device rows. + Schreibgeschützte Vorschau der Bereiche, Stromkreise und Gerätezeilen.

- Open editor + Editor öffnen - Back to legacy editor + Zurück zur bisherigen Listenansicht
diff --git a/src/app/projects/[projectId]/circuit-lists/page.tsx b/src/app/projects/[projectId]/circuit-lists/page.tsx index 26786fc..60d880f 100644 --- a/src/app/projects/[projectId]/circuit-lists/page.tsx +++ b/src/app/projects/[projectId]/circuit-lists/page.tsx @@ -128,7 +128,7 @@ const allColumns: Array<{ key: ColumnKey; label: string }> = [ { key: "description", label: "Beschreibung" }, { key: "name", label: "Verbraucher" }, { key: "projectDevice", label: "Projektgerät" }, - { key: "deviceLink", label: "Link" }, + { key: "deviceLink", label: "Verknüpfung" }, { key: "room", label: "Raum" }, { key: "floor", label: "Etage" }, { key: "category", label: "Kategorie" }, @@ -646,7 +646,7 @@ export default function CircuitListsPage() { await reloadConsumers(); setBulkEditForms((current) => ({ ...current, [slotIndex]: { ...defaultBulkEditState } })); } catch (err) { - setError(err instanceof Error ? err.message : "Bulk-Änderung fehlgeschlagen."); + setError(err instanceof Error ? err.message : "Sammeländerung fehlgeschlagen."); } finally { setIsSaving(false); } diff --git a/src/app/projects/[projectId]/page.tsx b/src/app/projects/[projectId]/page.tsx index dc6163c..15af332 100644 --- a/src/app/projects/[projectId]/page.tsx +++ b/src/app/projects/[projectId]/page.tsx @@ -611,7 +611,7 @@ export default function ProjectDetailPage() { className="btn btn-sm btn-outline-secondary" href={`/projects/${projectId}/circuit-lists?boardId=${board.id}`} > - Legacy + Bisherige Ansicht @@ -1084,7 +1084,7 @@ export default function ProjectDetailPage() { {syncPreview.rows.length === 0 ? ( - Keine verknüpften Circuit-First-Gerätezeilen vorhanden. + Keine verknüpften Gerätezeilen im neuen Stromkreismodell vorhanden. ) : null} diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index 0d743c9..efe3af7 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -142,12 +142,22 @@ function normalizeUiError(err: unknown): string { if (message.includes("Duplicate equipmentIdentifier")) { return "Das Betriebsmittelkennzeichen ist in dieser Stromkreisliste bereits vorhanden."; } - if (message.includes("Invalid number")) { + if (message.includes("Invalid number") || message.includes("Ungültiger Zahlenwert")) { return "Bitte einen gültigen Zahlenwert eingeben."; } return message; } +function formatPhaseTypeLabel(value: string): string { + if (value === "three_phase") { + return "Dreiphasig"; + } + if (value === "single_phase") { + return "Einphasig"; + } + return value; +} + function isPrintableKey(event: KeyboardEvent) { return event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey; } @@ -2587,7 +2597,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str > {device.displayName || device.name} Name: {device.name} - Phase: {device.phaseType} + Phasenart: {formatPhaseTypeLabel(device.phaseType)} Anzahl: {device.quantity} Leistung/Gerät: {device.powerPerUnit} Gleichzeitigkeit: {device.simultaneityFactor} diff --git a/src/frontend/components/circuit-tree-preview.tsx b/src/frontend/components/circuit-tree-preview.tsx index 26b5c20..f584491 100644 --- a/src/frontend/components/circuit-tree-preview.tsx +++ b/src/frontend/components/circuit-tree-preview.tsx @@ -15,12 +15,22 @@ function formatNumber(value: number | undefined, digits = 2) { }).format(value); } +function formatPhaseType(value: string | undefined) { + if (value === "three_phase") { + return "Dreiphasig"; + } + if (value === "single_phase") { + return "Einphasig"; + } + return value ?? "-"; +} + function renderCircuitSummaryLabel(circuit: CircuitTreeCircuitDto) { if (circuit.displayName?.trim()) { return circuit.displayName; } if (circuit.deviceRows.length > 1) { - return `${circuit.deviceRows.length} devices`; + return `${circuit.deviceRows.length} Geräte`; } return "Reserve"; } @@ -37,13 +47,13 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st getCircuitTree(projectId, circuitListId) .then(setData) .catch((err: unknown) => - setError(err instanceof Error ? err.message : "Circuit tree could not be loaded.") + setError(err instanceof Error ? err.message : "Die Stromkreisstruktur konnte nicht geladen werden.") ) .finally(() => setIsLoading(false)); }, [projectId, circuitListId]); if (isLoading) { - return
Loading circuit tree...
; + return
Stromkreisstruktur wird geladen …
; } if (error) { @@ -51,12 +61,12 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st } if (!data || !data.sections.length) { - return
No sections or circuits available.
; + return
Keine Bereiche oder Stromkreise vorhanden.
; } const hasAnyCircuits = data.sections.some((section) => section.circuits.length > 0); if (!hasAnyCircuits) { - return
Sections exist, but no circuits were found yet.
; + return
Bereiche sind vorhanden, enthalten aber noch keine Stromkreise.
; } return ( @@ -66,28 +76,28 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -136,7 +146,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number - + @@ -179,7 +189,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number - + @@ -206,7 +216,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number })} - + ); diff --git a/src/frontend/components/power-balance-workspace.tsx b/src/frontend/components/power-balance-workspace.tsx index d135d0e..3a246d1 100644 --- a/src/frontend/components/power-balance-workspace.tsx +++ b/src/frontend/components/power-balance-workspace.tsx @@ -321,7 +321,7 @@ export function PowerBalanceWorkspace() { setEditConsumerForm(null); } } catch (err) { - setError(err instanceof Error ? err.message : "Verbraucher konnte nicht geloescht werden."); + setError(err instanceof Error ? err.message : "Verbraucher konnte nicht gelöscht werden."); } finally { setIsSaving(false); } @@ -424,7 +424,7 @@ export function PowerBalanceWorkspace() { @@ -472,12 +472,12 @@ export function PowerBalanceWorkspace() {

Aktuelles Projekt

-

{selectedProject?.name || "Noch kein Projekt ausgewaehlt"}

+

{selectedProject?.name || "Noch kein Projekt ausgewählt"}

{selectedBoard ? `Verteilung: ${selectedBoard.name}` : "Alle Verteilungen"}

- {isLoading ? "Laedt" : "Bereit"} + {isLoading ? "Lädt" : "Bereit"}
@@ -505,7 +505,7 @@ export function PowerBalanceWorkspace() { {!totalsByBoard.length ? ( ) : null} @@ -528,7 +528,7 @@ export function PowerBalanceWorkspace() { - + @@ -642,7 +642,7 @@ export function PowerBalanceWorkspace() { )} - diff --git a/src/frontend/utils/api.ts b/src/frontend/utils/api.ts index 8de198a..7736373 100644 --- a/src/frontend/utils/api.ts +++ b/src/frontend/utils/api.ts @@ -37,7 +37,7 @@ async function request(url: string, init?: RequestInit): Promise { if (!response.ok) { const details = await response.text(); - throw new Error(details || `Request failed with ${response.status}`); + throw new Error(details || `Anfrage fehlgeschlagen (Status ${response.status})`); } if (response.status === 204) { diff --git a/src/frontend/utils/circuit-grid-model.ts b/src/frontend/utils/circuit-grid-model.ts index 56908a5..b34233c 100644 --- a/src/frontend/utils/circuit-grid-model.ts +++ b/src/frontend/utils/circuit-grid-model.ts @@ -184,7 +184,7 @@ export function parseNumeric(cellKey: CellKey, draft: string): number | undefine } const parsed = Number(trimmed); if (Number.isNaN(parsed)) { - throw new Error(`Invalid number in ${cellKey}`); + throw new Error(`Ungültiger Zahlenwert in ${cellKey}`); } return parsed; } diff --git a/tests/circuit-grid-model.test.ts b/tests/circuit-grid-model.test.ts index 6c0af13..3b20ee9 100644 --- a/tests/circuit-grid-model.test.ts +++ b/tests/circuit-grid-model.test.ts @@ -83,6 +83,6 @@ describe("circuit grid model", () => { it("parses numeric drafts and rejects invalid values", () => { assert.equal(parseNumeric("quantity", " 2.5 "), 2.5); assert.equal(parseNumeric("quantity", ""), undefined); - assert.throws(() => parseNumeric("quantity", "two"), /Invalid number/); + assert.throws(() => parseNumeric("quantity", "two"), /Ungültiger Zahlenwert/); }); });
Equipment identifierDisplay namePhase typeConnection kindCost groupCategoryLevelRoom numberRoom nameQuantityPower / unitSimultaneitycosPhiRow totalCircuit totalProtection typeProtection currentProtection characteristicCable typeCable cross-sectionCable lengthRemarkBetriebsmittelkennzeichenAnzeigenamePhasenartAnschlussartKostengruppeKategorieEbeneRaumnummerRaumnameAnzahlLeistung / GerätGleichzeitigkeitcos φZeilensummeStromkreissummeSchutzartBemessungsstromCharakteristikKabeltypKabelquerschnittKabellängeBemerkung
{circuit.equipmentIdentifier} {row.displayName || row.name}{row.phaseType ?? "-"}{formatPhaseType(row.phaseType)} {row.connectionKind ?? "-"} {row.costGroup ?? "-"} {row.category ?? "-"}
{row.displayName || row.name}{row.phaseType ?? "-"}{formatPhaseType(row.phaseType)} {row.connectionKind ?? "-"} {row.costGroup ?? "-"} {row.category ?? "-"}
-frei-free placeholderFreie Zeile
- Noch keine Verbraucher fuer eine Summenbildung vorhanden. + Noch keine Verbraucher für eine Summenbildung vorhanden.
Verteilung Kategorie AnzahlLeistung je Stueck [kW]Leistung je Stück [kW] Installierte Leistung [kW] GZF Berechnete Leistung [kW]