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

@@ -402,7 +402,13 @@ class DashboardService:
# Get reasoning type and convert to i18n key
reasoning_type = reasoning_data.get('type', 'inventory_replenishment')
reasoning_type_i18n_key = self._get_reasoning_type_i18n_key(reasoning_type)
reasoning_type_i18n_key = self._get_reasoning_type_i18n_key(reasoning_type, context="purchaseOrder")
# Preprocess parameters for i18n
params = reasoning_data.get('parameters', {})
# Convert product_names array to product_names_joined string
if 'product_names' in params and isinstance(params['product_names'], list):
params['product_names_joined'] = ', '.join(params['product_names'])
actions.append({
"id": po["id"],
@@ -418,7 +424,7 @@ class DashboardService:
},
"reasoning_i18n": {
"key": reasoning_type_i18n_key,
"params": reasoning_data.get('parameters', {})
"params": params
},
"consequence_i18n": {
"key": "action_queue.consequences.delayed_delivery",
@@ -465,18 +471,41 @@ class DashboardService:
return actions
def _get_reasoning_type_i18n_key(self, reasoning_type: str) -> str:
"""Map reasoning type identifiers to i18n keys"""
reasoning_type_map = {
"low_stock_detection": "reasoning.types.low_stock_detection",
"stockout_prevention": "reasoning.types.stockout_prevention",
"forecast_demand": "reasoning.types.forecast_demand",
"customer_orders": "reasoning.types.customer_orders",
"seasonal_demand": "reasoning.types.seasonal_demand",
"inventory_replenishment": "reasoning.types.inventory_replenishment",
"production_schedule": "reasoning.types.production_schedule",
}
return reasoning_type_map.get(reasoning_type, "reasoning.types.other")
def _get_reasoning_type_i18n_key(self, reasoning_type: str, context: str = "purchaseOrder") -> str:
"""Map reasoning type identifiers to i18n keys
Args:
reasoning_type: The type of reasoning (e.g., "low_stock_detection")
context: The context (either "purchaseOrder" or "productionBatch")
Returns:
Full i18n key with namespace and context prefix
"""
if context == "productionBatch":
reasoning_type_map = {
"forecast_demand": "reasoning.productionBatch.forecast_demand",
"customer_order": "reasoning.productionBatch.customer_order",
"stock_replenishment": "reasoning.productionBatch.stock_replenishment",
"seasonal_preparation": "reasoning.productionBatch.seasonal_preparation",
"promotion_event": "reasoning.productionBatch.promotion_event",
"urgent_order": "reasoning.productionBatch.urgent_order",
"regular_schedule": "reasoning.productionBatch.regular_schedule",
}
else: # purchaseOrder context
reasoning_type_map = {
"low_stock_detection": "reasoning.purchaseOrder.low_stock_detection",
"stockout_prevention": "reasoning.purchaseOrder.low_stock_detection",
"forecast_demand": "reasoning.purchaseOrder.forecast_demand",
"customer_orders": "reasoning.purchaseOrder.forecast_demand",
"seasonal_demand": "reasoning.purchaseOrder.seasonal_demand",
"inventory_replenishment": "reasoning.purchaseOrder.safety_stock_replenishment",
"production_schedule": "reasoning.purchaseOrder.production_requirement",
"supplier_contract": "reasoning.purchaseOrder.supplier_contract",
"safety_stock_replenishment": "reasoning.purchaseOrder.safety_stock_replenishment",
"production_requirement": "reasoning.purchaseOrder.production_requirement",
"manual_request": "reasoning.purchaseOrder.manual_request",
}
return reasoning_type_map.get(reasoning_type, f"reasoning.{context}.forecast_demand")
def _calculate_po_urgency(self, po: Dict[str, Any]) -> str:
"""Calculate urgency of PO approval based on delivery date"""
@@ -579,7 +608,7 @@ class DashboardService:
# Get reasoning type and convert to i18n key
reasoning_type = reasoning_data.get('type', 'forecast_demand')
reasoning_type_i18n_key = self._get_reasoning_type_i18n_key(reasoning_type)
reasoning_type_i18n_key = self._get_reasoning_type_i18n_key(reasoning_type, context="productionBatch")
timeline.append({
"id": batch["id"],