From f02a980c87c4ecc5272104b8ea95b9ea91455865 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Thu, 25 Sep 2025 12:14:46 +0200 Subject: [PATCH] Support multiple languages --- .../src/components/domain/auth/LoginForm.tsx | 22 +-- .../domain/auth/ProfileSettings.tsx | 1 - .../components/domain/auth/RegisterForm.tsx | 34 ++-- .../domain/dashboard/RealTimeAlerts.tsx | 36 ++-- .../inventory/CreateIngredientModal.tsx | 22 +-- .../domain/onboarding/OnboardingWizard.tsx | 84 ++++----- .../components/domain/sales/OrdersTable.tsx | 77 ++++---- .../layout/Breadcrumbs/Breadcrumbs.tsx | 11 +- .../src/components/layout/Footer/Footer.tsx | 58 +++--- .../src/components/layout/Header/Header.tsx | 6 +- .../layout/PublicHeader/PublicHeader.tsx | 29 ++- .../layout/PublicLayout/PublicLayout.tsx | 1 + .../src/components/layout/Sidebar/Sidebar.tsx | 26 +-- .../components/ui/DatePicker/DatePicker.tsx | 16 +- .../src/components/ui/PasswordCriteria.tsx | 37 ++-- frontend/src/components/ui/index.ts | 1 + frontend/src/locales/en/auth.json | 167 +++++++++++++++++- frontend/src/locales/en/common.json | 53 ++++++ frontend/src/locales/en/dashboard.json | 43 ++++- frontend/src/locales/en/database.json | 34 ++++ frontend/src/locales/en/errors.json | 14 +- frontend/src/locales/en/events.json | 25 +++ frontend/src/locales/en/landing.json | 127 +++++++++++++ frontend/src/locales/en/onboarding.json | 141 +++++++++++++++ frontend/src/locales/en/sales.json | 88 +++++++++ frontend/src/locales/en/settings.json | 91 ++++++++++ frontend/src/locales/en/traffic.json | 45 +++++ frontend/src/locales/en/ui.json | 51 ++++++ frontend/src/locales/en/weather.json | 149 ++++++++++++++++ frontend/src/locales/es/common.json | 53 ++++++ frontend/src/locales/es/dashboard.json | 43 ++++- frontend/src/locales/es/database.json | 34 ++++ frontend/src/locales/es/errors.json | 11 ++ frontend/src/locales/es/events.json | 25 +++ frontend/src/locales/es/landing.json | 127 +++++++++++++ frontend/src/locales/es/onboarding.json | 141 +++++++++++++++ frontend/src/locales/es/sales.json | 88 +++++++++ frontend/src/locales/es/settings.json | 91 ++++++++++ frontend/src/locales/es/traffic.json | 45 +++++ frontend/src/locales/es/ui.json | 51 ++++++ frontend/src/locales/es/weather.json | 149 ++++++++++++++++ frontend/src/locales/eu/auth.json | 167 +++++++++++++++++- frontend/src/locales/eu/common.json | 53 ++++++ frontend/src/locales/eu/dashboard.json | 43 ++++- frontend/src/locales/eu/database.json | 34 ++++ frontend/src/locales/eu/errors.json | 14 +- frontend/src/locales/eu/events.json | 25 +++ frontend/src/locales/eu/landing.json | 127 +++++++++++++ frontend/src/locales/eu/onboarding.json | 141 +++++++++++++++ frontend/src/locales/eu/sales.json | 88 +++++++++ frontend/src/locales/eu/settings.json | 91 ++++++++++ frontend/src/locales/eu/traffic.json | 45 +++++ frontend/src/locales/eu/ui.json | 51 ++++++ frontend/src/locales/eu/weather.json | 149 ++++++++++++++++ .../src/pages/app/data/events/EventsPage.tsx | 6 +- .../pages/app/data/traffic/TrafficPage.tsx | 6 +- .../pages/app/data/weather/WeatherPage.tsx | 114 ++++++------ .../src/pages/app/database/DatabasePage.tsx | 34 ++-- .../organizations/OrganizationsPage.tsx | 6 +- .../app/settings/profile/ProfilePage.tsx | 18 +- .../src/pages/app/settings/team/TeamPage.tsx | 6 +- .../src/pages/onboarding/OnboardingPage.tsx | 1 + frontend/src/pages/public/LandingPage.tsx | 31 ++-- frontend/src/pages/public/LoginPage.tsx | 1 + frontend/src/pages/public/RegisterPage.tsx | 1 + frontend/src/router/routes.config.ts | 8 +- 66 files changed, 3274 insertions(+), 333 deletions(-) create mode 100644 frontend/src/locales/en/database.json create mode 100644 frontend/src/locales/en/events.json create mode 100644 frontend/src/locales/en/landing.json create mode 100644 frontend/src/locales/en/onboarding.json create mode 100644 frontend/src/locales/en/sales.json create mode 100644 frontend/src/locales/en/settings.json create mode 100644 frontend/src/locales/en/traffic.json create mode 100644 frontend/src/locales/en/ui.json create mode 100644 frontend/src/locales/en/weather.json create mode 100644 frontend/src/locales/es/database.json create mode 100644 frontend/src/locales/es/events.json create mode 100644 frontend/src/locales/es/landing.json create mode 100644 frontend/src/locales/es/onboarding.json create mode 100644 frontend/src/locales/es/sales.json create mode 100644 frontend/src/locales/es/settings.json create mode 100644 frontend/src/locales/es/traffic.json create mode 100644 frontend/src/locales/es/ui.json create mode 100644 frontend/src/locales/es/weather.json create mode 100644 frontend/src/locales/eu/database.json create mode 100644 frontend/src/locales/eu/events.json create mode 100644 frontend/src/locales/eu/landing.json create mode 100644 frontend/src/locales/eu/onboarding.json create mode 100644 frontend/src/locales/eu/sales.json create mode 100644 frontend/src/locales/eu/settings.json create mode 100644 frontend/src/locales/eu/traffic.json create mode 100644 frontend/src/locales/eu/ui.json create mode 100644 frontend/src/locales/eu/weather.json diff --git a/frontend/src/components/domain/auth/LoginForm.tsx b/frontend/src/components/domain/auth/LoginForm.tsx index bb1ca853..5d69f02e 100644 --- a/frontend/src/components/domain/auth/LoginForm.tsx +++ b/frontend/src/components/domain/auth/LoginForm.tsx @@ -1,4 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { Button, Input, Card } from '../../ui'; import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/auth.store'; import { useToast } from '../../../hooks/ui/useToast'; @@ -24,6 +25,7 @@ export const LoginForm: React.FC = ({ className, autoFocus = true }) => { + const { t } = useTranslation(); const [credentials, setCredentials] = useState({ email: '', password: '', @@ -32,7 +34,7 @@ export const LoginForm: React.FC = ({ const [errors, setErrors] = useState>({}); const [showPassword, setShowPassword] = useState(false); const emailInputRef = useRef(null); - + const { login } = useAuthActions(); const isLoading = useAuthLoading(); const error = useAuthError(); @@ -49,13 +51,13 @@ export const LoginForm: React.FC = ({ const newErrors: Partial = {}; if (!credentials.email.trim()) { - newErrors.email = 'El email es requerido'; + newErrors.email = t('auth:validation.email_required', 'El email es requerido'); } else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(credentials.email)) { - newErrors.email = 'Por favor, ingrese un email válido'; + newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido'); } if (!credentials.password) { - newErrors.password = 'La contraseña es requerida'; + newErrors.password = t('auth:validation.password_required', 'La contraseña es requerida'); } setErrors(newErrors); @@ -114,10 +116,10 @@ export const LoginForm: React.FC = ({

- Iniciar Sesión + {t('auth:login.title', 'Iniciar Sesión')}

- Accede al panel de control de tu panadería + {t('auth:login.subtitle', 'Accede al panel de control de tu panadería')}

@@ -125,14 +127,14 @@ export const LoginForm: React.FC = ({ onSubmit={handleSubmit} className="space-y-6" noValidate - aria-label="Formulario de inicio de sesión" + aria-label={t('auth:login.title', 'Formulario de inicio de sesión')} onKeyDown={handleKeyDown} >
= ({
void; diff --git a/frontend/src/components/domain/auth/RegisterForm.tsx b/frontend/src/components/domain/auth/RegisterForm.tsx index 2c2f5e04..2e91ab44 100644 --- a/frontend/src/components/domain/auth/RegisterForm.tsx +++ b/frontend/src/components/domain/auth/RegisterForm.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Button, Input, Card } from '../../ui'; import { PasswordCriteria, validatePassword, getPasswordErrors } from '../../ui/PasswordCriteria'; import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/auth.store'; @@ -23,6 +24,7 @@ export const RegisterForm: React.FC = ({ onLoginClick, className }) => { + const { t } = useTranslation(); const [formData, setFormData] = useState({ full_name: '', email: '', @@ -53,19 +55,19 @@ export const RegisterForm: React.FC = ({ const newErrors: Partial = {}; if (!formData.full_name.trim()) { - newErrors.full_name = 'El nombre completo es requerido'; + newErrors.full_name = t('auth:validation.first_name_required', 'El nombre completo es requerido'); } else if (formData.full_name.trim().length < 2) { - newErrors.full_name = 'El nombre debe tener al menos 2 caracteres'; + newErrors.full_name = t('auth:validation.field_required', 'El nombre debe tener al menos 2 caracteres'); } if (!formData.email.trim()) { - newErrors.email = 'El email es requerido'; + newErrors.email = t('auth:validation.email_required', 'El email es requerido'); } else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(formData.email)) { - newErrors.email = 'Por favor, ingrese un email válido'; + newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido'); } if (!formData.password) { - newErrors.password = 'La contraseña es requerida'; + newErrors.password = t('auth:validation.password_required', 'La contraseña es requerida'); } else { const passwordErrors = getPasswordErrors(formData.password); if (passwordErrors.length > 0) { @@ -74,13 +76,13 @@ export const RegisterForm: React.FC = ({ } if (!formData.confirmPassword) { - newErrors.confirmPassword = 'Confirma tu contraseña'; + newErrors.confirmPassword = t('auth:register.confirm_password', 'Confirma tu contraseña'); } else if (formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = 'Las contraseñas no coinciden'; + newErrors.confirmPassword = t('auth:validation.passwords_must_match', 'Las contraseñas no coinciden'); } if (!formData.acceptTerms) { - newErrors.acceptTerms = 'Debes aceptar los términos y condiciones'; + newErrors.acceptTerms = t('auth:validation.terms_required', 'Debes aceptar los términos y condiciones'); } setErrors(newErrors); @@ -104,13 +106,13 @@ export const RegisterForm: React.FC = ({ await register(registrationData); - showSuccessToast('¡Bienvenido! Tu cuenta ha sido creada correctamente.', { - title: 'Cuenta creada exitosamente' + showSuccessToast(t('auth:register.registering', '¡Bienvenido! Tu cuenta ha sido creada correctamente.'), { + title: t('auth:alerts.success_create', 'Cuenta creada exitosamente') }); onSuccess?.(); } catch (err) { - showErrorToast(error || 'No se pudo crear la cuenta. Verifica que el email no esté en uso.', { - title: 'Error al crear la cuenta' + showErrorToast(error || t('auth:register.register_button', 'No se pudo crear la cuenta. Verifica que el email no esté en uso.'), { + title: t('auth:alerts.error_create', 'Error al crear la cuenta') }); } }; @@ -127,16 +129,16 @@ export const RegisterForm: React.FC = ({

- Crear Cuenta + {t('auth:register.title', 'Crear Cuenta')}

- Únete y comienza hoy mismo + {t('auth:register.subtitle', 'Únete y comienza hoy mismo')}

= ({ = ({ className, maxAlerts = 10 }) => { + const { t } = useTranslation(['dashboard']); const [expandedAlert, setExpandedAlert] = useState(null); const { notifications, isConnected, markAsRead, removeNotification } = useNotifications(); @@ -108,11 +110,13 @@ const RealTimeAlerts: React.FC = ({ const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / (1000 * 60)); - if (diffMins < 1) return 'Ahora'; - if (diffMins < 60) return `${diffMins}m`; + if (diffMins < 1) return t('dashboard:alerts.time.now', 'Ahora'); + if (diffMins < 60) return t('dashboard:alerts.time.minutes_ago', 'hace {{count}} min', { count: diffMins }); const diffHours = Math.floor(diffMins / 60); - if (diffHours < 24) return `${diffHours}h`; - return date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' }); + if (diffHours < 24) return t('dashboard:alerts.time.hours_ago', 'hace {{count}} h', { count: diffHours }); + return date.toLocaleDateString() === new Date(now.getTime() - 24 * 60 * 60 * 1000).toLocaleDateString() + ? t('dashboard:alerts.time.yesterday', 'Ayer') + : date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' }); }; const toggleExpanded = (alertId: string) => { @@ -147,7 +151,7 @@ const RealTimeAlerts: React.FC = ({

- Alertas + {t('dashboard:alerts.title', 'Alertas')}

{isConnected ? ( @@ -156,7 +160,10 @@ const RealTimeAlerts: React.FC = ({ )} - {isConnected ? 'En vivo' : 'Desconectado'} + {isConnected + ? t('dashboard:alerts.live', 'En vivo') + : t('dashboard:alerts.offline', 'Desconectado') + }
@@ -182,7 +189,7 @@ const RealTimeAlerts: React.FC = ({

- No hay alertas activas + {t('dashboard:alerts.no_alerts', 'No hay alertas activas')}

) : ( @@ -241,7 +248,10 @@ const RealTimeAlerts: React.FC = ({ {alert.severity.toUpperCase()} - {alert.item_type === 'alert' ? '🚨 Alerta' : '💡 Recomendación'} + {alert.item_type === 'alert' + ? `🚨 ${t('dashboard:alerts.types.alert', 'Alerta')}` + : `💡 ${t('dashboard:alerts.types.recommendation', 'Recomendación')}` + }
@@ -277,7 +287,7 @@ const RealTimeAlerts: React.FC = ({ {alert.actions && alert.actions.length > 0 && (

- Acciones Recomendadas + {t('dashboard:alerts.recommended_actions', 'Acciones Recomendadas')}

{alert.actions.map((action, index) => ( @@ -298,7 +308,7 @@ const RealTimeAlerts: React.FC = ({ {alert.metadata && Object.keys(alert.metadata).length > 0 && (

- Detalles Adicionales + {t('dashboard:alerts.additional_details', 'Detalles Adicionales')}

{Object.entries(alert.metadata).map(([key, value]) => ( @@ -323,7 +333,7 @@ const RealTimeAlerts: React.FC = ({ className="h-8 px-3 text-xs font-medium" > - Marcar como leído + {t('dashboard:alerts.mark_as_read', 'Marcar como leído')}
@@ -355,7 +365,7 @@ const RealTimeAlerts: React.FC = ({ }} >

- {activeAlerts.length} alertas activas + {t('dashboard:alerts.active_count', '{{count}} alertas activas', { count: activeAlerts.length })}

)} diff --git a/frontend/src/components/domain/inventory/CreateIngredientModal.tsx b/frontend/src/components/domain/inventory/CreateIngredientModal.tsx index e88f1823..d249370a 100644 --- a/frontend/src/components/domain/inventory/CreateIngredientModal.tsx +++ b/frontend/src/components/domain/inventory/CreateIngredientModal.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Plus, Package, Calculator, Settings } from 'lucide-react'; import { StatusModal } from '../../ui/StatusModal/StatusModal'; import { IngredientCreate, UnitOfMeasure, IngredientCategory, ProductCategory } from '../../../api/types/inventory'; @@ -20,6 +21,7 @@ export const CreateIngredientModal: React.FC = ({ onClose, onCreateIngredient }) => { + const { t } = useTranslation(['inventory']); const [formData, setFormData] = useState({ name: '', description: '', @@ -70,32 +72,32 @@ export const CreateIngredientModal: React.FC = ({ const handleSave = async () => { // Validation if (!formData.name?.trim()) { - alert('El nombre es requerido'); + alert(t('inventory:validation.name_required', 'El nombre es requerido')); return; } if (!formData.category) { - alert('La categoría es requerida'); + alert(t('inventory:validation.category_required', 'La categoría es requerida')); return; } if (!formData.unit_of_measure) { - alert('La unidad de medida es requerida'); + alert(t('inventory:validation.unit_required', 'La unidad de medida es requerida')); return; } if (!formData.low_stock_threshold || formData.low_stock_threshold < 0) { - alert('El umbral de stock bajo debe ser un número positivo'); + alert(t('inventory:validation.min_greater_than_zero', 'El umbral de stock bajo debe ser un número positivo')); return; } if (!formData.reorder_point || formData.reorder_point < 0) { - alert('El punto de reorden debe ser un número positivo'); + alert(t('inventory:validation.min_greater_than_zero', 'El punto de reorden debe ser un número positivo')); return; } if (formData.reorder_point <= formData.low_stock_threshold) { - alert('El punto de reorden debe ser mayor que el umbral de stock bajo'); + alert(t('inventory:validation.max_greater_than_min', 'El punto de reorden debe ser mayor que el umbral de stock bajo')); return; } @@ -153,7 +155,7 @@ export const CreateIngredientModal: React.FC = ({ const statusConfig = { color: statusColors.inProgress.primary, - text: 'Nuevo Artículo', + text: t('inventory:actions.add_item', 'Nuevo Artículo'), icon: Plus, isCritical: false, isHighlight: true @@ -161,11 +163,11 @@ export const CreateIngredientModal: React.FC = ({ const sections = [ { - title: 'Información Básica', + title: t('inventory:forms.item_details', 'Información Básica'), icon: Package, fields: [ { - label: 'Nombre', + label: t('inventory:fields.name', 'Nombre'), value: formData.name, type: 'text' as const, editable: true, @@ -173,7 +175,7 @@ export const CreateIngredientModal: React.FC = ({ placeholder: 'Ej: Harina de trigo 000' }, { - label: 'Descripción', + label: t('inventory:fields.description', 'Descripción'), value: formData.description || '', type: 'text' as const, editable: true, diff --git a/frontend/src/components/domain/onboarding/OnboardingWizard.tsx b/frontend/src/components/domain/onboarding/OnboardingWizard.tsx index 0c3efd67..6e09afc2 100644 --- a/frontend/src/components/domain/onboarding/OnboardingWizard.tsx +++ b/frontend/src/components/domain/onboarding/OnboardingWizard.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { Button } from '../../ui/Button'; import { Card, CardHeader, CardBody } from '../../ui/Card'; import { useAuth } from '../../../contexts/AuthContext'; @@ -29,40 +30,41 @@ interface StepProps { isLastStep: boolean; } -// Steps must match backend ONBOARDING_STEPS exactly -// Note: "user_registered" is auto-completed and not shown in UI -const STEPS: StepConfig[] = [ - { - id: 'setup', - title: 'Registrar Panadería', - description: 'Configura la información básica de tu panadería', - component: RegisterTenantStep, - }, - { - id: 'smart-inventory-setup', - title: 'Configurar Inventario', - description: 'Sube datos de ventas y configura tu inventario inicial', - component: UploadSalesDataStep, - }, - { - id: 'ml-training', - title: 'Entrenamiento IA', - description: 'Entrena tu modelo de inteligencia artificial personalizado', - component: MLTrainingStep, - }, - { - id: 'completion', - title: 'Configuración Completa', - description: '¡Bienvenido a tu sistema de gestión inteligente!', - component: CompletionStep, - }, -]; - export const OnboardingWizard: React.FC = () => { + const { t } = useTranslation(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const { user } = useAuth(); + // Steps must match backend ONBOARDING_STEPS exactly + // Note: "user_registered" is auto-completed and not shown in UI + const STEPS: StepConfig[] = [ + { + id: 'setup', + title: t('onboarding:wizard.steps.setup.title', 'Registrar Panadería'), + description: t('onboarding:wizard.steps.setup.description', 'Configura la información básica de tu panadería'), + component: RegisterTenantStep, + }, + { + id: 'smart-inventory-setup', + title: t('onboarding:wizard.steps.smart_inventory_setup.title', 'Configurar Inventario'), + description: t('onboarding:wizard.steps.smart_inventory_setup.description', 'Sube datos de ventas y configura tu inventario inicial'), + component: UploadSalesDataStep, + }, + { + id: 'ml-training', + title: t('onboarding:wizard.steps.ml_training.title', 'Entrenamiento IA'), + description: t('onboarding:wizard.steps.ml_training.description', 'Entrena tu modelo de inteligencia artificial personalizado'), + component: MLTrainingStep, + }, + { + id: 'completion', + title: t('onboarding:wizard.steps.completion.title', 'Configuración Completa'), + description: t('onboarding:wizard.steps.completion.description', '¡Bienvenido a tu sistema de gestión inteligente!'), + component: CompletionStep, + }, + ]; + // Check if this is a fresh onboarding (new tenant creation) const isNewTenant = searchParams.get('new') === 'true'; @@ -277,7 +279,7 @@ export const OnboardingWizard: React.FC = () => { } // Don't advance automatically on error - user should see the issue - alert(`Error al completar paso "${currentStep.title}": ${errorMessage}`); + alert(`${t('onboarding:errors.step_failed', 'Error al completar paso')} "${currentStep.title}": ${errorMessage}`); } }; @@ -289,7 +291,7 @@ export const OnboardingWizard: React.FC = () => {
-

Cargando tu progreso...

+

{t('common:loading', 'Cargando tu progreso...')}

@@ -311,17 +313,17 @@ export const OnboardingWizard: React.FC = () => {

- Error al cargar progreso + {t('onboarding:errors.network_error', 'Error al cargar progreso')}

- No pudimos cargar tu progreso de configuración. Puedes continuar desde el inicio. + {t('onboarding:errors.try_again', 'No pudimos cargar tu progreso de configuración. Puedes continuar desde el inicio.')}

@@ -350,10 +352,10 @@ export const OnboardingWizard: React.FC = () => {

- Creando Nueva Organización + {t('onboarding:wizard.title', 'Creando Nueva Organización')}

- Configurarás una nueva panadería desde cero. Este proceso es independiente de tus organizaciones existentes. + {t('onboarding:wizard.subtitle', 'Configurarás una nueva panadería desde cero. Este proceso es independiente de tus organizaciones existentes.')}

@@ -366,21 +368,21 @@ export const OnboardingWizard: React.FC = () => {

- {isNewTenant ? 'Crear Nueva Organización' : 'Bienvenido a Bakery IA'} + {isNewTenant ? t('onboarding:wizard.title', 'Crear Nueva Organización') : t('onboarding:wizard.title', 'Bienvenido a Bakery IA')}

{isNewTenant - ? 'Configura tu nueva panadería desde cero' - : 'Configura tu sistema de gestión inteligente paso a paso' + ? t('onboarding:wizard.subtitle', 'Configura tu nueva panadería desde cero') + : t('onboarding:wizard.subtitle', 'Configura tu sistema de gestión inteligente paso a paso') }

- Paso {currentStepIndex + 1} de {STEPS.length} + {t('onboarding:wizard.progress.step_of', 'Paso {{current}} de {{total}}', { current: currentStepIndex + 1, total: STEPS.length })}
- {Math.round(progressPercentage)}% completado + {Math.round(progressPercentage)}% {t('onboarding:wizard.progress.completed', 'completado')} {isNewTenant && (nuevo)}
diff --git a/frontend/src/components/domain/sales/OrdersTable.tsx b/frontend/src/components/domain/sales/OrdersTable.tsx index 3982ee30..e41b867b 100644 --- a/frontend/src/components/domain/sales/OrdersTable.tsx +++ b/frontend/src/components/domain/sales/OrdersTable.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { Table, Button, @@ -95,35 +96,7 @@ const StatusColors = { [OrderStatus.CANCELADO]: 'red' } as const; -const StatusLabels = { - [OrderStatus.PENDIENTE]: 'Pendiente', - [OrderStatus.CONFIRMADO]: 'Confirmado', - [OrderStatus.EN_PREPARACION]: 'En Preparación', - [OrderStatus.LISTO]: 'Listo para Entrega', - [OrderStatus.ENTREGADO]: 'Entregado', - [OrderStatus.CANCELADO]: 'Cancelado' -} as const; - -const ChannelLabels = { - [SalesChannel.STORE_FRONT]: 'Tienda', - [SalesChannel.ONLINE]: 'Online', - [SalesChannel.PHONE_ORDER]: 'Teléfono', - [SalesChannel.DELIVERY]: 'Delivery', - [SalesChannel.CATERING]: 'Catering', - [SalesChannel.WHOLESALE]: 'Mayorista', - [SalesChannel.FARMERS_MARKET]: 'Mercado', - [SalesChannel.THIRD_PARTY]: 'Terceros' -} as const; - -const PaymentLabels = { - [PaymentMethod.CASH]: 'Efectivo', - [PaymentMethod.CREDIT_CARD]: 'Tarjeta Crédito', - [PaymentMethod.DEBIT_CARD]: 'Tarjeta Débito', - [PaymentMethod.DIGITAL_WALLET]: 'Wallet Digital', - [PaymentMethod.BANK_TRANSFER]: 'Transferencia', - [PaymentMethod.CHECK]: 'Cheque', - [PaymentMethod.STORE_CREDIT]: 'Crédito Tienda' -} as const; +// Note: These will be replaced with translation functions inside the component export const OrdersTable: React.FC = ({ tenantId, @@ -132,6 +105,34 @@ export const OrdersTable: React.FC = ({ onOrderUpdate, initialFilters = {} }) => { + const { t } = useTranslation(['sales']); + + // Translation helper functions + const getStatusLabel = (status: OrderStatus) => { + const statusKey = status.toLowerCase(); + return t(`sales:orders.status.${statusKey}`, StatusLabels[status] || status); + }; + + const getChannelLabel = (channel: SalesChannel) => { + const channelKey = channel.toLowerCase(); + return t(`sales:orders.channels.${channelKey}`, channel); + }; + + const getPaymentLabel = (method: PaymentMethod) => { + const methodKey = method.toLowerCase(); + return t(`sales:orders.payment_methods.${methodKey}`, method); + }; + + // Legacy objects for fallbacks (will be removed after migration) + const StatusLabels = { + [OrderStatus.PENDIENTE]: 'Pendiente', + [OrderStatus.CONFIRMADO]: 'Confirmado', + [OrderStatus.EN_PREPARACION]: 'En Preparación', + [OrderStatus.LISTO]: 'Listo para Entrega', + [OrderStatus.ENTREGADO]: 'Entregado', + [OrderStatus.CANCELADO]: 'Cancelado' + } as const; + // State const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(false); @@ -270,7 +271,7 @@ export const OrdersTable: React.FC = ({ }, { key: 'id', - title: 'Nº Pedido', + title: t('sales:orders.table.columns.order_number', 'Nº Pedido'), sortable: true, render: (order: Order) => ( @@ -176,7 +176,7 @@ export const Header = forwardRef(({ 'self-center', sidebarCollapsed ? 'lg:block' : 'lg:hidden xl:block' )}> - Panadería IA + {t('common:app.name', 'Panadería IA')} )} diff --git a/frontend/src/components/layout/PublicHeader/PublicHeader.tsx b/frontend/src/components/layout/PublicHeader/PublicHeader.tsx index bff152f9..95eef84c 100644 --- a/frontend/src/components/layout/PublicHeader/PublicHeader.tsx +++ b/frontend/src/components/layout/PublicHeader/PublicHeader.tsx @@ -2,6 +2,7 @@ import React, { forwardRef } from 'react'; import { clsx } from 'clsx'; import { Link } from 'react-router-dom'; import { Button, ThemeToggle } from '../../ui'; +import { CompactLanguageSelector } from '../../ui/LanguageSelector'; export interface PublicHeaderProps { className?: string; @@ -17,6 +18,10 @@ export interface PublicHeaderProps { * Show authentication buttons (login/register) */ showAuthButtons?: boolean; + /** + * Show language selector + */ + showLanguageSelector?: boolean; /** * Custom navigation items */ @@ -53,6 +58,7 @@ export const PublicHeader = forwardRef(({ logo, showThemeToggle = true, showAuthButtons = true, + showLanguageSelector = true, navigationItems = [], variant = 'default', }, ref) => { @@ -149,6 +155,11 @@ export const PublicHeader = forwardRef(({ {/* Right side actions */}
+ {/* Language selector */} + {showLanguageSelector && ( + + )} + {/* Theme toggle */} {showThemeToggle && ( (({ {showAuthButtons && (
- -
))} - + + {/* Mobile language selector */} + {showLanguageSelector && ( +
+
+ Idioma +
+ +
+ )} + {/* Mobile auth buttons */} {showAuthButtons && (
diff --git a/frontend/src/components/layout/PublicLayout/PublicLayout.tsx b/frontend/src/components/layout/PublicLayout/PublicLayout.tsx index d03fd8cf..999d8f86 100644 --- a/frontend/src/components/layout/PublicLayout/PublicLayout.tsx +++ b/frontend/src/components/layout/PublicLayout/PublicLayout.tsx @@ -21,6 +21,7 @@ export interface PublicLayoutProps { headerProps?: { showThemeToggle?: boolean; showAuthButtons?: boolean; + showLanguageSelector?: boolean; navigationItems?: Array<{ id: string; label: string; diff --git a/frontend/src/components/layout/Sidebar/Sidebar.tsx b/frontend/src/components/layout/Sidebar/Sidebar.tsx index 37d2b4eb..5b696587 100644 --- a/frontend/src/components/layout/Sidebar/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar/Sidebar.tsx @@ -732,7 +732,7 @@ export const Sidebar = forwardRef(({ isCollapsed ? 'justify-center p-2 h-10 w-10 mx-auto rounded-lg' : 'p-4 gap-3', isProfileMenuOpen && 'bg-[var(--bg-secondary)]' )} - aria-label="Menú de perfil" + aria-label={t('common:profile.profile_menu', 'Menú de perfil')} aria-expanded={isProfileMenuOpen} aria-haspopup="true" > @@ -767,25 +767,25 @@ export const Sidebar = forwardRef(({
@@ -795,7 +795,7 @@ export const Sidebar = forwardRef(({ className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors text-[var(--color-error)]" > - Cerrar Sesión + {t('common:profile.logout', 'Cerrar Sesión')}
@@ -852,7 +852,7 @@ export const Sidebar = forwardRef(({ PI

- Panadería IA + {t('common:app.name', 'Panadería IA')}

@@ -931,7 +931,7 @@ export const Sidebar = forwardRef(({ 'hover:bg-[var(--bg-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20', isProfileMenuOpen && 'bg-[var(--bg-secondary)]' )} - aria-label="Menú de perfil" + aria-label={t('common:profile.profile_menu', 'Menú de perfil')} aria-expanded={isProfileMenuOpen} aria-haspopup="true" > @@ -966,7 +966,7 @@ export const Sidebar = forwardRef(({ className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors" > - Perfil + {t('common:profile.profile', 'Perfil')} @@ -987,7 +987,7 @@ export const Sidebar = forwardRef(({ className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors text-[var(--color-error)]" > - Cerrar Sesión + {t('common:profile.logout', 'Cerrar Sesión')} diff --git a/frontend/src/components/ui/DatePicker/DatePicker.tsx b/frontend/src/components/ui/DatePicker/DatePicker.tsx index 7adb3971..3b5e8d71 100644 --- a/frontend/src/components/ui/DatePicker/DatePicker.tsx +++ b/frontend/src/components/ui/DatePicker/DatePicker.tsx @@ -1,5 +1,6 @@ import React, { forwardRef, useState, useRef, useEffect, useMemo } from 'react'; import { clsx } from 'clsx'; +import { useTranslation } from 'react-i18next'; export interface DatePickerProps extends Omit, 'onChange' | 'value'> { label?: string; @@ -37,7 +38,7 @@ const DatePicker = forwardRef(({ label, error, helperText, - placeholder = 'Seleccionar fecha', + placeholder, size = 'md', variant = 'outline', value, @@ -68,6 +69,7 @@ const DatePicker = forwardRef(({ disabled, ...props }, ref) => { + const { t } = useTranslation(['ui']); const datePickerId = id || `datepicker-${Math.random().toString(36).substr(2, 9)}`; const [internalValue, setInternalValue] = useState( value !== undefined ? value : defaultValue || null @@ -110,7 +112,7 @@ const DatePicker = forwardRef(({ }, }; - const t = translations[locale]; + const localT = translations[locale]; // Format date for display const formatDate = (date: Date | null): string => { @@ -422,7 +424,7 @@ const DatePicker = forwardRef(({ id={datePickerId} type="text" className={inputClasses} - placeholder={placeholder} + placeholder={placeholder || t('ui:datepicker.placeholder', 'Seleccionar fecha')} value={inputValue} onChange={handleInputChange} onFocus={(e) => { @@ -475,7 +477,7 @@ const DatePicker = forwardRef(({ value={currentMonth} onChange={(e) => setCurrentMonth(parseInt(e.target.value))} > - {t.months.map((month, index) => ( + {localT.months.map((month, index) => ( @@ -512,7 +514,7 @@ const DatePicker = forwardRef(({ {/* Weekdays */}
- {(firstDayOfWeek === 0 ? t.weekdays : [...t.weekdays.slice(1), t.weekdays[0]]).map((day) => ( + {(firstDayOfWeek === 0 ? localT.weekdays : [...localT.weekdays.slice(1), localT.weekdays[0]]).map((day) => (
{day}
@@ -582,7 +584,7 @@ const DatePicker = forwardRef(({ className="px-3 py-1 text-sm text-color-primary hover:bg-color-primary/10 rounded transition-colors duration-150" onClick={handleTodayClick} > - {t.today} + {localT.today} )} @@ -592,7 +594,7 @@ const DatePicker = forwardRef(({ className="px-3 py-1 text-sm text-text-tertiary hover:text-color-error hover:bg-color-error/10 rounded transition-colors duration-150" onClick={handleClear} > - {t.clear} + {localT.clear} )}
diff --git a/frontend/src/components/ui/PasswordCriteria.tsx b/frontend/src/components/ui/PasswordCriteria.tsx index 84e3752f..6c85f412 100644 --- a/frontend/src/components/ui/PasswordCriteria.tsx +++ b/frontend/src/components/ui/PasswordCriteria.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; interface PasswordCriteria { label: string; @@ -18,29 +19,31 @@ export const PasswordCriteria: React.FC = ({ className = '', showOnlyFailed = false }) => { + const { t } = useTranslation(['ui']); + const criteria: PasswordCriteria[] = [ { - label: 'Al menos 8 caracteres', + label: t('ui:password_criteria.min_length', 'Al menos 8 caracteres'), isValid: password.length >= 8, checkFn: (pwd) => pwd.length >= 8 }, { - label: 'Máximo 128 caracteres', + label: t('ui:password_criteria.max_length', 'Máximo 128 caracteres'), isValid: password.length <= 128, checkFn: (pwd) => pwd.length <= 128 }, { - label: 'Al menos una letra mayúscula', + label: t('ui:password_criteria.uppercase', 'Al menos una letra mayúscula'), isValid: /[A-Z]/.test(password), regex: /[A-Z]/ }, { - label: 'Al menos una letra minúscula', + label: t('ui:password_criteria.lowercase', 'Al menos una letra minúscula'), isValid: /[a-z]/.test(password), regex: /[a-z]/ }, { - label: 'Al menos un número', + label: t('ui:password_criteria.number', 'Al menos un número'), isValid: /\d/.test(password), regex: /\d/ } @@ -104,28 +107,28 @@ export const validatePassword = (password: string): boolean => { ); }; -export const getPasswordErrors = (password: string): string[] => { +export const getPasswordErrors = (password: string, t?: (key: string, fallback: string) => string): string[] => { const errors: string[] = []; - + if (password.length < 8) { - errors.push('La contraseña debe tener al menos 8 caracteres'); + errors.push(t?.('ui:password_criteria.errors.min_length', 'La contraseña debe tener al menos 8 caracteres') ?? 'La contraseña debe tener al menos 8 caracteres'); } - + if (password.length > 128) { - errors.push('La contraseña no puede exceder 128 caracteres'); + errors.push(t?.('ui:password_criteria.errors.max_length', 'La contraseña no puede exceder 128 caracteres') ?? 'La contraseña no puede exceder 128 caracteres'); } - + if (!/[A-Z]/.test(password)) { - errors.push('La contraseña debe contener al menos una letra mayúscula'); + errors.push(t?.('ui:password_criteria.errors.uppercase', 'La contraseña debe contener al menos una letra mayúscula') ?? 'La contraseña debe contener al menos una letra mayúscula'); } - + if (!/[a-z]/.test(password)) { - errors.push('La contraseña debe contener al menos una letra minúscula'); + errors.push(t?.('ui:password_criteria.errors.lowercase', 'La contraseña debe contener al menos una letra minúscula') ?? 'La contraseña debe contener al menos una letra minúscula'); } - + if (!/\d/.test(password)) { - errors.push('La contraseña debe contener al menos un número'); + errors.push(t?.('ui:password_criteria.errors.number', 'La contraseña debe contener al menos un número') ?? 'La contraseña debe contener al menos un número'); } - + return errors; }; \ No newline at end of file diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index 30121646..f03f18f4 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -20,6 +20,7 @@ export { StatsCard, StatsGrid } from './Stats'; export { StatusCard, getStatusColor } from './StatusCard'; export { StatusModal } from './StatusModal'; export { TenantSwitcher } from './TenantSwitcher'; +export { LanguageSelector, CompactLanguageSelector } from './LanguageSelector'; // Export types export type { ButtonProps } from './Button'; diff --git a/frontend/src/locales/en/auth.json b/frontend/src/locales/en/auth.json index 9e26dfee..f6b1a561 100644 --- a/frontend/src/locales/en/auth.json +++ b/frontend/src/locales/en/auth.json @@ -1 +1,166 @@ -{} \ No newline at end of file +{ + "login": { + "title": "Sign in to your account", + "subtitle": "Access your bakery control panel", + "email": "Email address", + "password": "Password", + "remember_me": "Remember me", + "forgot_password": "Forgot your password?", + "login_button": "Sign in", + "logging_in": "Signing in...", + "no_account": "Don't have an account?", + "register_link": "Register here", + "demo_account": "Use demo account", + "welcome_back": "Welcome back!", + "invalid_credentials": "Invalid credentials", + "account_locked": "Account temporarily locked", + "too_many_attempts": "Too many failed attempts" + }, + "register": { + "title": "Create your account", + "subtitle": "Join the bakery management platform", + "personal_info": "Personal information", + "company_info": "Company information", + "account_setup": "Account setup", + "first_name": "First name", + "last_name": "Last name", + "email": "Email address", + "phone": "Phone", + "company_name": "Bakery name", + "company_type": "Business type", + "employee_count": "Number of employees", + "password": "Password", + "confirm_password": "Confirm password", + "password_requirements": "Minimum 8 characters, includes uppercase, lowercase and numbers", + "passwords_dont_match": "Passwords don't match", + "accept_terms": "I accept the terms and conditions", + "accept_privacy": "I accept the privacy policy", + "marketing_consent": "I want to receive newsletters and updates (optional)", + "register_button": "Create account", + "registering": "Creating account...", + "have_account": "Already have an account?", + "login_link": "Sign in here", + "terms_link": "Terms of Service", + "privacy_link": "Privacy Policy", + "step_of": "Step {{current}} of {{total}}", + "continue": "Continue", + "back": "Back" + }, + "forgot_password": { + "title": "Reset password", + "subtitle": "We'll send you a link to reset your password", + "email": "Email address", + "send_reset_link": "Send reset link", + "sending": "Sending...", + "reset_sent": "Reset link sent", + "reset_instructions": "Check your email for password reset instructions", + "back_to_login": "Back to login", + "didnt_receive": "Didn't receive the email?", + "resend_link": "Resend link" + }, + "reset_password": { + "title": "Reset password", + "subtitle": "Enter your new password", + "new_password": "New password", + "confirm_new_password": "Confirm new password", + "reset_button": "Reset password", + "resetting": "Resetting...", + "password_reset_success": "Password reset successfully", + "password_reset_error": "Error resetting password", + "invalid_token": "Invalid or expired reset link", + "expired_token": "Reset link has expired" + }, + "profile": { + "title": "My Profile", + "personal_information": "Personal Information", + "account_settings": "Account Settings", + "security": "Security", + "preferences": "Preferences", + "notifications": "Notifications", + "first_name": "First name", + "last_name": "Last name", + "email": "Email address", + "phone": "Phone", + "avatar": "Profile picture", + "change_avatar": "Change picture", + "current_password": "Current password", + "new_password": "New password", + "confirm_password": "Confirm password", + "change_password": "Change password", + "two_factor_auth": "Two-factor authentication", + "enable_2fa": "Enable 2FA", + "disable_2fa": "Disable 2FA", + "language": "Language", + "timezone": "Timezone", + "theme": "Theme", + "theme_light": "Light", + "theme_dark": "Dark", + "theme_auto": "Auto", + "email_notifications": "Email notifications", + "push_notifications": "Push notifications", + "marketing_emails": "Marketing emails", + "save_changes": "Save changes", + "changes_saved": "Changes saved successfully", + "profile_updated": "Profile updated successfully" + }, + "logout": { + "title": "Sign out", + "confirm": "Are you sure you want to sign out?", + "logout_button": "Sign out", + "logging_out": "Signing out...", + "logged_out": "You have signed out successfully", + "goodbye": "See you later!" + }, + "session": { + "expired": "Your session has expired", + "expires_soon": "Your session will expire soon", + "extend_session": "Extend session", + "login_required": "You must sign in to access this page", + "unauthorized": "You don't have permission to access this page", + "forbidden": "Access denied", + "session_timeout": "Session expired due to inactivity" + }, + "validation": { + "email_required": "Email address is required", + "email_invalid": "Please enter a valid email address", + "password_required": "Password is required", + "password_min_length": "Password must be at least {{min}} characters", + "password_weak": "Password is too weak", + "passwords_must_match": "Passwords must match", + "first_name_required": "First name is required", + "last_name_required": "Last name is required", + "phone_required": "Phone is required", + "company_name_required": "Company name is required", + "terms_required": "You must accept the terms and conditions", + "field_required": "This field is required", + "invalid_format": "Invalid format" + }, + "roles": { + "admin": "Administrator", + "manager": "Manager", + "baker": "Baker", + "staff": "Staff", + "owner": "Owner", + "supervisor": "Supervisor", + "cashier": "Cashier", + "assistant": "Assistant" + }, + "global_roles": { + "user": "User", + "admin": "Administrator", + "manager": "Manager", + "super_admin": "Super Administrator" + }, + "permissions": { + "read": "Read", + "write": "Write", + "delete": "Delete", + "admin": "Administrator", + "manage_users": "Manage users", + "manage_inventory": "Manage inventory", + "manage_production": "Manage production", + "manage_sales": "Manage sales", + "view_reports": "View reports", + "manage_settings": "Manage settings" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/common.json b/frontend/src/locales/en/common.json index 7ae639dc..10449d0a 100644 --- a/frontend/src/locales/en/common.json +++ b/frontend/src/locales/en/common.json @@ -237,5 +237,58 @@ "link": "Link", "tooltip": "Additional information", "search": "Search in the application" + }, + "app": { + "name": "Bakery AI", + "full_name": "Bakery AI - Intelligent System" + }, + "profile": { + "my_profile": "My Profile", + "my_locations": "My Locations", + "settings": "Settings", + "profile": "Profile", + "logout": "Logout", + "profile_menu": "Profile menu", + "close_navigation": "Close navigation" + }, + "header": { + "main_navigation": "Main navigation", + "open_menu": "Open navigation menu" + }, + "footer": { + "company_description": "Intelligent management system for bakeries. Optimize your production, inventory and sales with artificial intelligence.", + "sections": { + "product": "Product", + "support": "Support", + "company": "Company" + }, + "links": { + "dashboard": "Dashboard", + "inventory": "Inventory", + "production": "Production", + "sales": "Sales", + "forecasting": "Forecasting", + "help": "Help Center", + "docs": "Documentation", + "contact": "Contact", + "feedback": "Feedback", + "about": "About", + "blog": "Blog", + "careers": "Careers", + "press": "Press", + "privacy": "Privacy", + "terms": "Terms", + "cookies": "Cookies" + }, + "social_follow": "Follow us on social media", + "social_labels": { + "twitter": "Twitter", + "linkedin": "LinkedIn", + "github": "GitHub" + } + }, + "breadcrumbs": { + "home": "Home", + "truncation": "..." } } \ No newline at end of file diff --git a/frontend/src/locales/en/dashboard.json b/frontend/src/locales/en/dashboard.json index 0504d928..2b470a47 100644 --- a/frontend/src/locales/en/dashboard.json +++ b/frontend/src/locales/en/dashboard.json @@ -37,12 +37,43 @@ "manage_staff": "Manage Staff" }, "alerts": { - "low_stock": "Low Stock", - "production_delay": "Production Delay", - "quality_issue": "Quality Issue", - "equipment_maintenance": "Equipment Maintenance", - "order_pending": "Order Pending", - "delivery_due": "Delivery Due" + "title": "Alerts", + "live": "Live", + "offline": "Offline", + "no_alerts": "No active alerts", + "view_all": "View all alerts", + "time": { + "now": "Now", + "minutes_ago": "{{count}} min ago", + "hours_ago": "{{count}} h ago", + "yesterday": "Yesterday" + }, + "types": { + "low_stock": "Low Stock", + "production_delay": "Production Delay", + "quality_issue": "Quality Issue", + "equipment_maintenance": "Equipment Maintenance", + "order_pending": "Order Pending", + "delivery_due": "Delivery Due", + "critical": "Critical", + "warning": "Warning", + "info": "Information", + "success": "Success" + }, + "status": { + "new": "New", + "acknowledged": "Acknowledged", + "resolved": "Resolved" + }, + "types": { + "alert": "Alert", + "recommendation": "Recommendation" + }, + "recommended_actions": "Recommended Actions", + "additional_details": "Additional Details", + "mark_as_read": "Mark as read", + "remove": "Remove", + "active_count": "{{count}} active alerts" }, "messages": { "welcome": "Welcome back", diff --git a/frontend/src/locales/en/database.json b/frontend/src/locales/en/database.json new file mode 100644 index 00000000..cf35a1fb --- /dev/null +++ b/frontend/src/locales/en/database.json @@ -0,0 +1,34 @@ +{ + "title": "My Bakery", + "subtitle": "View and manage all your bakery information", + "sections": { + "recipes": { + "title": "Recipes", + "description": "Manage your product recipes" + }, + "orders": { + "title": "Orders", + "description": "Check the status of all orders" + }, + "suppliers": { + "title": "Suppliers", + "description": "Manage your suppliers" + }, + "inventory": { + "title": "Inventory", + "description": "Current inventory status" + }, + "bakery_config": { + "title": "Bakery Configuration", + "description": "General settings for your bakery" + }, + "team_management": { + "title": "Team Management", + "description": "Manage your work team" + }, + "communication_preferences": { + "title": "Communication Preferences", + "description": "Configure notifications and communications" + } + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/errors.json b/frontend/src/locales/en/errors.json index 9e26dfee..98889c36 100644 --- a/frontend/src/locales/en/errors.json +++ b/frontend/src/locales/en/errors.json @@ -1 +1,13 @@ -{} \ No newline at end of file +{ + "boundary": { + "title": "Oops! Something went wrong", + "description": "An unexpected error occurred in the application. Our team has been notified.", + "technical_details": "Technical Details", + "actions": { + "retry": "Retry", + "reload": "Reload Page", + "go_back": "Go Back", + "report": "Report this error" + } + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/events.json b/frontend/src/locales/en/events.json new file mode 100644 index 00000000..d2aaa511 --- /dev/null +++ b/frontend/src/locales/en/events.json @@ -0,0 +1,25 @@ +{ + "title": "Event Log", + "description": "Monitor system activity and important events", + "categories": { + "all": "All", + "sales": "Sales", + "production": "Production", + "inventory": "Inventory", + "system": "System", + "customer": "Customers" + }, + "types": { + "order_completed": "Order Completed", + "batch_started": "Batch Started", + "stock_updated": "Stock Updated", + "customer_registered": "Customer Registered", + "system_alert": "System Alert" + }, + "severity": { + "info": "Information", + "warning": "Warning", + "error": "Error", + "success": "Success" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/landing.json b/frontend/src/locales/en/landing.json new file mode 100644 index 00000000..83d94fa8 --- /dev/null +++ b/frontend/src/locales/en/landing.json @@ -0,0 +1,127 @@ +{ + "navigation": { + "features": "Features", + "benefits": "Benefits", + "pricing": "Pricing", + "testimonials": "Testimonials" + }, + "hero": { + "badge": "Advanced AI for Bakeries", + "title_line1": "Revolutionize your", + "title_line2": "Bakery with AI", + "subtitle": "Automatically optimize your production, reduce waste by up to 35%, predict demand with 92% accuracy and increase your sales with artificial intelligence.", + "cta_primary": "Start Free 14-Day Trial", + "cta_secondary": "Watch Live Demo", + "features": { + "no_credit_card": "No credit card required", + "quick_setup": "5-minute setup", + "support_24_7": "24/7 support in English" + } + }, + "features": { + "title": "Everything you need to optimize your bakery", + "subtitle": "Powerful tools designed specifically for modern bakeries", + "ai_forecasting": { + "title": "AI Demand Forecasting", + "description": "Advanced algorithms predict which products you'll need each day with 92% accuracy" + }, + "production_optimization": { + "title": "Production Optimization", + "description": "Automatically plan baking schedules and staff management for maximum efficiency" + }, + "waste_reduction": { + "title": "Waste Reduction", + "description": "Reduce waste by up to 35% with precise predictions and intelligent inventory management" + }, + "real_time_analytics": { + "title": "Real-Time Analytics", + "description": "Intuitive dashboard with sales, production and profitability metrics updated instantly" + }, + "inventory_management": { + "title": "Inventory Management", + "description": "Automatic stock control with smart alerts and automated purchase orders" + }, + "customer_insights": { + "title": "Customer Insights", + "description": "Understand buying patterns and preferences to improve customer experience" + } + }, + "benefits": { + "title": "Proven results that transform your business", + "subtitle": "Over 1,000 bakeries have already transformed their operations with our AI", + "waste_reduction": { + "value": "35%", + "label": "Waste reduction" + }, + "accuracy": { + "value": "92%", + "label": "Prediction accuracy" + }, + "time_saved": { + "value": "4h", + "label": "Daily planning time saved" + }, + "sales_increase": { + "value": "28%", + "label": "Average sales increase" + } + }, + "pricing": { + "title": "Plans designed for bakeries of all sizes", + "subtitle": "Start free and scale as you grow", + "starter": { + "name": "Starter", + "price": "Free", + "description": "Perfect for small bakeries getting started", + "features": [ + "Up to 50 products", + "Basic demand forecasting", + "Basic dashboard", + "Email support" + ] + }, + "professional": { + "name": "Professional", + "price": "$49", + "price_period": "/month", + "description": "Ideal for established bakeries", + "features": [ + "Unlimited products", + "Advanced AI forecasting", + "Complete analytics", + "Inventory management", + "Production optimization", + "Priority 24/7 support" + ] + }, + "enterprise": { + "name": "Enterprise", + "price": "Custom", + "description": "Complete solution for chains and franchises", + "features": [ + "Multi-location", + "Custom API", + "Advanced integrations", + "Dedicated support", + "Custom training", + "Guaranteed SLA" + ] + }, + "cta": "Get Started Now", + "contact": "Contact Sales" + }, + "testimonials": { + "title": "What our customers say", + "subtitle": "Bakeries worldwide trust our platform" + }, + "cta_section": { + "title": "Ready to revolutionize your bakery?", + "subtitle": "Join over 1,000 bakeries already using AI to optimize their operations", + "cta": "Start Free Today" + }, + "footer_cta": { + "security": "Enterprise security", + "uptime": "99.9% guaranteed uptime", + "data_protection": "GDPR data protection" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/onboarding.json b/frontend/src/locales/en/onboarding.json new file mode 100644 index 00000000..eb430d62 --- /dev/null +++ b/frontend/src/locales/en/onboarding.json @@ -0,0 +1,141 @@ +{ + "wizard": { + "title": "Initial Setup", + "subtitle": "We'll guide you step by step to configure your bakery", + "steps": { + "setup": { + "title": "Register Bakery", + "description": "Configure your bakery's basic information" + }, + "smart_inventory_setup": { + "title": "Configure Inventory", + "description": "Upload sales data and set up your initial inventory" + }, + "ml_training": { + "title": "AI Training", + "description": "Train your personalized artificial intelligence model" + }, + "completion": { + "title": "Setup Complete", + "description": "Welcome to your intelligent management system!" + } + }, + "navigation": { + "previous": "Previous", + "next": "Next", + "complete": "Complete", + "skip": "Skip", + "finish": "Finish" + }, + "progress": { + "step_of": "Step {{current}} of {{total}}", + "completed": "Completed", + "in_progress": "In progress", + "pending": "Pending" + } + }, + "steps": { + "tenant_registration": { + "title": "Your Bakery Information", + "subtitle": "Tell us about your business", + "fields": { + "business_name": "Business name", + "business_type": "Business type", + "address": "Address", + "phone": "Phone", + "email": "Contact email", + "website": "Website (optional)", + "description": "Business description" + }, + "placeholders": { + "business_name": "E.g: San José Bakery", + "address": "Main Street 123, City", + "phone": "+1 123 456 789", + "email": "contact@bakery.com", + "website": "https://mybakery.com", + "description": "Describe your bakery..." + } + }, + "inventory_setup": { + "title": "Configure Inventory", + "subtitle": "Upload your historical sales data", + "upload": { + "title": "Upload Sales Data", + "description": "Upload a CSV file with your historical sales data to train the AI", + "drag_drop": "Drag and drop your CSV file here", + "or": "or", + "browse": "select a file", + "supported_formats": "Supported formats: CSV", + "max_size": "Maximum size: 10MB" + }, + "sample": { + "download": "Download CSV template", + "example": "View data example" + }, + "processing": { + "uploading": "Uploading file...", + "processing": "Processing data...", + "success": "Data processed successfully", + "error": "Error processing data" + } + }, + "ml_training": { + "title": "AI Training", + "subtitle": "Creating your personalized model", + "status": { + "preparing": "Preparing data...", + "training": "Training model...", + "validating": "Validating results...", + "completed": "Training completed" + }, + "progress": { + "data_preparation": "Data preparation", + "model_training": "Model training", + "validation": "Validation", + "deployment": "Deployment" + }, + "estimated_time": "Estimated time: {{minutes}} minutes", + "description": "We're creating a personalized AI model for your bakery based on your historical data." + }, + "completion": { + "title": "Setup Complete!", + "subtitle": "Your bakery is ready to use AI", + "success_message": "Congratulations, you have successfully completed the initial setup.", + "next_steps": { + "title": "Next steps:", + "dashboard": "Explore your dashboard", + "first_prediction": "View your first prediction", + "inventory": "Configure your inventory", + "team": "Invite your team" + }, + "cta": { + "dashboard": "Go to Dashboard", + "tour": "Start Guided Tour" + }, + "features_unlocked": { + "title": "Features unlocked:", + "ai_forecasting": "AI demand forecasting", + "inventory_management": "Inventory management", + "production_planning": "Production planning", + "analytics": "Analytics and reports" + } + } + }, + "errors": { + "step_failed": "Error in this step", + "data_invalid": "Invalid data", + "upload_failed": "File upload error", + "training_failed": "Training error", + "network_error": "Connection error", + "try_again": "Try again", + "contact_support": "Contact support" + }, + "validation": { + "required": "This field is required", + "invalid_email": "Invalid email", + "invalid_phone": "Invalid phone", + "invalid_url": "Invalid URL", + "file_too_large": "File too large", + "invalid_file_type": "Invalid file type" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/sales.json b/frontend/src/locales/en/sales.json new file mode 100644 index 00000000..4d0b878d --- /dev/null +++ b/frontend/src/locales/en/sales.json @@ -0,0 +1,88 @@ +{ + "orders": { + "title": "Orders", + "table": { + "columns": { + "order_number": "Order No.", + "customer": "Customer", + "products": "Products", + "total": "Total", + "status": "Status", + "channel": "Channel", + "date": "Date", + "actions": "Actions" + }, + "filters": { + "all_status": "All status", + "all_channels": "All channels", + "items_per_page": { + "10": "10 per page", + "20": "20 per page", + "50": "50 per page", + "100": "100 per page" + } + }, + "bulk_actions": "Bulk actions", + "no_orders": "No orders available", + "loading": "Loading orders...", + "error": "Error loading orders" + }, + "status": { + "pendiente": "Pending", + "confirmado": "Confirmed", + "en_preparacion": "In Preparation", + "listo": "Ready for Delivery", + "entregado": "Delivered", + "cancelado": "Cancelled" + }, + "channels": { + "store_front": "Store", + "online": "Online", + "phone_order": "Phone", + "delivery": "Delivery", + "catering": "Catering", + "wholesale": "Wholesale", + "farmers_market": "Market", + "third_party": "Third Party" + }, + "payment_methods": { + "cash": "Cash", + "credit_card": "Credit Card", + "debit_card": "Debit Card", + "digital_wallet": "Digital Wallet", + "bank_transfer": "Bank Transfer", + "check": "Check", + "store_credit": "Store Credit" + }, + "actions": { + "view": "View", + "edit": "Edit", + "delete": "Delete", + "print": "Print", + "duplicate": "Duplicate", + "cancel": "Cancel", + "confirm": "Confirm", + "complete": "Complete" + } + }, + "customers": { + "title": "Customers", + "name": "Name", + "phone": "Phone", + "email": "Email", + "address": "Address", + "orders_count": "Orders", + "total_spent": "Total spent", + "last_order": "Last order" + }, + "analytics": { + "title": "Sales Analytics", + "revenue": "Revenue", + "orders": "Orders", + "avg_order": "Average order", + "growth": "Growth", + "trends": "Trends", + "top_products": "Top products", + "top_customers": "Top customers" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/settings.json b/frontend/src/locales/en/settings.json new file mode 100644 index 00000000..1bf0284c --- /dev/null +++ b/frontend/src/locales/en/settings.json @@ -0,0 +1,91 @@ +{ + "profile": { + "title": "User Profile", + "description": "Manage your personal information and preferences", + "personal_info": "Personal Information", + "edit_profile": "Edit Profile", + "change_password": "Change Password", + "online": "Online", + "offline": "Offline", + "save_changes": "Save Changes", + "cancel": "Cancel", + "fields": { + "first_name": "First Name", + "last_name": "Last Name", + "email": "Email Address", + "phone": "Phone Number", + "language": "Language", + "timezone": "Timezone", + "avatar": "Avatar" + }, + "password": { + "current_password": "Current Password", + "new_password": "New Password", + "confirm_password": "Confirm Password", + "change_password": "Change Password", + "password_requirements": "Password must be at least 8 characters long" + } + }, + "team": { + "title": "Team", + "description": "Manage your team members and their permissions", + "invite_member": "Invite Member", + "members": "Members", + "pending_invitations": "Pending Invitations", + "role": "Role", + "status": "Status", + "actions": "Actions" + }, + "organization": { + "title": "Organizations", + "description": "Manage your organizations and settings", + "current_organization": "Current Organization", + "switch_organization": "Switch Organization", + "create_organization": "Create Organization" + }, + "bakery_config": { + "title": "Bakery Configuration", + "description": "Configure your bakery-specific settings", + "general": "General", + "products": "Products", + "hours": "Operating Hours", + "notifications": "Notifications" + }, + "subscription": { + "title": "Subscription", + "description": "Manage your subscription plan", + "current_plan": "Current Plan", + "usage": "Usage", + "billing": "Billing", + "upgrade": "Upgrade Plan", + "manage": "Manage Subscription" + }, + "communication": { + "title": "Communication Preferences", + "description": "Configure how and when you receive notifications", + "email_notifications": "Email Notifications", + "push_notifications": "Push Notifications", + "sms_notifications": "SMS Notifications", + "marketing": "Marketing Communications", + "alerts": "System Alerts" + }, + "tabs": { + "profile": "Profile", + "team": "Team", + "organization": "Organization", + "bakery_config": "Configuration", + "subscription": "Subscription", + "communication": "Communication" + }, + "common": { + "save": "Save", + "cancel": "Cancel", + "edit": "Edit", + "delete": "Delete", + "loading": "Loading...", + "success": "Success", + "error": "Error", + "required": "Required", + "optional": "Optional" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/traffic.json b/frontend/src/locales/en/traffic.json new file mode 100644 index 00000000..8e1600b5 --- /dev/null +++ b/frontend/src/locales/en/traffic.json @@ -0,0 +1,45 @@ +{ + "title": "Traffic Analysis", + "description": "Monitor customer flow and optimize service hours", + "metrics": { + "total_visitors": "Total Visitors", + "peak_hour": "Peak Hour", + "avg_duration": "Average Duration", + "busy_days": "Busy Days", + "conversion_rate": "Conversion Rate" + }, + "periods": { + "week": "Week", + "month": "Month", + "year": "Year" + }, + "days": { + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "mon": "Mon", + "tue": "Tue", + "wed": "Wed", + "thu": "Thu", + "fri": "Fri", + "sat": "Sat", + "sun": "Sun" + }, + "sources": { + "walking": "Walk-in", + "local_search": "Local Search", + "recommendations": "Recommendations", + "social_media": "Social Media", + "advertising": "Advertising" + }, + "segments": { + "morning_regulars": "Morning Regulars", + "weekend_families": "Weekend Families", + "lunch_office": "Lunch Office Workers", + "occasional_customers": "Occasional Customers" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/ui.json b/frontend/src/locales/en/ui.json new file mode 100644 index 00000000..f438966f --- /dev/null +++ b/frontend/src/locales/en/ui.json @@ -0,0 +1,51 @@ +{ + "datepicker": { + "placeholder": "Select date", + "today": "Today", + "clear": "Clear", + "weekdays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + "months": [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ] + }, + "password_criteria": { + "min_length": "Minimum 8 characters", + "max_length": "Maximum 128 characters", + "uppercase": "At least one uppercase letter", + "lowercase": "At least one lowercase letter", + "number": "At least one number", + "special": "At least one special character", + "errors": { + "min_length": "Password must be at least 8 characters long", + "max_length": "Password cannot exceed 128 characters", + "uppercase": "Password must contain at least one uppercase letter", + "lowercase": "Password must contain at least one lowercase letter", + "number": "Password must contain at least one number", + "special": "Password must contain at least one special character" + } + }, + "avatar": { + "online": "Online", + "offline": "Offline", + "away": "Away", + "busy": "Busy" + }, + "notifications": { + "alert": "Alert", + "recommendation": "Recommendation", + "info": "Information", + "warning": "Warning" + }, + "common": { + "loading": "Loading...", + "error": "Error", + "success": "Success", + "cancel": "Cancel", + "save": "Save", + "delete": "Delete", + "edit": "Edit", + "close": "Close", + "confirm": "Confirm" + } +} \ No newline at end of file diff --git a/frontend/src/locales/en/weather.json b/frontend/src/locales/en/weather.json new file mode 100644 index 00000000..c08e7637 --- /dev/null +++ b/frontend/src/locales/en/weather.json @@ -0,0 +1,149 @@ +{ + "title": "Weather Data", + "description": "Integrate weather information to optimize production and sales", + "current": { + "title": "Current Conditions", + "temperature": "Temperature", + "humidity": "Humidity", + "wind": "Wind", + "pressure": "Pressure", + "uv": "UV", + "visibility": "Visibility", + "favorable_conditions": "Favorable conditions" + }, + "forecast": { + "title": "Extended Forecast", + "next_week": "Next Week", + "next_month": "Next Month", + "rain": "Rain" + }, + "conditions": { + "sunny": "Sunny", + "partly_cloudy": "Partly cloudy", + "cloudy": "Cloudy", + "rainy": "Rainy" + }, + "days": { + "saturday": "Saturday", + "sunday": "Sunday", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday" + }, + "impact": { + "title": "Weather Impact", + "high_demand": "High Demand", + "comfort_food": "Comfort Food", + "moderate": "Moderate Demand", + "normal": "Normal Demand", + "recommendations": "Recommendations" + }, + "impacts": { + "sunny_day": { + "condition": "Sunny Day", + "impact": "25% increase in cold drinks", + "recommendations": [ + "Increase ice cream production", + "More refreshing drinks", + "Salads and fresh products", + "Extended terrace hours" + ] + }, + "rainy_day": { + "condition": "Rainy Day", + "impact": "40% increase in hot products", + "recommendations": [ + "More soups and broths", + "Hot chocolates", + "Freshly baked bread", + "Pastry products" + ] + }, + "cold_day": { + "condition": "Intense Cold", + "impact": "Preference for comfort food", + "recommendations": [ + "Increase baked goods", + "Special hot drinks", + "Energy products", + "Indoor promotions" + ] + } + }, + "seasonal": { + "title": "Seasonal Trends", + "spring": { + "name": "Spring", + "period": "Mar - May", + "avg_temp": "15-20°C", + "trends": [ + "Increase in fresh products (+30%)", + "Higher demand for salads", + "Popular natural drinks", + "Effective extended hours" + ] + }, + "summer": { + "name": "Summer", + "period": "Jun - Aug", + "avg_temp": "25-35°C", + "trends": [ + "Peak of ice cream and slushies (+60%)", + "Light products preferred", + "Critical morning hours", + "Higher tourist traffic" + ] + }, + "autumn": { + "name": "Autumn", + "period": "Sep - Nov", + "avg_temp": "10-18°C", + "trends": [ + "Return to traditional products", + "Increase in pastries (+20%)", + "Popular hot drinks", + "Regular schedules" + ] + }, + "winter": { + "name": "Winter", + "period": "Dec - Feb", + "avg_temp": "5-12°C", + "trends": [ + "Maximum hot products (+50%)", + "Critical freshly baked bread", + "Festive chocolates and sweets", + "Lower general traffic (-15%)" + ] + }, + "impact_levels": { + "high": "High", + "positive": "Positive", + "comfort": "Comfort", + "stable": "Stable" + } + }, + "alerts": { + "title": "Weather Alerts", + "heat_wave": { + "title": "Heat wave expected", + "description": "Temperatures above 30°C expected for the next 3 days", + "recommendation": "Increase stock of cold drinks and ice cream" + }, + "heavy_rain": { + "title": "Heavy rain on Monday", + "description": "80% chance of precipitation with strong winds", + "recommendation": "Prepare more hot products and shelter items" + }, + "recommendation_label": "Recommendation" + }, + "recommendations": { + "increase_ice_cream": "Increase ice cream and cold drinks production", + "standard_production": "Standard production", + "comfort_foods": "Increase soups, hot chocolates and freshly baked bread", + "indoor_focus": "Focus on indoor products", + "fresh_products": "Increase fresh products and salads" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/common.json b/frontend/src/locales/es/common.json index ada36b5c..c1752b5d 100644 --- a/frontend/src/locales/es/common.json +++ b/frontend/src/locales/es/common.json @@ -237,5 +237,58 @@ "link": "Enlace", "tooltip": "Información adicional", "search": "Buscar en la aplicación" + }, + "app": { + "name": "Panadería IA", + "full_name": "Panadería IA - Sistema Inteligente" + }, + "profile": { + "my_profile": "Mi perfil", + "my_locations": "Mis Locales", + "settings": "Configuración", + "profile": "Perfil", + "logout": "Cerrar Sesión", + "profile_menu": "Menú de perfil", + "close_navigation": "Cerrar navegación" + }, + "header": { + "main_navigation": "Navegación principal", + "open_menu": "Abrir menú de navegación" + }, + "footer": { + "company_description": "Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.", + "sections": { + "product": "Producto", + "support": "Soporte", + "company": "Empresa" + }, + "links": { + "dashboard": "Panel de Control", + "inventory": "Inventario", + "production": "Producción", + "sales": "Ventas", + "forecasting": "Predicciones", + "help": "Centro de Ayuda", + "docs": "Documentación", + "contact": "Contacto", + "feedback": "Feedback", + "about": "Acerca de", + "blog": "Blog", + "careers": "Carreras", + "press": "Prensa", + "privacy": "Privacidad", + "terms": "Términos", + "cookies": "Cookies" + }, + "social_follow": "Síguenos en redes sociales", + "social_labels": { + "twitter": "Twitter", + "linkedin": "LinkedIn", + "github": "GitHub" + } + }, + "breadcrumbs": { + "home": "Inicio", + "truncation": "..." } } \ No newline at end of file diff --git a/frontend/src/locales/es/dashboard.json b/frontend/src/locales/es/dashboard.json index bf726423..65b40f83 100644 --- a/frontend/src/locales/es/dashboard.json +++ b/frontend/src/locales/es/dashboard.json @@ -37,12 +37,43 @@ "manage_staff": "Gestionar Personal" }, "alerts": { - "low_stock": "Stock Bajo", - "production_delay": "Retraso en Producción", - "quality_issue": "Problema de Calidad", - "equipment_maintenance": "Mantenimiento de Equipo", - "order_pending": "Pedido Pendiente", - "delivery_due": "Entrega Vencida" + "title": "Alertas", + "live": "En vivo", + "offline": "Desconectado", + "no_alerts": "No hay alertas activas", + "view_all": "Ver todas las alertas", + "time": { + "now": "Ahora", + "minutes_ago": "hace {{count}} min", + "hours_ago": "hace {{count}} h", + "yesterday": "Ayer" + }, + "types": { + "low_stock": "Stock Bajo", + "production_delay": "Retraso en Producción", + "quality_issue": "Problema de Calidad", + "equipment_maintenance": "Mantenimiento de Equipo", + "order_pending": "Pedido Pendiente", + "delivery_due": "Entrega Vencida", + "critical": "Crítico", + "warning": "Advertencia", + "info": "Información", + "success": "Éxito" + }, + "status": { + "new": "Nuevo", + "acknowledged": "Reconocido", + "resolved": "Resuelto" + }, + "types": { + "alert": "Alerta", + "recommendation": "Recomendación" + }, + "recommended_actions": "Acciones Recomendadas", + "additional_details": "Detalles Adicionales", + "mark_as_read": "Marcar como leído", + "remove": "Eliminar", + "active_count": "{{count}} alertas activas" }, "messages": { "welcome": "Bienvenido de vuelta", diff --git a/frontend/src/locales/es/database.json b/frontend/src/locales/es/database.json new file mode 100644 index 00000000..81fd9482 --- /dev/null +++ b/frontend/src/locales/es/database.json @@ -0,0 +1,34 @@ +{ + "title": "Mi Panadería", + "subtitle": "Consulta y gestiona toda la información de tu panadería", + "sections": { + "recipes": { + "title": "Recetas", + "description": "Gestiona las recetas de tus productos" + }, + "orders": { + "title": "Pedidos", + "description": "Consulta el estado de todos los pedidos" + }, + "suppliers": { + "title": "Proveedores", + "description": "Gestiona tus proveedores" + }, + "inventory": { + "title": "Inventario", + "description": "Estado actual del inventario" + }, + "bakery_config": { + "title": "Configuración de Panadería", + "description": "Configuración general de tu panadería" + }, + "team_management": { + "title": "Gestión de Equipo", + "description": "Administra tu equipo de trabajo" + }, + "communication_preferences": { + "title": "Preferencias de Comunicación", + "description": "Configura notificaciones y comunicaciones" + } + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/errors.json b/frontend/src/locales/es/errors.json index 4bc0611f..c8337759 100644 --- a/frontend/src/locales/es/errors.json +++ b/frontend/src/locales/es/errors.json @@ -199,5 +199,16 @@ "reload_page": "Recargar página", "clear_cache": "Limpiar caché", "check_permissions": "Verificar permisos" + }, + "boundary": { + "title": "Oops! Algo salió mal", + "description": "Ha ocurrido un error inesperado en la aplicación. Nuestro equipo ha sido notificado.", + "technical_details": "Detalles técnicos", + "actions": { + "retry": "Reintentar", + "reload": "Recargar página", + "go_back": "Volver atrás", + "report": "Reportar este error" + } } } \ No newline at end of file diff --git a/frontend/src/locales/es/events.json b/frontend/src/locales/es/events.json new file mode 100644 index 00000000..53595589 --- /dev/null +++ b/frontend/src/locales/es/events.json @@ -0,0 +1,25 @@ +{ + "title": "Registro de Eventos", + "description": "Monitorea la actividad del sistema y eventos importantes", + "categories": { + "all": "Todos", + "sales": "Ventas", + "production": "Producción", + "inventory": "Inventario", + "system": "Sistema", + "customer": "Clientes" + }, + "types": { + "order_completed": "Pedido Completado", + "batch_started": "Lote Iniciado", + "stock_updated": "Stock Actualizado", + "customer_registered": "Cliente Registrado", + "system_alert": "Alerta del Sistema" + }, + "severity": { + "info": "Información", + "warning": "Advertencia", + "error": "Error", + "success": "Éxito" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/landing.json b/frontend/src/locales/es/landing.json new file mode 100644 index 00000000..79529a18 --- /dev/null +++ b/frontend/src/locales/es/landing.json @@ -0,0 +1,127 @@ +{ + "navigation": { + "features": "Características", + "benefits": "Beneficios", + "pricing": "Precios", + "testimonials": "Testimonios" + }, + "hero": { + "badge": "IA Avanzada para Panaderías", + "title_line1": "Revoluciona tu", + "title_line2": "Panadería con IA", + "subtitle": "Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%, predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.", + "cta_primary": "Comenzar Gratis 14 Días", + "cta_secondary": "Ver Demo en Vivo", + "features": { + "no_credit_card": "Sin tarjeta de crédito", + "quick_setup": "Configuración en 5 minutos", + "support_24_7": "Soporte 24/7 en español" + } + }, + "features": { + "title": "Todo lo que necesitas para optimizar tu panadería", + "subtitle": "Herramientas potentes diseñadas específicamente para panaderías modernas", + "ai_forecasting": { + "title": "Predicción IA de Demanda", + "description": "Algoritmos avanzados predicen qué productos necesitarás cada día con 92% de precisión" + }, + "production_optimization": { + "title": "Optimización de Producción", + "description": "Planifica automáticamente horarios de horneado y gestión de personal para máxima eficiencia" + }, + "waste_reduction": { + "title": "Reducción de Desperdicios", + "description": "Reduce hasta 35% el desperdicio con predicciones precisas y gestión inteligente de inventario" + }, + "real_time_analytics": { + "title": "Análisis en Tiempo Real", + "description": "Dashboard intuitivo con métricas de ventas, producción y rentabilidad actualizadas al instante" + }, + "inventory_management": { + "title": "Gestión de Inventario", + "description": "Control automático de stock con alertas inteligentes y órdenes de compra automatizadas" + }, + "customer_insights": { + "title": "Insights de Clientes", + "description": "Entiende patrones de compra y preferencias para mejorar la experiencia del cliente" + } + }, + "benefits": { + "title": "Resultados comprobados que transforman tu negocio", + "subtitle": "Más de 1,000 panaderías ya han transformado sus operaciones con nuestra IA", + "waste_reduction": { + "value": "35%", + "label": "Reducción en desperdicios" + }, + "accuracy": { + "value": "92%", + "label": "Precisión en predicciones" + }, + "time_saved": { + "value": "4h", + "label": "Ahorro diario en planificación" + }, + "sales_increase": { + "value": "28%", + "label": "Incremento promedio en ventas" + } + }, + "pricing": { + "title": "Planes diseñados para panaderías de todos los tamaños", + "subtitle": "Comienza gratis y escala según crezcas", + "starter": { + "name": "Iniciante", + "price": "Gratis", + "description": "Perfecto para panaderías pequeñas que empiezan", + "features": [ + "Hasta 50 productos", + "Predicción básica de demanda", + "Dashboard básico", + "Soporte por email" + ] + }, + "professional": { + "name": "Profesional", + "price": "€49", + "price_period": "/mes", + "description": "Ideal para panaderías establecidas", + "features": [ + "Productos ilimitados", + "IA avanzada de predicción", + "Analytics completos", + "Gestión de inventario", + "Optimización de producción", + "Soporte prioritario 24/7" + ] + }, + "enterprise": { + "name": "Empresa", + "price": "Personalizado", + "description": "Solución completa para cadenas y franquicias", + "features": [ + "Multi-ubicación", + "API personalizada", + "Integraciones avanzadas", + "Soporte dedicado", + "Capacitación personalizada", + "SLA garantizado" + ] + }, + "cta": "Comenzar Ahora", + "contact": "Contactar Ventas" + }, + "testimonials": { + "title": "Lo que dicen nuestros clientes", + "subtitle": "Panaderías de todo el mundo confían en nuestra plataforma" + }, + "cta_section": { + "title": "¿Listo para revolucionar tu panadería?", + "subtitle": "Únete a más de 1,000 panaderías que ya están usando IA para optimizar sus operaciones", + "cta": "Comenzar Gratis Hoy" + }, + "footer_cta": { + "security": "Seguridad empresarial", + "uptime": "99.9% uptime garantizado", + "data_protection": "Protección de datos RGPD" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/onboarding.json b/frontend/src/locales/es/onboarding.json new file mode 100644 index 00000000..e49f2742 --- /dev/null +++ b/frontend/src/locales/es/onboarding.json @@ -0,0 +1,141 @@ +{ + "wizard": { + "title": "Configuración Inicial", + "subtitle": "Te guiaremos paso a paso para configurar tu panadería", + "steps": { + "setup": { + "title": "Registrar Panadería", + "description": "Configura la información básica de tu panadería" + }, + "smart_inventory_setup": { + "title": "Configurar Inventario", + "description": "Sube datos de ventas y configura tu inventario inicial" + }, + "ml_training": { + "title": "Entrenamiento IA", + "description": "Entrena tu modelo de inteligencia artificial personalizado" + }, + "completion": { + "title": "Configuración Completa", + "description": "¡Bienvenido a tu sistema de gestión inteligente!" + } + }, + "navigation": { + "previous": "Anterior", + "next": "Siguiente", + "complete": "Completar", + "skip": "Omitir", + "finish": "Finalizar" + }, + "progress": { + "step_of": "Paso {{current}} de {{total}}", + "completed": "Completado", + "in_progress": "En progreso", + "pending": "Pendiente" + } + }, + "steps": { + "tenant_registration": { + "title": "Información de tu Panadería", + "subtitle": "Cuéntanos sobre tu negocio", + "fields": { + "business_name": "Nombre del negocio", + "business_type": "Tipo de negocio", + "address": "Dirección", + "phone": "Teléfono", + "email": "Email de contacto", + "website": "Sitio web (opcional)", + "description": "Descripción del negocio" + }, + "placeholders": { + "business_name": "Ej: Panadería San José", + "address": "Calle Principal 123, Ciudad", + "phone": "+34 123 456 789", + "email": "contacto@panaderia.com", + "website": "https://mipanaderia.com", + "description": "Describe tu panadería..." + } + }, + "inventory_setup": { + "title": "Configurar Inventario", + "subtitle": "Sube tus datos de ventas históricos", + "upload": { + "title": "Subir Datos de Ventas", + "description": "Sube un archivo CSV con tus datos históricos de ventas para entrenar la IA", + "drag_drop": "Arrastra y suelta tu archivo CSV aquí", + "or": "o", + "browse": "selecciona un archivo", + "supported_formats": "Formatos soportados: CSV", + "max_size": "Tamaño máximo: 10MB" + }, + "sample": { + "download": "Descargar plantilla CSV", + "example": "Ver ejemplo de datos" + }, + "processing": { + "uploading": "Subiendo archivo...", + "processing": "Procesando datos...", + "success": "Datos procesados exitosamente", + "error": "Error al procesar los datos" + } + }, + "ml_training": { + "title": "Entrenamiento de IA", + "subtitle": "Creando tu modelo personalizado", + "status": { + "preparing": "Preparando datos...", + "training": "Entrenando modelo...", + "validating": "Validando resultados...", + "completed": "Entrenamiento completado" + }, + "progress": { + "data_preparation": "Preparación de datos", + "model_training": "Entrenamiento del modelo", + "validation": "Validación", + "deployment": "Despliegue" + }, + "estimated_time": "Tiempo estimado: {{minutes}} minutos", + "description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos." + }, + "completion": { + "title": "¡Configuración Completa!", + "subtitle": "Tu panadería está lista para usar IA", + "success_message": "Felicitaciones, has completado exitosamente la configuración inicial.", + "next_steps": { + "title": "Próximos pasos:", + "dashboard": "Explora tu dashboard", + "first_prediction": "Ve tu primera predicción", + "inventory": "Configura tu inventario", + "team": "Invita a tu equipo" + }, + "cta": { + "dashboard": "Ir al Dashboard", + "tour": "Iniciar Tour Guiado" + }, + "features_unlocked": { + "title": "Características desbloqueadas:", + "ai_forecasting": "Predicción IA de demanda", + "inventory_management": "Gestión de inventario", + "production_planning": "Planificación de producción", + "analytics": "Análisis y reportes" + } + } + }, + "errors": { + "step_failed": "Error en este paso", + "data_invalid": "Datos inválidos", + "upload_failed": "Error al subir archivo", + "training_failed": "Error en entrenamiento", + "network_error": "Error de conexión", + "try_again": "Intentar de nuevo", + "contact_support": "Contactar soporte" + }, + "validation": { + "required": "Este campo es requerido", + "invalid_email": "Email inválido", + "invalid_phone": "Teléfono inválido", + "invalid_url": "URL inválida", + "file_too_large": "Archivo demasiado grande", + "invalid_file_type": "Tipo de archivo no válido" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/sales.json b/frontend/src/locales/es/sales.json new file mode 100644 index 00000000..7ed8e511 --- /dev/null +++ b/frontend/src/locales/es/sales.json @@ -0,0 +1,88 @@ +{ + "orders": { + "title": "Pedidos", + "table": { + "columns": { + "order_number": "Nº Pedido", + "customer": "Cliente", + "products": "Productos", + "total": "Total", + "status": "Estado", + "channel": "Canal", + "date": "Fecha", + "actions": "Acciones" + }, + "filters": { + "all_status": "Todos los estados", + "all_channels": "Todos los canales", + "items_per_page": { + "10": "10 por página", + "20": "20 por página", + "50": "50 por página", + "100": "100 por página" + } + }, + "bulk_actions": "Acciones masivas", + "no_orders": "No hay pedidos disponibles", + "loading": "Cargando pedidos...", + "error": "Error al cargar pedidos" + }, + "status": { + "pendiente": "Pendiente", + "confirmado": "Confirmado", + "en_preparacion": "En Preparación", + "listo": "Listo para Entrega", + "entregado": "Entregado", + "cancelado": "Cancelado" + }, + "channels": { + "store_front": "Tienda", + "online": "Online", + "phone_order": "Teléfono", + "delivery": "Delivery", + "catering": "Catering", + "wholesale": "Mayorista", + "farmers_market": "Mercado", + "third_party": "Terceros" + }, + "payment_methods": { + "cash": "Efectivo", + "credit_card": "Tarjeta Crédito", + "debit_card": "Tarjeta Débito", + "digital_wallet": "Wallet Digital", + "bank_transfer": "Transferencia", + "check": "Cheque", + "store_credit": "Crédito Tienda" + }, + "actions": { + "view": "Ver", + "edit": "Editar", + "delete": "Eliminar", + "print": "Imprimir", + "duplicate": "Duplicar", + "cancel": "Cancelar", + "confirm": "Confirmar", + "complete": "Completar" + } + }, + "customers": { + "title": "Clientes", + "name": "Nombre", + "phone": "Teléfono", + "email": "Email", + "address": "Dirección", + "orders_count": "Pedidos", + "total_spent": "Total gastado", + "last_order": "Último pedido" + }, + "analytics": { + "title": "Análisis de Ventas", + "revenue": "Ingresos", + "orders": "Pedidos", + "avg_order": "Pedido promedio", + "growth": "Crecimiento", + "trends": "Tendencias", + "top_products": "Productos más vendidos", + "top_customers": "Mejores clientes" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/settings.json b/frontend/src/locales/es/settings.json new file mode 100644 index 00000000..50a6e880 --- /dev/null +++ b/frontend/src/locales/es/settings.json @@ -0,0 +1,91 @@ +{ + "profile": { + "title": "Perfil de Usuario", + "description": "Gestiona tu información personal y preferencias", + "personal_info": "Información Personal", + "edit_profile": "Editar Perfil", + "change_password": "Cambiar Contraseña", + "online": "En línea", + "offline": "Desconectado", + "save_changes": "Guardar Cambios", + "cancel": "Cancelar", + "fields": { + "first_name": "Nombre", + "last_name": "Apellidos", + "email": "Correo Electrónico", + "phone": "Teléfono", + "language": "Idioma", + "timezone": "Zona Horaria", + "avatar": "Avatar" + }, + "password": { + "current_password": "Contraseña Actual", + "new_password": "Nueva Contraseña", + "confirm_password": "Confirmar Contraseña", + "change_password": "Cambiar Contraseña", + "password_requirements": "La contraseña debe tener al menos 8 caracteres" + } + }, + "team": { + "title": "Equipo", + "description": "Gestiona los miembros de tu equipo y sus permisos", + "invite_member": "Invitar Miembro", + "members": "Miembros", + "pending_invitations": "Invitaciones Pendientes", + "role": "Rol", + "status": "Estado", + "actions": "Acciones" + }, + "organization": { + "title": "Organizaciones", + "description": "Gestiona tus organizaciones y configuraciones", + "current_organization": "Organización Actual", + "switch_organization": "Cambiar Organización", + "create_organization": "Crear Organización" + }, + "bakery_config": { + "title": "Configuración de Panadería", + "description": "Configura los ajustes específicos de tu panadería", + "general": "General", + "products": "Productos", + "hours": "Horarios", + "notifications": "Notificaciones" + }, + "subscription": { + "title": "Suscripción", + "description": "Gestiona tu plan de suscripción", + "current_plan": "Plan Actual", + "usage": "Uso", + "billing": "Facturación", + "upgrade": "Actualizar Plan", + "manage": "Gestionar Suscripción" + }, + "communication": { + "title": "Preferencias de Comunicación", + "description": "Configura cómo y cuándo recibes notificaciones", + "email_notifications": "Notificaciones por Email", + "push_notifications": "Notificaciones Push", + "sms_notifications": "Notificaciones SMS", + "marketing": "Comunicaciones de Marketing", + "alerts": "Alertas del Sistema" + }, + "tabs": { + "profile": "Perfil", + "team": "Equipo", + "organization": "Organización", + "bakery_config": "Configuración", + "subscription": "Suscripción", + "communication": "Comunicación" + }, + "common": { + "save": "Guardar", + "cancel": "Cancelar", + "edit": "Editar", + "delete": "Eliminar", + "loading": "Cargando...", + "success": "Éxito", + "error": "Error", + "required": "Requerido", + "optional": "Opcional" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/traffic.json b/frontend/src/locales/es/traffic.json new file mode 100644 index 00000000..b7c6a753 --- /dev/null +++ b/frontend/src/locales/es/traffic.json @@ -0,0 +1,45 @@ +{ + "title": "Análisis de Tráfico", + "description": "Monitorea el flujo de clientes y optimiza las horas de atención", + "metrics": { + "total_visitors": "Visitantes Totales", + "peak_hour": "Hora Pico", + "avg_duration": "Duración Promedio", + "busy_days": "Días Ocupados", + "conversion_rate": "Tasa de Conversión" + }, + "periods": { + "week": "Semana", + "month": "Mes", + "year": "Año" + }, + "days": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo", + "mon": "Lun", + "tue": "Mar", + "wed": "Mié", + "thu": "Jue", + "fri": "Vie", + "sat": "Sáb", + "sun": "Dom" + }, + "sources": { + "walking": "Pie", + "local_search": "Búsqueda Local", + "recommendations": "Recomendaciones", + "social_media": "Redes Sociales", + "advertising": "Publicidad" + }, + "segments": { + "morning_regulars": "Regulares Matutinos", + "weekend_families": "Familia Fin de Semana", + "lunch_office": "Oficinistas Almuerzo", + "occasional_customers": "Clientes Ocasionales" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/ui.json b/frontend/src/locales/es/ui.json new file mode 100644 index 00000000..606ed5c1 --- /dev/null +++ b/frontend/src/locales/es/ui.json @@ -0,0 +1,51 @@ +{ + "datepicker": { + "placeholder": "Seleccionar fecha", + "today": "Hoy", + "clear": "Limpiar", + "weekdays": ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"], + "months": [ + "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", + "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre" + ] + }, + "password_criteria": { + "min_length": "Mínimo 8 caracteres", + "max_length": "Máximo 128 caracteres", + "uppercase": "Al menos una letra mayúscula", + "lowercase": "Al menos una letra minúscula", + "number": "Al menos un número", + "special": "Al menos un carácter especial", + "errors": { + "min_length": "La contraseña debe tener al menos 8 caracteres", + "max_length": "La contraseña no puede exceder 128 caracteres", + "uppercase": "La contraseña debe contener al menos una letra mayúscula", + "lowercase": "La contraseña debe contener al menos una letra minúscula", + "number": "La contraseña debe contener al menos un número", + "special": "La contraseña debe contener al menos un carácter especial" + } + }, + "avatar": { + "online": "En línea", + "offline": "Desconectado", + "away": "Ausente", + "busy": "Ocupado" + }, + "notifications": { + "alert": "Alerta", + "recommendation": "Recomendación", + "info": "Información", + "warning": "Advertencia" + }, + "common": { + "loading": "Cargando...", + "error": "Error", + "success": "Éxito", + "cancel": "Cancelar", + "save": "Guardar", + "delete": "Eliminar", + "edit": "Editar", + "close": "Cerrar", + "confirm": "Confirmar" + } +} \ No newline at end of file diff --git a/frontend/src/locales/es/weather.json b/frontend/src/locales/es/weather.json new file mode 100644 index 00000000..8f9933b6 --- /dev/null +++ b/frontend/src/locales/es/weather.json @@ -0,0 +1,149 @@ +{ + "title": "Datos Meteorológicos", + "description": "Integra información del clima para optimizar la producción y ventas", + "current": { + "title": "Condiciones Actuales", + "temperature": "Temperatura", + "humidity": "Humedad", + "wind": "Viento", + "pressure": "Presión", + "uv": "UV", + "visibility": "Visibilidad", + "favorable_conditions": "Condiciones favorables" + }, + "forecast": { + "title": "Pronóstico Extendido", + "next_week": "Próxima Semana", + "next_month": "Próximo Mes", + "rain": "Lluvia" + }, + "conditions": { + "sunny": "Soleado", + "partly_cloudy": "Parcialmente nublado", + "cloudy": "Nublado", + "rainy": "Lluvioso" + }, + "days": { + "saturday": "Sábado", + "sunday": "Domingo", + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes" + }, + "impact": { + "title": "Impacto del Clima", + "high_demand": "Alta Demanda", + "comfort_food": "Comida Reconfortante", + "moderate": "Demanda Moderada", + "normal": "Demanda Normal", + "recommendations": "Recomendaciones" + }, + "impacts": { + "sunny_day": { + "condition": "Día Soleado", + "impact": "Aumento del 25% en bebidas frías", + "recommendations": [ + "Incrementar producción de helados", + "Más bebidas refrescantes", + "Ensaladas y productos frescos", + "Horario extendido de terraza" + ] + }, + "rainy_day": { + "condition": "Día Lluvioso", + "impact": "Aumento del 40% en productos calientes", + "recommendations": [ + "Más sopas y caldos", + "Chocolates calientes", + "Pan recién horneado", + "Productos de repostería" + ] + }, + "cold_day": { + "condition": "Frío Intenso", + "impact": "Preferencia por comida reconfortante", + "recommendations": [ + "Aumentar productos horneados", + "Bebidas calientes especiales", + "Productos energéticos", + "Promociones de interior" + ] + } + }, + "seasonal": { + "title": "Tendencias Estacionales", + "spring": { + "name": "Primavera", + "period": "Mar - May", + "avg_temp": "15-20°C", + "trends": [ + "Aumento en productos frescos (+30%)", + "Mayor demanda de ensaladas", + "Bebidas naturales populares", + "Horarios extendidos efectivos" + ] + }, + "summer": { + "name": "Verano", + "period": "Jun - Ago", + "avg_temp": "25-35°C", + "trends": [ + "Pico de helados y granizados (+60%)", + "Productos ligeros preferidos", + "Horario matutino crítico", + "Mayor tráfico de turistas" + ] + }, + "autumn": { + "name": "Otoño", + "period": "Sep - Nov", + "avg_temp": "10-18°C", + "trends": [ + "Regreso a productos tradicionales", + "Aumento en bollería (+20%)", + "Bebidas calientes populares", + "Horarios regulares" + ] + }, + "winter": { + "name": "Invierno", + "period": "Dec - Feb", + "avg_temp": "5-12°C", + "trends": [ + "Máximo de productos calientes (+50%)", + "Pan recién horneado crítico", + "Chocolates y dulces festivos", + "Menor tráfico general (-15%)" + ] + }, + "impact_levels": { + "high": "Alto", + "positive": "Positivo", + "comfort": "Confort", + "stable": "Estable" + } + }, + "alerts": { + "title": "Alertas Meteorológicas", + "heat_wave": { + "title": "Ola de calor prevista", + "description": "Se esperan temperaturas superiores a 30°C los próximos 3 días", + "recommendation": "Incrementar stock de bebidas frías y helados" + }, + "heavy_rain": { + "title": "Lluvia intensa el lunes", + "description": "80% probabilidad de precipitación con vientos fuertes", + "recommendation": "Preparar más productos calientes y de refugio" + }, + "recommendation_label": "Recomendación" + }, + "recommendations": { + "increase_ice_cream": "Incrementar producción de helados y bebidas frías", + "standard_production": "Producción estándar", + "comfort_foods": "Aumentar sopas, chocolates calientes y pan recién horneado", + "indoor_focus": "Enfoque en productos de interior", + "fresh_products": "Incrementar productos frescos y ensaladas" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/auth.json b/frontend/src/locales/eu/auth.json index 9e26dfee..ae413224 100644 --- a/frontend/src/locales/eu/auth.json +++ b/frontend/src/locales/eu/auth.json @@ -1 +1,166 @@ -{} \ No newline at end of file +{ + "login": { + "title": "Hasi saioa zure kontuan", + "subtitle": "Sartu zure okindegiaren kontrol panelera", + "email": "Helbide elektronikoa", + "password": "Pasahitza", + "remember_me": "Gogoratu nazazu", + "forgot_password": "Pasahitza ahaztu duzu?", + "login_button": "Hasi saioa", + "logging_in": "Saioa hasten...", + "no_account": "Ez duzu konturik?", + "register_link": "Erregistratu hemen", + "demo_account": "Erabili demo kontua", + "welcome_back": "Ongi etorri berriz!", + "invalid_credentials": "Kredentzial baliogabeak", + "account_locked": "Kontua aldi baterako blokeatuta", + "too_many_attempts": "Saiakera gehiegi huts egin" + }, + "register": { + "title": "Sortu zure kontua", + "subtitle": "Sartu okindegiaren kudeaketa plataforman", + "personal_info": "Informazio pertsonala", + "company_info": "Enpresaren informazioa", + "account_setup": "Kontuaren konfigurazioa", + "first_name": "Izena", + "last_name": "Abizena", + "email": "Helbide elektronikoa", + "phone": "Telefonoa", + "company_name": "Okindegiaren izena", + "company_type": "Negozio mota", + "employee_count": "Langile kopurua", + "password": "Pasahitza", + "confirm_password": "Berretsi pasahitza", + "password_requirements": "Gutxienez 8 karaktere, letra larriak, txikiak eta zenbakiak", + "passwords_dont_match": "Pasahitzak ez datoz bat", + "accept_terms": "Baldintza eta baldintzak onartzen ditut", + "accept_privacy": "Pribatutasun politika onartzen dut", + "marketing_consent": "Newsletter eta berriak jaso nahi ditut (aukerakoa)", + "register_button": "Sortu kontua", + "registering": "Kontua sortzen...", + "have_account": "Dagoeneko baduzu kontua?", + "login_link": "Hasi saioa hemen", + "terms_link": "Zerbitzu baldintzak", + "privacy_link": "Pribatutasun politika", + "step_of": "{{current}}. urratsa {{total}}-tik", + "continue": "Jarraitu", + "back": "Atzera" + }, + "forgot_password": { + "title": "Berrezarri pasahitza", + "subtitle": "Pasahitza berrezartzeko esteka bidaliko dizugu", + "email": "Helbide elektronikoa", + "send_reset_link": "Berrezartze esteka bidali", + "sending": "Bidaltzen...", + "reset_sent": "Berrezartze esteka bidali da", + "reset_instructions": "Begiratu zure emaila pasahitza berrezartzeko argibideentzat", + "back_to_login": "Itzuli saio hasierara", + "didnt_receive": "Ez duzu emaila jaso?", + "resend_link": "Birbidali esteka" + }, + "reset_password": { + "title": "Berrezarri pasahitza", + "subtitle": "Sartu zure pasahitz berria", + "new_password": "Pasahitz berria", + "confirm_new_password": "Berretsi pasahitz berria", + "reset_button": "Berrezarri pasahitza", + "resetting": "Berrezartzen...", + "password_reset_success": "Pasahitza behar bezala berrezarri da", + "password_reset_error": "Errorea pasahitza berrezartzean", + "invalid_token": "Berrezartze esteka baliogabea edo iraungi", + "expired_token": "Berrezartze esteka iraungi egin da" + }, + "profile": { + "title": "Nire profila", + "personal_information": "Informazio Pertsonala", + "account_settings": "Kontu ezarpenak", + "security": "Segurtasuna", + "preferences": "Hobespenak", + "notifications": "Jakinarazpenak", + "first_name": "Izena", + "last_name": "Abizena", + "email": "Helbide elektronikoa", + "phone": "Telefonoa", + "avatar": "Profileko argazkia", + "change_avatar": "Aldatu argazkia", + "current_password": "Oraingo pasahitza", + "new_password": "Pasahitz berria", + "confirm_password": "Berretsi pasahitza", + "change_password": "Aldatu pasahitza", + "two_factor_auth": "Bi faktoreko autentifikazioa", + "enable_2fa": "Gaitu 2FA", + "disable_2fa": "Desgaitu 2FA", + "language": "Hizkuntza", + "timezone": "Ordu zona", + "theme": "Itxura", + "theme_light": "Argia", + "theme_dark": "Iluna", + "theme_auto": "Automatikoa", + "email_notifications": "Email jakinarazpenak", + "push_notifications": "Push jakinarazpenak", + "marketing_emails": "Marketing emailak", + "save_changes": "Gorde aldaketak", + "changes_saved": "Aldaketak behar bezala gorde dira", + "profile_updated": "Profila behar bezala eguneratu da" + }, + "logout": { + "title": "Itxi saioa", + "confirm": "Ziur zaude saioa itxi nahi duzula?", + "logout_button": "Itxi saioa", + "logging_out": "Saioa ixten...", + "logged_out": "Saioa behar bezala itxi duzu", + "goodbye": "Gero arte!" + }, + "session": { + "expired": "Zure saioa iraungitu da", + "expires_soon": "Zure saioa laster iraungiko da", + "extend_session": "Luzatu saioa", + "login_required": "Saioa hasi behar duzu orri honetara sartzeko", + "unauthorized": "Ez duzu baimenik orri honetara sartzeko", + "forbidden": "Sarbidea ukatu", + "session_timeout": "Saioa iraungitu da jarduera ezagatik" + }, + "validation": { + "email_required": "Helbide elektronikoa beharrezkoa da", + "email_invalid": "Mesedez, sartu baliozko helbide elektroniko bat", + "password_required": "Pasahitza beharrezkoa da", + "password_min_length": "Pasahitzak gutxienez {{min}} karaktere izan behar ditu", + "password_weak": "Pasahitza ahulegia da", + "passwords_must_match": "Pasahitzak bat etorri behar dira", + "first_name_required": "Izena beharrezkoa da", + "last_name_required": "Abizena beharrezkoa da", + "phone_required": "Telefonoa beharrezkoa da", + "company_name_required": "Enpresaren izena beharrezkoa da", + "terms_required": "Baldintza eta baldintzak onartu behar dituzu", + "field_required": "Eremu hau beharrezkoa da", + "invalid_format": "Formatu baliogabea" + }, + "roles": { + "admin": "Administratzailea", + "manager": "Kudeatzailea", + "baker": "Okindegigilea", + "staff": "Langilea", + "owner": "Jabea", + "supervisor": "Gainbegiralea", + "cashier": "Kutxazaina", + "assistant": "Laguntzailea" + }, + "global_roles": { + "user": "Erabiltzailea", + "admin": "Administratzailea", + "manager": "Kudeatzailea", + "super_admin": "Super Administratzailea" + }, + "permissions": { + "read": "Irakurri", + "write": "Idatzi", + "delete": "Ezabatu", + "admin": "Administratzailea", + "manage_users": "Kudeatu erabiltzaileak", + "manage_inventory": "Kudeatu inbentarioa", + "manage_production": "Kudeatu ekoizpena", + "manage_sales": "Kudeatu salmentak", + "view_reports": "Ikusi txostenak", + "manage_settings": "Kudeatu ezarpenak" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/common.json b/frontend/src/locales/eu/common.json index e1e4a63e..962320d1 100644 --- a/frontend/src/locales/eu/common.json +++ b/frontend/src/locales/eu/common.json @@ -237,5 +237,58 @@ "link": "Esteka", "tooltip": "Informazio gehigarria", "search": "Aplikazioan bilatu" + }, + "app": { + "name": "Okindegiaren AA", + "full_name": "Okindegiaren AA - Sistema Adimentsua" + }, + "profile": { + "my_profile": "Nire profila", + "my_locations": "Nire Kokalekuak", + "settings": "Ezarpenak", + "profile": "Profila", + "logout": "Saioa Itxi", + "profile_menu": "Profil menua", + "close_navigation": "Nabigazioa itxi" + }, + "header": { + "main_navigation": "Nabigazio nagusia", + "open_menu": "Nabigazioa ireki" + }, + "footer": { + "company_description": "Okindegirentzako kudeaketa sistema adimentsua. Optimizatu zure ekoizpena, inbentarioa eta salmentak adimen artifizialarekin.", + "sections": { + "product": "Produktua", + "support": "Laguntza", + "company": "Enpresa" + }, + "links": { + "dashboard": "Aginte-panela", + "inventory": "Inbentarioa", + "production": "Ekoizpena", + "sales": "Salmentak", + "forecasting": "Aurreikuspenak", + "help": "Laguntza Zentroa", + "docs": "Dokumentazioa", + "contact": "Kontaktua", + "feedback": "Iritzia", + "about": "Guri buruz", + "blog": "Bloga", + "careers": "Lana", + "press": "Prentsa", + "privacy": "Pribatutasuna", + "terms": "Baldintzak", + "cookies": "Cookie-ak" + }, + "social_follow": "Jarraitu gaitzazu sare sozialetan", + "social_labels": { + "twitter": "Twitter", + "linkedin": "LinkedIn", + "github": "GitHub" + } + }, + "breadcrumbs": { + "home": "Hasiera", + "truncation": "..." } } \ No newline at end of file diff --git a/frontend/src/locales/eu/dashboard.json b/frontend/src/locales/eu/dashboard.json index 0e0dceb3..9e8c85f4 100644 --- a/frontend/src/locales/eu/dashboard.json +++ b/frontend/src/locales/eu/dashboard.json @@ -37,12 +37,43 @@ "manage_staff": "Langilea Kudeatu" }, "alerts": { - "low_stock": "Stock Baxua", - "production_delay": "Ekoizpen Atzerapena", - "quality_issue": "Kalitate Arazoa", - "equipment_maintenance": "Ekipo Mantentze", - "order_pending": "Eskaera Zain", - "delivery_due": "Entrega Atzeratua" + "title": "Alertak", + "live": "Zuzenean", + "offline": "Deskonektatuta", + "no_alerts": "Ez dago alerta aktiborik", + "view_all": "Alerta guztiak ikusi", + "time": { + "now": "Orain", + "minutes_ago": "duela {{count}} min", + "hours_ago": "duela {{count}} h", + "yesterday": "Atzo" + }, + "types": { + "low_stock": "Stock Baxua", + "production_delay": "Ekoizpen Atzerapena", + "quality_issue": "Kalitate Arazoa", + "equipment_maintenance": "Ekipo Mantentze", + "order_pending": "Eskaera Zain", + "delivery_due": "Entrega Atzeratua", + "critical": "Kritikoa", + "warning": "Abisua", + "info": "Informazioa", + "success": "Arrakasta" + }, + "status": { + "new": "Berria", + "acknowledged": "Onartu", + "resolved": "Ebatzi" + }, + "types": { + "alert": "Alerta", + "recommendation": "Gomendioa" + }, + "recommended_actions": "Gomendatutako Ekintzak", + "additional_details": "Xehetasun Gehigarriak", + "mark_as_read": "Irakurritako gisa markatu", + "remove": "Kendu", + "active_count": "{{count}} alerta aktibo" }, "messages": { "welcome": "Ongi etorri berriro", diff --git a/frontend/src/locales/eu/database.json b/frontend/src/locales/eu/database.json new file mode 100644 index 00000000..56584e8d --- /dev/null +++ b/frontend/src/locales/eu/database.json @@ -0,0 +1,34 @@ +{ + "title": "Nire Okindegia", + "subtitle": "Ikusi eta kudeatu zure okindegiaren informazio guztia", + "sections": { + "recipes": { + "title": "Errezetak", + "description": "Kudeatu zure produktuen errezetak" + }, + "orders": { + "title": "Eskaerak", + "description": "Egiaztatu eskaera guztien egoera" + }, + "suppliers": { + "title": "Hornitzaileak", + "description": "Kudeatu zure hornitzaileak" + }, + "inventory": { + "title": "Inbentarioa", + "description": "Oraingo inbentarioaren egoera" + }, + "bakery_config": { + "title": "Okindegiaren Konfigurazioa", + "description": "Zure okindegiaren ezarpen orokorrak" + }, + "team_management": { + "title": "Taldearen Kudeaketa", + "description": "Kudeatu zure lan taldea" + }, + "communication_preferences": { + "title": "Komunikazio Hobespenak", + "description": "Konfiguratu jakinarazpenak eta komunikazioak" + } + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/errors.json b/frontend/src/locales/eu/errors.json index 0967ef42..a1d24325 100644 --- a/frontend/src/locales/eu/errors.json +++ b/frontend/src/locales/eu/errors.json @@ -1 +1,13 @@ -{} +{ + "boundary": { + "title": "Oops! Zerbait gaizki joan da", + "description": "Ustekabeko errore bat gertatu da aplikazioan. Gure taldea jakinarazi da.", + "technical_details": "Xehetasun Teknikoak", + "actions": { + "retry": "Saiatu berriz", + "reload": "Orria Birkargatu", + "go_back": "Itzuli", + "report": "Errore hau salatu" + } + } +} diff --git a/frontend/src/locales/eu/events.json b/frontend/src/locales/eu/events.json new file mode 100644 index 00000000..987705fa --- /dev/null +++ b/frontend/src/locales/eu/events.json @@ -0,0 +1,25 @@ +{ + "title": "Gertaeren Erregistroa", + "description": "Kontrolatu sistemaren jarduerak eta gertaera garrantzitsuak", + "categories": { + "all": "Denak", + "sales": "Salmentak", + "production": "Ekoizpena", + "inventory": "Inbentarioa", + "system": "Sistema", + "customer": "Bezeroak" + }, + "types": { + "order_completed": "Eskaera Osatua", + "batch_started": "Lote Hasita", + "stock_updated": "Stock Eguneratua", + "customer_registered": "Bezero Erregistratua", + "system_alert": "Sistemaren Alerta" + }, + "severity": { + "info": "Informazioa", + "warning": "Abisua", + "error": "Errorea", + "success": "Arrakasta" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/landing.json b/frontend/src/locales/eu/landing.json new file mode 100644 index 00000000..e7a021a3 --- /dev/null +++ b/frontend/src/locales/eu/landing.json @@ -0,0 +1,127 @@ +{ + "navigation": { + "features": "Ezaugarriak", + "benefits": "Onurak", + "pricing": "Prezioak", + "testimonials": "Testigantzak" + }, + "hero": { + "badge": "AA Aurreratua Okindegirentzat", + "title_line1": "Irauli zure", + "title_line2": "Okindegia AA-rekin", + "subtitle": "Optimizatu automatikoki zure ekoizpena, murriztu hondakinak %35era, aurreikusi eskaria %92ko zehaztasunarekin eta handitu zure salmentak adimen artifizialarekin.", + "cta_primary": "Hasi Doan 14 Egunez", + "cta_secondary": "Ikusi Demo Zuzenan", + "features": { + "no_credit_card": "Kreditu txartelik ez", + "quick_setup": "5 minutuko konfigurazioa", + "support_24_7": "24/7 laguntza euskeraz" + } + }, + "features": { + "title": "Zure okindegia optimizatzeko behar duzun guztia", + "subtitle": "Okindegi modernoetarako diseinu bereziki egindako tresna indartsuak", + "ai_forecasting": { + "title": "AA Eskariaren Aurreikuspena", + "description": "Algoritmo aurreratuek egunero behar dituzun produktuak aurreikusten dituzte %92ko zehaztasunarekin" + }, + "production_optimization": { + "title": "Ekoizpenaren Optimizazioa", + "description": "Planifikatu automatikoki labe ordutegiak eta langile kudeaketa eraginkortasun maximorakoak" + }, + "waste_reduction": { + "title": "Hondakinen Murrizketa", + "description": "Murriztu hondakinak %35era aurreikuspen zehatzak eta inbentario kudeaketa adimendunarekin" + }, + "real_time_analytics": { + "title": "Denbora Errealeko Analisiak", + "description": "Dashboard intuitiboa salmenta, ekoizpen eta errentagarritasun metriken eguneratuta berehala" + }, + "inventory_management": { + "title": "Inbentario Kudeaketa", + "description": "Stock kontrola automatikoa alerta adimentsu eta erosketako agindu automatizatuekin" + }, + "customer_insights": { + "title": "Bezeroen Ikuspegia", + "description": "Ulertu erosketa ereduak eta lehentasunak bezeroaren esperientzia hobetzeko" + } + }, + "benefits": { + "title": "Zure negozioa transformatzen duten emaitza frogatuak", + "subtitle": "1.000 okindegi baino gehiagok transformatu dituzte jadanik euren eragiketak gure AA-rekin", + "waste_reduction": { + "value": "%35", + "label": "Hondakinen murrizketa" + }, + "accuracy": { + "value": "%92", + "label": "Aurreikuspenaren zehaztasuna" + }, + "time_saved": { + "value": "4o", + "label": "Eguneko planifikazioan aurreztutako denbora" + }, + "sales_increase": { + "value": "%28", + "label": "Batez besteko salmenta igoera" + } + }, + "pricing": { + "title": "Tamaina guztietako okindegirentzat diseinatutako planak", + "subtitle": "Hasi doan eta eskala hazi ahala", + "starter": { + "name": "Hasiberria", + "price": "Doan", + "description": "Hasitzen ari diren okindegi txikientzat perfektua", + "features": [ + "50 produktu arte", + "Oinarrizko eskariaren aurreikuspena", + "Dashboard basikoa", + "Email laguntza" + ] + }, + "professional": { + "name": "Profesionala", + "price": "49€", + "price_period": "/hilabetea", + "description": "Ezarritako okindegientzat egokia", + "features": [ + "Produktu mugagabeak", + "AA aurreratuko aurreikuspena", + "Analisiak osoak", + "Inbentario kudeaketa", + "Ekoizpen optimizazioa", + "Lehentasunezko 24/7 laguntza" + ] + }, + "enterprise": { + "name": "Enpresa", + "price": "Pertsonalizatua", + "description": "Kate eta frantzizientzako soluzio osoa", + "features": [ + "Hainbat kokaleku", + "API pertsonalizatua", + "Integrazio aurreratuak", + "Laguntza dedikatua", + "Prestakuntza pertsonalizatua", + "SLA bermatua" + ] + }, + "cta": "Hasi Orain", + "contact": "Salmenta Harremanetan Jarri" + }, + "testimonials": { + "title": "Zer dioten gure bezeroek", + "subtitle": "Munduko okindegiok konfiantza jartzen dute gure plataforman" + }, + "cta_section": { + "title": "Prest zaude zure okindegia iraultzeko?", + "subtitle": "Sartu dagoeneko AA erabiltzen ari diren 1.000 okindegitan euren eragiketak optimizatzeko", + "cta": "Hasi Doan Gaur" + }, + "footer_cta": { + "security": "Enpresako segurtasuna", + "uptime": "%99.9 bermatutako erabilgarritasuna", + "data_protection": "RGPD datuen babesa" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/onboarding.json b/frontend/src/locales/eu/onboarding.json new file mode 100644 index 00000000..c681f584 --- /dev/null +++ b/frontend/src/locales/eu/onboarding.json @@ -0,0 +1,141 @@ +{ + "wizard": { + "title": "Hasierako Konfigurazioa", + "subtitle": "Pausoz pauso gidatuko zaitugu zure okindegia konfiguratzeko", + "steps": { + "setup": { + "title": "Okindegia Erregistratu", + "description": "Konfiguratu zure okindegiko oinarrizko informazioa" + }, + "smart_inventory_setup": { + "title": "Inbentarioa Konfiguratu", + "description": "Salmenten datuak igo eta hasierako inbentarioa ezarri" + }, + "ml_training": { + "title": "AA Prestakuntza", + "description": "Entrenatu zure adimen artifizial modelo pertsonalizatua" + }, + "completion": { + "title": "Konfigurazioa Osatuta", + "description": "Ongi etorri zure kudeaketa sistema adimentsu honetara!" + } + }, + "navigation": { + "previous": "Aurrekoa", + "next": "Hurrengoa", + "complete": "Osatu", + "skip": "Saltatu", + "finish": "Amaitu" + }, + "progress": { + "step_of": "{{current}}. pausoa {{total}}tik", + "completed": "Osatuta", + "in_progress": "Abian", + "pending": "Zain" + } + }, + "steps": { + "tenant_registration": { + "title": "Zure Okindegiko Informazioa", + "subtitle": "Kontaiguzu zure negozioari buruz", + "fields": { + "business_name": "Negozioaren izena", + "business_type": "Negozio mota", + "address": "Helbidea", + "phone": "Telefonoa", + "email": "Harremaneko emaila", + "website": "Webgunea (aukerakoa)", + "description": "Negozioaren deskripzioa" + }, + "placeholders": { + "business_name": "Adib.: San José Okindegia", + "address": "Kale Nagusia 123, Hiria", + "phone": "+34 123 456 789", + "email": "kontaktua@okindegia.com", + "website": "https://nireokindegia.com", + "description": "Deskribatu zure okindegia..." + } + }, + "inventory_setup": { + "title": "Inbentarioa Konfiguratu", + "subtitle": "Igo zure salmenta historikoko datuak", + "upload": { + "title": "Salmenta Datuak Igo", + "description": "Igo CSV fitxategi bat zure salmenta historikoko datuekin AA entrenatzeko", + "drag_drop": "Arrastatu eta jaregin zure CSV fitxategia hemen", + "or": "edo", + "browse": "hautatu fitxategi bat", + "supported_formats": "Onartutako formatuak: CSV", + "max_size": "Gehienezko tamaina: 10MB" + }, + "sample": { + "download": "CSV txantiloia jaitsi", + "example": "Datuen adibidea ikusi" + }, + "processing": { + "uploading": "Fitxategia igotzen...", + "processing": "Datuak prozesatzen...", + "success": "Datuak ongi prozesatu dira", + "error": "Errorea datuak prozesatzean" + } + }, + "ml_training": { + "title": "AA Prestakuntza", + "subtitle": "Zure modelo pertsonalizatua sortzen", + "status": { + "preparing": "Datuak prestatzen...", + "training": "Modeloa entrenatzen...", + "validating": "Emaitzak balioesten...", + "completed": "Prestakuntza osatuta" + }, + "progress": { + "data_preparation": "Datuen prestaketa", + "model_training": "Modeloaren prestakuntza", + "validation": "Balioespena", + "deployment": "Hedapena" + }, + "estimated_time": "Aurreikusitako denbora: {{minutes}} minutu", + "description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian." + }, + "completion": { + "title": "Konfigurazioa Osatuta!", + "subtitle": "Zure okindegia AA erabiltzeko prest dago", + "success_message": "Zorionak, hasierako konfigurazioa ongi osatu duzu.", + "next_steps": { + "title": "Hurrengo pausoak:", + "dashboard": "Arakatu zure panela", + "first_prediction": "Ikusi zure lehen aurreikuspena", + "inventory": "Konfiguratu zure inbentarioa", + "team": "Gonbidatu zure taldea" + }, + "cta": { + "dashboard": "Panelera Joan", + "tour": "Gidatutako Bisita Hasi" + }, + "features_unlocked": { + "title": "Desblokeatutako ezaugarriak:", + "ai_forecasting": "AA eskaera aurreikuspena", + "inventory_management": "Inbentario kudeaketa", + "production_planning": "Ekoizpen planifikazioa", + "analytics": "Analisiak eta txostenak" + } + } + }, + "errors": { + "step_failed": "Errorea pauso honetan", + "data_invalid": "Datu baliogabeak", + "upload_failed": "Errorea fitxategia igotzean", + "training_failed": "Errorea prestakuntzan", + "network_error": "Konexio errorea", + "try_again": "Saiatu berriz", + "contact_support": "Laguntzarekin harremanetan jarri" + }, + "validation": { + "required": "Eremu hau derrigorrezkoa da", + "invalid_email": "Email baliogabea", + "invalid_phone": "Telefono baliogabea", + "invalid_url": "URL baliogabea", + "file_too_large": "Fitxategia handiegia", + "invalid_file_type": "Fitxategi mota baliogabea" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/sales.json b/frontend/src/locales/eu/sales.json new file mode 100644 index 00000000..6b416811 --- /dev/null +++ b/frontend/src/locales/eu/sales.json @@ -0,0 +1,88 @@ +{ + "orders": { + "title": "Eskaerak", + "table": { + "columns": { + "order_number": "Eskaera Zk.", + "customer": "Bezeroa", + "products": "Produktuak", + "total": "Guztira", + "status": "Egoera", + "channel": "Kanala", + "date": "Data", + "actions": "Ekintzak" + }, + "filters": { + "all_status": "Egoera guztiak", + "all_channels": "Kanal guztiak", + "items_per_page": { + "10": "10 orriko", + "20": "20 orriko", + "50": "50 orriko", + "100": "100 orriko" + } + }, + "bulk_actions": "Ekintza masiboak", + "no_orders": "Ez dago eskaerarik", + "loading": "Eskaerak kargatzen...", + "error": "Errorea eskaerak kargatzean" + }, + "status": { + "pendiente": "Zain", + "confirmado": "Berretsi", + "en_preparacion": "Prestakuntzan", + "listo": "Entregatzeko prest", + "entregado": "Entregatua", + "cancelado": "Bertan behera" + }, + "channels": { + "store_front": "Denda", + "online": "Online", + "phone_order": "Telefonoa", + "delivery": "Banaketa", + "catering": "Katering", + "wholesale": "Handizkako", + "farmers_market": "Merkatua", + "third_party": "Hirugarrenak" + }, + "payment_methods": { + "cash": "Dirutan", + "credit_card": "Kreditu Txartela", + "debit_card": "Zordunketa Txartela", + "digital_wallet": "Digital Poltsikoa", + "bank_transfer": "Banku Transferentzia", + "check": "Txekea", + "store_credit": "Denda Kreditua" + }, + "actions": { + "view": "Ikusi", + "edit": "Editatu", + "delete": "Ezabatu", + "print": "Inprimatu", + "duplicate": "Bikoiztu", + "cancel": "Utzi", + "confirm": "Berretsi", + "complete": "Osatu" + } + }, + "customers": { + "title": "Bezeroak", + "name": "Izena", + "phone": "Telefonoa", + "email": "Emaila", + "address": "Helbidea", + "orders_count": "Eskaerak", + "total_spent": "Guztira gastatu", + "last_order": "Azken eskaera" + }, + "analytics": { + "title": "Salmenten Analisia", + "revenue": "Diru-sarrerak", + "orders": "Eskaerak", + "avg_order": "Batez besteko eskaera", + "growth": "Hazkundea", + "trends": "Joerak", + "top_products": "Produktu onenak", + "top_customers": "Bezero onenak" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/settings.json b/frontend/src/locales/eu/settings.json new file mode 100644 index 00000000..9bb11f51 --- /dev/null +++ b/frontend/src/locales/eu/settings.json @@ -0,0 +1,91 @@ +{ + "profile": { + "title": "Erabiltzaile Profila", + "description": "Kudeatu zure informazio pertsonala eta lehentasunak", + "personal_info": "Informazio Pertsonala", + "edit_profile": "Profila Editatu", + "change_password": "Pasahitza Aldatu", + "online": "Konektatuta", + "offline": "Deskonektatuta", + "save_changes": "Aldaketak Gorde", + "cancel": "Utzi", + "fields": { + "first_name": "Izena", + "last_name": "Abizenak", + "email": "Helbide Elektronikoa", + "phone": "Telefono Zenbakia", + "language": "Hizkuntza", + "timezone": "Ordu Zona", + "avatar": "Avatarra" + }, + "password": { + "current_password": "Oraingo Pasahitza", + "new_password": "Pasahitz Berria", + "confirm_password": "Pasahitza Berretsi", + "change_password": "Pasahitza Aldatu", + "password_requirements": "Pasahitzak gutxienez 8 karaktere izan behar ditu" + } + }, + "team": { + "title": "Taldea", + "description": "Kudeatu zure taldekideak eta euren baimenak", + "invite_member": "Kidea Gonbidatu", + "members": "Kideak", + "pending_invitations": "Zain dauden Gonbidapenak", + "role": "Rola", + "status": "Egoera", + "actions": "Ekintzak" + }, + "organization": { + "title": "Erakundeak", + "description": "Kudeatu zure erakundeak eta konfigurazioak", + "current_organization": "Oraingo Erakundea", + "switch_organization": "Erakundea Aldatu", + "create_organization": "Erakundea Sortu" + }, + "bakery_config": { + "title": "Okindegi Konfigurazioa", + "description": "Konfiguratu zure okindegiko ezarpen bereziak", + "general": "Orokorra", + "products": "Produktuak", + "hours": "Ordu Koadroa", + "notifications": "Jakinarazpenak" + }, + "subscription": { + "title": "Harpidetza", + "description": "Kudeatu zure harpidetza plana", + "current_plan": "Oraingo Plana", + "usage": "Erabilera", + "billing": "Fakturazioa", + "upgrade": "Plana Eguneratu", + "manage": "Harpidetza Kudeatu" + }, + "communication": { + "title": "Komunikazio Lehentasunak", + "description": "Konfiguratu nola eta noiz jasotzen dituzun jakinarazpenak", + "email_notifications": "Email Jakinarazpenak", + "push_notifications": "Push Jakinarazpenak", + "sms_notifications": "SMS Jakinarazpenak", + "marketing": "Marketing Komunikazioak", + "alerts": "Sistemaren Alertak" + }, + "tabs": { + "profile": "Profila", + "team": "Taldea", + "organization": "Erakundea", + "bakery_config": "Konfigurazioa", + "subscription": "Harpidetza", + "communication": "Komunikazioa" + }, + "common": { + "save": "Gorde", + "cancel": "Utzi", + "edit": "Editatu", + "delete": "Ezabatu", + "loading": "Kargatzen...", + "success": "Arrakasta", + "error": "Errorea", + "required": "Beharrezkoa", + "optional": "Aukerakoa" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/traffic.json b/frontend/src/locales/eu/traffic.json new file mode 100644 index 00000000..a22ea4b2 --- /dev/null +++ b/frontend/src/locales/eu/traffic.json @@ -0,0 +1,45 @@ +{ + "title": "Trafiko Analisia", + "description": "Kontrolatu bezeroen fluxua eta optimizatu zerbitzuaren ordutegia", + "metrics": { + "total_visitors": "Bisitari Guztiak", + "peak_hour": "Gailur Ordua", + "avg_duration": "Batezbesteko Iraupena", + "busy_days": "Egun Okupatuak", + "conversion_rate": "Bihurtze Tasa" + }, + "periods": { + "week": "Astea", + "month": "Hilabetea", + "year": "Urtea" + }, + "days": { + "monday": "Astelehena", + "tuesday": "Asteartea", + "wednesday": "Asteazkena", + "thursday": "Osteguna", + "friday": "Ostirala", + "saturday": "Larunbata", + "sunday": "Igandea", + "mon": "Asl", + "tue": "Ast", + "wed": "Azk", + "thu": "Ost", + "fri": "Orl", + "sat": "Lar", + "sun": "Ign" + }, + "sources": { + "walking": "Oinez", + "local_search": "Tokiko Bilaketa", + "recommendations": "Gomendioak", + "social_media": "Sare Sozialak", + "advertising": "Publizitatea" + }, + "segments": { + "morning_regulars": "Goizeko Erregularrak", + "weekend_families": "Asteburu Familiak", + "lunch_office": "Bazkari Bulegokideak", + "occasional_customers": "Bezero Okazionalak" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/ui.json b/frontend/src/locales/eu/ui.json new file mode 100644 index 00000000..19710029 --- /dev/null +++ b/frontend/src/locales/eu/ui.json @@ -0,0 +1,51 @@ +{ + "datepicker": { + "placeholder": "Data hautatu", + "today": "Gaur", + "clear": "Garbitu", + "weekdays": ["Ign", "Asl", "Ast", "Azk", "Ost", "Orl", "Lar"], + "months": [ + "Urtarrila", "Otsaila", "Martxoa", "Apirila", "Maiatza", "Ekaina", + "Uztaila", "Abuztua", "Iraila", "Urria", "Azaroa", "Abendua" + ] + }, + "password_criteria": { + "min_length": "Gutxienez 8 karaktere", + "max_length": "Gehienez 128 karaktere", + "uppercase": "Gutxienez letra larri bat", + "lowercase": "Gutxienez letra xehe bat", + "number": "Gutxienez zenbaki bat", + "special": "Gutxienez karaktere berezi bat", + "errors": { + "min_length": "Pasahitzak gutxienez 8 karaktere izan behar ditu", + "max_length": "Pasahitzak ezin ditu 128 karaktere baino gehiago izan", + "uppercase": "Pasahitzak gutxienez letra larri bat izan behar du", + "lowercase": "Pasahitzak gutxienez letra xehe bat izan behar du", + "number": "Pasahitzak gutxienez zenbaki bat izan behar du", + "special": "Pasahitzak gutxienez karaktere berezi bat izan behar du" + } + }, + "avatar": { + "online": "Konektatuta", + "offline": "Deskonektatuta", + "away": "Kanpoan", + "busy": "Okupatuta" + }, + "notifications": { + "alert": "Alerta", + "recommendation": "Gomendioa", + "info": "Informazioa", + "warning": "Abisua" + }, + "common": { + "loading": "Kargatzen...", + "error": "Errorea", + "success": "Arrakasta", + "cancel": "Utzi", + "save": "Gorde", + "delete": "Ezabatu", + "edit": "Editatu", + "close": "Itxi", + "confirm": "Berretsi" + } +} \ No newline at end of file diff --git a/frontend/src/locales/eu/weather.json b/frontend/src/locales/eu/weather.json new file mode 100644 index 00000000..4f05044f --- /dev/null +++ b/frontend/src/locales/eu/weather.json @@ -0,0 +1,149 @@ +{ + "title": "Eguraldiaren Datuak", + "description": "Integratu eguraldiaren informazioa ekoizpena eta salmentak optimizatzeko", + "current": { + "title": "Uneko Baldintzak", + "temperature": "Tenperatura", + "humidity": "Hezetasuna", + "wind": "Haizea", + "pressure": "Presioa", + "uv": "UV", + "visibility": "Ikusgarritasuna", + "favorable_conditions": "Baldintza onak" + }, + "forecast": { + "title": "Aurreikuspena Zabaldua", + "next_week": "Hurrengo Astea", + "next_month": "Hurrengo Hilabetea", + "rain": "Euria" + }, + "conditions": { + "sunny": "Eguzkitsua", + "partly_cloudy": "Partzialki lainotsua", + "cloudy": "Lainotsua", + "rainy": "Euriatsua" + }, + "days": { + "saturday": "Larunbata", + "sunday": "Igandea", + "monday": "Astelehena", + "tuesday": "Asteartea", + "wednesday": "Asteazkena", + "thursday": "Osteguna", + "friday": "Ostirala" + }, + "impact": { + "title": "Eguraldiaren Eragina", + "high_demand": "Eskari Handia", + "comfort_food": "Erosotasun Janaria", + "moderate": "Eskari Moderatua", + "normal": "Eskari Normala", + "recommendations": "Gomendioak" + }, + "impacts": { + "sunny_day": { + "condition": "Eguzki Eguna", + "impact": "%25eko igoera edari hotzetan", + "recommendations": [ + "Handitu izozkien ekoizpena", + "Edari freskagarri gehiago", + "Entsaladak eta produktu freskoak", + "Terrazako ordutegia luzatu" + ] + }, + "rainy_day": { + "condition": "Euri Eguna", + "impact": "%40ko igoera produktu beroetan", + "recommendations": [ + "Zopa eta saltsa gehiago", + "Txokolate beroak", + "Ogi freskoa", + "Gozogintza produktuak" + ] + }, + "cold_day": { + "condition": "Hotz Sakona", + "impact": "Erosotasun janarien lehentasuna", + "recommendations": [ + "Handitu produktu labetuak", + "Edari bero bereziak", + "Energia produktuak", + "Barruko promozioak" + ] + } + }, + "seasonal": { + "title": "Sasoi Joerak", + "spring": { + "name": "Udaberria", + "period": "Mar - Mai", + "avg_temp": "15-20°C", + "trends": [ + "Produktu freskoen igoera (+%30)", + "Entsaladen eskari handiagoa", + "Edari naturalak ezagunak", + "Ordutegia luzatzea eraginkorra" + ] + }, + "summer": { + "name": "Uda", + "period": "Eka - Abu", + "avg_temp": "25-35°C", + "trends": [ + "Izozkien eta granizatuen gailurra (+%60)", + "Produktu arinak hobetsiak", + "Goizeko ordutegia kritikoa", + "Turista trafiko handiagoa" + ] + }, + "autumn": { + "name": "Udazkena", + "period": "Ira - Aza", + "avg_temp": "10-18°C", + "trends": [ + "Produktu tradizionaletara itzulera", + "Gozogintzan igoera (+%20)", + "Edari bero ezagunak", + "Ordutegia erregularra" + ] + }, + "winter": { + "name": "Negua", + "period": "Abe - Ots", + "avg_temp": "5-12°C", + "trends": [ + "Produktu beroen maximoa (+%50)", + "Ogi freskoa kritikoa", + "Txokolate eta gozoki festiboak", + "Trafiko orokorra txikiagoa (-%15)" + ] + }, + "impact_levels": { + "high": "Altua", + "positive": "Positiboa", + "comfort": "Erosotasuna", + "stable": "Egonkorra" + } + }, + "alerts": { + "title": "Eguraldi Alertak", + "heat_wave": { + "title": "Bero olatu aurreikusia", + "description": "30°C baino tenperatura altuagoak espero dira hurrengo 3 egunetan", + "recommendation": "Handitu edari hotz eta izozkien stocka" + }, + "heavy_rain": { + "title": "Euri sakona astelehenean", + "description": "%80ko euritze probabilitatea haize indartsuarekin", + "recommendation": "Prestatu produktu bero gehiago eta babeslekukoak" + }, + "recommendation_label": "Gomendioa" + }, + "recommendations": { + "increase_ice_cream": "Handitu izozkien eta edari hotzen ekoizpena", + "standard_production": "Ekoizpen estandarra", + "comfort_foods": "Handitu zopak, txokolate beroak eta ogi freskoa", + "indoor_focus": "Barruko produktuetan zentratu", + "fresh_products": "Handitu produktu freskoak eta entsaladak" + } +} \ No newline at end of file diff --git a/frontend/src/pages/app/data/events/EventsPage.tsx b/frontend/src/pages/app/data/events/EventsPage.tsx index 01f000b9..fc036835 100644 --- a/frontend/src/pages/app/data/events/EventsPage.tsx +++ b/frontend/src/pages/app/data/events/EventsPage.tsx @@ -1,9 +1,11 @@ import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Calendar, Activity, Filter, Download, Eye, BarChart3 } from 'lucide-react'; import { Button, Card, Badge } from '../../../../components/ui'; import { PageHeader } from '../../../../components/layout'; const EventsPage: React.FC = () => { + const { t } = useTranslation(); const [selectedPeriod, setSelectedPeriod] = useState('week'); const [selectedCategory, setSelectedCategory] = useState('all'); @@ -145,8 +147,8 @@ const EventsPage: React.FC = () => { return (
)}
@@ -435,11 +435,11 @@ const ProfilePage: React.FC = () => { {/* Profile Form */} {activeTab === 'profile' && ( -

Información Personal

+

{t('settings:profile.personal_info', 'Información Personal')}

{ /> { { { + const { t } = useTranslation(['settings']); const { addToast } = useToast(); const currentUser = useAuthUser(); const currentTenant = useCurrentTenant(); @@ -299,8 +301,8 @@ const TeamPage: React.FC = () => { return (
{ headerProps={{ showThemeToggle: true, showAuthButtons: false, + showLanguageSelector: true, variant: "minimal" }} > diff --git a/frontend/src/pages/public/LandingPage.tsx b/frontend/src/pages/public/LandingPage.tsx index 73c70c12..fe0ab9e8 100644 --- a/frontend/src/pages/public/LandingPage.tsx +++ b/frontend/src/pages/public/LandingPage.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { Button } from '../../components/ui'; import { PublicLayout } from '../../components/layout'; import { @@ -23,6 +24,8 @@ import { } from 'lucide-react'; const LandingPage: React.FC = () => { + const { t } = useTranslation(); + const scrollToSection = (sectionId: string) => { const element = document.getElementById(sectionId); if (element) { @@ -37,12 +40,13 @@ const LandingPage: React.FC = () => { headerProps={{ showThemeToggle: true, showAuthButtons: true, + showLanguageSelector: true, variant: "default", navigationItems: [ - { id: 'features', label: 'Características', href: '#features' }, - { id: 'benefits', label: 'Beneficios', href: '#benefits' }, - { id: 'pricing', label: 'Precios', href: '#pricing' }, - { id: 'testimonials', label: 'Testimonios', href: '#testimonials' } + { id: 'features', label: t('landing:navigation.features', 'Características'), href: '#features' }, + { id: 'benefits', label: t('landing:navigation.benefits', 'Beneficios'), href: '#benefits' }, + { id: 'pricing', label: t('landing:navigation.pricing', 'Precios'), href: '#pricing' }, + { id: 'testimonials', label: t('landing:navigation.testimonials', 'Testimonios'), href: '#testimonials' } ] }} > @@ -54,24 +58,23 @@ const LandingPage: React.FC = () => {
- IA Avanzada para Panaderías + {t('landing:hero.badge', 'IA Avanzada para Panaderías')}

- Revoluciona tu - Panadería con IA + {t('landing:hero.title_line1', 'Revoluciona tu')} + {t('landing:hero.title_line2', 'Panadería con IA')}

- Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%, - predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial. + {t('landing:hero.subtitle', 'Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%, predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.')}

@@ -82,22 +85,22 @@ const LandingPage: React.FC = () => { onClick={() => scrollToSection('demo')} > - Ver Demo en Vivo + {t('landing:hero.cta_secondary', 'Ver Demo en Vivo')}
- Sin tarjeta de crédito + {t('landing:hero.features.no_credit_card', 'Sin tarjeta de crédito')}
- Configuración en 5 minutos + {t('landing:hero.features.quick_setup', 'Configuración en 5 minutos')}
- Soporte 24/7 en español + {t('landing:hero.features.support_24_7', 'Soporte 24/7 en español')}
diff --git a/frontend/src/pages/public/LoginPage.tsx b/frontend/src/pages/public/LoginPage.tsx index e447d007..0e013909 100644 --- a/frontend/src/pages/public/LoginPage.tsx +++ b/frontend/src/pages/public/LoginPage.tsx @@ -37,6 +37,7 @@ const LoginPage: React.FC = () => { headerProps={{ showThemeToggle: true, showAuthButtons: false, + showLanguageSelector: true, variant: "minimal" }} > diff --git a/frontend/src/pages/public/RegisterPage.tsx b/frontend/src/pages/public/RegisterPage.tsx index 3de18eba..ea77baca 100644 --- a/frontend/src/pages/public/RegisterPage.tsx +++ b/frontend/src/pages/public/RegisterPage.tsx @@ -21,6 +21,7 @@ const RegisterPage: React.FC = () => { headerProps={{ showThemeToggle: true, showAuthButtons: false, + showLanguageSelector: true, variant: "minimal" }} > diff --git a/frontend/src/router/routes.config.ts b/frontend/src/router/routes.config.ts index db2a1939..6039d43e 100644 --- a/frontend/src/router/routes.config.ts +++ b/frontend/src/router/routes.config.ts @@ -415,7 +415,7 @@ export const routesConfig: RouteConfig[] = [ path: '/app/database/team', name: 'Team', component: 'TeamPage', - title: 'Gestión de Equipo', + title: 'Trabajadores', icon: 'user', requiresAuth: true, requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS, @@ -460,7 +460,7 @@ export const routesConfig: RouteConfig[] = [ path: '/app/settings/personal-info', name: 'PersonalInfo', component: 'PersonalInfoPage', - title: 'Información Personal', + title: 'Información', icon: 'user', requiresAuth: true, showInNavigation: true, @@ -470,7 +470,7 @@ export const routesConfig: RouteConfig[] = [ path: '/app/settings/communication-preferences', name: 'CommunicationPreferences', component: 'CommunicationPreferencesPage', - title: 'Preferencias de Comunicación', + title: 'Notificaciones', icon: 'bell', requiresAuth: true, showInNavigation: true, @@ -490,7 +490,7 @@ export const routesConfig: RouteConfig[] = [ path: '/app/settings/organizations', name: 'Organizations', component: 'OrganizationsPage', - title: 'Mis Organizaciones', + title: 'Establecimientos', icon: 'database', requiresAuth: true, showInNavigation: true,