fixed sysuser and removed redundant fallbacks

This commit is contained in:
ValueOn AG 2026-04-11 22:23:35 +02:00
parent d1f0b3c3d6
commit 994664f0b4
27 changed files with 140 additions and 177 deletions

View file

@ -28,20 +28,13 @@ function _getModeOptions(t: (key: string) => string): { value: ScheduleMode; tit
];
}
const MONTH_NAMES_DE = [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember',
];
function _monthNames(t: (k: string) => string): string[] {
return [
t('Januar'), t('Februar'), t('März'), t('April'),
t('Mai'), t('Juni'), t('Juli'), t('August'),
t('September'), t('Oktober'), t('November'), t('Dezember'),
];
}
function _getIntervalUnits(t: (key: string) => string): { value: IntervalUnit; label: string; title: string }[] {
return [
@ -285,7 +278,7 @@ export const ScheduleStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ par
<div className={styles.scheduleFieldCol}>
<span className={styles.scheduleFieldLabel}>{t('Wochentage')}</span>
<div className={styles.scheduleWeekdayToggles}>
{WEEKDAYS_MO_SO.map(({ cronDow, label }) => (
{WEEKDAYS_MO_SO.map(({ cronDow }) => (
<button
key={cronDow}
type="button"
@ -294,7 +287,7 @@ export const ScheduleStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ par
}
onClick={() => toggleWeekday(cronDow)}
>
{t(label)}
{cronDow === 1 ? t('Mo') : cronDow === 2 ? t('Di') : cronDow === 3 ? t('Mi') : cronDow === 4 ? t('Do') : cronDow === 5 ? t('Fr') : cronDow === 6 ? t('Sa') : t('So')}
</button>
))}
</div>
@ -365,9 +358,9 @@ export const ScheduleStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ par
value={spec.monthIndex}
onChange={(e) => push({ ...spec, monthIndex: Number(e.target.value) })}
>
{MONTH_NAMES_DE.map((name, i) => (
{_monthNames(t).map((name, i) => (
<option key={i + 1} value={i + 1}>
{t(name)}
{name}
</option>
))}
</select>

View file

@ -117,7 +117,7 @@ export const SwitchNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, up
<option value="">{t('MIME-Typ wählen')}</option>
{mimeTypeOptions.map((o) => (
<option key={o.value} value={o.value}>
{t(o.label)} ({o.value})
{o.label} ({o.value})
</option>
))}
</select>
@ -221,7 +221,7 @@ export const SwitchNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, up
>
{operators.map((o) => (
<option key={o.value} value={o.value}>
{t(o.label)}
{o.label}
</option>
))}
</select>

View file

@ -53,7 +53,7 @@ export function CustomActionButton<T = any>({
if (typeof title === 'function') {
buttonTitle = title(row);
} else if (typeof title === 'string') {
buttonTitle = t(title, title); // Try to translate, fallback to original
buttonTitle = title;
}
// Determine the final tooltip

View file

@ -258,7 +258,7 @@ export const MandateNavigation: React.FC = () => {
for (const sg of systemBlock.subgroups) {
children.push({
id: sg.id,
label: t(sg.title),
label: sg.title,
children: sg.items.map(i => navigationItemToTreeNode(i, t)),
defaultExpanded: true,
});
@ -288,7 +288,7 @@ export const MandateNavigation: React.FC = () => {
if (items.length > 0) items.push({ type: 'separator' });
const subgroupNodes: TreeNodeItem[] = adminSubgroups.map(sg => ({
id: sg.id,
label: t(sg.title),
label: sg.title,
children: sg.items.map(i => navigationItemToTreeNode(i, t)),
defaultExpanded: false,
}));

View file

@ -87,9 +87,9 @@ const _PROVIDER_LABEL_KEYS: Record<string, string> = {
internal: 'Internal',
};
function _providerLabel(provider: string, t: (key: string) => string): string {
function _providerLabel(provider: string): string {
const key = _PROVIDER_LABEL_KEYS[provider];
return key ? t(key) : provider;
return key ? key : provider;
}
const PROVIDER_ICONS: Record<string, string> = {
@ -135,7 +135,7 @@ export const ProviderSelect: React.FC<ProviderSelectProps> = ({ value,
const providerOptions = useMemo(() => {
return allowedProviders.map((provider) => ({
value: provider,
label: `${PROVIDER_ICONS[provider] || '🔌'} ${_providerLabel(provider, t)}`,
label: `${PROVIDER_ICONS[provider] || '🔌'} ${_providerLabel(provider)}`,
}));
}, [allowedProviders, t]);
@ -325,7 +325,7 @@ export const ProviderMultiSelect: React.FC<ProviderMultiSelectProps> = ({
/>
<span className={styles.icon}>{PROVIDER_ICONS[provider] || '🔌'}</span>
<span className={styles.providerName}>
{_providerLabel(provider, t)}
{_providerLabel(provider)}
</span>
</label>
))}
@ -361,7 +361,7 @@ export const ProviderBadges: React.FC<ProviderBadgesProps> = ({
<div className={`${styles.providerBadges} ${className || ''}`}>
{providers.map((provider) => (
<span key={provider} className={styles.badge}>
{PROVIDER_ICONS[provider] || '🔌'} {_providerLabel(provider, t)}
{PROVIDER_ICONS[provider] || '🔌'} {_providerLabel(provider)}
</span>
))}
</div>

View file

@ -144,21 +144,8 @@ const CreateButton: React.FC<CreateButtonProps> = ({
const isDisabled = disabled || loading || isCreating;
// Resolve language text for popup title
const resolvedPopupTitle = typeof popupTitle === 'string'
? t(popupTitle, popupTitle)
: popupTitle;
// Resolve language text for attributes
const resolvedAttributes: AttributeDefinition[] = useMemo(() => {
return attributes.map(attr => ({
...attr,
label: typeof attr.label === 'string' ? t(attr.label, attr.label) : attr.label,
placeholder: attr.placeholder
? (typeof attr.placeholder === 'string' ? t(attr.placeholder, attr.placeholder) : attr.placeholder)
: undefined
}));
}, [attributes, t]);
const resolvedPopupTitle = popupTitle;
const resolvedAttributes = attributes;
return (
<>

View file

@ -23,13 +23,13 @@ const getStatusBadgeClass = (status?: string | null): string => {
const Log: React.FC<LogProps> = ({
className = '',
emptyMessage = 'Keine Log-Informationen verfügbar',
emptyMessage,
dashboardTree,
onToggleOperationExpanded,
getChildOperations
}) => {
const { t } = useLanguage();
const resolvedEmptyMessage = typeof emptyMessage === 'string' ? t(emptyMessage, emptyMessage) : emptyMessage;
const resolvedEmptyMessage = emptyMessage || t('Keine Log-Informationen verfügbar');
const formatLogTimestamp = (timestamp: number): string => {
try {
const formatted = formatUnixTimestamp(timestamp, undefined, {

View file

@ -48,7 +48,7 @@ const MapViewLeaflet: React.FC<MapViewProps> = ({ parcels = [],
onParcelClick,
height = '600px',
className = '',
emptyMessage = 'Klicken Sie auf die Karte, um einen Standort auszuwählen',
emptyMessage,
showWfsParcels = false,
parcelsApiBaseUrl = ''
}) => {
@ -361,7 +361,7 @@ const MapViewLeaflet: React.FC<MapViewProps> = ({ parcels = [],
<div ref={mapContainerRef} style={{ width: '100%', height: '100%' }} />
{parcels.length === 0 && !center && (
<div className={styles.emptyStateOverlay}>
<p>{typeof emptyMessage === 'string' ? t(emptyMessage, emptyMessage) : emptyMessage}</p>
<p>{emptyMessage || t('Klicken Sie auf die Karte, um einen Standort auszuwählen')}</p>
</div>
)}
{showWfsParcels && isWfsLoading && (

View file

@ -16,7 +16,7 @@ const Messages: React.FC<MessagesProps> = ({
showProgress = true,
renderMessage,
renderDocument,
emptyMessage = 'Noch keine Nachrichten',
emptyMessage,
onFileDelete,
onFileRemove,
onFileView,
@ -30,7 +30,7 @@ const Messages: React.FC<MessagesProps> = ({
deletingMessages
}) => {
const { t } = useLanguage();
const resolvedEmptyMessage = typeof emptyMessage === 'string' ? t(emptyMessage, emptyMessage) : emptyMessage;
const resolvedEmptyMessage = emptyMessage || t('Noch keine Nachrichten');
if (!messages || messages.length === 0) {
return (
<div className={`${styles.messagesContainer} ${styles.emptyContainer} ${className}`}>

View file

@ -30,11 +30,14 @@ interface UnifiedDataBarProps {
className?: string;
}
const _TAB_KEYS: Record<UdbTab, string> = {
chats: 'Chatverläufe',
files: 'Dateien',
sources: 'Quellen',
};
function _tabLabel(tab: UdbTab, t: (k: string) => string): string {
switch (tab) {
case 'chats': return t('Chatverläufe');
case 'files': return t('Dateien');
case 'sources': return t('Quellen');
default: return tab;
}
}
const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
context,
@ -72,7 +75,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
className={`${styles.tab} ${currentTab === tab ? styles.tabActive : ''}`}
onClick={() => _handleTabChange(tab)}
>
{t(_TAB_KEYS[tab])}
{_tabLabel(tab, t)}
</button>
))}
</div>

View file

@ -82,23 +82,10 @@ export const SidebarProvider: React.FC<SidebarProviderProps> = ({ children }) =>
}
// Helper function to resolve node name
const resolveNodeName = (pathSegment: string, fullPath: string, page?: GenericPageData): string => {
const resolveNodeName = (pathSegment: string, _fullPath: string, page?: GenericPageData): string => {
if (page) {
return resolveLanguageText(page.name, t);
}
// Try translation key (e.g., "start.real-estate.title")
const translationKey = `${fullPath}.title`;
const translated = t(translationKey);
if (translated !== translationKey) {
return translated;
}
// Try just the segment (e.g., "real-estate.title")
const segmentKey = `${pathSegment}.title`;
const segmentTranslated = t(segmentKey);
if (segmentTranslated !== segmentKey) {
return segmentTranslated;
}
// Fallback to capitalized segment
return pathSegment.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ');
};

View file

@ -82,11 +82,11 @@ export const FeatureLayout: React.FC = () => {
? navFeature.instances.find(i => i.id === instanceId)
: undefined;
return {
mandate: t(navMandate.uiLabel),
feature: navFeature ? t(navFeature.uiLabel) : undefined,
instance: navInstance ? t(navInstance.uiLabel) : undefined,
mandate: navMandate.uiLabel,
feature: navFeature ? navFeature.uiLabel : undefined,
instance: navInstance ? navInstance.uiLabel : undefined,
};
}, [dynamicBlock, mandateId, featureCode, instanceId, t]);
}, [dynamicBlock, mandateId, featureCode, instanceId]);
// Warten bis Features geladen sind
if (!initialized || loading || isLoading) {

View file

@ -171,7 +171,7 @@ export const AutomationsDashboardPage: React.FC = () => {
filterable: true,
formatter: (v: string) => (
<span style={{ color: _STATUS_COLORS[v] || 'inherit', fontWeight: 600 }}>
{t(v === 'completed' ? 'Abgeschlossen' : v === 'failed' ? 'Fehlgeschlagen' : v === 'running' ? 'Laufend' : v)}
{v === 'completed' ? t('Abgeschlossen') : v === 'failed' ? t('Fehlgeschlagen') : v === 'running' ? t('Laufend') : v}
</span>
),
},

View file

@ -29,11 +29,9 @@ const STORE_FEATURE_DESCRIPTION_FALLBACK: Record<string, string> = {
commcoach: 'CommCoach: Kommunikation trainieren mit KI-gestütztem Coaching und Feedback.',
};
function _storeCardDescription(feature: StoreFeature, t: (key: string, fallback?: string) => string): string {
const raw =
(feature.description && feature.description.trim()) ||
STORE_FEATURE_DESCRIPTION_FALLBACK[feature.featureCode];
return raw ? t(raw) : '';
function _storeCardDescription(feature: StoreFeature): string {
return (feature.description && feature.description.trim()) ||
STORE_FEATURE_DESCRIPTION_FALLBACK[feature.featureCode] || '';
}
interface FeatureCardProps {
@ -62,13 +60,13 @@ const FeatureCard: React.FC<FeatureCardProps> = ({
<div className={styles.cardHeader}>
{icon && <span className={styles.cardIcon}>{icon}</span>}
<h3 className={styles.cardTitle}>
{t(feature.label)}
{feature.label}
</h3>
</div>
<div className={styles.cardBody}>
<p className={styles.cardDescription}>
{_storeCardDescription(feature, t)}
{_storeCardDescription(feature)}
</p>
</div>

View file

@ -29,8 +29,8 @@ function getMandateName(mandate: Mandate): string {
return mandate.label || mandate.name || mandate.id;
}
function getFeatureLabel(feature: Feature, t: (k: string) => string): string {
return t(feature.label || feature.code);
function getFeatureLabel(feature: Feature): string {
return feature.label || feature.code;
}
export interface InstanceWithStats extends FeatureInstance {
@ -165,7 +165,7 @@ export const AccessManagementHub: React.FC = () => {
instance,
mandateId: mandateId || '',
mandateName: mandate ? getMandateName(mandate) : mandateId || '',
featureLabel: feature ? getFeatureLabel(feature, t) : instance.featureCode,
featureLabel: feature ? getFeatureLabel(feature) : instance.featureCode,
});
};
@ -294,7 +294,7 @@ export const AccessManagementHub: React.FC = () => {
return {
id: inst.id,
label: inst.label,
featureLabel: feature ? getFeatureLabel(feature, t) : inst.featureCode,
featureLabel: feature ? getFeatureLabel(feature) : inst.featureCode,
userCount: inst.userCount ?? 0,
};
}),
@ -363,7 +363,7 @@ export const AccessManagementHub: React.FC = () => {
<option value="">{t('Alle')}</option>
{features.map((f) => (
<option key={f.code} value={f.code}>
{getFeatureLabel(f, t)}
{getFeatureLabel(f)}
</option>
))}
</select>
@ -424,7 +424,7 @@ export const AccessManagementHub: React.FC = () => {
instancesByMandate={instancesByMandate}
instanceUsersMap={instanceUsersMap}
features={features}
getFeatureLabel={(f) => getFeatureLabel(f, t)}
getFeatureLabel={getFeatureLabel}
loading={hierarchyUsersLoading}
onOpenDetail={handleOpenDetail}
/>
@ -526,7 +526,7 @@ export const AccessManagementHub: React.FC = () => {
</span>
</div>
<div className={hubStyles.instanceMeta}>
<span>{getFeatureLabel(features.find((f) => f.code === inst.featureCode) || { code: inst.featureCode, label: inst.featureCode }, t)}</span>
<span>{getFeatureLabel(features.find((f) => f.code === inst.featureCode) || { code: inst.featureCode, label: inst.featureCode })}</span>
<span>
{inst.userCount ?? '—'} {t('Benutzer')}
</span>

View file

@ -92,10 +92,7 @@ export const AdminFeatureAccessPage: React.FC = () => {
{ key: 'featureCode', label: t('Feature'), type: 'string' as const, sortable: true, filterable: true, width: 150,
render: (value: string) => {
const feature = features.find(f => f.code === value);
if (feature) {
return t(feature.label || value);
}
return value;
return feature ? (feature.label || value) : value;
}
},
{ key: 'enabled', label: t('Aktiv'), type: 'boolean' as const, sortable: true, filterable: true, width: 80 },
@ -323,10 +320,7 @@ export const AdminFeatureAccessPage: React.FC = () => {
// Get feature label
const getFeatureLabel = (code: string) => {
const feature = features.find(f => f.code === code);
if (feature) {
return t(feature.label || code);
}
return code;
return feature ? (feature.label || code) : code;
};
if (error && !selectedMandateId) {
@ -514,7 +508,7 @@ export const AdminFeatureAccessPage: React.FC = () => {
<DropdownSelect
items={features.map(f => ({
id: f.code,
label: t(f.label || f.code),
label: f.label || f.code,
value: f.code
}))}
selectedItemId={createFeatureCode}

View file

@ -367,10 +367,7 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => {
// Get feature label
const getFeatureLabel = (code: string) => {
const feature = features.find(f => f.code === code);
if (feature) {
return t(feature.label || code);
}
return code;
return feature ? (feature.label || code) : code;
};
// Get selected instance info from combined options

View file

@ -260,10 +260,8 @@ export const AdminFeatureRolesPage: React.FC = () => {
setEditingRole(role);
};
// Get feature name - Backend uses 'label' field (German i18n key or legacy multilingual)
const getFeatureName = (feature: Feature) => {
const raw = getTextValue(feature.label || feature.name);
return raw === '-' ? '-' : t(raw);
return feature.label || feature.name || '-';
};
if (error && !selectedFeatureCode) {

View file

@ -109,10 +109,7 @@ export const AdminMandateWizardPage: React.FC = () => {
const getFeatureLabel = (code: string): string => {
const f = features.find(feat => feat.code === code);
if (f) {
return t(f.label || code);
}
return code;
return f ? (f.label || code) : code;
};
const getUserDisplayName = (u: { fullName?: string; firstname?: string | null; lastname?: string | null; username: string }): string => {

View file

@ -57,8 +57,8 @@ export const FeatureInstanceWizard: React.FC<FeatureInstanceWizardProps> = ({ ma
const [selectedUserRoles, setSelectedUserRoles] = useState<Array<{ userId: string; roleIds: string[] }>>([]);
const featureOptions = useMemo(
() => features.map((f) => ({ value: f.code, label: t(f.label || f.code) })),
[features, t]
() => features.map((f) => ({ value: f.code, label: f.label || f.code })),
[features]
);
const mandateOptions = useMemo(
() => mandates.map((m) => ({ value: m.id, label: getMandateName(m) })),

View file

@ -858,7 +858,7 @@ export const CommcoachDossierView: React.FC<CommcoachDossierViewProps> = ({ pers
{_groupScoresByDimension(coach.scores).map(group => (
<div key={group.dimension} className={styles.scoreGroup}>
<div className={styles.scoreDimension}>
<span className={styles.scoreDimensionLabel}>{t(_dimensionLabel(group.dimension))}</span>
<span className={styles.scoreDimensionLabel}>{_dimensionLabel(group.dimension, t)}</span>
<span className={styles.scoreLatest}>{Math.round(group.latest.score)}/100</span>
<span className={`${styles.scoreTrend} ${styles[`trend_${group.latest.trend}`]}`}>
{group.latest.trend === 'improving' ? 'steigend' : group.latest.trend === 'declining' ? 'sinkend' : 'stabil'}
@ -926,13 +926,15 @@ function _groupScoresByDimension(scores: any[]): ScoreGroup[] {
return Object.values(groups);
}
function _dimensionLabel(dim: string): string {
const labels: Record<string, string> = {
empathy: 'Einfühlungsvermögen', clarity: 'Klarheit',
assertiveness: 'Durchsetzung', listening: 'Zuhören',
selfReflection: 'Selbstreflexion',
};
return labels[dim] || dim;
function _dimensionLabel(dim: string, t: (k: string) => string): string {
switch (dim) {
case 'empathy': return t('Einfühlungsvermögen');
case 'clarity': return t('Klarheit');
case 'assertiveness': return t('Durchsetzung');
case 'listening': return t('Zuhören');
case 'selfReflection': return t('Selbstreflexion');
default: return dim;
}
}
function _formatToolPayload(payload: Record<string, unknown>): string {

View file

@ -30,15 +30,18 @@ import styles from './Automation2WorkflowsTasks.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext';
const NODE_TYPE_LABELS: Record<string, string> = {
'input.form': 'Formular',
'input.approval': 'Genehmigung',
'input.upload': 'Upload',
'input.comment': 'Kommentar',
'input.review': 'Prüfung',
'input.selection': 'Auswahl',
'input.confirmation': 'Bestätigung',
};
function _nodeTypeLabel(nodeType: string, t: (k: string) => string): string {
switch (nodeType) {
case 'input.form': return t('Formular');
case 'input.approval': return t('Genehmigung');
case 'input.upload': return t('Upload');
case 'input.comment': return t('Kommentar');
case 'input.review': return t('Prüfung');
case 'input.selection': return t('Auswahl');
case 'input.confirmation': return t('Bestätigung');
default: return nodeType;
}
}
function formatTimestamp(ts?: number): string {
if (ts == null || ts <= 0) return '—';
@ -941,7 +944,7 @@ const TaskCard: React.FC<TaskCardProps> = ({
<div className={styles.taskMetaRow}>
<span className={styles.metaLabel}>{t('Typ')}</span>
<span className={styles.metaValue}>
{t(NODE_TYPE_LABELS[nodeType] ?? nodeType)}
{_nodeTypeLabel(nodeType, t)}
</span>
</div>

View file

@ -30,14 +30,19 @@ const _TABS: TabDef[] = [
{ id: 'year-end', templateTag: 'template:trustee-year-end-check', icon: '\u2705', color: '#795548' },
];
const _TAB_LABEL_KEYS: Record<string, string> = {
'year-end': 'Jahresabschluss prüfen',
};
function _tabLabel(tabId: string, t: (k: string) => string): string {
switch (tabId) {
case 'year-end': return t('Jahresabschluss prüfen');
default: return tabId;
}
}
const _TAB_DESCRIPTION_KEYS: Record<string, string> = {
'year-end':
'Automatische Prüfungen für den Jahresabschluss: Saldovalidierung, Vorjahresvergleich, gesetzliche Checks.',
};
function _tabDescription(tabId: string, t: (k: string) => string): string {
switch (tabId) {
case 'year-end': return t('Automatische Prüfungen für den Jahresabschluss: Saldovalidierung, Vorjahresvergleich, gesetzliche Checks.');
default: return '';
}
}
// ---------------------------------------------------------------------------
// Types
@ -225,7 +230,7 @@ export const TrusteeAbschlussView: React.FC = () => {
}}
>
<span style={{ marginRight: '0.375rem' }}>{tab.icon}</span>
{t(_TAB_LABEL_KEYS[tab.id] || tab.id)}
{_tabLabel(tab.id, t)}
</button>
))}
</div>
@ -234,7 +239,7 @@ export const TrusteeAbschlussView: React.FC = () => {
{/* Tab content */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<p className={styles.sectionDescription}>
{_TAB_DESCRIPTION_KEYS[activeTab] ? t(_TAB_DESCRIPTION_KEYS[activeTab]) : ''}
{_tabDescription(activeTab, t)}
</p>
{workflowsLoading ? (

View file

@ -33,19 +33,25 @@ const _TABS: TabDef[] = [
{ id: 'forecast', templateTag: 'template:trustee-forecast', icon: '\uD83D\uDCC8', color: '#E91E63' },
];
const _TAB_LABEL_KEYS: Record<string, string> = {
budget: 'Budget-Vergleich',
kpi: 'KPI-Dashboard',
cashflow: 'Cashflow-Rechnung',
forecast: 'Prognose',
};
function _tabLabel(tabId: string, t: (k: string) => string): string {
switch (tabId) {
case 'budget': return t('Budget-Vergleich');
case 'kpi': return t('KPI-Dashboard');
case 'cashflow': return t('Cashflow-Rechnung');
case 'forecast': return t('Prognose');
default: return tabId;
}
}
const _TAB_DESCRIPTION_KEYS: Record<string, string> = {
budget: 'Soll/Ist-Vergleich der Buchhaltung mit Budget-Excel',
kpi: 'Kennzahlen berechnen und visualisieren',
cashflow: 'Cashflow berechnen und analysieren',
forecast: 'Trend-Analyse und Prognose der nächsten Monate',
};
function _tabDescription(tabId: string, t: (k: string) => string): string {
switch (tabId) {
case 'budget': return t('Soll/Ist-Vergleich der Buchhaltung mit Budget-Excel');
case 'kpi': return t('Kennzahlen berechnen und visualisieren');
case 'cashflow': return t('Cashflow berechnen und analysieren');
case 'forecast': return t('Trend-Analyse und Prognose der nächsten Monate');
default: return '';
}
}
// ---------------------------------------------------------------------------
// Types
@ -240,7 +246,7 @@ export const TrusteeAnalyseView: React.FC = () => {
}}
>
<span style={{ marginRight: '0.375rem' }}>{tab.icon}</span>
{t(_TAB_LABEL_KEYS[tab.id] || tab.id)}
{_tabLabel(tab.id, t)}
</button>
))}
</div>
@ -248,7 +254,7 @@ export const TrusteeAnalyseView: React.FC = () => {
{/* Tab content */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<p className={styles.sectionDescription}>
{_TAB_DESCRIPTION_KEYS[activeTab] ? t(_TAB_DESCRIPTION_KEYS[activeTab]) : ''}
{_tabDescription(activeTab, t)}
</p>
{workflowsLoading ? (

View file

@ -7,7 +7,7 @@
* - Full details shown on error for debugging
*/
import React, { useState, useCallback } from 'react';
import React, { useState} from 'react';
import type { ToolActivity } from './useWorkspace';
import { useLanguage } from '../../../providers/language/LanguageContext';
@ -124,7 +124,7 @@ export const ToolActivityLog: React.FC<ToolActivityLogProps> = ({ activities })
const { t } = useLanguage();
const [expandedId, setExpandedId] = useState<string | null>(null);
const translate = useCallback<TranslateFn>((key, params) => t(key, params), [t]);
const translate: TranslateFn = t;
if (!activities.length) {
return (

View file

@ -25,16 +25,19 @@ import { formatBinaryDataSizeBytes } from '../../../utils/formatDataSize';
import { useLanguage } from '../../../providers/language/LanguageContext';
const MIME_LABELS: Record<string, string> = {
pdf: 'PDF',
office_doc: 'Office (Text)',
office_sheet: 'Office (Tabellen)',
office_slides: 'Office (Folien)',
text: 'Text',
image: 'Bild',
html: 'HTML',
other: 'Sonstige',
};
function _mimeLabel(key: string, t: (k: string) => string): string {
switch (key) {
case 'pdf': return t('PDF');
case 'office_doc': return t('Office (Text)');
case 'office_sheet': return t('Office (Tabellen)');
case 'office_slides': return t('Office (Folien)');
case 'text': return t('Text');
case 'image': return t('Bild');
case 'html': return t('HTML');
case 'other': return t('Sonstige');
default: return key;
}
}
const CHART_COLORS = ['#1976d2', '#00897b', '#6a1b9a', '#e65100', '#5d4037', '#455a64', '#c62828'];
@ -118,7 +121,7 @@ export const WorkspaceRagInsightsPage: React.FC = () => {
const kpis = stats?.kpis;
const timeline = stats?.timelineIndexedDocuments ?? [];
const mimeRows = Object.entries(stats?.documentsByMimeCategory ?? {}).map(([key, value]) => ({
name: t(MIME_LABELS[key] ?? key),
name: _mimeLabel(key, t),
value,
}));
const statusRows = Object.entries(stats?.indexedDocumentsByStatus ?? {}).map(([name, value]) => ({

View file

@ -10,7 +10,7 @@ type TranslateParams = Record<string, string | number | boolean | null | undefin
interface LanguageContextType {
currentLanguage: Language;
setLanguage: (language: Language) => void;
t: (key: string, paramsOrFallback?: TranslateParams | string) => string;
t: (key: string, params?: TranslateParams) => string;
isLoading: boolean;
reloadLanguage: () => Promise<void>;
availableLanguages: I18nCodeInfo[];
@ -123,18 +123,8 @@ export const LanguageProvider: React.FC<LanguageProviderProps> = ({ children })
return out;
};
const t = (key: string, paramsOrFallback?: TranslateParams | string): string => {
let params: TranslateParams | undefined;
if (typeof paramsOrFallback === 'string') {
params = undefined;
} else {
params = paramsOrFallback;
}
const resolved =
translations[key] ??
(typeof paramsOrFallback === 'string' ? paramsOrFallback : undefined) ??
`[${key}]`;
const t = (key: string, params?: TranslateParams): string => {
const resolved = translations[key] ?? `[${key}]`;
return _applyParams(resolved, params);
};