diff --git a/frontend/src/components/dashboard/ActionQueueCard.tsx b/frontend/src/components/dashboard/ActionQueueCard.tsx index 0298ebc0..25d56ae9 100644 --- a/frontend/src/components/dashboard/ActionQueueCard.tsx +++ b/frontend/src/components/dashboard/ActionQueueCard.tsx @@ -67,6 +67,30 @@ const urgencyConfig = { }, }; +/** + * Helper function to translate keys with proper namespace handling + * Maps backend key formats to correct i18next namespaces + */ +function translateKey( + key: string, + params: Record, + tDashboard: any, + tReasoning: any +): string { + // Determine namespace based on key prefix + if (key.startsWith('reasoning.')) { + // Remove 'reasoning.' prefix and use reasoning namespace + const translationKey = key.substring('reasoning.'.length); + return tReasoning(translationKey, { ...params, defaultValue: key }); + } else if (key.startsWith('action_queue.') || key.startsWith('dashboard.') || key.startsWith('health.')) { + // Use dashboard namespace + return tDashboard(key, { ...params, defaultValue: key }); + } + + // Default to dashboard + return tDashboard(key, { ...params, defaultValue: key }); +} + function ActionItemCard({ action, onApprove, @@ -102,25 +126,25 @@ function ActionItemCard({ // Translate i18n fields (or fallback to deprecated text fields or reasoning_data for alerts) const reasoning = useMemo(() => { if (action.reasoning_i18n) { - return tDashboard(action.reasoning_i18n.key, action.reasoning_i18n.params); + return translateKey(action.reasoning_i18n.key, action.reasoning_i18n.params, tDashboard, tReasoning); } if (action.reasoning_data) { const formatted = formatPOAction(action.reasoning_data); return formatted.reasoning; } return action.reasoning || ''; - }, [action.reasoning_i18n, action.reasoning_data, action.reasoning, tDashboard, formatPOAction]); + }, [action.reasoning_i18n, action.reasoning_data, action.reasoning, tDashboard, tReasoning, formatPOAction]); const consequence = useMemo(() => { if (action.consequence_i18n) { - return tDashboard(action.consequence_i18n.key, action.consequence_i18n.params); + return translateKey(action.consequence_i18n.key, action.consequence_i18n.params, tDashboard, tReasoning); } if (action.reasoning_data) { const formatted = formatPOAction(action.reasoning_data); return formatted.consequence; } return ''; - }, [action.consequence_i18n, action.reasoning_data, tDashboard, formatPOAction]); + }, [action.consequence_i18n, action.reasoning_data, tDashboard, tReasoning, formatPOAction]); return (

- {action.title_i18n ? tDashboard(action.title_i18n.key, action.title_i18n.params) : (action.title || 'Action Required')} + {action.title_i18n ? translateKey(action.title_i18n.key, action.title_i18n.params, tDashboard, tReasoning) : (action.title || 'Action Required')}

- {action.subtitle_i18n ? tDashboard(action.subtitle_i18n.key, action.subtitle_i18n.params) : (action.subtitle || '')} + {action.subtitle_i18n ? translateKey(action.subtitle_i18n.key, action.subtitle_i18n.params, tDashboard, tReasoning) : (action.subtitle || '')}

@@ -493,7 +517,7 @@ function ActionItemCard({ {button.action === 'reject' && } {button.action === 'view_details' && } {button.action === 'modify' && } - {tDashboard(button.label_i18n.key, button.label_i18n.params)} + {translateKey(button.label_i18n.key, button.label_i18n.params, tDashboard, tReasoning)} ); })} diff --git a/frontend/src/components/dashboard/HealthStatusCard.tsx b/frontend/src/components/dashboard/HealthStatusCard.tsx index 5f89995d..a8ea5d37 100644 --- a/frontend/src/components/dashboard/HealthStatusCard.tsx +++ b/frontend/src/components/dashboard/HealthStatusCard.tsx @@ -50,8 +50,46 @@ const iconMap = { alert: AlertCircle, }; +/** + * Helper function to translate keys with proper namespace handling + * Maps backend key formats to correct i18next namespaces + */ +function translateKey( + key: string, + params: Record, + t: any +): string { + // Map key prefixes to their correct namespaces + const namespaceMap: Record = { + 'health.': 'dashboard', + 'dashboard.': 'dashboard', + 'reasoning.': 'reasoning', + 'production.': 'production', + 'jtbd.': 'reasoning', + }; + + // Find the matching namespace + let namespace = 'common'; + let translationKey = key; + + for (const [prefix, ns] of Object.entries(namespaceMap)) { + if (key.startsWith(prefix)) { + namespace = ns; + // Remove the first segment if it matches the namespace + // e.g., "health.headline_yellow" stays as is for dashboard:health.headline_yellow + // e.g., "reasoning.types.xyz" -> keep as "types.xyz" for reasoning:types.xyz + if (prefix === 'reasoning.') { + translationKey = key.substring(prefix.length); + } + break; + } + } + + return t(translationKey, { ...params, ns: namespace, defaultValue: key }); +} + export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProps) { - const { t, i18n } = useTranslation('reasoning'); + const { t, i18n } = useTranslation(['dashboard', 'reasoning', 'production']); // Get date-fns locale based on current language const dateLocale = i18n.language === 'es' ? es : i18n.language === 'eu' ? eu : enUS; @@ -85,21 +123,21 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp

{typeof healthStatus.headline === 'object' && healthStatus.headline?.key - ? t(healthStatus.headline.key, healthStatus.headline.params || {}) - : healthStatus.headline || t(`jtbd.health_status.${status}`)} + ? translateKey(healthStatus.headline.key, healthStatus.headline.params || {}, t) + : healthStatus.headline || t(`jtbd.health_status.${status}`, { ns: 'reasoning' })}

