Fix dashboard translation issues and template variable interpolation

This commit resolves three critical translation/localization issues in the bakery dashboard:

1. **Health Status Translation Keys**: Fixed HealthStatusCard's translateKey function to properly handle `dashboard.health.*` keys by correctly stripping the `dashboard.` prefix while preserving the `health.` namespace path. This ensures checklist items like "production_on_schedule" and "all_ingredients_in_stock" display correctly in Spanish.

2. **Reasoning Translation Keys**: Updated backend dashboard_service.py to use the correct i18n key prefixes:
   - Purchase orders now use `reasoning.purchaseOrder.*` instead of `reasoning.types.*`
   - Production batches now use `reasoning.productionBatch.*`
   - Added context parameter to `_get_reasoning_type_i18n_key()` method for proper namespace routing

3. **Template Variable Interpolation**: Fixed template variable replacement in action cards:
   - Added array preprocessing logic in both backend and frontend to convert `product_names` arrays to `product_names_joined` strings
   - Updated ActionQueueCard's translateKey to preprocess array parameters before i18n interpolation
   - Fixed ProductionTimelineCard to properly handle reasoning namespace prefix removal

These fixes ensure that:
- Health status indicators show translated text instead of raw keys (e.g., "Producción a tiempo" vs "dashboard.health.production_on_schedule")
- Purchase order reasoning displays with proper product names and stockout days instead of literal template variables (e.g., "Stock bajo para Harina. El stock se agotará en 7 días" vs "Stock bajo para {{product_name}}")
- All dashboard components consistently handle i18n key namespaces and parameter interpolation

Affected files:
- frontend/src/components/dashboard/HealthStatusCard.tsx
- frontend/src/components/dashboard/ActionQueueCard.tsx
- frontend/src/components/dashboard/ProductionTimelineCard.tsx
- services/orchestrator/app/services/dashboard_service.py
This commit is contained in:
Claude
2025-11-20 19:03:39 +00:00
parent 5a2b0b0757
commit 8aa1b0d859
4 changed files with 80 additions and 22 deletions

View File

@@ -77,18 +77,31 @@ function translateKey(
tDashboard: any,
tReasoning: any
): string {
// Preprocess parameters - join arrays into strings
const processedParams = { ...params };
// Convert product_names array to product_names_joined string
if ('product_names' in processedParams && Array.isArray(processedParams.product_names)) {
processedParams.product_names_joined = processedParams.product_names.join(', ');
}
// Convert affected_products array to affected_products_joined string
if ('affected_products' in processedParams && Array.isArray(processedParams.affected_products)) {
processedParams.affected_products_joined = processedParams.affected_products.join(', ');
}
// 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 });
return tReasoning(translationKey, { ...processedParams, defaultValue: key });
} else if (key.startsWith('action_queue.') || key.startsWith('dashboard.') || key.startsWith('health.')) {
// Use dashboard namespace
return tDashboard(key, { ...params, defaultValue: key });
return tDashboard(key, { ...processedParams, defaultValue: key });
}
// Default to dashboard
return tDashboard(key, { ...params, defaultValue: key });
return tDashboard(key, { ...processedParams, defaultValue: key });
}
function ActionItemCard({

View File

@@ -62,6 +62,7 @@ function translateKey(
// Map key prefixes to their correct namespaces
const namespaceMap: Record<string, string> = {
'health.': 'dashboard',
'dashboard.health.': 'dashboard',
'dashboard.': 'dashboard',
'reasoning.': 'reasoning',
'production.': 'production',
@@ -76,10 +77,16 @@ function translateKey(
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
// e.g., "dashboard.health.production_on_schedule" -> "health.production_on_schedule"
// e.g., "reasoning.types.xyz" -> "types.xyz" for reasoning namespace
if (prefix === 'reasoning.') {
translationKey = key.substring(prefix.length);
} else if (prefix === 'dashboard.health.') {
// Keep "health." prefix but remove "dashboard."
translationKey = key.substring('dashboard.'.length);
} else if (prefix === 'dashboard.' && !key.startsWith('dashboard.health.')) {
// For other dashboard keys, remove "dashboard." prefix
translationKey = key.substring('dashboard.'.length);
}
break;
}

View File

@@ -45,8 +45,17 @@ function TimelineItemCard({
const { reasoning } = useMemo(() => {
if (item.reasoning_i18n) {
// Use new i18n structure if available
const { key, params } = item.reasoning_i18n;
return { reasoning: t(key, params, { defaultValue: item.reasoning || '' }) };
const { key, params = {} } = item.reasoning_i18n;
// Handle namespace - remove "reasoning." prefix for reasoning namespace
let translationKey = key;
let namespace = 'reasoning';
if (key.startsWith('reasoning.')) {
translationKey = key.substring('reasoning.'.length);
} else if (key.startsWith('production.')) {
translationKey = key.substring('production.'.length);
namespace = 'production';
}
return { reasoning: t(translationKey, { ...params, ns: namespace, defaultValue: item.reasoning || '' }) };
} else if (item.reasoning_data) {
return formatBatchAction(item.reasoning_data);
}