Translate remaining frontend
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function CircuitTreeEditPage() {
|
||||
Zum Projekt
|
||||
</Link>
|
||||
<Link href={`/projects/${params.projectId}/circuit-lists`} className="btn btn-outline-secondary btn-sm">
|
||||
Legacy-Editor
|
||||
Bisherige Listenansicht
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,22 +13,22 @@ export default function CircuitTreePreviewPage() {
|
||||
<main className="container py-4">
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h1 className="h4 mb-1">Circuit Tree Preview</h1>
|
||||
<h1 className="h4 mb-1">Vorschau der Stromkreisstruktur</h1>
|
||||
<p className="text-secondary mb-0">
|
||||
Read-only preview of section blocks, circuits and device rows.
|
||||
Schreibgeschützte Vorschau der Bereiche, Stromkreise und Gerätezeilen.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={`/projects/${projectId}/circuit-lists/${circuitListId}/tree-edit`}
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
>
|
||||
Open editor
|
||||
Editor öffnen
|
||||
</Link>
|
||||
<Link
|
||||
href={`/projects/${projectId}/circuit-lists`}
|
||||
className="btn btn-outline-secondary btn-sm"
|
||||
>
|
||||
Back to legacy editor
|
||||
Zurück zur bisherigen Listenansicht
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
@@ -1084,7 +1084,7 @@ export default function ProjectDetailPage() {
|
||||
{syncPreview.rows.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={4} className="text-center text-secondary py-3">
|
||||
Keine verknüpften Circuit-First-Gerätezeilen vorhanden.
|
||||
Keine verknüpften Gerätezeilen im neuen Stromkreismodell vorhanden.
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
|
||||
@@ -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<HTMLElement>) {
|
||||
return event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey;
|
||||
}
|
||||
@@ -2587,7 +2597,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
>
|
||||
<strong>{device.displayName || device.name}</strong>
|
||||
<span>Name: {device.name}</span>
|
||||
<span>Phase: {device.phaseType}</span>
|
||||
<span>Phasenart: {formatPhaseTypeLabel(device.phaseType)}</span>
|
||||
<span>Anzahl: {device.quantity}</span>
|
||||
<span>Leistung/Gerät: {device.powerPerUnit}</span>
|
||||
<span>Gleichzeitigkeit: {device.simultaneityFactor}</span>
|
||||
|
||||
@@ -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 <div className="alert alert-info">Loading circuit tree...</div>;
|
||||
return <div className="alert alert-info">Stromkreisstruktur wird geladen …</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
@@ -51,12 +61,12 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st
|
||||
}
|
||||
|
||||
if (!data || !data.sections.length) {
|
||||
return <div className="alert alert-secondary">No sections or circuits available.</div>;
|
||||
return <div className="alert alert-secondary">Keine Bereiche oder Stromkreise vorhanden.</div>;
|
||||
}
|
||||
|
||||
const hasAnyCircuits = data.sections.some((section) => section.circuits.length > 0);
|
||||
if (!hasAnyCircuits) {
|
||||
return <div className="alert alert-secondary">Sections exist, but no circuits were found yet.</div>;
|
||||
return <div className="alert alert-secondary">Bereiche sind vorhanden, enthalten aber noch keine Stromkreise.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -66,28 +76,28 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st
|
||||
<table className="table table-sm align-middle circuit-tree-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Equipment identifier</th>
|
||||
<th>Display name</th>
|
||||
<th>Phase type</th>
|
||||
<th>Connection kind</th>
|
||||
<th>Cost group</th>
|
||||
<th>Category</th>
|
||||
<th>Level</th>
|
||||
<th>Room number</th>
|
||||
<th>Room name</th>
|
||||
<th className="text-end">Quantity</th>
|
||||
<th className="text-end">Power / unit</th>
|
||||
<th className="text-end">Simultaneity</th>
|
||||
<th className="text-end">cosPhi</th>
|
||||
<th className="text-end">Row total</th>
|
||||
<th className="text-end">Circuit total</th>
|
||||
<th>Protection type</th>
|
||||
<th className="text-end">Protection current</th>
|
||||
<th>Protection characteristic</th>
|
||||
<th>Cable type</th>
|
||||
<th>Cable cross-section</th>
|
||||
<th className="text-end">Cable length</th>
|
||||
<th>Remark</th>
|
||||
<th>Betriebsmittelkennzeichen</th>
|
||||
<th>Anzeigename</th>
|
||||
<th>Phasenart</th>
|
||||
<th>Anschlussart</th>
|
||||
<th>Kostengruppe</th>
|
||||
<th>Kategorie</th>
|
||||
<th>Ebene</th>
|
||||
<th>Raumnummer</th>
|
||||
<th>Raumname</th>
|
||||
<th className="text-end">Anzahl</th>
|
||||
<th className="text-end">Leistung / Gerät</th>
|
||||
<th className="text-end">Gleichzeitigkeit</th>
|
||||
<th className="text-end">cos φ</th>
|
||||
<th className="text-end">Zeilensumme</th>
|
||||
<th className="text-end">Stromkreissumme</th>
|
||||
<th>Schutzart</th>
|
||||
<th className="text-end">Bemessungsstrom</th>
|
||||
<th>Charakteristik</th>
|
||||
<th>Kabeltyp</th>
|
||||
<th>Kabelquerschnitt</th>
|
||||
<th className="text-end">Kabellänge</th>
|
||||
<th>Bemerkung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -136,7 +146,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
||||
<tr key={circuit.id} className={circuit.isReserve ? "reserve-row" : ""}>
|
||||
<td>{circuit.equipmentIdentifier}</td>
|
||||
<td>{row.displayName || row.name}</td>
|
||||
<td>{row.phaseType ?? "-"}</td>
|
||||
<td>{formatPhaseType(row.phaseType)}</td>
|
||||
<td>{row.connectionKind ?? "-"}</td>
|
||||
<td>{row.costGroup ?? "-"}</td>
|
||||
<td>{row.category ?? "-"}</td>
|
||||
@@ -179,7 +189,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
||||
<tr key={row.id} className="device-row">
|
||||
<td className="text-muted"> </td>
|
||||
<td className="indented-cell">{row.displayName || row.name}</td>
|
||||
<td>{row.phaseType ?? "-"}</td>
|
||||
<td>{formatPhaseType(row.phaseType)}</td>
|
||||
<td>{row.connectionKind ?? "-"}</td>
|
||||
<td>{row.costGroup ?? "-"}</td>
|
||||
<td>{row.category ?? "-"}</td>
|
||||
@@ -206,7 +216,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
||||
})}
|
||||
<tr className="placeholder-row">
|
||||
<td>-frei-</td>
|
||||
<td colSpan={21}>free placeholder</td>
|
||||
<td colSpan={21}>Freie Zeile</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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() {
|
||||
<form className="consumerForm" onSubmit={handleCreateConsumer}>
|
||||
<label>
|
||||
Verbraucher
|
||||
<input value={consumerForm.name} onChange={(event) => updateConsumerForm("name", event.target.value)} placeholder="z. B. Steckdosen Buero" />
|
||||
<input value={consumerForm.name} onChange={(event) => updateConsumerForm("name", event.target.value)} placeholder="z. B. Steckdosen Büro" />
|
||||
</label>
|
||||
<label>
|
||||
Kategorie
|
||||
@@ -435,7 +435,7 @@ export function PowerBalanceWorkspace() {
|
||||
<input min="0" type="number" value={consumerForm.quantity} onChange={(event) => updateConsumerForm("quantity", event.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
Leistung je Stueck [kW]
|
||||
Leistung je Stück [kW]
|
||||
<input min="0" step="0.01" type="number" value={consumerForm.installedPowerPerUnitKw} onChange={(event) => updateConsumerForm("installedPowerPerUnitKw", event.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
@@ -463,7 +463,7 @@ export function PowerBalanceWorkspace() {
|
||||
</label>
|
||||
<button className="primaryButton" type="submit" disabled={!selectedProjectId || isSaving}>
|
||||
<Plus size={17} />
|
||||
Hinzufuegen
|
||||
Hinzufügen
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
@@ -472,12 +472,12 @@ export function PowerBalanceWorkspace() {
|
||||
<div className="tableHeader">
|
||||
<div>
|
||||
<p className="eyebrow">Aktuelles Projekt</p>
|
||||
<h2>{selectedProject?.name || "Noch kein Projekt ausgewaehlt"}</h2>
|
||||
<h2>{selectedProject?.name || "Noch kein Projekt ausgewählt"}</h2>
|
||||
<p className="subline">{selectedBoard ? `Verteilung: ${selectedBoard.name}` : "Alle Verteilungen"}</p>
|
||||
</div>
|
||||
<div className="statusPill">
|
||||
<Activity size={16} />
|
||||
{isLoading ? "Laedt" : "Bereit"}
|
||||
{isLoading ? "Lädt" : "Bereit"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -505,7 +505,7 @@ export function PowerBalanceWorkspace() {
|
||||
{!totalsByBoard.length ? (
|
||||
<tr>
|
||||
<td colSpan={4} className="emptyState">
|
||||
Noch keine Verbraucher fuer eine Summenbildung vorhanden.
|
||||
Noch keine Verbraucher für eine Summenbildung vorhanden.
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
@@ -528,7 +528,7 @@ export function PowerBalanceWorkspace() {
|
||||
<th>Verteilung</th>
|
||||
<th>Kategorie</th>
|
||||
<th>Anzahl</th>
|
||||
<th>Leistung je Stueck [kW]</th>
|
||||
<th>Leistung je Stück [kW]</th>
|
||||
<th>Installierte Leistung [kW]</th>
|
||||
<th>GZF</th>
|
||||
<th>Berechnete Leistung [kW]</th>
|
||||
@@ -642,7 +642,7 @@ export function PowerBalanceWorkspace() {
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
)}
|
||||
<button className="iconButton small danger" type="button" title="Loeschen" onClick={() => handleDeleteConsumer(consumer.id)} disabled={isSaving}>
|
||||
<button className="iconButton small danger" type="button" title="Löschen" onClick={() => handleDeleteConsumer(consumer.id)} disabled={isSaving}>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user