- Workflow
+ {t('Workflow')}
{task.workflowLabel || task.workflowId || '—'}
@@ -935,14 +934,14 @@ const TaskCard: React.FC = ({
{stepLabel && (
- Schritt
+ {t('Schritt')}
{stepLabel}
)}
- Typ
+ {t('Typ')}
- {NODE_TYPE_LABELS[nodeType] ?? nodeType}
+ {t(NODE_TYPE_LABELS[nodeType] ?? nodeType)}
diff --git a/src/pages/views/neutralization/NeutralizationView.tsx b/src/pages/views/neutralization/NeutralizationView.tsx
index 44f8677..b6b10e3 100644
--- a/src/pages/views/neutralization/NeutralizationView.tsx
+++ b/src/pages/views/neutralization/NeutralizationView.tsx
@@ -76,13 +76,13 @@ const ConfigTab: React.FC = () => {
(typeof detail === 'string' ? detail : null) ||
(Array.isArray(detail) ? detail.map((e: { msg?: string }) => e.msg || JSON.stringify(e)).join(', ') : null) ||
errObj.message ||
- 'Error loading configuration';
+ t('Fehler beim Laden der Konfiguration');
setError(message);
- showError('Error', message);
+ showError(t('Fehler'), message);
} finally {
setLoading(false);
}
- }, [showError]);
+ }, [showError, t]);
useEffect(() => {
loadConfig();
@@ -95,7 +95,7 @@ const ConfigTab: React.FC = () => {
const mandateId = instance?.mandateId;
const featureInstanceId = instance?.id;
if (!mandateId || !featureInstanceId) {
- throw new Error('Missing mandate or instance context');
+ throw new Error(t('Mandat- oder Instanzkontext fehlt'));
}
await saveNeutralizationConfig({
mandateId,
@@ -106,16 +106,16 @@ const ConfigTab: React.FC = () => {
sharepointSourcePath: config?.sharepointSourcePath || '',
sharepointTargetPath: config?.sharepointTargetPath || '',
});
- showSuccess('Saved', 'Configuration saved successfully.');
+ showSuccess(t('Gespeichert'), t('Konfiguration erfolgreich gespeichert.'));
await loadConfig();
} catch (err: unknown) {
const errObj = err as { response?: { data?: { detail?: string } }; message?: string };
const message =
(typeof errObj.response?.data?.detail === 'string' ? errObj.response.data.detail : null) ||
errObj.message ||
- 'Failed to save configuration';
+ t('Konfiguration konnte nicht gespeichert werden.');
setError(message);
- showError('Error', message);
+ showError(t('Fehler'), message);
} finally {
setSaving(false);
}
@@ -131,14 +131,14 @@ const ConfigTab: React.FC = () => {
{t('Neutralisierungskonfiguration')}
- Configure data neutralization settings for this instance.
+ {t('Datenneutralisierung für diese Instanz konfigurieren.')}
{error && (
{error}
-
+
×
@@ -163,7 +163,7 @@ const ConfigTab: React.FC = () => {
onClick={handleSave}
disabled={saving}
>
- {saving ? 'Saving...' : 'Save Configuration'}
+ {saving ? t('Wird gespeichert…') : t('Konfiguration speichern')}
@@ -219,12 +219,12 @@ const PlaygroundTab: React.FC = () => {
setSiteOptions(response.data || []);
} catch (err: unknown) {
const detail = (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail;
- setBrowseError(typeof detail === 'string' ? detail : 'Failed to load SharePoint sites');
+ setBrowseError(typeof detail === 'string' ? detail : t('SharePoint-Sites konnten nicht geladen werden.'));
setSiteOptions([]);
} finally {
setIsLoadingSites(false);
}
- }, [msftConnection, getConnectionReference]);
+ }, [msftConnection, getConnectionReference, t]);
const loadFolderOptions = useCallback(async (siteId: string, path: string = '') => {
if (!msftConnection || !siteId) return;
@@ -238,12 +238,12 @@ const PlaygroundTab: React.FC = () => {
setFolderOptions(response.data || []);
} catch (err: unknown) {
const detail = (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail;
- setBrowseError(typeof detail === 'string' ? detail : 'Failed to load folders');
+ setBrowseError(typeof detail === 'string' ? detail : t('Ordner konnten nicht geladen werden.'));
setFolderOptions([]);
} finally {
setIsLoadingFolders(false);
}
- }, [msftConnection, getConnectionReference]);
+ }, [msftConnection, getConnectionReference, t]);
const handleOpenBrowse = useCallback((target: 'source' | 'target') => {
setBrowseTarget(target);
@@ -323,7 +323,7 @@ const PlaygroundTab: React.FC = () => {
const handleNeutralize = async () => {
if (!inputText.trim()) {
- showError('Error', 'Please enter text to neutralize.');
+ showError(t('Fehler'), t('Bitte Text zum Neutralisieren eingeben.'));
return;
}
setNeutralizing(true);
@@ -331,14 +331,14 @@ const PlaygroundTab: React.FC = () => {
try {
const result = await neutralizeText(inputText);
setNeutralizedText(result.neutralized_text || '');
- showSuccess('Done', 'Text neutralized successfully.');
+ showSuccess(t('Fertig'), t('Text erfolgreich neutralisiert.'));
} catch (err: unknown) {
const errObj = err as { response?: { data?: { detail?: string } }; message?: string };
const message =
(typeof errObj.response?.data?.detail === 'string' ? errObj.response.data.detail : null) ||
errObj.message ||
- 'Failed to neutralize text';
- showError('Error', message);
+ t('Text konnte nicht neutralisiert werden.');
+ showError(t('Fehler'), message);
} finally {
setNeutralizing(false);
}
@@ -362,15 +362,15 @@ const PlaygroundTab: React.FC = () => {
fileId: result.neutralized_file_id,
});
setNeutralizedText('');
- showSuccess('Done', 'File neutralized. Download below or find it in your Files.');
+ showSuccess(t('Fertig'), t('Datei neutralisiert. Laden Sie unten herunter oder finden Sie sie unter Dateien.'));
} else if (result.neutralized_text !== undefined) {
setNeutralizedText(result.neutralized_text || '');
setFileResult(result.neutralized_file_id ? { fileName, fileId: result.neutralized_file_id } : { fileName });
- showSuccess('Done', result.neutralized_file_id ? t('Datei neutralisiert. Herunterladen oder im Ordner finden') : t('Datei neutralisiert'));
+ showSuccess(t('Fertig'), result.neutralized_file_id ? t('Datei neutralisiert. Herunterladen oder im Ordner finden') : t('Datei neutralisiert'));
} else {
- const err = (result.processed_info as { error?: string })?.error || 'No result returned';
+ const err = (result.processed_info as { error?: string })?.error || t('Kein Ergebnis zurückgegeben');
console.warn('[Neutralization] Unexpected result:', result);
- showError('Error', err);
+ showError(t('Fehler'), err);
}
if (result.original_file_id || result.neutralized_file_id) {
refetchFiles();
@@ -380,8 +380,8 @@ const PlaygroundTab: React.FC = () => {
const message =
(typeof errObj.response?.data?.detail === 'string' ? errObj.response.data.detail : null) ||
errObj.message ||
- 'Failed to neutralize file';
- showError('Error', message);
+ t('Datei konnte nicht neutralisiert werden.');
+ showError(t('Fehler'), message);
} finally {
setNeutralizing(false);
e.target.value = '';
@@ -394,7 +394,7 @@ const PlaygroundTab: React.FC = () => {
try {
await handleFileDownload(fileResult.fileId, fileResult.fileName);
} catch {
- showError('Error', 'Failed to download file');
+ showError(t('Fehler'), t('Datei konnte nicht heruntergeladen werden.'));
}
return;
}
@@ -411,7 +411,7 @@ const PlaygroundTab: React.FC = () => {
a.click();
URL.revokeObjectURL(url);
} catch (e) {
- showError('Error', 'Failed to download file');
+ showError(t('Fehler'), t('Datei konnte nicht heruntergeladen werden.'));
}
return;
}
@@ -425,26 +425,26 @@ const PlaygroundTab: React.FC = () => {
URL.revokeObjectURL(url);
return;
}
- showError('Error', 'No file data available to download. The neutralized file may not have been generated.');
+ showError(t('Fehler'), t('Keine Dateidaten zum Herunterladen. Die neutralisierte Datei wurde möglicherweise nicht erzeugt.'));
};
const handleResolve = async () => {
if (!neutralizedText.trim()) {
- showError('Error', 'No neutralized text to resolve.');
+ showError(t('Fehler'), t('Kein neutralisierter Text zum Auflösen.'));
return;
}
setResolving(true);
try {
const result = await resolveText(neutralizedText);
setNeutralizedText(result.resolved_text || '');
- showSuccess('Done', 'Text resolved successfully.');
+ showSuccess(t('Fertig'), t('Text erfolgreich aufgelöst.'));
} catch (err: unknown) {
const errObj = err as { response?: { data?: { detail?: string } }; message?: string };
const message =
(typeof errObj.response?.data?.detail === 'string' ? errObj.response.data.detail : null) ||
errObj.message ||
- 'Failed to resolve text';
- showError('Error', message);
+ t('Text konnte nicht aufgelöst werden.');
+ showError(t('Fehler'), message);
} finally {
setResolving(false);
}
@@ -457,24 +457,24 @@ const PlaygroundTab: React.FC = () => {
const handleProcessSharepoint = async () => {
if (!sharepointSourcePath.trim() || !sharepointTargetPath.trim()) {
- showError('Error', 'Both SharePoint source and target paths are required.');
+ showError(t('Fehler'), t('SharePoint-Quell- und Zielpfad sind erforderlich.'));
return;
}
setProcessingSharepoint(true);
try {
const result = await processSharepointFiles(sharepointSourcePath, sharepointTargetPath);
if (result.success) {
- showSuccess('Done', result.message || 'SharePoint files processed successfully.');
+ showSuccess(t('Fertig'), result.message || t('SharePoint-Dateien erfolgreich verarbeitet.'));
} else {
- showError('Error', result.message || 'Processing failed');
+ showError(t('Fehler'), result.message || t('Verarbeitung fehlgeschlagen'));
}
} catch (err: unknown) {
const errObj = err as { response?: { data?: { detail?: string } }; message?: string };
const message =
(typeof errObj.response?.data?.detail === 'string' ? errObj.response.data.detail : null) ||
errObj.message ||
- 'Failed to process SharePoint files';
- showError('Error', message);
+ t('SharePoint-Dateien konnten nicht verarbeitet werden.');
+ showError(t('Fehler'), message);
} finally {
setProcessingSharepoint(false);
}
@@ -485,12 +485,14 @@ const PlaygroundTab: React.FC = () => {
{t('Manuelle Text- & Dateineutralisierung')}
- Upload a file, paste text, or neutralize SharePoint files. Use "Resolve Text" to convert placeholders back to the original text.
+ {t(
+ 'Laden Sie eine Datei hoch, fügen Sie Text ein oder neutralisieren Sie SharePoint-Dateien. Mit „Text auflösen“ wandeln Sie Platzhalter wieder in den Originaltext um.'
+ )}
- {t('neutralization.2OrPasteText')}
+ {t('2. Oder Text einfügen')}
-
{t('neutralization.3OrNeutralizeSharepointFiles')}
+
{t('3. Oder SharePoint-Dateien neutralisieren')}
-
Source:
+
{t('Quelle:')}
{
disabled={!hasMsftConnection}
title={hasMsftConnection ? t('SharePoint durchsuchen') : t('Fügen Sie zuerst eine Microsoft-Verbindung hinzu')}
>
- Browse
+ {t('Durchsuchen')}
-
Target:
+
{t('Ziel:')}
{
disabled={!hasMsftConnection}
title={hasMsftConnection ? t('SharePoint durchsuchen') : t('Fügen Sie zuerst eine Microsoft-Verbindung hinzu')}
>
- Browse
+ {t('Durchsuchen')}
@@ -608,7 +610,7 @@ const PlaygroundTab: React.FC = () => {
disabled={processingSharepoint || !sharepointSourcePath.trim() || !sharepointTargetPath.trim()}
title={
!sharepointSourcePath.trim() || !sharepointTargetPath.trim()
- ? 'Enter source and target SharePoint paths to enable'
+ ? t('Quell- und Ziel-SharePoint-Pfad eingeben, um zu aktivieren')
: undefined
}
>
@@ -637,7 +639,7 @@ const PlaygroundTab: React.FC = () => {
className={styles.clearLink}
onClick={handleClear}
>
- Clear Result
+ {t('Ergebnis löschen')}
)}
@@ -647,7 +649,9 @@ const PlaygroundTab: React.FC = () => {
e.stopPropagation()}>
- Browse SharePoint {browseTarget === 'source' ? 'Source' : 'Target'} Folder
+ {browseTarget === 'source'
+ ? t('SharePoint-Quellordner durchsuchen')
+ : t('SharePoint-Zielordner durchsuchen')}
{browseError && (
@@ -658,7 +662,7 @@ const PlaygroundTab: React.FC = () => {
{t('Sites werden geladen')}
) : (
<>
-
Site:
+
{t('Site:')}
{
{selectedSite && (
<>
- Current path: {selectedSite.path}{browseCurrentPath ? '/' + browseCurrentPath : ''}
+ {t('Aktueller Pfad:')}{' '}
+ {selectedSite.path}{browseCurrentPath ? '/' + browseCurrentPath : ''}
{isLoadingFolders ? (
{t('Ordner werden geladen')}
@@ -686,7 +691,7 @@ const PlaygroundTab: React.FC = () => {
{browseCurrentPath && (
- ↑ Go Up
+ {t('↑ Eine Ebene höher')}
)}
{
className={styles.primaryButton}
onClick={handleSelectCurrentFolder}
>
- Select This Folder
+ {t('Diesen Ordner auswählen')}
@@ -711,13 +716,13 @@ const PlaygroundTab: React.FC = () => {
className={styles.selectButton}
onClick={(e) => { e.stopPropagation(); handleFolderSelect(folder); }}
>
- Select
+ {t('Auswählen')}
))}
{folderOptions.length === 0 && (
- No subfolders in this location
+ {t('Keine Unterordner an diesem Ort')}
)}
@@ -727,7 +732,7 @@ const PlaygroundTab: React.FC = () => {
)}
- Cancel
+ {t('Abbrechen')}
diff --git a/src/pages/views/realestate/pek/PekLocationInput.tsx b/src/pages/views/realestate/pek/PekLocationInput.tsx
index 03a6f65..b1e1452 100644
--- a/src/pages/views/realestate/pek/PekLocationInput.tsx
+++ b/src/pages/views/realestate/pek/PekLocationInput.tsx
@@ -62,7 +62,7 @@ const PekLocationInput: React.FC = () => {
loading={isSearchingParcel}
className={styles.searchButton}
>
- Suchen
+ {t('Suchen')}
{
loading={isGettingLocation}
className={styles.locationButton}
>
- Meine Position
+ {t('Meine Position')}
diff --git a/src/pages/views/realestate/pek/PekMapView.tsx b/src/pages/views/realestate/pek/PekMapView.tsx
index 38998f5..f56c991 100644
--- a/src/pages/views/realestate/pek/PekMapView.tsx
+++ b/src/pages/views/realestate/pek/PekMapView.tsx
@@ -42,7 +42,7 @@ const PekMapView: React.FC = () => {
onClick={clearSelectedParcels}
disabled={selectedParcels.length === 0}
>
- Alle Parzellen abwählen
+ {t('Alle Parzellen abwählen')}
diff --git a/src/pages/views/teamsbot/TeamsbotDashboardView.tsx b/src/pages/views/teamsbot/TeamsbotDashboardView.tsx
index e2a783d..6db3a44 100644
--- a/src/pages/views/teamsbot/TeamsbotDashboardView.tsx
+++ b/src/pages/views/teamsbot/TeamsbotDashboardView.tsx
@@ -56,11 +56,11 @@ export const TeamsbotDashboardView: React.FC = () => {
setSessions(result.sessions || []);
setError(null);
} catch (err: any) {
- setError(err.message || 'Fehler beim Laden der Sitzungen');
+ setError(err.message || t('Fehler beim Laden der Sitzungen'));
} finally {
setLoading(false);
}
- }, [instanceId]);
+ }, [instanceId, t]);
useEffect(() => {
_loadSessions();
@@ -120,7 +120,7 @@ export const TeamsbotDashboardView: React.FC = () => {
}
} catch { /* ignore parse errors */ }
};
- }, [instanceId, _loadSessions]);
+ }, [instanceId, _loadSessions, t]);
const _handleStartSession = async () => {
if (!meetingLink.trim()) return;
@@ -140,7 +140,7 @@ export const TeamsbotDashboardView: React.FC = () => {
await teamsbotApi.saveUserAccount(instanceId, credEmail, credPassword);
setUserAccount({ hasSavedCredentials: true, email: credEmail });
} catch (err: any) {
- setError(err.message || 'Fehler beim Speichern der Zugangsdaten');
+ setError(err.message || t('Fehler beim Speichern der Zugangsdaten'));
setSavingCredentials(false);
return;
} finally {
@@ -172,7 +172,7 @@ export const TeamsbotDashboardView: React.FC = () => {
await _loadSessions();
} catch (err: any) {
- setError(err.message || 'Fehler beim Starten der Sitzung');
+ setError(err.message || t('Fehler beim Starten der Sitzung'));
} finally {
setIsStarting(false);
}
@@ -192,7 +192,7 @@ export const TeamsbotDashboardView: React.FC = () => {
setMfaWaitingPush(true);
}
} catch (err: any) {
- setError(err.message || 'Fehler beim Senden des MFA-Codes');
+ setError(err.message || t('Fehler beim Senden des MFA-Codes'));
}
};
@@ -203,7 +203,7 @@ export const TeamsbotDashboardView: React.FC = () => {
setCredEmail('');
setCredPassword('');
} catch (err: any) {
- setError(err.message || 'Fehler beim Loeschen der Zugangsdaten');
+ setError(err.message || t('Fehler beim Löschen der Zugangsdaten'));
}
};
@@ -212,7 +212,7 @@ export const TeamsbotDashboardView: React.FC = () => {
await teamsbotApi.stopSession(instanceId, sessionId);
await _loadSessions();
} catch (err: any) {
- setError(err.message || 'Fehler beim Stoppen der Sitzung');
+ setError(err.message || t('Fehler beim Stoppen der Sitzung'));
}
};
@@ -221,7 +221,7 @@ export const TeamsbotDashboardView: React.FC = () => {
await teamsbotApi.deleteSession(instanceId, sessionId);
await _loadSessions();
} catch (err: any) {
- setError(err.message || 'Fehler beim Loeschen der Sitzung');
+ setError(err.message || t('Fehler beim Löschen der Sitzung'));
}
};
diff --git a/src/pages/views/teamsbot/TeamsbotSessionView.tsx b/src/pages/views/teamsbot/TeamsbotSessionView.tsx
index 3324a7e..f12a2f0 100644
--- a/src/pages/views/teamsbot/TeamsbotSessionView.tsx
+++ b/src/pages/views/teamsbot/TeamsbotSessionView.tsx
@@ -88,11 +88,11 @@ export const TeamsbotSessionView: React.FC = () => {
setBotResponses(result.botResponses || []);
setError(null);
} catch (err: any) {
- setError(err.message || 'Fehler beim Laden der Sitzung');
+ setError(err.message || t('Fehler beim Laden der Sitzung'));
} finally {
setLoading(false);
}
- }, [instanceId, sessionId, setSearchParams]);
+ }, [instanceId, sessionId, setSearchParams, t]);
useEffect(() => {
_loadSession();
@@ -128,20 +128,20 @@ export const TeamsbotSessionView: React.FC = () => {
break;
case 'transcript': {
- const t = sseEvent.data as TeamsbotTranscript;
- _dlog('TRANSCRIPT', `[${t?.speaker || '?'}] ${(t?.text || '').substring(0, 50)}...`);
- if (t?.isContinuation && t?.id) {
+ const tr = sseEvent.data as TeamsbotTranscript;
+ _dlog('TRANSCRIPT', `[${tr?.speaker || '?'}] ${(tr?.text || '').substring(0, 50)}...`);
+ if (tr?.isContinuation && tr?.id) {
setTranscripts(prev => {
- const idx = prev.findIndex(x => x.id === t.id);
+ const idx = prev.findIndex(x => x.id === tr.id);
if (idx >= 0) {
const updated = [...prev];
- updated[idx] = { ...updated[idx], ...t };
+ updated[idx] = { ...updated[idx], ...tr };
return updated;
}
- return [...prev, t];
+ return [...prev, tr];
});
} else {
- setTranscripts(prev => [...prev, t]);
+ setTranscripts(prev => [...prev, tr]);
}
break;
}
@@ -184,7 +184,9 @@ export const TeamsbotSessionView: React.FC = () => {
case 'chatSendFailed': {
const failData = sseEvent.data || {};
- const failMsg = `Chat-Nachricht konnte nicht gesendet werden: ${failData.reason || 'unbekannt'}`;
+ const failMsg = t('Chat-Nachricht konnte nicht gesendet werden: {reason}', {
+ reason: failData.reason || t('unbekannt'),
+ });
_dlog('CHAT-FAIL', failMsg);
setTtsStatusEvents((prev) => [
...prev.slice(-24),
@@ -200,7 +202,7 @@ export const TeamsbotSessionView: React.FC = () => {
case 'error': {
const errData = sseEvent.data || {};
- const errMsg = errData.message || 'Unbekannter Fehler';
+ const errMsg = errData.message || t('Unbekannter Fehler');
_dlog('ERROR', errMsg);
setError(errMsg);
break;
@@ -228,7 +230,7 @@ export const TeamsbotSessionView: React.FC = () => {
sseSessionRef.current = null;
setIsLive(false);
};
- }, [instanceId, sessionId, sessionStatus, _dlog]);
+ }, [instanceId, sessionId, sessionStatus, _dlog, t]);
// Polling fallback: refresh session data every 5s when SSE is not connected
const pollRef = useRef
| null>(null);
@@ -285,7 +287,7 @@ export const TeamsbotSessionView: React.FC = () => {
if (noSessions) return (
{t('Keine Sitzungen vorhanden')}
-
Starte eine neue Sitzung im Dashboard .
+
{t('Starte eine neue Sitzung im Dashboard.')}
);
if (!session) return {t('Sitzung nicht gefunden')}
;
@@ -343,18 +345,20 @@ export const TeamsbotSessionView: React.FC = () => {
{/* Left: Transcript */}
-
Transkript ({transcripts.length} Segmente)
+
+ {t('Transkript ({count} Segmente)', { count: transcripts.length })}
+
- {transcripts.map((t) => (
-
-
{_formatTime(t.timestamp)}
+ {transcripts.map((seg) => (
+
+ {_formatTime(seg.timestamp)}
- {t.speaker || 'Unknown'}:
+ {seg.speaker || t('Unbekannt')}:
- {t.text}
+ {seg.text}
))}
@@ -377,7 +381,7 @@ export const TeamsbotSessionView: React.FC = () => {
{r.responseText}
{r.reasoning && (
- Reasoning: {r.reasoning}
+ {t('Begründung: {text}', { text: r.reasoning })}
)}
{(r.modelName || r.processingTime != null) && (
@@ -399,7 +403,7 @@ export const TeamsbotSessionView: React.FC = () => {
{/* Summary (for ended sessions) */}
{session.summary && (
-
Meeting-Zusammenfassung
+
{t('Meeting-Zusammenfassung')}
{session.summary}
)}
@@ -460,7 +464,7 @@ export const TeamsbotSessionView: React.FC = () => {
}}
disabled={screenshotsLoading}
>
- {screenshotsLoading ? 'Laden...' : screenshotsLoaded ? t('Aktualisieren') : t('Screenshots laden')}
+ {screenshotsLoading ? t('Laden…') : screenshotsLoaded ? t('Aktualisieren') : t('Screenshots laden')}
{screenshotsLoaded && screenshots.length === 0 && (
diff --git a/src/pages/views/teamsbot/TeamsbotSettingsView.tsx b/src/pages/views/teamsbot/TeamsbotSettingsView.tsx
index f8c5307..ed2ca21 100644
--- a/src/pages/views/teamsbot/TeamsbotSettingsView.tsx
+++ b/src/pages/views/teamsbot/TeamsbotSettingsView.tsx
@@ -118,7 +118,7 @@ export const TeamsbotSettingsView: React.FC = () => {
setTestingVoice(true);
try {
const language = formData.language || 'de-DE';
- const botName = formData.botName || 'AI Assistant';
+ const botName = formData.botName || t('KI-Assistent');
const result = await teamsbotApi.testVoice(instanceId, botName, language, formData.voiceId);
if (result.success && result.audio) {
@@ -136,11 +136,11 @@ export const TeamsbotSettingsView: React.FC = () => {
audio.play();
audio.onended = () => URL.revokeObjectURL(audioUrl);
} else {
- setError(result.error || 'Stimmtest fehlgeschlagen');
+ setError(result.error || t('Stimmtest fehlgeschlagen'));
setTimeout(() => setError(null), 3000);
}
} catch (err: any) {
- setError(err.message || 'Stimmtest fehlgeschlagen');
+ setError(err.message || t('Stimmtest fehlgeschlagen'));
setTimeout(() => setError(null), 3000);
} finally {
setTestingVoice(false);
diff --git a/src/pages/views/trustee/TrusteeAbschlussView.tsx b/src/pages/views/trustee/TrusteeAbschlussView.tsx
index 02522c9..6418a87 100644
--- a/src/pages/views/trustee/TrusteeAbschlussView.tsx
+++ b/src/pages/views/trustee/TrusteeAbschlussView.tsx
@@ -98,7 +98,7 @@ export const TrusteeAbschlussView: React.FC = () => {
}, [instanceId]);
const _findWorkflow = useCallback((tab: string): WorkflowSummary | undefined => {
- const tabDef = _TABS.find((t) => t.id === tab);
+ const tabDef = _TABS.find((tabItem) => tabItem.id === tab);
if (!tabDef) return undefined;
return workflows.find((w) => w.tags.includes(tabDef.templateTag));
}, [workflows]);
@@ -124,11 +124,11 @@ export const TrusteeAbschlussView: React.FC = () => {
setRunSummary(`${completed.length}/${steps.length} ${t('Schritte abgeschlossen')}`);
if (failed.length > 0) {
- const errMsg = failed[failed.length - 1].error || 'Step failed';
+ const errMsg = failed[failed.length - 1].error || t('Schritt fehlgeschlagen');
setRunState('error');
setRunError(errMsg);
_stopPolling();
- showError('Pipeline error', errMsg);
+ showError(t('Pipeline-Fehler'), errMsg);
return;
}
if (running.length === 0 && completed.length === steps.length && steps.length > 0) {
@@ -141,7 +141,7 @@ export const TrusteeAbschlussView: React.FC = () => {
} catch (err: any) {
if (err?.response?.status === 404) { setRunState('running'); return; }
setRunState('error');
- setRunError(err.message || 'Polling failed');
+ setRunError(err.message || t('Abfrage fehlgeschlagen'));
_stopPolling();
} finally {
isPollingRef.current = false;
@@ -168,41 +168,41 @@ export const TrusteeAbschlussView: React.FC = () => {
const _handleExecute = useCallback(async () => {
const wf = _findWorkflow(activeTab);
if (!wf || !instanceId) {
- showError('Error', t('Kein Workflow für diesen Tab gefunden.'));
+ showError(t('Fehler'), t('Kein Workflow für diesen Tab gefunden.'));
return;
}
setRunState('starting');
setRunError(null);
- setRunSummary(t('startet', 'Workflow wird gestartet...'));
+ setRunSummary(t('Workflow wird gestartet…'));
try {
const res = await api.post(`/api/workflows/${instanceId}/execute`, { workflowId: wf.id });
const rid = res?.data?.runId;
if (rid) {
setRunId(rid);
setRunState('running');
- setRunSummary(`Run ${rid.slice(0, 8)} ${t('gestartet', 'gestartet')}`);
+ setRunSummary(t('Lauf {prefix} gestartet', { prefix: rid.slice(0, 8) }));
} else if (res?.data?.success) {
setRunState('completed');
- setRunSummary(t('synchronisierung abgeschlossen', 'Workflow synchron abgeschlossen.'));
+ setRunSummary(t('Workflow synchron abgeschlossen.'));
showSuccess(t('Abgeschlossen'), t('Prüfungs-Workflow erfolgreich beendet.'));
} else {
- throw new Error(res?.data?.error || 'Unexpected response');
+ throw new Error(res?.data?.error || t('Unerwartete Antwort'));
}
} catch (err: any) {
- const msg = err?.response?.data?.detail || err.message || 'Failed to start workflow';
+ const msg = err?.response?.data?.detail || err.message || t('Workflow konnte nicht gestartet werden.');
setRunState('error');
setRunError(typeof msg === 'string' ? msg : JSON.stringify(msg));
- showError('Error', typeof msg === 'string' ? msg : JSON.stringify(msg));
+ showError(t('Fehler'), typeof msg === 'string' ? msg : JSON.stringify(msg));
}
}, [activeTab, instanceId, _findWorkflow, showError, showSuccess, t]);
- const currentTab = _TABS.find((t) => t.id === activeTab) || _TABS[0];
+ const currentTab = _TABS.find((tabItem) => tabItem.id === activeTab) || _TABS[0];
const currentWorkflow = _findWorkflow(activeTab);
return (
-
{t('trustee completion title', 'Abschluss & Prüfung')}
+
{t('Abschluss & Prüfung')}
{/* Tab bar */}
{_TABS.length > 1 && (
@@ -250,7 +250,7 @@ export const TrusteeAbschlussView: React.FC = () => {
{currentWorkflow.label}
- Workflow ID: {currentWorkflow.id.slice(0, 8)}...
+ {t('Workflow-ID:')} {currentWorkflow.id.slice(0, 8)}…
@@ -262,19 +262,19 @@ export const TrusteeAbschlussView: React.FC = () => {
style={{ alignSelf: 'flex-start' }}
>
{runState === 'starting' || runState === 'running'
- ? t('läuft', 'Läuft...')
- : t('ausführen', 'Prüfung starten')}
+ ? t('Läuft…')
+ : t('Prüfung starten')}
>
)}
{runState !== 'idle' && (
-
{t('status', 'Status')}: {' '}
+
{t('Status')}: {' '}
{runState === 'starting' && t('Wird gestartet…')}
- {runState === 'running' && t('abschluss läuft', 'Läuft')}
- {runState === 'completed' && t('abschluss abgeschlossen', 'Abgeschlossen')}
- {runState === 'error' && t('fehlerbezeichnung', 'Fehler')}
+ {runState === 'running' && t('Läuft')}
+ {runState === 'completed' && t('Abgeschlossen')}
+ {runState === 'error' && t('Fehler')}
{runSummary &&
{runSummary}
}
{runError &&
{runError}
}
diff --git a/src/pages/views/trustee/TrusteeAccountingSettingsView.tsx b/src/pages/views/trustee/TrusteeAccountingSettingsView.tsx
index 52bbef7..7fd453c 100644
--- a/src/pages/views/trustee/TrusteeAccountingSettingsView.tsx
+++ b/src/pages/views/trustee/TrusteeAccountingSettingsView.tsx
@@ -238,7 +238,7 @@ export const TrusteeAccountingSettingsView: React.FC = () => {
{t('System auswählen…')}
{connectors.map(c => (
- {c.label?.de || c.label?.en || c.connectorType}
+ {c.label || c.connectorType}
))}
@@ -250,7 +250,7 @@ export const TrusteeAccountingSettingsView: React.FC = () => {
2
-
Credentials
+
{t('Zugangsdaten')}
@@ -267,7 +267,7 @@ export const TrusteeAccountingSettingsView: React.FC = () => {
{selectedConnector.configFields.map(field => (
- {field.label?.de || field.label?.en || field.key}
+ {field.label || field.key}
{field.required && * }
{
// Find the workflow for the active tab
const _findWorkflow = useCallback((tab: string): WorkflowSummary | undefined => {
- const tabDef = _TABS.find((t) => t.id === tab);
+ const tabDef = _TABS.find((tabItem) => tabItem.id === tab);
if (!tabDef) return undefined;
return workflows.find((w) => w.tags.includes(tabDef.templateTag));
}, [workflows]);
@@ -135,11 +135,11 @@ export const TrusteeAnalyseView: React.FC = () => {
setRunSummary(`${completed.length}/${steps.length} ${t('Schritte abgeschlossen')}`);
if (failed.length > 0) {
- const errMsg = failed[failed.length - 1].error || 'Step failed';
+ const errMsg = failed[failed.length - 1].error || t('Schritt fehlgeschlagen');
setRunState('error');
setRunError(errMsg);
_stopPolling();
- showError('Pipeline error', errMsg);
+ showError(t('Pipeline-Fehler'), errMsg);
return;
}
if (running.length === 0 && completed.length === steps.length && steps.length > 0) {
@@ -155,7 +155,7 @@ export const TrusteeAnalyseView: React.FC = () => {
return;
}
setRunState('error');
- setRunError(err.message || 'Polling failed');
+ setRunError(err.message || t('Abfrage fehlgeschlagen'));
_stopPolling();
} finally {
isPollingRef.current = false;
@@ -184,41 +184,41 @@ export const TrusteeAnalyseView: React.FC = () => {
const _handleExecute = useCallback(async () => {
const wf = _findWorkflow(activeTab);
if (!wf || !instanceId) {
- showError('Error', t('kein Workflow', 'Kein Workflow für diesen Tab gefunden.'));
+ showError(t('Fehler'), t('Kein Workflow für diesen Tab gefunden.'));
return;
}
setRunState('starting');
setRunError(null);
- setRunSummary(t('analyse startet', 'Workflow wird gestartet...'));
+ setRunSummary(t('Workflow wird gestartet…'));
try {
const res = await api.post(`/api/workflows/${instanceId}/execute`, { workflowId: wf.id });
const rid = res?.data?.runId;
if (rid) {
setRunId(rid);
setRunState('running');
- setRunSummary(`Run ${rid.slice(0, 8)} ${t('gestartet')}`);
+ setRunSummary(t('Lauf {prefix} gestartet', { prefix: rid.slice(0, 8) }));
} else if (res?.data?.success) {
setRunState('completed');
setRunSummary(t('Workflow synchron abgeschlossen.'));
showSuccess(t('Abgeschlossen'), t('Analyse-Workflow erfolgreich beendet.'));
} else {
- throw new Error(res?.data?.error || 'Unexpected response');
+ throw new Error(res?.data?.error || t('Unerwartete Antwort'));
}
} catch (err: any) {
- const msg = err?.response?.data?.detail || err.message || 'Failed to start workflow';
+ const msg = err?.response?.data?.detail || err.message || t('Workflow konnte nicht gestartet werden.');
setRunState('error');
setRunError(typeof msg === 'string' ? msg : JSON.stringify(msg));
- showError('Error', typeof msg === 'string' ? msg : JSON.stringify(msg));
+ showError(t('Fehler'), typeof msg === 'string' ? msg : JSON.stringify(msg));
}
}, [activeTab, instanceId, _findWorkflow, showError, showSuccess, t]);
- const currentTab = _TABS.find((t) => t.id === activeTab) || _TABS[0];
+ const currentTab = _TABS.find((tabItem) => tabItem.id === activeTab) || _TABS[0];
const currentWorkflow = _findWorkflow(activeTab);
return (
-
{t('titel', 'Analyse & Reporting')}
+
{t('Analyse & Reporting')}
{/* Tab bar */}
@@ -252,7 +252,7 @@ export const TrusteeAnalyseView: React.FC = () => {
{workflowsLoading ? (
-
{t('trustee analysis loading workflows', 'Workflows werden geladen...')}
+
{t('Workflows werden geladen…')}
) : !currentWorkflow ? (
{t('Für diesen Tab wurde kein Workflow in der Instanz gefunden. Der Workflow wird beim Erstellen der Instanz automatisch angelegt.')}
@@ -264,7 +264,7 @@ export const TrusteeAnalyseView: React.FC = () => {
{currentWorkflow.label}
- Workflow ID: {currentWorkflow.id.slice(0, 8)}...
+ {t('Workflow-ID:')} {currentWorkflow.id.slice(0, 8)}…
@@ -276,7 +276,7 @@ export const TrusteeAnalyseView: React.FC = () => {
style={{ alignSelf: 'flex-start' }}
>
{runState === 'starting' || runState === 'running'
- ? t('analyse läuft', 'Läuft...')
+ ? t('Läuft…')
: t('Ausführen')}
>
@@ -285,11 +285,11 @@ export const TrusteeAnalyseView: React.FC = () => {
{/* Pipeline status */}
{runState !== 'idle' && (
-
{t('status', 'Status')}: {' '}
- {runState === 'starting' && t('analyse startet', 'Wird gestartet...')}
+
{t('Status')}: {' '}
+ {runState === 'starting' && t('Wird gestartet…')}
{runState === 'running' && t('Läuft')}
- {runState === 'completed' && t('analyse abgeschlossen', 'Abgeschlossen')}
- {runState === 'error' && t('analysefehler', 'Fehler')}
+ {runState === 'completed' && t('Abgeschlossen')}
+ {runState === 'error' && t('Fehler')}
{runSummary &&
{runSummary}
}
{runError &&
{runError}
}
diff --git a/src/pages/views/trustee/TrusteeExpenseImportView.tsx b/src/pages/views/trustee/TrusteeExpenseImportView.tsx
index d863b90..12804ce 100644
--- a/src/pages/views/trustee/TrusteeExpenseImportView.tsx
+++ b/src/pages/views/trustee/TrusteeExpenseImportView.tsx
@@ -241,12 +241,12 @@ export const TrusteeExpenseImportView: React.FC = () => {
setSiteOptions(response.data || []);
} catch (err: any) {
console.error('Failed to load sites:', err);
- setError(_parseErrorDetail(err.response?.data?.detail) || 'Failed to load SharePoint sites');
+ setError(_parseErrorDetail(err.response?.data?.detail) || t('SharePoint-Websites konnten nicht geladen werden'));
setSiteOptions([]);
} finally {
setIsLoadingSites(false);
}
- }, [msftConnection, _getConnectionReference]);
+ }, [msftConnection, _getConnectionReference, t]);
const loadFolderOptions = useCallback(async (siteId: string, path: string = '') => {
if (!msftConnection || !siteId) return;
@@ -263,12 +263,12 @@ export const TrusteeExpenseImportView: React.FC = () => {
setFolderOptions(response.data || []);
} catch (err: any) {
console.error('Failed to load folders:', err);
- setError(_parseErrorDetail(err.response?.data?.detail) || 'Failed to load folders');
+ setError(_parseErrorDetail(err.response?.data?.detail) || t('Ordner konnten nicht geladen werden'));
setFolderOptions([]);
} finally {
setIsLoadingFolders(false);
}
- }, [msftConnection, _getConnectionReference]);
+ }, [msftConnection, _getConnectionReference, t]);
useEffect(() => {
if (msftConnection) {
@@ -318,7 +318,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
await fetchConnections();
} catch (err: any) {
console.error('Connection failed:', err);
- setError(err.message || 'Microsoft connection failed');
+ setError(err.message || t('Microsoft-Verbindung fehlgeschlagen'));
} finally {
setIsConnecting(false);
}
@@ -326,15 +326,15 @@ export const TrusteeExpenseImportView: React.FC = () => {
const handleSave = async (activate: boolean = true) => {
if (!msftConnection) {
- showError('Missing Connection', 'Please select a Microsoft connection first.');
+ showError(t('Fehlende Verbindung'), t('Bitte wählen Sie zuerst eine Microsoft-Verbindung aus.'));
return;
}
if (!selectedFolder) {
- showError('Missing Folder', 'Please select a SharePoint folder first.');
+ showError(t('Fehlender Ordner'), t('Bitte wählen Sie zuerst einen SharePoint-Ordner aus.'));
return;
}
if (!instanceId || !mandateId) {
- showError('Error', 'Feature instance not found. Please refresh the page.');
+ showError(t('Fehler'), t('Feature-Instanz nicht gefunden. Bitte Seite neu laden.'));
return;
}
@@ -374,7 +374,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
);
const msg = t('Ausgabenimport-Workflow aktualisiert und');
setSuccessMessage(msg);
- showSuccess('Success', msg);
+ showSuccess(t('Erfolg'), msg);
} else {
response = await api.post(
`/api/workflows/${instanceId}/workflows`,
@@ -385,9 +385,9 @@ export const TrusteeExpenseImportView: React.FC = () => {
invocations,
}
);
- const msg = 'Expense import workflow created and activated! It will run daily at 22:00.';
+ const msg = t('Ausgabenimport-Workflow erstellt und aktiviert! Er wird täglich um 22:00 Uhr ausgeführt.');
setSuccessMessage(msg);
- showSuccess('Success', msg);
+ showSuccess(t('Erfolg'), msg);
}
const savedWorkflow = response.data;
@@ -402,9 +402,9 @@ export const TrusteeExpenseImportView: React.FC = () => {
} catch (err: any) {
console.error('Save failed:', err);
- const errorMsg = _parseErrorDetail(err.response?.data?.detail) || err.message || 'Failed to save workflow';
+ const errorMsg = _parseErrorDetail(err.response?.data?.detail) || err.message || t('Workflow konnte nicht gespeichert werden');
setError(errorMsg);
- showError('Error', errorMsg);
+ showError(t('Fehler'), errorMsg);
} finally {
setIsActivating(false);
}
@@ -412,7 +412,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
const handleRunNow = async () => {
if (!msftConnection || !selectedFolder || !instanceId) {
- showError('Missing data', 'Please select connection and folder first.');
+ showError(t('Daten unvollständig'), t('Bitte zuerst Verbindung und Ordner wählen.'));
return;
}
setIsRunningNow(true);
@@ -429,11 +429,11 @@ export const TrusteeExpenseImportView: React.FC = () => {
`/api/workflows/${instanceId}/execute`,
{ graph }
);
- showSuccess('Started', 'Workflow started. Extract → Process → Sync will run once.');
+ showSuccess(t('Gestartet'), t('Workflow gestartet. Extrahieren → Verarbeiten → Sync wird einmal ausgeführt.'));
} catch (err: any) {
- const msg = _parseErrorDetail(err.response?.data?.detail) || err.message || 'Failed to start workflow';
+ const msg = _parseErrorDetail(err.response?.data?.detail) || err.message || t('Workflow konnte nicht gestartet werden');
setError(msg);
- showError('Error', msg);
+ showError(t('Fehler'), msg);
} finally {
setIsRunningNow(false);
}
@@ -452,13 +452,13 @@ export const TrusteeExpenseImportView: React.FC = () => {
);
setExistingWorkflow(prev => prev ? { ...prev, active: false } : null);
- setSuccessMessage('Expense import workflow deactivated.');
- showSuccess('Deactivated', 'Expense import workflow deactivated.');
+ setSuccessMessage(t('Ausgabenimport-Workflow deaktiviert.'));
+ showSuccess(t('Deaktiviert'), t('Ausgabenimport-Workflow deaktiviert.'));
} catch (err: any) {
console.error('Deactivation failed:', err);
- const errorMsg = _parseErrorDetail(err.response?.data?.detail) || err.message || 'Failed to deactivate workflow';
+ const errorMsg = _parseErrorDetail(err.response?.data?.detail) || err.message || t('Workflow konnte nicht deaktiviert werden');
setError(errorMsg);
- showError('Error', errorMsg);
+ showError(t('Fehler'), errorMsg);
} finally {
setIsActivating(false);
}
@@ -469,8 +469,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
{t('Einrichtung des Ausgabenimports')}
- Connect your Microsoft account and select a SharePoint folder containing expense PDFs.
- The system will automatically extract expense data daily and save it as positions.
+ {t('Verbinden Sie Ihr Microsoft-Konto und wählen Sie einen SharePoint-Ordner mit Ausgaben-PDFs. Das System extrahiert automatisch täglich die Ausgabendaten und speichert sie als Positionen.')}
setShowInfoTooltip(true)}
@@ -513,7 +512,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
{t('Aktueller Status')} {existingWorkflow.active ? t('Aktiv') : t('Inaktiv')}
{existingWorkflow.sharepointFolder && (
- <> Folder: {existingWorkflow.sharepointFolder}>
+ <> {t('Ordner:')} {existingWorkflow.sharepointFolder}>
)}
)}
@@ -529,13 +528,13 @@ export const TrusteeExpenseImportView: React.FC = () => {
onClick={handleConnect}
disabled={isConnecting}
>
- {isConnecting ? 'Connecting...' : 'Connect Microsoft Account'}
+ {isConnecting ? t('Verbindung wird hergestellt...') : t('Microsoft-Konto verbinden')}
) : msftConnections.length === 1 ? (
✓
- Connected as {msftConnections[0].accountName || 'Microsoft Account'}
+ {t('Verbunden als')} {msftConnections[0].accountName || t('Microsoft-Konto')}
) : (
@@ -565,7 +564,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
disabled={isConnecting}
style={{ marginTop: '0.5rem' }}
>
- {isConnecting ? 'Connecting...' : 'Add another account'}
+ {isConnecting ? t('Verbindung wird hergestellt...') : t('Weiteres Konto verbinden')}
>
)}
@@ -605,7 +604,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
{t('Ausgabenordner')}
- Current path: {selectedSite.path}/{currentPath || '(root)'}
+ {t('Aktueller Pfad:')} {selectedSite.path}/{currentPath || t('(Stammverzeichnis)')}
{isLoadingFolders ? (
{t('Lade Ordner')}
@@ -617,7 +616,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
className={styles.secondaryButton}
onClick={handleGoUp}
>
- ↑ Go Up
+ ↑ {t('Übergeordneter Ordner')}
)}
{
setSelectedFolder(fullPath || selectedSite?.path || '');
}}
>
- ✓ Select This Folder
+ ✓ {t('Diesen Ordner auswählen')}
@@ -644,7 +643,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
className={styles.selectButton}
onClick={() => handleFolderSelect(folder)}
>
- Select
+ {t('Auswählen')}
))}
@@ -654,7 +653,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
{selectedFolder && (
- Selected: {selectedFolder}
+ {t('Ausgewählt:')} {selectedFolder}
)}
@@ -670,8 +669,7 @@ export const TrusteeExpenseImportView: React.FC = () => {
{existingWorkflow ? t('Konfiguration aktualisieren') : t('Täglichen Import aktivieren')}
- PDF files in {selectedFolder} will be processed daily at 22:00.
- Successfully processed files will be moved to a "processed" subfolder.
+ {t('PDF-Dateien in')}{' '}{selectedFolder} {' '}{t('werden täglich um 22:00 Uhr verarbeitet. Erfolgreich verarbeitete Dateien werden in einen Unterordner „verarbeitet“ verschoben.')}
{
onClick={() => handleSave(true)}
disabled={isActivating}
>
- {isActivating ? 'Saving...' : (existingWorkflow ? t('Speichern & Aktivieren') : t('Täglichen Import aktivieren'))}
+ {isActivating ? t('Wird gespeichert...') : (existingWorkflow ? t('Speichern & Aktivieren') : t('Täglichen Import aktivieren'))}
- {isRunningNow ? 'Starting...' : 'Jetzt ausführen'}
+ {isRunningNow ? t('Wird gestartet...') : t('Jetzt ausführen')}
{existingWorkflow && existingWorkflow.active && (
{
onClick={handleDeactivate}
disabled={isActivating}
>
- Deactivate
+ {t('Deaktivieren')}
)}
diff --git a/src/pages/views/trustee/TrusteeInstanceRolesView.tsx b/src/pages/views/trustee/TrusteeInstanceRolesView.tsx
index 76e41c5..c89fb65 100644
--- a/src/pages/views/trustee/TrusteeInstanceRolesView.tsx
+++ b/src/pages/views/trustee/TrusteeInstanceRolesView.tsx
@@ -20,7 +20,7 @@ import { useLanguage } from '../../../providers/language/LanguageContext';
interface InstanceRole {
id: string;
roleLabel: string;
- description?: { [key: string]: string };
+ description?: string;
featureCode: string;
mandateId: string;
featureInstanceId: string;
@@ -36,11 +36,8 @@ export const TrusteeInstanceRolesView: React.FC = () => {
const [error, setError] = useState
(null);
const [expandedRoleId, setExpandedRoleId] = useState(null);
- // Get display text from multilingual object
- const getTextValue = (value: string | { [key: string]: string } | undefined): string => {
- if (!value) return '';
- if (typeof value === 'string') return value;
- return value.de || value.en || Object.values(value)[0] || '';
+ const getTextValue = (value: string | undefined): string => {
+ return value || '';
};
// Load instance roles
@@ -114,7 +111,7 @@ export const TrusteeInstanceRolesView: React.FC = () => {
- Aktualisieren
+ {t('Aktualisieren')}
diff --git a/src/pages/views/trustee/TrusteeScanUploadView.tsx b/src/pages/views/trustee/TrusteeScanUploadView.tsx
index d346a18..6147c2b 100644
--- a/src/pages/views/trustee/TrusteeScanUploadView.tsx
+++ b/src/pages/views/trustee/TrusteeScanUploadView.tsx
@@ -248,7 +248,7 @@ export const TrusteeScanUploadView: React.FC = () => {
const msg = _parseErrorDetail(err.response?.data?.detail) || err.message || 'Failed to start workflow';
setPipelineState('error');
setError(msg);
- showError('Error', msg);
+ showError(t('Fehler'), msg);
} finally {
setIsStarting(false);
}
diff --git a/src/pages/views/workspace/FilePreview.tsx b/src/pages/views/workspace/FilePreview.tsx
index 8a8138d..61c905f 100644
--- a/src/pages/views/workspace/FilePreview.tsx
+++ b/src/pages/views/workspace/FilePreview.tsx
@@ -135,8 +135,8 @@ export const FilePreview: React.FC
= ({ instanceId, fileId, fi
{!loading && content === null && !previewUrl && (
{file.fileSize > 500_000
- ? 'File too large for inline preview'
- : `No preview available for ${file.mimeType}`}
+ ? t('Datei zu gross für die Vorschau.')
+ : t('Keine Vorschau für {mime}', { mime: file.mimeType })}
)}
diff --git a/src/pages/views/workspace/NeutralizationPanel.tsx b/src/pages/views/workspace/NeutralizationPanel.tsx
index 72d73e4..265603c 100644
--- a/src/pages/views/workspace/NeutralizationPanel.tsx
+++ b/src/pages/views/workspace/NeutralizationPanel.tsx
@@ -299,16 +299,16 @@ const NeutralizationPanel: React.FC
= ({ instanceId })
return (
-
Neutralisierung
+
{t('Neutralisierung')}
- Neutralisierte Texte mit Platzhaltern und die zugehörigen Mappings (Original ↔ Platzhalter).
+ {t('Neutralisierte Texte mit Platzhaltern und die zugehörigen Mappings (Original ↔ Platzhalter).')}
{/* ── Snapshots: neutralisierter Text ──────────────────────── */}
{snapshots.length > 0 && (
- Neutralisierter Text ({snapshots.length})
+ {t('Neutralisierter Text')} ({snapshots.length})
{snapshots.map((snap) => {
const _isExpanded = expandedSnapshot === snap.id;
@@ -328,7 +328,7 @@ const NeutralizationPanel: React.FC
= ({ instanceId })
>
{snap.sourceLabel}
- {snap.placeholderCount} Platzhalter {_isExpanded ? '\u25BC' : '\u25B6'}
+ {snap.placeholderCount} {t('Platzhalter')} {_isExpanded ? '\u25BC' : '\u25B6'}
{_isExpanded && (
@@ -357,7 +357,7 @@ const NeutralizationPanel: React.FC
= ({ instanceId })
{sources.length > 0 && (
- Datenquellen
+ {t('Datenquellen')}
{sources.map((src) => (
= ({ instanceId })
{_statusBadge(src.neutralizationStatus)}
{src.mappingCount > 0 && (
- {src.mappingCount} Mapping(s)
+ {src.mappingCount} {t('Mapping(s)')}
)}
@@ -413,7 +413,7 @@ const NeutralizationPanel: React.FC
= ({ instanceId })
{selectedSource && mappings.length > 0 && (
- Platzhalter-Mappings ({mappings.length})
+ {t('Platzhalter-Mappings')} ({mappings.length})
{mappings.map((m) => (
@@ -457,7 +457,7 @@ const NeutralizationPanel: React.FC
= ({ instanceId })
{!_hasAnyData && (
- Noch keine Neutralisierungsdaten vorhanden. Sende eine Nachricht mit aktivierter Neutralisierung.
+ {t('Noch keine Neutralisierungsdaten vorhanden. Sende eine Nachricht mit aktivierter Neutralisierung.')}
)}
diff --git a/src/pages/views/workspace/ToolActivityLog.tsx b/src/pages/views/workspace/ToolActivityLog.tsx
index d29a09b..6e405ca 100644
--- a/src/pages/views/workspace/ToolActivityLog.tsx
+++ b/src/pages/views/workspace/ToolActivityLog.tsx
@@ -7,8 +7,11 @@
* - Full details shown on error for debugging
*/
-import React, { useState } from 'react';
+import React, { useState, useCallback } from 'react';
import type { ToolActivity } from './useWorkspace';
+import { useLanguage } from '../../../providers/language/LanguageContext';
+
+type TranslateFn = (key: string, params?: Record
) => string;
interface ToolActivityLogProps {
activities: ToolActivity[];
@@ -16,25 +19,6 @@ interface ToolActivityLogProps {
const _UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
-const _TOOL_LABELS: Record = {
- browseTable: 'Tabelle durchsuchen',
- queryTable: 'Tabelle abfragen',
- aggregateTable: 'Tabelle aggregieren',
- browseContainer: 'Datei durchsuchen',
- readContentObjects: 'Inhalte lesen',
- extractContainerItem: 'Element extrahieren',
- queryFeatureInstance: 'Feature abfragen',
- requestToolbox: 'Toolbox anfordern',
- searchDocuments: 'Dokumente suchen',
- getFileInfo: 'Datei-Info abrufen',
- listFiles: 'Dateien auflisten',
- listTables: 'Tabellen auflisten',
- getTableSchema: 'Tabellenschema abrufen',
- createRecord: 'Datensatz erstellen',
- updateRecord: 'Datensatz aktualisieren',
- deleteRecord: 'Datensatz löschen',
-};
-
const _HIDDEN_ARG_KEYS = new Set([
'mandateId', 'userId', 'featureInstanceId', 'workflowId', 'sessionId',
]);
@@ -68,7 +52,7 @@ function _formatArgs(args: Record, isError: boolean): string {
return parts.join(', ') || (isError ? JSON.stringify(args) : '');
}
-function _formatResult(result: string): string {
+function _formatResult(result: string, translate: TranslateFn): string {
if (!result) return '';
const trimmed = result.trim();
@@ -76,14 +60,18 @@ function _formatResult(result: string): string {
try {
const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) {
- return `${parsed.length} Ergebnisse`;
+ return translate('{count} Ergebnisse', { count: parsed.length });
}
if (typeof parsed === 'object' && parsed !== null) {
const keys = Object.keys(parsed);
- if (parsed.count !== undefined) return `${parsed.count} Einträge`;
- if (parsed.total !== undefined) return `${parsed.total} Einträge`;
- if (parsed.rows && Array.isArray(parsed.rows)) return `${parsed.rows.length} Zeilen`;
- if (parsed.data && Array.isArray(parsed.data)) return `${parsed.data.length} Einträge`;
+ if (parsed.count !== undefined) return translate('{count} Einträge', { count: parsed.count });
+ if (parsed.total !== undefined) return translate('{count} Einträge', { count: parsed.total });
+ if (parsed.rows && Array.isArray(parsed.rows)) {
+ return translate('{count} Zeilen', { count: parsed.rows.length });
+ }
+ if (parsed.data && Array.isArray(parsed.data)) {
+ return translate('{count} Einträge', { count: parsed.data.length });
+ }
if (parsed.result !== undefined) {
const r = String(parsed.result);
return r.length > 120 ? r.slice(0, 117) + '...' : r;
@@ -91,7 +79,7 @@ function _formatResult(result: string): string {
if (keys.length <= 3) {
return keys.map((k) => `${k}: ${String(parsed[k]).slice(0, 40)}`).join(', ');
}
- return `Objekt (${keys.length} Felder)`;
+ return translate('Objekt ({count} Felder)', { count: keys.length });
}
} catch {
// not valid JSON
@@ -101,26 +89,47 @@ function _formatResult(result: string): string {
return trimmed.length > 150 ? trimmed.slice(0, 147) + '...' : trimmed;
}
-function _getToolLabel(toolName: string): string {
- return _TOOL_LABELS[toolName] || toolName;
+function _getToolLabel(toolName: string, translate: TranslateFn): string {
+ const labels: Record = {
+ browseTable: translate('Tabelle durchsuchen'),
+ queryTable: translate('Tabelle abfragen'),
+ aggregateTable: translate('Tabelle aggregieren'),
+ browseContainer: translate('Datei durchsuchen'),
+ readContentObjects: translate('Inhalte lesen'),
+ extractContainerItem: translate('Element extrahieren'),
+ queryFeatureInstance: translate('Feature abfragen'),
+ requestToolbox: translate('Toolbox anfordern'),
+ searchDocuments: translate('Dokumente suchen'),
+ getFileInfo: translate('Datei-Info abrufen'),
+ listFiles: translate('Dateien auflisten'),
+ listTables: translate('Tabellen auflisten'),
+ getTableSchema: translate('Tabellenschema abrufen'),
+ createRecord: translate('Datensatz erstellen'),
+ updateRecord: translate('Datensatz aktualisieren'),
+ deleteRecord: translate('Datensatz löschen'),
+ };
+ return labels[toolName] || toolName;
}
-function _getStatusLabel(status: string): string {
+function _getStatusLabel(status: string, translate: TranslateFn): string {
switch (status) {
- case 'calling': return 'läuft';
- case 'success': return 'OK';
- case 'error': return 'Fehler';
+ case 'calling': return translate('läuft');
+ case 'success': return translate('OK');
+ case 'error': return translate('Fehler');
default: return status;
}
}
export const ToolActivityLog: React.FC = ({ activities }) => {
+ const { t } = useLanguage();
const [expandedId, setExpandedId] = useState(null);
+ const translate = useCallback((key, params) => t(key, params), [t]);
+
if (!activities.length) {
return (
- Noch keine Aktivität
+ {t('Noch keine Aktivität')}
);
}
@@ -130,11 +139,11 @@ export const ToolActivityLog: React.FC = ({ activities })
{activities.map(activity => {
const isError = activity.status === 'error';
const isExpanded = expandedId === activity.id;
- const friendlyName = _getToolLabel(activity.toolName);
+ const friendlyName = _getToolLabel(activity.toolName, translate);
const argsText = activity.args && Object.keys(activity.args).length > 0
? _formatArgs(activity.args, isError)
: '';
- const resultText = activity.result ? _formatResult(activity.result) : '';
+ const resultText = activity.result ? _formatResult(activity.result, translate) : '';
return (
= ({ activities })
: 'var(--color-error, #f44336)',
color: '#fff',
}}>
- {_getStatusLabel(activity.status)}
+ {_getStatusLabel(activity.status, translate)}
@@ -212,7 +221,7 @@ export const ToolActivityLog: React.FC = ({ activities })
{isExpanded && activity.args && Object.keys(activity.args).length > 0 && (
- Alle Parameter
+ {t('Alle Parameter')}
= ({ activities })
{isExpanded && activity.result && (
- Vollständiges Ergebnis
+ {t('Vollständiges Ergebnis')}
{
if (!instanceId) {
return (
- Keine Workspace-Instanz ausgewaehlt.
+ {t('Keine Workspace-Instanz ausgewählt.')}
);
}
@@ -76,10 +76,10 @@ export const WorkspaceEditorPage: React.FC = () => {
- File Edit Review
+ {t('Dateiänderungen prüfen')}
- {editor.pendingCount} pending
+ {t('{count} ausstehend', { count: editor.pendingCount })}
@@ -91,14 +91,14 @@ export const WorkspaceEditorPage: React.FC = () => {
disabled={editor.pendingCount === 0}
style={{ ..._actionBtnStyle, background: 'var(--success-color, #4caf50)', color: '#fff' }}
>
- Accept All
+ {t('Alle akzeptieren')}
- Reject All
+ {t('Alle ablehnen')}
@@ -125,14 +125,14 @@ export const WorkspaceEditorPage: React.FC = () => {
{editor.isLoading ? (
- Lade Aenderungsvorschlaege...
+ {t('Lade Änderungsvorschläge…')}
) : pendingEdits.length === 0 ? (
✓
{t('Keine offenen Änderungsvorschläge')}
- Zurueck zum Dashboard
+ {t('Zurück zum Dashboard')}
) : activeEdit ? (
@@ -154,21 +154,21 @@ export const WorkspaceEditorPage: React.FC = () => {
}}>
{activeEdit.fileName}
- Original: {formatBinaryDataSizeBytes(activeEdit.oldContent.length)}
- Geaendert: {formatBinaryDataSizeBytes(activeEdit.newContent.length)}
+ {t('Original:')} {formatBinaryDataSizeBytes(activeEdit.oldContent.length)}
+ {t('Geändert:')} {formatBinaryDataSizeBytes(activeEdit.newContent.length)}
editor.acceptEdit(activeEdit.id)}
style={{ ..._actionBtnStyle, background: 'var(--success-color, #4caf50)', color: '#fff' }}
>
- Accept
+ {t('Akzeptieren')}
editor.rejectEdit(activeEdit.id)}
style={{ ..._actionBtnStyle, border: '1px solid var(--error-color, #f44336)', color: 'var(--error-color, #f44336)' }}
>
- Reject
+ {t('Ablehnen')}
diff --git a/src/pages/views/workspace/WorkspaceGeneralSettings.tsx b/src/pages/views/workspace/WorkspaceGeneralSettings.tsx
index 0d87fba..5ceb8cd 100644
--- a/src/pages/views/workspace/WorkspaceGeneralSettings.tsx
+++ b/src/pages/views/workspace/WorkspaceGeneralSettings.tsx
@@ -106,11 +106,11 @@ export const WorkspaceGeneralSettings: React.FC = ({ insta
{success && {success}
}
-
Agenten-Konfiguration
+
{t('Agenten-Konfiguration')}
diff --git a/src/pages/views/workspace/WorkspacePage.tsx b/src/pages/views/workspace/WorkspacePage.tsx
index c79d641..4540f55 100644
--- a/src/pages/views/workspace/WorkspacePage.tsx
+++ b/src/pages/views/workspace/WorkspacePage.tsx
@@ -22,6 +22,7 @@ import api from '../../../api';
import { _defaultProviderSelection, _toBackendProviders } from '../../../components/ProviderSelector';
import type { ProviderSelection } from '../../../components/ProviderSelector';
import { useBilling } from '../../../hooks/useBilling';
+import { useLanguage } from '../../../providers/language/LanguageContext';
function _useResizable(initialWidth: number, minWidth: number, maxWidth: number) {
const [width, setWidth] = useState(initialWidth);
@@ -68,6 +69,7 @@ interface WorkspacePageProps {
}
export const WorkspacePage: React.FC = ({ persistentInstanceId }) => {
+ const { t } = useLanguage();
const { instance } = useCurrentInstance();
const instanceId = persistentInstanceId || instance?.id || '';
const workspace = useWorkspace(instanceId);
@@ -216,7 +218,7 @@ export const WorkspacePage: React.FC = ({ persistentInstance
if (!instanceId) {
return (
- No workspace instance selected.
+ {t('Keine Workspace-Instanz ausgewählt.')}
);
}
@@ -296,8 +298,8 @@ export const WorkspacePage: React.FC = ({ persistentInstance
<>
- setRightTab('activity')}>Activity
- setRightTab('preview')}>Preview
+ setRightTab('activity')}>{t('Aktivität')}
+ setRightTab('preview')}>{t('Vorschau')}
{!isMobile && (
setRightCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}>▶
@@ -332,7 +334,7 @@ export const WorkspacePage: React.FC
= ({ persistentInstance
flexShrink: 0,
}}>
- Workspace
+ {t('Workspace')}
setLeftCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}>◀
{_leftPanelBody}
@@ -387,7 +389,7 @@ export const WorkspacePage: React.FC = ({ persistentInstance
fontWeight: 600,
}}
>
- Workspace
+ {t('Workspace')}
{ setRightTab('activity'); setMobileRightOpen(true); }}
@@ -400,7 +402,7 @@ export const WorkspacePage: React.FC = ({ persistentInstance
fontSize: 12,
}}
>
- Activity
+ {t('Aktivität')}
{ setRightTab('preview'); setMobileRightOpen(true); }}
@@ -413,7 +415,7 @@ export const WorkspacePage: React.FC = ({ persistentInstance
fontSize: 12,
}}
>
- Preview
+ {t('Vorschau')}
)}
@@ -521,7 +523,7 @@ export const WorkspacePage: React.FC = ({ persistentInstance
onClick={e => e.stopPropagation()}
>
- Workspace
+ {t('Workspace')}
setMobileLeftOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: '#666' }}>×
{_leftPanelBody}
@@ -555,7 +557,7 @@ export const WorkspacePage: React.FC = ({ persistentInstance
onClick={e => e.stopPropagation()}
>
- {rightTab === 'activity' ? 'Activity' : 'Preview'}
+ {rightTab === 'activity' ? t('Aktivität') : t('Vorschau')}
setMobileRightOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: '#666' }}>×
{_rightPanelBody}
diff --git a/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx b/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx
index 4cc3fad..a411fee 100644
--- a/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx
+++ b/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx
@@ -88,12 +88,12 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
setStats(data ?? null);
}
} catch (e) {
- setError(e instanceof Error ? e.message : 'Laden fehlgeschlagen');
+ setError(e instanceof Error ? e.message : t('Laden fehlgeschlagen'));
setStats(null);
} finally {
setLoading(false);
}
- }, [instanceId, request]);
+ }, [instanceId, request, t]);
useEffect(() => {
void load();
@@ -102,7 +102,7 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
if (!instanceId) {
return (
- Keine Workspace-Instanz ausgewählt.
+ {t('Keine Workspace-Instanz ausgewählt.')}
);
}
@@ -118,7 +118,7 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
const kpis = stats?.kpis;
const timeline = stats?.timelineIndexedDocuments ?? [];
const mimeRows = Object.entries(stats?.documentsByMimeCategory ?? {}).map(([key, value]) => ({
- name: MIME_LABELS[key] ?? key,
+ name: t(MIME_LABELS[key] ?? key),
value,
}));
const statusRows = Object.entries(stats?.indexedDocumentsByStatus ?? {}).map(([name, value]) => ({
@@ -133,17 +133,17 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
return (
- Dargestellt sind ausschliesslich aggregierte technische Masszahlen dieser Instanz (Anzahl
- Dokumente, Fragmente, Speicherumfang, Verteilungen). Es werden keine Inhalte, Dateinamen
- oder personenbezogene Angaben ausgewiesen. Geeignet für interne Berichte und Präsentationen.
+ {t(
+ 'Dargestellt sind ausschliesslich aggregierte technische Masszahlen dieser Instanz (Anzahl Dokumente, Fragmente, Speicherumfang, Verteilungen). Es werden keine Inhalte, Dateinamen oder personenbezogene Angaben ausgewiesen. Geeignet für interne Berichte und Präsentationen.',
+ )}
{stats?.scope?.workspaceFileIdsResolved !== undefined && (
- Zuordnung Knowledge ↔ Dateien: {stats.scope.workspaceFileIdsResolved} Datei-ID(s) mit
- dieser Feature-Instanz in der Dateiverwaltung. Neu indexierte Uploads erhalten die
- Instanz automatisch; ältere Einträge ohne Zuordnung erscheinen erst nach erneuter
- Indexierung.
+ {t(
+ 'Zuordnung Knowledge ↔ Dateien: {workspaceFileIdsResolved} Datei-ID(s) mit dieser Feature-Instanz in der Dateiverwaltung. Neu indexierte Uploads erhalten die Instanz automatisch; ältere Einträge ohne Zuordnung erscheinen erst nach erneuter Indexierung.',
+ { workspaceFileIdsResolved: stats.scope.workspaceFileIdsResolved },
+ )}
)}
@@ -189,7 +189,7 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
-
+
)}
@@ -207,14 +207,14 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
-
+
)}
-
Index-Status
+
{t('Index-Status')}
{statusRows.length === 0 ? (
{t('Keine Daten')}
) : (
@@ -252,14 +252,16 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
-
+
)}
{stats?.generatedAtUtc && (
- Stand (UTC): {stats.generatedAtUtc}
+
+ {t('Stand (UTC):')} {stats.generatedAtUtc}
+
)}
);
diff --git a/src/pages/views/workspace/WorkspaceSettingsPage.tsx b/src/pages/views/workspace/WorkspaceSettingsPage.tsx
index 4fc364a..938a4d9 100644
--- a/src/pages/views/workspace/WorkspaceSettingsPage.tsx
+++ b/src/pages/views/workspace/WorkspaceSettingsPage.tsx
@@ -69,9 +69,9 @@ export const WorkspaceSettingsPage: React.FC = () => {
{activeTab === 'neutralization' && (
<>
- Hier erscheinen die zuletzt an die KI gesendeten neutralisierten Texte und Platzhalter dieser
- Workspace-Instanz. (Die Benutzer-Einstellungen unter /settings → „Neutralisierung (lokal)“
- ist eine andere Seite.)
+ {t(
+ 'Hier erscheinen die zuletzt an die KI gesendeten neutralisierten Texte und Platzhalter dieser Workspace-Instanz. (Die Benutzer-Einstellungen unter /settings → „Neutralisierung (lokal)“ sind eine andere Seite.)',
+ )}
>
diff --git a/src/providers/auth/AuthProvider.tsx b/src/providers/auth/AuthProvider.tsx
index 5835037..c433e4b 100644
--- a/src/providers/auth/AuthProvider.tsx
+++ b/src/providers/auth/AuthProvider.tsx
@@ -6,12 +6,14 @@ import {
import { msalConfig } from "./authConfig";
import { MsalProvider } from "@azure/msal-react";
import { ReactNode, useEffect, useState } from "react";
+ import { useLanguage } from "../language/LanguageContext";
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider = ({ children }: AuthProviderProps) => {
+ const { t } = useLanguage();
const [msalInstance, setMsalInstance] = useState(null);
const [isInitialized, setIsInitialized] = useState(false);
@@ -86,7 +88,7 @@ import {
}, []);
if (!isInitialized || !msalInstance) {
- return Loading authentication...
;
+ return {t('Authentifizierung wird geladen…')}
;
}
return {children} ;
diff --git a/src/providers/auth/ProtectedRoute.tsx b/src/providers/auth/ProtectedRoute.tsx
index 07f11dc..12a57a7 100644
--- a/src/providers/auth/ProtectedRoute.tsx
+++ b/src/providers/auth/ProtectedRoute.tsx
@@ -1,6 +1,7 @@
import { useMsal } from "@azure/msal-react";
import { Navigate, useLocation } from "react-router-dom";
import { ReactNode, useEffect, useState } from "react";
+import { useLanguage } from "../language/LanguageContext";
interface ProtectedRouteProps {
children: ReactNode;
@@ -11,6 +12,7 @@ export const ProtectedRoute = ({
children,
redirectPath = "/login"
}: ProtectedRouteProps) => {
+ const { t } = useLanguage();
const { accounts } = useMsal();
const location = useLocation();
const [isChecking, setIsChecking] = useState(true);
@@ -106,7 +108,7 @@ export const ProtectedRoute = ({
// If still checking, show loading
if (isChecking) {
- return Checking authentication...
;
+ return {t('Authentifizierung wird geprüft…')}
;
}
// Check if user is authenticated through either method
diff --git a/src/stores/featureStore.tsx b/src/stores/featureStore.tsx
index 6d49c16..cd75899 100644
--- a/src/stores/featureStore.tsx
+++ b/src/stores/featureStore.tsx
@@ -6,6 +6,7 @@
*/
import React, { createContext, useContext, useState, useCallback, useRef, useEffect, ReactNode } from 'react';
+import { useLanguage } from '../providers/language/LanguageContext';
import type {
Mandate,
MandateFeature,
@@ -73,6 +74,7 @@ interface FeatureProviderProps {
}
export const FeatureProvider: React.FC = ({ children }) => {
+ const { t } = useLanguage();
const [state, setState] = useState(initialState);
// Cache für schnellen Zugriff auf Instanzen
@@ -121,7 +123,7 @@ export const FeatureProvider: React.FC = ({ children }) =>
initialized: true,
});
} catch (err) {
- const errorMessage = err instanceof Error ? err.message : 'Failed to load features';
+ const errorMessage = err instanceof Error ? err.message : t('Features konnten nicht geladen werden.');
console.error('FeatureStore: Error loading features:', err);
setState(prev => ({
...prev,
@@ -130,7 +132,7 @@ export const FeatureProvider: React.FC = ({ children }) =>
initialized: true,
}));
}
- }, []);
+ }, [t]);
/**
* Setzt Features direkt (z.B. nach Login)
diff --git a/src/types/mandate.ts b/src/types/mandate.ts
index e35f5f4..474c34f 100644
--- a/src/types/mandate.ts
+++ b/src/types/mandate.ts
@@ -7,16 +7,6 @@
* Er hat Zugriff auf Feature-Instanzen, die zu Mandanten gehören.
*/
-// =============================================================================
-// I18N
-// =============================================================================
-
-export interface I18nLabel {
- de: string;
- en: string;
- fr?: string;
-}
-
// =============================================================================
// ACCESS LEVELS
// =============================================================================
@@ -331,21 +321,3 @@ export function canAccessRecord(
}
}
-/**
- * Holt Navigations-Label: i18n-Key (String) oder Legacy-I18nLabel.
- */
-export function getLabel(label: I18nLabel | string, lang: 'de' | 'en' | 'fr' = 'de'): string {
- if (typeof label === 'string') return label;
- return label[lang] || label.de || label.en || '';
-}
-
-/** German i18n key from API label (string or legacy multilingual object). */
-export function labelAsI18nKey(
- label: string | I18nLabel | { [key: string]: string } | undefined,
- fallback: string
-): string {
- if (label === undefined || label === null) return fallback;
- if (typeof label === 'string') return label || fallback;
- const o = label as I18nLabel;
- return o.de || o.en || o.fr || fallback;
-}