feat(i18n): migrate hardcoded German strings to t() across 104 components/pages — AST-based batch with re-parse gate, 423 new de.json keys; tsc clean; vitest failures byte-identical to clean-tree baseline (pre-existing)
This commit is contained in:
@@ -3,6 +3,7 @@ import { Select } from '@/components/ui/Select';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Code2, FormInput } from 'lucide-react';
|
||||
import type { WorkflowStep, WorkflowStepType } from '@/api/workflows';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const stepTypeOptions: { value: WorkflowStepType; label: string }[] = [
|
||||
{ value: 'action', label: 'Action' },
|
||||
@@ -53,6 +54,7 @@ export interface StepConfigPanelProps {
|
||||
}
|
||||
|
||||
export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const [mode, setMode] = useState<ConfigMode>('form');
|
||||
const [configText, setConfigText] = useState('');
|
||||
const [configError, setConfigError] = useState<string | undefined>(undefined);
|
||||
@@ -111,6 +113,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
};
|
||||
|
||||
const renderTypeForm = () => {
|
||||
const { t } = useTranslation();
|
||||
switch (step.type) {
|
||||
case 'wait':
|
||||
return (
|
||||
@@ -126,13 +129,13 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
e.target.value === '' ? undefined : Number(e.target.value)
|
||||
)
|
||||
}
|
||||
placeholder="z.B. 3600"
|
||||
placeholder={t('stepConfigPanel.zb3600')}
|
||||
/>
|
||||
<Input
|
||||
label="Resume-Zeitpunkt (ISO)"
|
||||
value={strVal('resume_at')}
|
||||
onChange={(e) => setConfig('resume_at', e.target.value || undefined)}
|
||||
placeholder="2026-08-18T09:00:00Z"
|
||||
placeholder={t('stepConfigPanel.20260818t090000z')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -151,7 +154,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
required
|
||||
value={strVal('url')}
|
||||
onChange={(e) => setConfig('url', e.target.value)}
|
||||
placeholder="https://api.example.com/webhook"
|
||||
placeholder={t('stepConfigPanel.httpsapiexamplecomwebhook')}
|
||||
/>
|
||||
</div>
|
||||
<JsonField
|
||||
@@ -168,7 +171,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
onChange={(e) => setConfig('body', e.target.value)}
|
||||
rows={3}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder='{"key": "value"}'
|
||||
placeholder={t('stepConfigPanel.keyvalue')}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
@@ -194,14 +197,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
required
|
||||
value={strVal('to')}
|
||||
onChange={(e) => setConfig('to', e.target.value)}
|
||||
placeholder="empfaenger@example.com"
|
||||
placeholder={t('stepConfigPanel.empfaengerexamplecom')}
|
||||
/>
|
||||
<Input
|
||||
label="Betreff"
|
||||
required
|
||||
value={strVal('subject')}
|
||||
onChange={(e) => setConfig('subject', e.target.value)}
|
||||
placeholder="Betreff"
|
||||
placeholder={t('stepConfigPanel.betreff')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -213,14 +216,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
onChange={(e) => setConfig('body', e.target.value)}
|
||||
rows={4}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="Nachrichtentext"
|
||||
placeholder={t('stepConfigPanel.nachrichtentext')}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
label="Account-ID (optional)"
|
||||
value={strVal('account_id')}
|
||||
onChange={(e) => setConfig('account_id', e.target.value || undefined)}
|
||||
placeholder="Standard-Konto wenn leer"
|
||||
placeholder={t('stepConfigPanel.standardkontowennleer')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -240,19 +243,19 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
label="Titel"
|
||||
value={strVal('title')}
|
||||
onChange={(e) => setConfig('title', e.target.value)}
|
||||
placeholder="Event-Titel"
|
||||
placeholder={t('stepConfigPanel.eventtitel')}
|
||||
/>
|
||||
<Input
|
||||
label="Start (ISO)"
|
||||
value={strVal('start')}
|
||||
onChange={(e) => setConfig('start', e.target.value)}
|
||||
placeholder="2026-08-18T09:00:00Z"
|
||||
placeholder={t('stepConfigPanel.20260818t090000z')}
|
||||
/>
|
||||
<Input
|
||||
label="Ende (ISO)"
|
||||
value={strVal('end')}
|
||||
onChange={(e) => setConfig('end', e.target.value)}
|
||||
placeholder="2026-08-18T10:00:00Z"
|
||||
placeholder={t('stepConfigPanel.20260818t100000z')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -261,7 +264,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
label="Event-ID"
|
||||
value={strVal('event_id')}
|
||||
onChange={(e) => setConfig('event_id', e.target.value)}
|
||||
placeholder="Event-UUID"
|
||||
placeholder={t('stepConfigPanel.eventuuid')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -282,7 +285,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
label="Suchbegriff"
|
||||
value={strVal('query')}
|
||||
onChange={(e) => setConfig('query', e.target.value)}
|
||||
placeholder="Suchbegriff"
|
||||
placeholder={t('stepConfigPanel.suchbegriff')}
|
||||
/>
|
||||
)}
|
||||
{action === 'download' && (
|
||||
@@ -290,7 +293,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
label="Datei-ID"
|
||||
value={strVal('file_id')}
|
||||
onChange={(e) => setConfig('file_id', e.target.value)}
|
||||
placeholder="Datei-UUID"
|
||||
placeholder={t('stepConfigPanel.dateiuuid')}
|
||||
/>
|
||||
)}
|
||||
{action === 'upload' && (
|
||||
@@ -299,13 +302,13 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
label="Dateiname"
|
||||
value={strVal('file_name')}
|
||||
onChange={(e) => setConfig('file_name', e.target.value)}
|
||||
placeholder="datei.pdf"
|
||||
placeholder={t('stepConfigPanel.dateipdf')}
|
||||
/>
|
||||
<Input
|
||||
label="Inhalt"
|
||||
value={strVal('content')}
|
||||
onChange={(e) => setConfig('content', e.target.value)}
|
||||
placeholder="Dateiinhalt"
|
||||
placeholder={t('stepConfigPanel.dateiinhalt')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -320,13 +323,13 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
required
|
||||
value={strVal('query')}
|
||||
onChange={(e) => setConfig('query', e.target.value)}
|
||||
placeholder="Suchbegriff"
|
||||
placeholder={t('stepConfigPanel.suchbegriff')}
|
||||
/>
|
||||
<Input
|
||||
label="Entity-Typ (optional)"
|
||||
value={strVal('entity_type')}
|
||||
onChange={(e) => setConfig('entity_type', e.target.value || undefined)}
|
||||
placeholder="contact, company, file, ..."
|
||||
placeholder={t('stepConfigPanel.contactcompanyfile')}
|
||||
/>
|
||||
<Input
|
||||
label="Limit"
|
||||
@@ -347,7 +350,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
required
|
||||
value={strVal('agent_id')}
|
||||
onChange={(e) => setConfig('agent_id', e.target.value)}
|
||||
placeholder="Agent-UUID"
|
||||
placeholder={t('stepConfigPanel.agentuuid')}
|
||||
/>
|
||||
<JsonField
|
||||
label="Input (JSON)"
|
||||
@@ -361,7 +364,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
onChange={(e) => setConfig('wait_for_completion', e.target.checked)}
|
||||
className="rounded border-secondary-300 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
Auf Abschluss warten
|
||||
{t('stepConfigPanel.aufabschlusswarten')}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
@@ -380,7 +383,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
label="Entity-ID"
|
||||
value={strVal('entity_id')}
|
||||
onChange={(e) => setConfig('entity_id', e.target.value)}
|
||||
placeholder="Entity-UUID"
|
||||
placeholder={t('stepConfigPanel.entityuuid')}
|
||||
/>
|
||||
)}
|
||||
{(action.includes('create') || action.includes('update')) && (
|
||||
@@ -397,14 +400,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 mb-1">
|
||||
Konfiguration (JSON)
|
||||
{t('stepConfigPanel.konfigurationjson')}
|
||||
</label>
|
||||
<textarea
|
||||
value={configText}
|
||||
onChange={(e) => handleConfigChange(e.target.value)}
|
||||
rows={5}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder='{"key": "value"}'
|
||||
placeholder={t('stepConfigPanel.keyvalue')}
|
||||
/>
|
||||
{configError && (
|
||||
<p className="mt-1 text-sm text-danger-600" role="alert">
|
||||
@@ -424,7 +427,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
required
|
||||
value={step.name}
|
||||
onChange={(e) => onChange({ ...step, name: e.target.value })}
|
||||
placeholder="z.B. Genehmigung einholen"
|
||||
placeholder={t('stepConfigPanel.zbgenehmigungeinholen')}
|
||||
/>
|
||||
<Select
|
||||
label="Typ"
|
||||
@@ -445,7 +448,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
onChange={(e) => onChange({ ...step, description: e.target.value || null })}
|
||||
rows={2}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="Optionale Beschreibung"
|
||||
placeholder={t('stepConfigPanel.optionalebeschreibung')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -484,14 +487,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
|
||||
) : (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 mb-1">
|
||||
Konfiguration (JSON)
|
||||
{t('stepConfigPanel.konfigurationjson')}
|
||||
</label>
|
||||
<textarea
|
||||
value={configText}
|
||||
onChange={(e) => handleConfigChange(e.target.value)}
|
||||
rows={5}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder='{"key": "value"}'
|
||||
placeholder={t('stepConfigPanel.keyvalue')}
|
||||
/>
|
||||
{configError && (
|
||||
<p className="mt-1 text-sm text-danger-600" role="alert">
|
||||
@@ -511,6 +514,7 @@ interface JsonFieldProps {
|
||||
}
|
||||
|
||||
function JsonField({ label, value, onChange }: JsonFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
const [text, setText] = useState(value);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
@@ -543,7 +547,7 @@ function JsonField({ label, value, onChange }: JsonFieldProps) {
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
rows={3}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder='{"key": "value"}'
|
||||
placeholder={t('stepConfigPanel.keyvalue')}
|
||||
/>
|
||||
{error && (
|
||||
<p className="mt-1 text-sm text-danger-600" role="alert">
|
||||
|
||||
@@ -391,7 +391,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
}`}
|
||||
aria-pressed={mode === 'json'}
|
||||
>
|
||||
<Code2 className="h-3.5 w-3.5" /> JSON Expert
|
||||
<Code2 className="h-3.5 w-3.5" /> {t('workflowEditor.jsonexpert')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -428,7 +428,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
required
|
||||
value={form.name}
|
||||
onChange={(e) => updateField('name', e.target.value)}
|
||||
placeholder="z.B. Deal-Genehmigungsprozess"
|
||||
placeholder={t('workflowEditor.zbdealgenehmigungsprozess')}
|
||||
/>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 mb-1">
|
||||
@@ -439,7 +439,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
onChange={(e) => updateField('description', e.target.value)}
|
||||
rows={2}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="Optionale Beschreibung"
|
||||
placeholder={t('workflowEditor.optionalebeschreibung')}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
@@ -472,12 +472,12 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
type="button"
|
||||
icon={<Plus className="h-4 w-4" />}
|
||||
>
|
||||
Schritt hinzufuegen
|
||||
{t('workflowEditor.schritthinzufuegen')}
|
||||
</Button>
|
||||
</div>
|
||||
{form.steps.length === 0 && (
|
||||
<p className="text-sm text-secondary-400 italic">
|
||||
Keine Schritte definiert. Klicke auf Schritt hinzufuegen.
|
||||
{t('workflowEditor.keineschrittedefiniertklickeaufschritt')}
|
||||
</p>
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
@@ -493,7 +493,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
onClick={() => moveStep(i, 'up')}
|
||||
disabled={i === 0}
|
||||
className="text-secondary-400 hover:text-secondary-700 disabled:opacity-30 p-1"
|
||||
aria-label="Nach oben"
|
||||
aria-label={t('workflowEditor.nachoben')}
|
||||
>
|
||||
<ArrowUp className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -502,7 +502,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
onClick={() => moveStep(i, 'down')}
|
||||
disabled={i === form.steps.length - 1}
|
||||
className="text-secondary-400 hover:text-secondary-700 disabled:opacity-30 p-1"
|
||||
aria-label="Nach unten"
|
||||
aria-label={t('workflowEditor.nachunten')}
|
||||
>
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -510,7 +510,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
type="button"
|
||||
onClick={() => removeStep(i)}
|
||||
className="text-danger-500 hover:text-danger-700 p-1"
|
||||
aria-label="Schritt entfernen"
|
||||
aria-label={t('workflowEditor.schrittentfernen')}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -528,14 +528,14 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
) : (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 mb-1">
|
||||
Workflow (JSON)
|
||||
{t('workflowEditor.workflowjson')}
|
||||
</label>
|
||||
<textarea
|
||||
value={jsonText}
|
||||
onChange={(e) => handleJsonChange(e.target.value)}
|
||||
rows={18}
|
||||
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder='{"name": "...", "steps": [...]}'
|
||||
placeholder={t('workflowEditor.namesteps')}
|
||||
/>
|
||||
{jsonError && (
|
||||
<p className="mt-1 text-sm text-danger-600" role="alert">
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Skeleton } from '@/components/ui/Skeleton';
|
||||
import { EmptyState } from '@/components/ui/EmptyState';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { AlertCircle, ChevronRight } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const statusFilterOptions = [
|
||||
{ value: '', label: 'Alle Status' },
|
||||
@@ -54,6 +55,7 @@ export interface WorkflowInstanceListProps {
|
||||
export function WorkflowInstanceList({
|
||||
onSelectInstance,
|
||||
}: WorkflowInstanceListProps) {
|
||||
const { t } = useTranslation();
|
||||
const [statusFilter, setStatusFilter] = useState<string>('');
|
||||
const [page, setPage] = useState(1);
|
||||
const pageSize = 20;
|
||||
@@ -103,12 +105,12 @@ export function WorkflowInstanceList({
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 text-danger-600">
|
||||
<AlertCircle className="h-5 w-5" />
|
||||
<span>Fehler beim Laden der Instanzen</span>
|
||||
<span>{t('workflowInstanceList.fehlerbeimladenderinstanzen')}</span>
|
||||
<button
|
||||
onClick={() => refetch()}
|
||||
className="text-sm text-primary-600 hover:underline"
|
||||
>
|
||||
Erneut versuchen
|
||||
{t('workflowInstanceList.erneutversuchen')}
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -117,7 +119,7 @@ export function WorkflowInstanceList({
|
||||
{/* Empty state */}
|
||||
{!isLoading && !isError && instances.length === 0 && (
|
||||
<EmptyState
|
||||
title="Keine Workflow-Instanzen"
|
||||
title={t('workflowInstanceList.keineworkflowinstanzen')}
|
||||
description="Es wurden keine Instanzen gefunden, die dem Filter entsprechen."
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user