From 206672a858df2e4bb13dbaa2b495a3bd0daf27cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Sun, 8 Mar 2026 20:07:01 +0100 Subject: [PATCH] i18n(frontend): translate all German UI strings to English MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace German labels, button text, toast messages, table headers, tooltips, and placeholder strings across 7 files: - WorkflowEditor: buttons, toasts, node labels - Tenants: buttons, toasts, dialog text, table headers - Admin: widget layout description - OrderDetail: column headers (Baureihe→Series, Ebene→Level, Lagertyp→Bearing Type) - ExcelSpreadsheet: column label definitions - Upload: series/duplicate warning strings - TemplateEditor: ALL_FIELD_DEFS default labels API field names (baureihe, ebene1, produkt_baureihe etc.) unchanged. Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/admin/TemplateEditor.tsx | 16 ++-- .../components/upload/ExcelSpreadsheet.tsx | 14 ++-- frontend/src/pages/Admin.tsx | 10 +-- frontend/src/pages/OrderDetail.tsx | 48 ++++++------ frontend/src/pages/Tenants.tsx | 74 +++++++++---------- frontend/src/pages/Upload.tsx | 18 ++--- frontend/src/pages/WorkflowEditor.tsx | 64 ++++++++-------- 7 files changed, 122 insertions(+), 122 deletions(-) diff --git a/frontend/src/components/admin/TemplateEditor.tsx b/frontend/src/components/admin/TemplateEditor.tsx index 2410dbd..50c6549 100644 --- a/frontend/src/components/admin/TemplateEditor.tsx +++ b/frontend/src/components/admin/TemplateEditor.tsx @@ -39,15 +39,15 @@ interface Template { // --------------------------------------------------------------------------- const ALL_FIELD_DEFS: { key: string; defaultLabel: string }[] = [ - { key: 'ebene1', defaultLabel: 'Ebene 1' }, - { key: 'ebene2', defaultLabel: 'Ebene 2' }, - { key: 'baureihe', defaultLabel: 'Baureihe' }, + { key: 'ebene1', defaultLabel: 'Level 1' }, + { key: 'ebene2', defaultLabel: 'Level 2' }, + { key: 'baureihe', defaultLabel: 'Series' }, { key: 'pim_id', defaultLabel: 'PIM-ID' }, - { key: 'produkt_baureihe', defaultLabel: 'Produkt / Baureihe' }, - { key: 'gewaehltes_produkt', defaultLabel: 'Gewähltes Produkt' }, - { key: 'name_cad_modell', defaultLabel: 'Name CAD-Modell' }, - { key: 'gewuenschte_bildnummer',defaultLabel: 'Gewünschte Bildnummer' }, - { key: 'lagertyp', defaultLabel: 'Lagertyp' }, + { key: 'produkt_baureihe', defaultLabel: 'Product / Series' }, + { key: 'gewaehltes_produkt', defaultLabel: 'Selected Product' }, + { key: 'name_cad_modell', defaultLabel: 'CAD Model Name' }, + { key: 'gewuenschte_bildnummer',defaultLabel: 'Desired Image No.' }, + { key: 'lagertyp', defaultLabel: 'Bearing Type' }, { key: 'medias_rendering', defaultLabel: 'Medias Rendering' }, ] diff --git a/frontend/src/components/upload/ExcelSpreadsheet.tsx b/frontend/src/components/upload/ExcelSpreadsheet.tsx index 92966e8..dbaa183 100644 --- a/frontend/src/components/upload/ExcelSpreadsheet.tsx +++ b/frontend/src/components/upload/ExcelSpreadsheet.tsx @@ -8,15 +8,15 @@ interface Props { } const STANDARD_FIELDS: { key: keyof ParsedRow; label: string; width: number; mono?: boolean }[] = [ - { key: 'ebene1', label: 'Ebene 1', width: 140 }, - { key: 'ebene2', label: 'Ebene 2', width: 120 }, - { key: 'baureihe', label: 'Baureihe', width: 160 }, + { key: 'ebene1', label: 'Level 1', width: 140 }, + { key: 'ebene2', label: 'Level 2', width: 120 }, + { key: 'baureihe', label: 'Series', width: 160 }, { key: 'pim_id', label: 'PIM-ID', width: 110 }, - { key: 'produkt_baureihe', label: 'Produkt-Baureihe', width: 150 }, - { key: 'gewaehltes_produkt', label: 'Gewähltes Produkt', width: 150 }, + { key: 'produkt_baureihe', label: 'Product Series', width: 150 }, + { key: 'gewaehltes_produkt', label: 'Selected Product', width: 150 }, { key: 'name_cad_modell', label: 'CAD-Modell', width: 190, mono: true }, - { key: 'gewuenschte_bildnummer', label: 'Bildnummer', width: 170, mono: true }, - { key: 'lagertyp', label: 'Lagertyp', width: 100 }, + { key: 'gewuenschte_bildnummer', label: 'Image No.', width: 170, mono: true }, + { key: 'lagertyp', label: 'Bearing Type', width: 100 }, ] export default function ExcelSpreadsheet({ parsed, rows, onChange }: Props) { diff --git a/frontend/src/pages/Admin.tsx b/frontend/src/pages/Admin.tsx index 0d28f26..7330375 100644 --- a/frontend/src/pages/Admin.tsx +++ b/frontend/src/pages/Admin.tsx @@ -950,18 +950,18 @@ export default function AdminPage() {

Dashboard Widget-Konfiguration

- Legt das Standard-Widget-Layout für alle Nutzer dieses Tenants fest. Nutzer können ihr eigenes Layout individuell anpassen. + Sets the default widget layout for all users of this tenant. Users can customize their own layout individually.

- Tenant-Standard:{' '} + Tenant default:{' '} {tenantDefaultWidgets && tenantDefaultWidgets.length > 0 - ? `${tenantDefaultWidgets.length} Widget${tenantDefaultWidgets.length !== 1 ? 's' : ''} konfiguriert` - : 'Noch kein Standard festgelegt (Systemvorgabe aktiv)'} + ? `${tenantDefaultWidgets.length} Widget${tenantDefaultWidgets.length !== 1 ? 's' : ''} configured` + : 'No default set yet (system default active)'}