{/* Last Update */}
- {t('jtbd.health_status.last_updated')}:{' '} + {t('jtbd.health_status.last_updated', { ns: 'reasoning' })}:{' '} {healthStatus.lastOrchestrationRun ? formatDistanceToNow(new Date(healthStatus.lastOrchestrationRun), { addSuffix: true, locale: dateLocale, }) - : t('jtbd.health_status.never')} + : t('jtbd.health_status.never', { ns: 'reasoning' })}
@@ -108,7 +146,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
- {t('jtbd.health_status.next_check')}:{' '} + {t('jtbd.health_status.next_check', { ns: 'reasoning' })}:{' '} {formatDistanceToNow(new Date(healthStatus.nextScheduledRun), { addSuffix: true, locale: dateLocale })}
@@ -126,7 +164,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp // Translate using textKey if available, otherwise use text const displayText = item.textKey - ? t(item.textKey.replace('.', ':'), item.textParams || {}) + ? translateKey(item.textKey, item.textParams || {}, t) : item.text || ''; return ( @@ -156,7 +194,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
- {t('jtbd.health_status.critical_issues', { count: healthStatus.criticalIssues })} + {t('jtbd.health_status.critical_issues', { count: healthStatus.criticalIssues, ns: 'reasoning' })}
)} @@ -164,7 +202,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
- {t('jtbd.health_status.actions_needed', { count: healthStatus.pendingActions })} + {t('jtbd.health_status.actions_needed', { count: healthStatus.pendingActions, ns: 'reasoning' })}
)} diff --git a/frontend/src/components/dashboard/ProductionTimelineCard.tsx b/frontend/src/components/dashboard/ProductionTimelineCard.tsx index de41e6a2..f2779c59 100644 --- a/frontend/src/components/dashboard/ProductionTimelineCard.tsx +++ b/frontend/src/components/dashboard/ProductionTimelineCard.tsx @@ -38,7 +38,7 @@ function TimelineItemCard({ }) { const priorityColor = priorityColors[item.priority as keyof typeof priorityColors] || 'var(--text-tertiary)'; const { formatBatchAction } = useReasoningFormatter(); - const { t } = useTranslation(['reasoning', 'dashboard']); + const { t } = useTranslation(['reasoning', 'dashboard', 'production']); // Translate reasoning_data (or use new reasoning_i18n or fallback to deprecated text field) // Memoize to prevent undefined values from being created on each render @@ -102,7 +102,13 @@ function TimelineItemCard({
- {item.status_i18n ? t(item.status_i18n.key, item.status_i18n.params) : item.statusText || 'Status'} + {item.status_i18n + ? t(item.status_i18n.key, { + ...item.status_i18n.params, + ns: item.status_i18n.key.startsWith('production.') ? 'production' : 'reasoning', + defaultValue: item.statusText || 'Status' + }) + : item.statusText || 'Status'} {item.status === 'IN_PROGRESS' && ( {item.progress || 0}%