Merge pull request #22 from ualsweb/claude/bakery-dashboard-panel-01NWN5wz8Njp6c5766rzggj6

Fix dashboard translation issues and template variable interpolation
This commit is contained in:
ualsweb
2025-11-20 20:05:03 +01:00
committed by GitHub
4 changed files with 80 additions and 22 deletions

View File

@@ -77,18 +77,31 @@ function translateKey(
tDashboard: any, tDashboard: any,
tReasoning: any tReasoning: any
): string { ): 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 // Determine namespace based on key prefix
if (key.startsWith('reasoning.')) { if (key.startsWith('reasoning.')) {
// Remove 'reasoning.' prefix and use reasoning namespace // Remove 'reasoning.' prefix and use reasoning namespace
const translationKey = key.substring('reasoning.'.length); 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.')) { } else if (key.startsWith('action_queue.') || key.startsWith('dashboard.') || key.startsWith('health.')) {
// Use dashboard namespace // Use dashboard namespace
return tDashboard(key, { ...params, defaultValue: key }); return tDashboard(key, { ...processedParams, defaultValue: key });
} }
// Default to dashboard // Default to dashboard
return tDashboard(key, { ...params, defaultValue: key }); return tDashboard(key, { ...processedParams, defaultValue: key });
} }
function ActionItemCard({ function ActionItemCard({

View File

@@ -62,6 +62,7 @@ function translateKey(
// Map key prefixes to their correct namespaces // Map key prefixes to their correct namespaces
const namespaceMap: Record<string, string> = { const namespaceMap: Record<string, string> = {
'health.': 'dashboard', 'health.': 'dashboard',
'dashboard.health.': 'dashboard',
'dashboard.': 'dashboard', 'dashboard.': 'dashboard',
'reasoning.': 'reasoning', 'reasoning.': 'reasoning',
'production.': 'production', 'production.': 'production',
@@ -76,10 +77,16 @@ function translateKey(
if (key.startsWith(prefix)) { if (key.startsWith(prefix)) {
namespace = ns; namespace = ns;
// Remove the first segment if it matches the namespace // Remove the first segment if it matches the namespace
// e.g., "health.headline_yellow" stays as is for dashboard:health.headline_yellow // e.g., "dashboard.health.production_on_schedule" -> "health.production_on_schedule"
// e.g., "reasoning.types.xyz" -> keep as "types.xyz" for reasoning:types.xyz // e.g., "reasoning.types.xyz" -> "types.xyz" for reasoning namespace
if (prefix === 'reasoning.') { if (prefix === 'reasoning.') {
translationKey = key.substring(prefix.length); 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; break;
} }

View File

@@ -45,8 +45,17 @@ function TimelineItemCard({
const { reasoning } = useMemo(() => { const { reasoning } = useMemo(() => {
if (item.reasoning_i18n) { if (item.reasoning_i18n) {
// Use new i18n structure if available // Use new i18n structure if available
const { key, params } = item.reasoning_i18n; const { key, params = {} } = item.reasoning_i18n;
return { reasoning: t(key, params, { defaultValue: item.reasoning || '' }) }; // 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) { } else if (item.reasoning_data) {
return formatBatchAction(item.reasoning_data); return formatBatchAction(item.reasoning_data);
} }

View File

@@ -402,7 +402,13 @@ class DashboardService:
# Get reasoning type and convert to i18n key # Get reasoning type and convert to i18n key
reasoning_type = reasoning_data.get('type', 'inventory_replenishment') 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({ actions.append({
"id": po["id"], "id": po["id"],
@@ -418,7 +424,7 @@ class DashboardService:
}, },
"reasoning_i18n": { "reasoning_i18n": {
"key": reasoning_type_i18n_key, "key": reasoning_type_i18n_key,
"params": reasoning_data.get('parameters', {}) "params": params
}, },
"consequence_i18n": { "consequence_i18n": {
"key": "action_queue.consequences.delayed_delivery", "key": "action_queue.consequences.delayed_delivery",
@@ -465,18 +471,41 @@ class DashboardService:
return actions return actions
def _get_reasoning_type_i18n_key(self, reasoning_type: str) -> str: def _get_reasoning_type_i18n_key(self, reasoning_type: str, context: str = "purchaseOrder") -> str:
"""Map reasoning type identifiers to i18n keys""" """Map reasoning type identifiers to i18n keys
reasoning_type_map = {
"low_stock_detection": "reasoning.types.low_stock_detection", Args:
"stockout_prevention": "reasoning.types.stockout_prevention", reasoning_type: The type of reasoning (e.g., "low_stock_detection")
"forecast_demand": "reasoning.types.forecast_demand", context: The context (either "purchaseOrder" or "productionBatch")
"customer_orders": "reasoning.types.customer_orders",
"seasonal_demand": "reasoning.types.seasonal_demand", Returns:
"inventory_replenishment": "reasoning.types.inventory_replenishment", Full i18n key with namespace and context prefix
"production_schedule": "reasoning.types.production_schedule", """
} if context == "productionBatch":
return reasoning_type_map.get(reasoning_type, "reasoning.types.other") 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: def _calculate_po_urgency(self, po: Dict[str, Any]) -> str:
"""Calculate urgency of PO approval based on delivery date""" """Calculate urgency of PO approval based on delivery date"""
@@ -579,7 +608,7 @@ class DashboardService:
# Get reasoning type and convert to i18n key # Get reasoning type and convert to i18n key
reasoning_type = reasoning_data.get('type', 'forecast_demand') 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({ timeline.append({
"id": batch["id"], "id": batch["id"],