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:
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user