diff --git a/REASONING_I18N_IMPLEMENTATION_SUMMARY.md b/REASONING_I18N_IMPLEMENTATION_SUMMARY.md
new file mode 100644
index 00000000..676170c0
--- /dev/null
+++ b/REASONING_I18N_IMPLEMENTATION_SUMMARY.md
@@ -0,0 +1,402 @@
+# Reasoning i18n Implementation - Complete Summary
+
+## ✅ Completed Implementation
+
+### 1. **Backend: Structured Reasoning Data Generation**
+
+#### Created Standard Reasoning Types (`shared/schemas/reasoning_types.py`)
+```python
+# Purchase Order Types
+- low_stock_detection
+- forecast_demand
+- safety_stock_replenishment
+- supplier_contract
+- seasonal_demand
+- production_requirement
+- manual_request
+
+# Production Batch Types
+- forecast_demand
+- customer_order
+- stock_replenishment
+- seasonal_preparation
+- promotion_event
+- urgent_order
+- regular_schedule
+```
+
+#### Helper Functions
+```python
+create_po_reasoning_low_stock()
+create_po_reasoning_forecast_demand()
+create_batch_reasoning_forecast_demand()
+create_batch_reasoning_customer_order()
+```
+
+### 2. **Backend: Services Updated**
+
+#### ✅ Production Service
+**File:** `services/production/app/services/production_service.py:1839-1867`
+- Generates `reasoning_data` when creating production batches
+- Includes: product_name, predicted_demand, current_stock, confidence_score
+
+**Example Output:**
+```json
+{
+ "type": "forecast_demand",
+ "parameters": {
+ "product_name": "Croissant",
+ "predicted_demand": 500,
+ "current_stock": 120,
+ "production_needed": 380,
+ "confidence_score": 87
+ },
+ "urgency": {
+ "level": "normal",
+ "ready_by_time": "08:00"
+ },
+ "metadata": {
+ "trigger_source": "orchestrator_auto",
+ "ai_assisted": true
+ }
+}
+```
+
+#### ✅ Procurement Service
+**File:** `services/procurement/app/services/procurement_service.py:874-1040`
+- **NEW:** Implemented actual PO creation (replaced placeholder!)
+- Groups requirements by supplier
+- Intelligently chooses reasoning type based on context
+- Generates comprehensive reasoning_data
+
+**Example Output:**
+```json
+{
+ "type": "low_stock_detection",
+ "parameters": {
+ "supplier_name": "Harinas del Norte",
+ "product_names": ["Flour Type 55", "Flour Type 45"],
+ "current_stock": 45.5,
+ "required_stock": 200,
+ "days_until_stockout": 3,
+ "stock_percentage": 22.8
+ },
+ "consequence": {
+ "type": "stockout_risk",
+ "severity": "high",
+ "impact_days": 3,
+ "affected_products": ["Baguette", "Croissant"],
+ "estimated_lost_orders": 15
+ },
+ "metadata": {
+ "trigger_source": "orchestrator_auto",
+ "forecast_confidence": 0.85,
+ "ai_assisted": true
+ }
+}
+```
+
+#### ✅ Dashboard Service
+**File:** `services/orchestrator/app/services/dashboard_service.py`
+- Returns `reasoning_data` instead of TEXT fields
+- Creates defaults if missing
+- Both PO actions and production timeline use structured data
+
+### 3. **Backend: Database Schema**
+
+#### ✅ Models Updated
+- **PurchaseOrder:** Removed `reasoning`, `consequence` TEXT columns
+- **ProductionBatch:** Removed `reasoning` TEXT column
+- Both use only `reasoning_data` (JSONB/JSON)
+
+#### ✅ Unified Schemas Updated
+- `services/procurement/migrations/001_unified_initial_schema.py`
+- `services/production/migrations/001_unified_initial_schema.py`
+- No separate migration needed - updated initial schema
+
+### 4. **Frontend: i18n Translation System**
+
+#### ✅ Translation Files Created
+**Languages:** English (EN), Spanish (ES), Basque/Euskara (EU)
+
+**Files:**
+- `frontend/src/locales/en/reasoning.json`
+- `frontend/src/locales/es/reasoning.json`
+- `frontend/src/locales/eu/reasoning.json`
+
+**Translation Coverage:**
+- ✅ All purchase order reasoning types
+- ✅ All production batch reasoning types
+- ✅ All consequence types
+- ✅ Severity levels
+- ✅ Error codes
+- ✅ Complete JTBD dashboard UI text
+
+**Example Translations:**
+
+| Language | Translation |
+|---|---|
+| 🇬🇧 EN | "Low stock for {{supplier_name}}. Stock runs out in {{days_until_stockout}} days." |
+| 🇪🇸 ES | "Stock bajo para {{supplier_name}}. Se agota en {{days_until_stockout}} días." |
+| 🇪🇺 EU | "{{supplier_name}}-rentzat stock baxua. {{days_until_stockout}} egunetan amaituko da." |
+
+#### ✅ Translation Hook Created
+**File:** `frontend/src/hooks/useReasoningTranslation.ts`
+
+**Functions:**
+```typescript
+translatePOReasonng(reasoningData) // Purchase orders
+translateBatchReasoning(reasoningData) // Production batches
+translateConsequence(consequenceData) // Consequences
+translateSeverity(severity) // Severity levels
+translateTrigger(trigger) // Trigger sources
+translateError(errorCode) // Error codes
+
+// High-level formatters
+formatPOAction(reasoningData) // Complete PO formatting
+formatBatchAction(reasoningData) // Complete batch formatting
+```
+
+**Usage Example:**
+```typescript
+import { useReasoningFormatter } from '@/hooks/useReasoningTranslation';
+
+function ActionQueueCard({ action }) {
+ const { formatPOAction } = useReasoningFormatter();
+
+ const { reasoning, consequence, severity } = formatPOAction(action.reasoning_data);
+
+ return (
+
+
{reasoning}
{/* Translated! */}
+
{consequence}
{/* Translated! */}
+
+ );
+}
+```
+
+---
+
+## 🔄 Remaining Work
+
+### 1. **Frontend Components Need Updates**
+
+#### ❌ ActionQueueCard.tsx
+**Current:** Expects `reasoning` and `consequence` TEXT fields
+**Needed:** Use `useReasoningFormatter()` to translate `reasoning_data`
+
+**Change Required:**
+```typescript
+// BEFORE
+{action.reasoning}
+{action.consequence}
+
+// AFTER
+import { useReasoningFormatter } from '@/hooks/useReasoningTranslation';
+
+const { formatPOAction } = useReasoningFormatter();
+const { reasoning, consequence } = formatPOAction(action.reasoning_data);
+
+{reasoning}
+{consequence}
+```
+
+#### ❌ ProductionTimelineCard.tsx
+**Needed:** Use `formatBatchAction()` to translate batch reasoning
+
+#### ❌ OrchestrationSummaryCard.tsx
+**Needed:** Replace hardcoded English text with i18n keys:
+- "Last Night I Planned Your Day" → `t('reasoning:jtbd.orchestration_summary.title')`
+- "All caught up!" → `t('reasoning:jtbd.action_queue.all_caught_up')`
+- etc.
+
+#### ❌ HealthStatusCard.tsx
+**Needed:** Replace hardcoded text with i18n
+
+### 2. **Backend Services Need Error Code Updates**
+
+#### ❌ Safety Stock Calculator
+**File:** `services/procurement/app/services/safety_stock_calculator.py`
+
+**Current:**
+```python
+reasoning='Lead time or demand std dev is zero or negative'
+reasoning='Insufficient historical demand data...'
+```
+
+**Needed:**
+```python
+reasoning_data={
+ "type": "error",
+ "code": "LEAD_TIME_INVALID",
+ "parameters": {}
+}
+```
+
+#### ❌ Replenishment Planning Service
+**File:** `services/procurement/app/services/replenishment_planning_service.py`
+
+**Current:**
+```python
+reasoning='Insufficient data for safety stock calculation'
+```
+
+**Needed:**
+```python
+reasoning_data={
+ "type": "error",
+ "code": "INSUFFICIENT_DATA",
+ "parameters": {}
+}
+```
+
+### 3. **Demo Seed Scripts Need Updates**
+
+#### ❌ Purchase Orders Seed
+**File:** `services/procurement/scripts/demo/seed_demo_purchase_orders.py`
+
+**Current (lines 126-127):**
+```python
+reasoning_text = f"Low stock detected for {supplier.name} items..."
+consequence_text = f"Stock-out risk in {days_until_delivery + 2} days..."
+```
+
+**Needed:**
+```python
+from shared.schemas.reasoning_types import create_po_reasoning_low_stock
+
+reasoning_data = create_po_reasoning_low_stock(
+ supplier_name=supplier.name,
+ product_names=[...],
+ current_stock=...,
+ required_stock=...,
+ days_until_stockout=days_until_delivery + 2
+)
+```
+
+#### ❌ Production Batches Seed
+**File:** `services/production/scripts/demo/seed_demo_batches.py`
+**Needed:** Similar update using `create_batch_reasoning_*()` functions
+
+---
+
+## 📋 Quick Implementation Checklist
+
+### High Priority (Breaks Current Functionality)
+- [ ] Update `ActionQueueCard.tsx` to use reasoning translation
+- [ ] Update `ProductionTimelineCard.tsx` to use reasoning translation
+- [ ] Update demo seed scripts to use structured reasoning_data
+
+### Medium Priority (Improves UX)
+- [ ] Update `OrchestrationSummaryCard.tsx` with i18n
+- [ ] Update `HealthStatusCard.tsx` with i18n
+- [ ] Update `InsightsGrid.tsx` with i18n (if needed)
+
+### Low Priority (Code Quality)
+- [ ] Update safety stock calculator with error codes
+- [ ] Update replenishment service with error codes
+- [ ] Audit ML services for hardcoded text
+
+---
+
+## 🎯 Example Implementation for ActionQueueCard
+
+```typescript
+// frontend/src/components/dashboard/ActionQueueCard.tsx
+
+import { useReasoningFormatter } from '@/hooks/useReasoningTranslation';
+import { useTranslation } from 'react-i18next';
+
+function ActionItemCard({ action, onApprove, onViewDetails, onModify }: ...) {
+ const { formatPOAction } = useReasoningFormatter();
+ const { t } = useTranslation('reasoning');
+
+ // Translate reasoning_data
+ const { reasoning, consequence, severity } = formatPOAction(action.reasoning_data);
+
+ return (
+
+ {/* Reasoning (always visible) */}
+
+
+ {t('jtbd.action_queue.why_needed')}
+
+
{reasoning}
+
+
+ {/* Consequence (expandable) */}
+
+
+ {expanded && (
+
+
{consequence}
+ {severity && (
+
{severity}
+ )}
+
+ )}
+
+ );
+}
+```
+
+---
+
+## 🚀 Benefits Achieved
+
+1. **✅ Multilingual Support**
+ - Dashboard works in EN, ES, and EU
+ - Easy to add more languages (CA, FR, etc.)
+
+2. **✅ Maintainability**
+ - Backend: One place to define reasoning logic
+ - Frontend: Translations in organized JSON files
+ - No hardcoded text scattered across code
+
+3. **✅ Consistency**
+ - Same reasoning type always translates the same way
+ - Centralized terminology
+
+4. **✅ Flexibility**
+ - Can change wording without touching code
+ - Can A/B test different phrasings
+ - Translators can work independently
+
+5. **✅ Type Safety**
+ - TypeScript interfaces for reasoning_data
+ - Compile-time checks for translation keys
+
+---
+
+## 📚 Documentation
+
+- **Reasoning Types:** `shared/schemas/reasoning_types.py`
+- **Translation Hook:** `frontend/src/hooks/useReasoningTranslation.ts`
+- **Translation Files:** `frontend/src/locales/{en,es,eu}/reasoning.json`
+- **Audit Report:** `REASONING_I18N_AUDIT.md`
+
+---
+
+## Next Steps
+
+1. **Update frontend components** (30-60 min)
+ - Replace TEXT field usage with reasoning_data translation
+ - Use `useReasoningFormatter()` hook
+ - Replace hardcoded strings with `t()` calls
+
+2. **Update demo seed scripts** (15-30 min)
+ - Replace hardcoded text with helper functions
+ - Test demo data generation
+
+3. **Update backend services** (15-30 min)
+ - Replace hardcoded error messages with error codes
+ - Frontend will translate error codes
+
+4. **Test** (30 min)
+ - Switch between EN, ES, EU
+ - Verify all reasoning types display correctly
+ - Check mobile responsiveness
+
+**Total Estimated Time:** 2-3 hours for complete implementation
diff --git a/frontend/src/api/hooks/newDashboard.ts b/frontend/src/api/hooks/newDashboard.ts
index 075ca33b..71bfc112 100644
--- a/frontend/src/api/hooks/newDashboard.ts
+++ b/frontend/src/api/hooks/newDashboard.ts
@@ -81,8 +81,9 @@ export interface ActionItem {
urgency: 'critical' | 'important' | 'normal';
title: string;
subtitle: string;
- reasoning: string;
- consequence: string;
+ reasoning?: string; // Deprecated: Use reasoning_data instead
+ consequence?: string; // Deprecated: Use reasoning_data instead
+ reasoning_data?: any; // Structured reasoning data for i18n translation
amount?: number;
currency?: string;
actions: ActionButton[];
@@ -111,7 +112,8 @@ export interface ProductionTimelineItem {
progress: number;
readyBy: string | null;
priority: string;
- reasoning: string;
+ reasoning?: string; // Deprecated: Use reasoning_data instead
+ reasoning_data?: any; // Structured reasoning data for i18n translation
}
export interface ProductionTimeline {
diff --git a/frontend/src/components/dashboard/ActionQueueCard.tsx b/frontend/src/components/dashboard/ActionQueueCard.tsx
index db7ab8c2..131a784f 100644
--- a/frontend/src/components/dashboard/ActionQueueCard.tsx
+++ b/frontend/src/components/dashboard/ActionQueueCard.tsx
@@ -21,6 +21,8 @@ import {
ChevronUp,
} from 'lucide-react';
import { ActionItem, ActionQueue } from '../../api/hooks/newDashboard';
+import { useReasoningFormatter } from '../../hooks/useReasoningTranslation';
+import { useTranslation } from 'react-i18next';
interface ActionQueueCardProps {
actionQueue: ActionQueue;
@@ -65,6 +67,13 @@ function ActionItemCard({
const [expanded, setExpanded] = useState(false);
const config = urgencyConfig[action.urgency];
const UrgencyIcon = config.icon;
+ const { formatPOAction } = useReasoningFormatter();
+ const { t } = useTranslation('reasoning');
+
+ // Translate reasoning_data (or fallback to deprecated text fields)
+ const { reasoning, consequence, severity } = action.reasoning_data
+ ? formatPOAction(action.reasoning_data)
+ : { reasoning: action.reasoning || '', consequence: action.consequence || '', severity: '' };
return (
-
Why this is needed:
-
{action.reasoning}
+
+ {t('jtbd.action_queue.why_needed')}
+
+
{reasoning}
{/* Consequence (expandable) */}
-
+ {consequence && (
+ <>
+
- {expanded && (
-
+ {expanded && (
+
+
{consequence}
+ {severity && (
+
+ {severity}
+
+ )}
+
+ )}
+ >
)}
{/* Time Estimate */}
- Estimated time: {action.estimatedTimeMinutes} min
+
+ {t('jtbd.action_queue.estimated_time')}: {action.estimatedTimeMinutes} min
+
{/* Action Buttons */}
@@ -167,6 +189,7 @@ export function ActionQueueCard({
}: ActionQueueCardProps) {
const [showAll, setShowAll] = useState(false);
const displayedActions = showAll ? actionQueue.actions : actionQueue.actions.slice(0, 3);
+ const { t } = useTranslation('reasoning');
if (loading) {
return (
@@ -184,8 +207,10 @@ export function ActionQueueCard({
return (
-
All caught up!
-
No actions requiring your attention right now.
+
+ {t('jtbd.action_queue.all_caught_up')}
+
+
{t('jtbd.action_queue.no_actions')}
);
}
@@ -194,10 +219,10 @@ export function ActionQueueCard({
{/* Header */}
-
What Needs Your Attention
+ {t('jtbd.action_queue.title')}
{actionQueue.totalActions > 3 && (
- {actionQueue.totalActions} total
+ {actionQueue.totalActions} {t('jtbd.action_queue.total')}
)}
@@ -207,12 +232,12 @@ export function ActionQueueCard({
{actionQueue.criticalCount > 0 && (
- {actionQueue.criticalCount} critical
+ {actionQueue.criticalCount} {t('jtbd.action_queue.critical')}
)}
{actionQueue.importantCount > 0 && (
- {actionQueue.importantCount} important
+ {actionQueue.importantCount} {t('jtbd.action_queue.important')}
)}
@@ -238,10 +263,8 @@ export function ActionQueueCard({
className="w-full mt-4 py-3 bg-gray-100 hover:bg-gray-200 rounded-lg font-semibold text-gray-700 transition-colors duration-200"
>
{showAll
- ? 'Show Less'
- : `Show ${actionQueue.totalActions - 3} More Action${
- actionQueue.totalActions - 3 !== 1 ? 's' : ''
- }`}
+ ? t('jtbd.action_queue.show_less')
+ : t('jtbd.action_queue.show_more', { count: actionQueue.totalActions - 3 })}
)}
diff --git a/frontend/src/components/dashboard/HealthStatusCard.tsx b/frontend/src/components/dashboard/HealthStatusCard.tsx
index 4675efe1..97a5dfcb 100644
--- a/frontend/src/components/dashboard/HealthStatusCard.tsx
+++ b/frontend/src/components/dashboard/HealthStatusCard.tsx
@@ -12,6 +12,7 @@ import React from 'react';
import { CheckCircle, AlertTriangle, AlertCircle, Clock, RefreshCw } from 'lucide-react';
import { BakeryHealthStatus } from '../../api/hooks/newDashboard';
import { formatDistanceToNow } from 'date-fns';
+import { useTranslation } from 'react-i18next';
interface HealthStatusCardProps {
healthStatus: BakeryHealthStatus;
@@ -51,6 +52,7 @@ const iconMap = {
export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProps) {
const config = statusConfig[healthStatus.status];
const StatusIcon = config.icon;
+ const { t } = useTranslation('reasoning');
if (loading) {
return (
@@ -79,12 +81,12 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
- Last updated:{' '}
+ {t('jtbd.health_status.last_updated')}:{' '}
{healthStatus.lastOrchestrationRun
? formatDistanceToNow(new Date(healthStatus.lastOrchestrationRun), {
addSuffix: true,
})
- : 'Never'}
+ : t('jtbd.health_status.never')}
@@ -92,7 +94,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
- Next check:{' '}
+ {t('jtbd.health_status.next_check')}:{' '}
{formatDistanceToNow(new Date(healthStatus.nextScheduledRun), { addSuffix: true })}
@@ -129,7 +131,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
- {healthStatus.criticalIssues} critical issue{healthStatus.criticalIssues !== 1 ? 's' : ''}
+ {t('jtbd.health_status.critical_issues', { count: healthStatus.criticalIssues })}
)}
@@ -137,7 +139,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
- {healthStatus.pendingActions} action{healthStatus.pendingActions !== 1 ? 's' : ''} needed
+ {t('jtbd.health_status.actions_needed', { count: healthStatus.pendingActions })}
)}
diff --git a/frontend/src/components/dashboard/OrchestrationSummaryCard.tsx b/frontend/src/components/dashboard/OrchestrationSummaryCard.tsx
index d9e8c503..b2d2682b 100644
--- a/frontend/src/components/dashboard/OrchestrationSummaryCard.tsx
+++ b/frontend/src/components/dashboard/OrchestrationSummaryCard.tsx
@@ -24,6 +24,7 @@ import {
} from 'lucide-react';
import { OrchestrationSummary } from '../../api/hooks/newDashboard';
import { formatDistanceToNow } from 'date-fns';
+import { useTranslation } from 'react-i18next';
interface OrchestrationSummaryCardProps {
summary: OrchestrationSummary;
@@ -32,6 +33,7 @@ interface OrchestrationSummaryCardProps {
export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSummaryCardProps) {
const [expanded, setExpanded] = useState(false);
+ const { t } = useTranslation('reasoning');
if (loading) {
return (
@@ -58,11 +60,11 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
- Ready to Plan Your Bakery Day
+ {t('jtbd.orchestration_summary.ready_to_plan')}
{summary.message}
@@ -83,13 +85,17 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
- Last Night I Planned Your Day
+ {t('jtbd.orchestration_summary.title')}
- Orchestration run #{summary.runNumber} • {runTime}
+
+ {t('jtbd.orchestration_summary.run_info', { runNumber: summary.runNumber })} • {runTime}
+
{summary.durationSeconds && (
- • Took {summary.durationSeconds}s
+
+ • {t('jtbd.orchestration_summary.took', { seconds: summary.durationSeconds })}
+
)}
@@ -101,8 +107,7 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
- Created {summary.purchaseOrdersCreated} purchase order
- {summary.purchaseOrdersCreated !== 1 ? 's' : ''}
+ {t('jtbd.orchestration_summary.created_pos', { count: summary.purchaseOrdersCreated })}
@@ -129,8 +134,9 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
- Scheduled {summary.productionBatchesCreated} production batch
- {summary.productionBatchesCreated !== 1 ? 'es' : ''}
+ {t('jtbd.orchestration_summary.scheduled_batches', {
+ count: summary.productionBatchesCreated,
+ })}
@@ -160,12 +166,14 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
{expanded ? (
<>
- Show less
+ {t('jtbd.orchestration_summary.show_less')}
>
) : (
<>
- Show {summary.productionBatchesSummary.length - 3} more
+ {t('jtbd.orchestration_summary.show_more', {
+ count: summary.productionBatchesSummary.length - 3,
+ })}
>
)}
@@ -178,7 +186,7 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
-
No new actions needed - everything is on track!
+
{t('jtbd.orchestration_summary.no_actions')}
)}
@@ -187,7 +195,7 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
-
Based on:
+ {t('jtbd.orchestration_summary.based_on')}
@@ -195,8 +203,9 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
- {summary.reasoningInputs.customerOrders} customer order
- {summary.reasoningInputs.customerOrders !== 1 ? 's' : ''}
+ {t('jtbd.orchestration_summary.customer_orders', {
+ count: summary.reasoningInputs.customerOrders,
+ })}
)}
@@ -204,21 +213,25 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
{summary.reasoningInputs.historicalDemand && (
- Historical demand
+
+ {t('jtbd.orchestration_summary.historical_demand')}
+
)}
{summary.reasoningInputs.inventoryLevels && (
-
Inventory levels
+
{t('jtbd.orchestration_summary.inventory_levels')}
)}
{summary.reasoningInputs.aiInsights && (
- AI optimization
+
+ {t('jtbd.orchestration_summary.ai_optimization')}
+
)}
@@ -230,8 +243,9 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
- {summary.userActionsRequired} item{summary.userActionsRequired !== 1 ? 's' : ''} need
- {summary.userActionsRequired === 1 ? 's' : ''} your approval before proceeding
+ {t('jtbd.orchestration_summary.actions_required', {
+ count: summary.userActionsRequired,
+ })}
diff --git a/frontend/src/components/dashboard/ProductionTimelineCard.tsx b/frontend/src/components/dashboard/ProductionTimelineCard.tsx
index 73afe750..7fa47e27 100644
--- a/frontend/src/components/dashboard/ProductionTimelineCard.tsx
+++ b/frontend/src/components/dashboard/ProductionTimelineCard.tsx
@@ -10,6 +10,8 @@
import React from 'react';
import { Factory, Clock, Play, Pause, CheckCircle2 } from 'lucide-react';
import { ProductionTimeline, ProductionTimelineItem } from '../../api/hooks/newDashboard';
+import { useReasoningFormatter } from '../../hooks/useReasoningTranslation';
+import { useTranslation } from 'react-i18next';
interface ProductionTimelineCardProps {
timeline: ProductionTimeline;
@@ -35,6 +37,13 @@ function TimelineItemCard({
onPause?: (id: string) => void;
}) {
const priorityColor = priorityColors[item.priority as keyof typeof priorityColors] || 'text-gray-600';
+ const { formatBatchAction } = useReasoningFormatter();
+ const { t } = useTranslation('reasoning');
+
+ // Translate reasoning_data (or fallback to deprecated text field)
+ const { reasoning } = item.reasoning_data
+ ? formatBatchAction(item.reasoning_data)
+ : { reasoning: item.reasoning || '' };
const startTime = item.plannedStartTime
? new Date(item.plannedStartTime).toLocaleTimeString('en-US', {
@@ -99,13 +108,15 @@ function TimelineItemCard({
{item.status !== 'COMPLETED' && (
- Ready by: {readyByTime}
+
+ {t('jtbd.production_timeline.ready_by')}: {readyByTime}
+
)}
{/* Reasoning */}
- {item.reasoning && (
- "{item.reasoning}"
+ {reasoning && (
+ "{reasoning}"
)}
{/* Actions */}
@@ -115,7 +126,7 @@ function TimelineItemCard({
className="flex items-center gap-2 px-3 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg text-sm font-semibold transition-colors duration-200"
>
- Start Batch
+ {t('jtbd.production_timeline.start_batch')}
)}
@@ -125,14 +136,14 @@ function TimelineItemCard({
className="flex items-center gap-2 px-3 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded-lg text-sm font-semibold transition-colors duration-200"
>
- Pause Batch
+ {t('jtbd.production_timeline.pause_batch')}
)}
{item.status === 'COMPLETED' && (
- Completed
+ {t('jtbd.production_timeline.completed')}
)}
@@ -146,6 +157,8 @@ export function ProductionTimelineCard({
onStart,
onPause,
}: ProductionTimelineCardProps) {
+ const { t } = useTranslation('reasoning');
+
if (loading) {
return (
@@ -164,8 +177,10 @@ export function ProductionTimelineCard({
return (
-
No Production Scheduled
-
No batches are scheduled for production today.
+
+ {t('jtbd.production_timeline.no_production')}
+
+
{t('jtbd.production_timeline.no_batches')}
);
}
@@ -176,7 +191,7 @@ export function ProductionTimelineCard({
-
Your Production Plan Today
+ {t('jtbd.production_timeline.title')}
@@ -184,19 +199,19 @@ export function ProductionTimelineCard({
{timeline.totalBatches}
-
Total
+
{t('jtbd.production_timeline.total')}
{timeline.completedBatches}
-
Done
+
{t('jtbd.production_timeline.done')}
{timeline.inProgressBatches}
-
Active
+
{t('jtbd.production_timeline.active')}
{timeline.pendingBatches}
-
Pending
+
{t('jtbd.production_timeline.pending')}
@@ -215,7 +230,7 @@ export function ProductionTimelineCard({
{/* View Full Schedule Link */}
{timeline.totalBatches > 5 && (
)}
diff --git a/frontend/src/locales/en/reasoning.json b/frontend/src/locales/en/reasoning.json
index c0e96a66..9dc66104 100644
--- a/frontend/src/locales/en/reasoning.json
+++ b/frontend/src/locales/en/reasoning.json
@@ -50,52 +50,55 @@
"yellow": "Some items need attention",
"red": "Critical issues require immediate action",
"last_updated": "Last updated",
- "next_check": "Next check"
+ "next_check": "Next check",
+ "never": "Never",
+ "critical_issues": "{{count}} critical issue{{count, plural, one {} other {s}}}",
+ "actions_needed": "{{count}} action{{count, plural, one {} other {s}}} needed"
},
"action_queue": {
"title": "What Needs Your Attention",
"why_needed": "Why this is needed:",
"what_if_not": "What happens if I don't do this?",
- "estimated_time": "Estimated time: {{minutes}} min",
+ "estimated_time": "Estimated time",
"all_caught_up": "All caught up!",
"no_actions": "No actions requiring your attention right now.",
- "show_more": "Show {{count}} More Action{{plural}}",
+ "show_more": "Show {{count}} More Action{{count, plural, one {} other {s}}}",
"show_less": "Show Less",
- "critical_badge": "{{count}} critical",
- "important_badge": "{{count}} important"
+ "total": "total",
+ "critical": "critical",
+ "important": "important"
},
"orchestration_summary": {
"title": "Last Night I Planned Your Day",
- "no_runs": "Ready to Plan Your Bakery Day",
- "no_runs_message": "The system hasn't run daily planning yet. Click 'Run Daily Planning' to generate your first plan.",
- "run_number": "Orchestration run #{{number}}",
- "duration": "Took {{seconds}}s",
- "pos_created": "Created {{count}} purchase order{{plural}}",
- "batches_created": "Scheduled {{count}} production batch{{plural}}",
+ "ready_to_plan": "Ready to Plan Your Bakery Day",
+ "run_planning": "Run Daily Planning",
+ "run_info": "Orchestration run #{{runNumber}}",
+ "took": "Took {{seconds}}s",
+ "created_pos": "Created {{count}} purchase order{{count, plural, one {} other {s}}}",
+ "scheduled_batches": "Scheduled {{count}} production batch{{count, plural, one {} other {es}}}",
+ "show_more": "Show {{count}} more",
+ "show_less": "Show less",
"no_actions": "No new actions needed - everything is on track!",
"based_on": "Based on:",
- "customer_orders": "{{count}} customer order{{plural}}",
+ "customer_orders": "{{count}} customer order{{count, plural, one {} other {s}}}",
"historical_demand": "Historical demand",
"inventory_levels": "Inventory levels",
"ai_optimization": "AI optimization",
- "actions_required": "{{count}} item{{plural}} need{{verb}} your approval before proceeding"
+ "actions_required": "{{count}} item{{count, plural, one {} other {s}}} need{{count, plural, one {s} other {}}} your approval before proceeding"
},
"production_timeline": {
- "title": "Today's Production Timeline",
- "no_batches": "No production batches scheduled for today",
- "status": {
- "pending": "Pending",
- "in_progress": "In Progress",
- "completed": "Completed",
- "cancelled": "Cancelled"
- },
- "ready_by": "Ready by {{time}}",
- "priority": {
- "low": "Low Priority",
- "normal": "Normal",
- "high": "High Priority",
- "urgent": "Urgent"
- }
+ "title": "Your Production Plan Today",
+ "no_production": "No Production Scheduled",
+ "no_batches": "No batches are scheduled for production today.",
+ "ready_by": "Ready by",
+ "start_batch": "Start Batch",
+ "pause_batch": "Pause Batch",
+ "completed": "Completed",
+ "total": "Total",
+ "done": "Done",
+ "active": "Active",
+ "pending": "Pending",
+ "view_full_schedule": "View Full Production Schedule"
},
"insights": {
"savings": "Savings This Week",
diff --git a/frontend/src/locales/es/reasoning.json b/frontend/src/locales/es/reasoning.json
index ed502e2a..9bf0e17e 100644
--- a/frontend/src/locales/es/reasoning.json
+++ b/frontend/src/locales/es/reasoning.json
@@ -50,52 +50,55 @@
"yellow": "Algunos elementos necesitan atención",
"red": "Problemas críticos requieren acción inmediata",
"last_updated": "Última actualización",
- "next_check": "Próxima verificación"
+ "next_check": "Próxima verificación",
+ "never": "Nunca",
+ "critical_issues": "{{count}} problema{{count, plural, one {} other {s}}} crítico{{count, plural, one {} other {s}}}",
+ "actions_needed": "{{count}} acción{{count, plural, one {} other {es}}} necesaria{{count, plural, one {} other {s}}}"
},
"action_queue": {
"title": "Qué Necesita Tu Atención",
"why_needed": "Por qué es necesario esto:",
"what_if_not": "¿Qué pasa si no hago esto?",
- "estimated_time": "Tiempo estimado: {{minutes}} min",
+ "estimated_time": "Tiempo estimado",
"all_caught_up": "¡Todo al día!",
"no_actions": "No hay acciones que requieran tu atención en este momento.",
- "show_more": "Mostrar {{count}} Acción{{plural}} Más",
+ "show_more": "Mostrar {{count}} Acción{{count, plural, one {} other {es}}} Más",
"show_less": "Mostrar Menos",
- "critical_badge": "{{count}} crítico{{plural}}",
- "important_badge": "{{count}} importante{{plural}}"
+ "total": "total",
+ "critical": "críticas",
+ "important": "importantes"
},
"orchestration_summary": {
"title": "Anoche Planifiqué Tu Día",
- "no_runs": "Listo para Planificar Tu Día en la Panadería",
- "no_runs_message": "El sistema aún no ha ejecutado la planificación diaria. Haz clic en 'Ejecutar Planificación Diaria' para generar tu primer plan.",
- "run_number": "Ejecución de orquestación #{{number}}",
- "duration": "Tardó {{seconds}}s",
- "pos_created": "Creé {{count}} orden{{plural}} de compra",
- "batches_created": "Programé {{count}} lote{{plural}} de producción",
- "no_actions": "¡No se necesitan nuevas acciones - todo está en marcha!",
+ "ready_to_plan": "Listo Para Planificar Tu Día en la Panadería",
+ "run_planning": "Ejecutar Planificación Diaria",
+ "run_info": "Ejecución de orquestación #{{runNumber}}",
+ "took": "Duró {{seconds}}s",
+ "created_pos": "{{count}} orden{{count, plural, one {} other {es}}} de compra creada{{count, plural, one {} other {s}}}",
+ "scheduled_batches": "{{count}} lote{{count, plural, one {} other {s}}} de producción programado{{count, plural, one {} other {s}}}",
+ "show_more": "Mostrar {{count}} más",
+ "show_less": "Mostrar menos",
+ "no_actions": "¡No se necesitan nuevas acciones - todo va según lo planeado!",
"based_on": "Basado en:",
- "customer_orders": "{{count}} pedido{{plural}} de cliente{{plural}}",
+ "customer_orders": "{{count}} pedido{{count, plural, one {} other {s}}} de cliente",
"historical_demand": "Demanda histórica",
"inventory_levels": "Niveles de inventario",
- "ai_optimization": "Optimización con IA",
- "actions_required": "{{count}} elemento{{plural}} necesita{{verb}} tu aprobación antes de proceder"
+ "ai_optimization": "Optimización por IA",
+ "actions_required": "{{count}} elemento{{count, plural, one {} other {s}}} necesita{{count, plural, one {} other {n}}} tu aprobación antes de continuar"
},
"production_timeline": {
- "title": "Línea de Tiempo de Producción de Hoy",
- "no_batches": "No hay lotes de producción programados para hoy",
- "status": {
- "pending": "Pendiente",
- "in_progress": "En Proceso",
- "completed": "Completado",
- "cancelled": "Cancelado"
- },
- "ready_by": "Listo para {{time}}",
- "priority": {
- "low": "Prioridad Baja",
- "normal": "Normal",
- "high": "Prioridad Alta",
- "urgent": "Urgente"
- }
+ "title": "Tu Plan de Producción de Hoy",
+ "no_production": "No Hay Producción Programada",
+ "no_batches": "No hay lotes programados para producción hoy.",
+ "ready_by": "Listo para",
+ "start_batch": "Iniciar Lote",
+ "pause_batch": "Pausar Lote",
+ "completed": "Completado",
+ "total": "Total",
+ "done": "Hecho",
+ "active": "Activo",
+ "pending": "Pendiente",
+ "view_full_schedule": "Ver Cronograma Completo de Producción"
},
"insights": {
"savings": "Ahorros Esta Semana",
diff --git a/frontend/src/locales/eu/reasoning.json b/frontend/src/locales/eu/reasoning.json
index 73bac8e2..81851d73 100644
--- a/frontend/src/locales/eu/reasoning.json
+++ b/frontend/src/locales/eu/reasoning.json
@@ -52,52 +52,55 @@ ko da.",
"yellow": "Elementu batzuek arreta behar dute",
"red": "Arazo kritikoek ekintza berehalakoa behar dute",
"last_updated": "Azken eguneratzea",
- "next_check": "Hurrengo egiaztapena"
+ "next_check": "Hurrengo egiaztapena",
+ "never": "Inoiz ez",
+ "critical_issues": "{{count}} arazo kritiko",
+ "actions_needed": "{{count}} ekintza behar"
},
"action_queue": {
"title": "Zer Behar Du Zure Arreta",
"why_needed": "Zergatik behar da hau:",
"what_if_not": "Zer gertatzen da hau egiten ez badut?",
- "estimated_time": "Estimatutako denbora: {{minutes}} min",
+ "estimated_time": "Estimatutako denbora",
"all_caught_up": "Dena egunean!",
"no_actions": "Ez dago une honetan zure arreta behar duen ekintzarik.",
- "show_more": "Erakutsi {{count}} Ekintza{{plural}} Gehiago",
+ "show_more": "Erakutsi {{count}} Ekintza gehiago",
"show_less": "Erakutsi Gutxiago",
- "critical_badge": "{{count}} kritiko{{plural}}",
- "important_badge": "{{count}} garrantzitsu{{plural}}"
+ "total": "guztira",
+ "critical": "kritiko",
+ "important": "garrantzitsu"
},
"orchestration_summary": {
"title": "Bart Gauean Zure Eguna Planifikatu Nuen",
- "no_runs": "Zure Okindegiko Eguna Planifikatzeko Prest",
- "no_runs_message": "Sistemak oraindik ez du eguneko plangintza exekutatu. Egin klik 'Exekutatu Eguneko Plangintza'-n zure lehen plana sortzeko.",
- "run_number": "Orkestazio exekuzioa #{{number}}",
- "duration": "{{seconds}}s behar izan zituen",
- "pos_created": "{{count}} erosketa agindu{{plural}} sortu nituen",
- "batches_created": "{{count}} ekoizpen lote{{plural}} programatu nituen",
+ "ready_to_plan": "Zure Okindegiko Eguna Planifikatzeko Prest",
+ "run_planning": "Exekutatu Eguneko Plangintza",
+ "run_info": "Orkestazio exekuzioa #{{runNumber}}",
+ "took": "{{seconds}}s behar izan zituen",
+ "created_pos": "{{count}} erosketa agindu sortu",
+ "scheduled_batches": "{{count}} ekoizpen lote programatu",
+ "show_more": "Erakutsi {{count}} gehiago",
+ "show_less": "Erakutsi gutxiago",
"no_actions": "Ez dira ekintza berriak behar - dena bidean dago!",
"based_on": "Oinarrituta:",
- "customer_orders": "{{count}} bezero eskaera{{plural}}",
+ "customer_orders": "{{count}} bezero eskaera",
"historical_demand": "Eskaera historikoa",
"inventory_levels": "Inbentario mailak",
"ai_optimization": "IA optimizazioa",
- "actions_required": "{{count}} elementu{{plural}}k zure onespena behar du{{verb}} aurrera jarraitu aurretik"
+ "actions_required": "{{count}} elementuk zure onespena behar du aurrera jarraitu aurretik"
},
"production_timeline": {
- "title": "Gaurko Ekoizpen Denbora-lerroa",
- "no_batches": "Ez dago ekoizpen loterik programatuta gaurko",
- "status": {
- "pending": "Zain",
- "in_progress": "Prozesuan",
- "completed": "Osatua",
- "cancelled": "Bertan behera utzita"
- },
- "ready_by": "{{time}}rako prest",
- "priority": {
- "low": "Lehentasun Baxua",
- "normal": "Normala",
- "high": "Lehentasun Altua",
- "urgent": "Larria"
- }
+ "title": "Zure Gaurko Ekoizpen Plana",
+ "no_production": "Ez Dago Ekoizpenik Programatuta",
+ "no_batches": "Ez dago loterik programatuta gaur ekoizpenerako.",
+ "ready_by": "Prest egongo da",
+ "start_batch": "Hasi Lotea",
+ "pause_batch": "Pausatu Lotea",
+ "completed": "Osatua",
+ "total": "Guztira",
+ "done": "Eginda",
+ "active": "Aktibo",
+ "pending": "Zain",
+ "view_full_schedule": "Ikusi Ekoizpen Egutegi Osoa"
},
"insights": {
"savings": "Aste Honetako Aurrezkiak",