@@ -970,7 +970,7 @@ export default function AdminPage() { className="btn-secondary text-sm flex items-center gap-2" > - Tenant-Standard-Dashboard bearbeiten + Edit Tenant Default Dashboard
diff --git a/frontend/src/pages/OrderDetail.tsx b/frontend/src/pages/OrderDetail.tsx index e298c26..48e5a96 100644 --- a/frontend/src/pages/OrderDetail.tsx +++ b/frontend/src/pages/OrderDetail.tsx @@ -584,7 +584,7 @@ export default function OrderDetailPage() { updateFilter('search', e.target.value)} className="w-full pl-8 pr-3 py-1.5 text-sm border border-border-default rounded-md focus:outline-none focus:ring-2 focus:ring-accent" @@ -955,10 +955,10 @@ function OrderItemsTable({ Img - - Ebene 1 - Ebene 2 - Lagertyp + + Level 1 + Level 2 + Bearing Type Rendering Parts STEP @@ -1076,22 +1076,22 @@ function ItemTableRow({

{item.name_cad_modell || '—'}

- {/* Baureihe */} + {/* Series */}

{item.baureihe || '—'}

- {/* Ebene 1 */} + {/* Level 1 */}

{item.ebene1 || '—'}

- {/* Ebene 2 */} + {/* Level 2 */}

{item.ebene2 || '—'}

- {/* Lagertyp */} + {/* Bearing Type */}

{item.lagertyp || '—'}

@@ -1175,12 +1175,12 @@ function ItemTableRow({
- + - - - - + + + +
@@ -1350,15 +1350,15 @@ function GalleryCard({ item, isDraft, onClick }: { item: any; isDraft: boolean; // ── Source Spreadsheet ──────────────────────────────────────────────────────── const STD_COLS: { key: keyof OrderItem; label: string; editable?: boolean }[] = [ - { key: 'ebene1', label: 'Ebene 1', editable: true }, - { key: 'ebene2', label: 'Ebene 2', editable: true }, - { key: 'baureihe', label: 'Baureihe', editable: true }, + { key: 'ebene1', label: 'Level 1', editable: true }, + { key: 'ebene2', label: 'Level 2', editable: true }, + { key: 'baureihe', label: 'Series', editable: true }, { key: 'pim_id', label: 'PIM-ID', editable: true }, - { key: 'produkt_baureihe', label: 'Produkt/Baureihe',editable: true }, - { key: 'gewaehltes_produkt', label: 'Gew. Produkt', editable: true }, + { key: 'produkt_baureihe', label: 'Product/Series', editable: true }, + { key: 'gewaehltes_produkt', label: 'Sel. Product', editable: true }, { key: 'name_cad_modell', label: 'CAD-Modell', editable: true }, - { key: 'gewuenschte_bildnummer', label: 'Bildnummer', editable: true }, - { key: 'lagertyp', label: 'Lagertyp', editable: true }, + { key: 'gewuenschte_bildnummer', label: 'Image No.', editable: true }, + { key: 'lagertyp', label: 'Bearing Type', editable: true }, ] function SourceSpreadsheet({ @@ -1606,9 +1606,9 @@ function GalleryModal({ {/* Fields */}
- - - + + + diff --git a/frontend/src/pages/Tenants.tsx b/frontend/src/pages/Tenants.tsx index 4eb4b2d..a61f9fc 100644 --- a/frontend/src/pages/Tenants.tsx +++ b/frontend/src/pages/Tenants.tsx @@ -66,34 +66,34 @@ export default function TenantsPage() { const createMut = useMutation({ mutationFn: (data: TenantCreate) => createTenant(data), onSuccess: () => { - toast.success('Tenant erstellt') + toast.success('Tenant created') qc.invalidateQueries({ queryKey: ['tenants'] }) setShowCreate(false) setCreateForm({ name: '', slug: '', is_active: true }) setSlugEdited(false) }, - onError: (e: any) => toast.error(e.response?.data?.detail || 'Fehler beim Erstellen'), + onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to create'), }) const updateMut = useMutation({ mutationFn: ({ id, data }: { id: string; data: TenantUpdate }) => updateTenant(id, data), onSuccess: () => { - toast.success('Tenant aktualisiert') + toast.success('Tenant updated') qc.invalidateQueries({ queryKey: ['tenants'] }) setEditingTenant(null) }, - onError: (e: any) => toast.error(e.response?.data?.detail || 'Fehler beim Aktualisieren'), + onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to update'), }) const deleteMut = useMutation({ mutationFn: (id: string) => deleteTenant(id), onSuccess: (_data, id) => { - toast.success('Tenant gelöscht') + toast.success('Tenant deleted') qc.invalidateQueries({ queryKey: ['tenants'] }) if (activeTenantId === id) setActiveTenantId(null) setDeletingId(null) }, - onError: (e: any) => toast.error(e.response?.data?.detail || 'Fehler beim Löschen'), + onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to delete'), }) // Auto-generate slug from name unless manually edited @@ -123,7 +123,7 @@ export default function TenantsPage() {

Tenants

-

Mandanten verwalten und Kontext wählen

+

Manage tenants and select context

{/* Tenant Selector */}

- Admin Cross-Tenant-Ansicht + Admin Cross-Tenant View

@@ -159,7 +159,7 @@ export default function TenantsPage() { > {activeTenantId === null && } {activeTenantId !== null && } - Alle Tenants / Admin-Ansicht + All Tenants / Admin View {tenants.map((t) => ( ))} @@ -180,7 +180,7 @@ export default function TenantsPage() {
{activeTenant && (

- API-Requests werden mit Header X-Tenant-ID: {activeTenant.id} gesendet. + API requests are sent with header X-Tenant-ID: {activeTenant.id}.

)}
@@ -194,22 +194,22 @@ export default function TenantsPage() { Slug Status - Nutzer + Users - Erstellt + Created {isLoading && ( - Lade Tenants… + Loading tenants… )} {!isLoading && tenants.length === 0 && ( - Noch keine Tenants vorhanden. + No tenants yet. )} @@ -218,7 +218,7 @@ export default function TenantsPage() {
{activeTenantId === tenant.id && ( - + )} {tenant.name}
@@ -227,11 +227,11 @@ export default function TenantsPage() { {tenant.is_active ? ( - aktiv + active ) : ( - inaktiv + inactive )} @@ -244,14 +244,14 @@ export default function TenantsPage() { @@ -268,7 +268,7 @@ export default function TenantsPage() {
-

Neuer Tenant

+

New Tenant

@@ -292,13 +292,13 @@ export default function TenantsPage() {
handleCreateSlugChange(e.target.value)} - placeholder="z.B. schaeffler-gmbh" + placeholder="e.g. schaeffler-gmbh" className="w-full px-3 py-2 rounded-md border border-border-default bg-surface text-content text-sm font-mono focus:outline-none focus:ring-2 focus:ring-accent/50" />
@@ -313,7 +313,7 @@ export default function TenantsPage() { />
- Aktiv + Active
@@ -322,14 +322,14 @@ export default function TenantsPage() { onClick={() => setShowCreate(false)} className="px-4 py-2 text-sm rounded-md border border-border-default text-content-secondary hover:bg-surface-alt transition-colors" > - Abbrechen + Cancel
@@ -341,7 +341,7 @@ export default function TenantsPage() {
-

Tenant bearbeiten

+

Edit Tenant

@@ -390,14 +390,14 @@ export default function TenantsPage() { onClick={() => setEditingTenant(null)} className="px-4 py-2 text-sm rounded-md border border-border-default text-content-secondary hover:bg-surface-alt transition-colors" > - Abbrechen + Cancel
@@ -413,9 +413,9 @@ export default function TenantsPage() {
-

Tenant löschen?

+

Delete tenant?

- Diese Aktion kann nicht rückgängig gemacht werden. + This action cannot be undone.

@@ -424,14 +424,14 @@ export default function TenantsPage() { onClick={() => setDeletingId(null)} className="px-4 py-2 text-sm rounded-md border border-border-default text-content-secondary hover:bg-surface-alt transition-colors" > - Abbrechen + Cancel diff --git a/frontend/src/pages/Upload.tsx b/frontend/src/pages/Upload.tsx index ca3de23..7374e13 100644 --- a/frontend/src/pages/Upload.tsx +++ b/frontend/src/pages/Upload.tsx @@ -299,7 +299,7 @@ export default function UploadPage() {

No products have been created yet. This is a preview of what will happen when you finalize the order. - Each unique Produkt (Baureihe) in the Excel becomes one product in the library. + Each unique Product (Series) in the Excel becomes one product in the library.

@@ -321,14 +321,14 @@ export default function UploadPage() { icon={} value={previewResult.no_pim_id_count} label="rows skipped" - description="No PIM-ID or Baureihe found. Cannot be matched to a product." + description="No PIM-ID or Product Series found. Cannot be matched to a product." color="bg-status-warning-bg border-border-default text-status-warning-text" /> } value={previewResult.duplicate_count} - label="duplicate Baureihe" - description="Same Produkt-Baureihe appears multiple times. Pre-unchecked — only first occurrence imported." + label="duplicate Series" + description="Same Product Series appears multiple times. Pre-unchecked — only first occurrence imported." color="bg-status-warning-bg border-border-default text-status-warning-text" />
@@ -341,10 +341,10 @@ export default function UploadPage() {

- {previewResult.duplicate_count} duplicate Produkt-Baureihe row{previewResult.duplicate_count !== 1 ? 's' : ''} detected + {previewResult.duplicate_count} duplicate Product Series row{previewResult.duplicate_count !== 1 ? 's' : ''} detected

- Each product is unique — only the first occurrence of a Baureihe will be imported. + Each product is unique — only the first occurrence of a Product Series will be imported. Duplicate rows are pre-unchecked (shown in amber). You can manually re-check them to overwrite the first.

@@ -378,7 +378,7 @@ export default function UploadPage() { /> PIM-ID - Baureihe + Series Gew. Produkt @@ -424,14 +424,14 @@ export default function UploadPage() { {!hasId ? ( skipped ) : row.is_duplicate ? ( Duplicate of row {row.duplicate_of_row} diff --git a/frontend/src/pages/WorkflowEditor.tsx b/frontend/src/pages/WorkflowEditor.tsx index 9a2269e..2ada68f 100644 --- a/frontend/src/pages/WorkflowEditor.tsx +++ b/frontend/src/pages/WorkflowEditor.tsx @@ -81,7 +81,7 @@ function InputNode({ selected }: { selected?: boolean }) { label="STEP Input" icon={} color="green" - description="STEP-Datei Eingang" + description="STEP file input" selected={selected} hasTarget={false} /> @@ -91,7 +91,7 @@ function InputNode({ selected }: { selected?: boolean }) { function ConvertNode({ selected }: { selected?: boolean }) { return ( } color="blue" description="STEP → STL (cadquery)" @@ -144,7 +144,7 @@ function OutputNode({ data, selected }: { data: { label?: string }; selected?: b label={data.label ?? 'Output'} icon={} color="gray" - description="Ergebnis-Datei" + description="Output file" selected={selected} hasSource={false} /> @@ -168,7 +168,7 @@ function workflowToGraph(config: WorkflowConfig): { nodes: Node[]; edges: Edge[] if (config.type === 'still') { const nodes: Node[] = [ { id: 'input', type: 'inputNode', position: { x: 0, y: Y }, data: { label: 'STEP Input' } }, - { id: 'convert', type: 'convertNode', position: { x: 220, y: Y }, data: { label: 'STL Konvertierung' } }, + { id: 'convert', type: 'convertNode', position: { x: 220, y: Y }, data: { label: 'STL Conversion' } }, { id: 'render', type: 'renderNode', position: { x: 440, y: Y }, data: { label: 'Still Render', params: config.params } }, { id: 'output', type: 'outputNode', position: { x: 660, y: Y }, data: { label: 'PNG Output' } }, ] @@ -238,7 +238,7 @@ function ConfigSidepanel({ }) { return (
-

Node-Konfiguration

+

Node Configuration

{/* Render Engine */}
@@ -282,7 +282,7 @@ function ConfigSidepanel({ {/* Resolution */}
- +
{([[1024, 1024], [2048, 2048], [4096, 4096]] as [number, number][]).map(([w]) => ( @@ -378,7 +378,7 @@ function NewWorkflowModal({ onClose, onCreate, isLoading }: NewWorkflowModalProp setName(e.target.value)} autoFocus @@ -386,7 +386,7 @@ function NewWorkflowModal({ onClose, onCreate, isLoading }: NewWorkflowModalProp
- +
{([ { value: 'still', label: 'Still', desc: 'Single PNG image' }, @@ -417,14 +417,14 @@ function NewWorkflowModal({ onClose, onCreate, isLoading }: NewWorkflowModalProp onClick={onClose} className="px-4 py-2 text-sm rounded-lg border border-border-default text-content-secondary hover:bg-surface-hover" > - Abbrechen + Cancel
@@ -529,7 +529,7 @@ function FlowCanvas({ workflow, onSave, isSaving }: FlowCanvasProps) {
{/* Canvas Toolbar */}
- Nodes: + Nodes {NODE_PALETTE.map(item => (
- {isSaving ? 'Speichere…' : 'Speichern'} + {isSaving ? 'Saving…' : 'Save'}
@@ -605,9 +605,9 @@ export default function WorkflowEditor() { queryClient.invalidateQueries({ queryKey: ['workflows'] }) setSelectedId(wf.id) setShowNewModal(false) - toast.success('Workflow erstellt') + toast.success('Workflow created') }, - onError: () => toast.error('Fehler beim Erstellen'), + onError: () => toast.error('Failed to create workflow'), }) const updateMutation = useMutation({ @@ -615,9 +615,9 @@ export default function WorkflowEditor() { updateWorkflow(id, { config }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['workflows'] }) - toast.success('Workflow gespeichert') + toast.success('Workflow saved') }, - onError: () => toast.error('Fehler beim Speichern'), + onError: () => toast.error('Failed to save workflow'), }) const deleteMutation = useMutation({ @@ -625,9 +625,9 @@ export default function WorkflowEditor() { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['workflows'] }) setSelectedId(null) - toast.success('Workflow gelöscht') + toast.success('Workflow deleted') }, - onError: () => toast.error('Fehler beim Löschen'), + onError: () => toast.error('Failed to delete workflow'), }) const handleCreate = (name: string, type: WorkflowConfig['type']) => { @@ -675,7 +675,7 @@ export default function WorkflowEditor() { @@ -683,17 +683,17 @@ export default function WorkflowEditor() {
{isLoading && ( -

Lade…

+

Loading…

)} {!isLoading && workflows.length === 0 && (

- Noch keine Workflows. + No workflows yet.

)} @@ -712,12 +712,12 @@ export default function WorkflowEditor() { @@ -730,7 +730,7 @@ export default function WorkflowEditor() { {typeLabel[wf.config.type]} {!wf.is_active && ( - (inaktiv) + (inactive) )} ))} @@ -742,7 +742,7 @@ export default function WorkflowEditor() { {/* Header */}
-

Workflow-Editor

+

Workflow Editor

{selectedWorkflow && (

{selectedWorkflow.name}

)} @@ -752,7 +752,7 @@ export default function WorkflowEditor() { className="flex items-center gap-2 px-4 py-2 rounded-lg bg-accent text-white text-sm font-medium hover:bg-accent-hover transition-colors" > - Neuer Workflow + New Workflow
@@ -768,16 +768,16 @@ export default function WorkflowEditor() {
-

Kein Workflow ausgewählt

+

No workflow selected

- Wähle einen Workflow aus der Liste oder erstelle einen neuen. + Select a workflow from the list or create a new one.