Add English language support with i18n implementation

Implemented comprehensive internationalization (i18n) for both frontend and backend:

Frontend:
- Added react-i18next configuration with Spanish (default) and English
- Created translation files (locales/es.json, locales/en.json)
- Refactored core components to use i18n: LoginPage, DashboardHeader, DataUploader
- Created LanguageSelector component with toggle between ES/EN
- Updated API client to send Accept-Language header

Backend:
- Created i18n module with translation dictionary for error messages
- Updated security.py to return localized authentication errors
- Updated api/analysis.py to return localized validation errors
- Implemented language detection from Accept-Language header

Spanish remains the default language ensuring backward compatibility.
Users can switch between languages using the language selector in the dashboard header.

https://claude.ai/code/session_1N9VX
This commit is contained in:
Claude
2026-02-06 17:46:01 +00:00
parent 9457d3d02f
commit f719d181c0
15 changed files with 768 additions and 58 deletions

View File

@@ -1,5 +1,7 @@
import { motion } from 'framer-motion';
import { LayoutDashboard, Layers, Bot, Map, ShieldCheck, Info, Scale } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { LanguageSelector } from './LanguageSelector';
export type TabId = 'executive' | 'dimensions' | 'readiness' | 'roadmap' | 'law10';
@@ -16,37 +18,41 @@ interface DashboardHeaderProps {
onMetodologiaClick?: () => void;
}
const TABS: TabConfig[] = [
{ id: 'executive', label: 'Resumen', icon: LayoutDashboard },
{ id: 'dimensions', label: 'Dimensiones', icon: Layers },
{ id: 'readiness', label: 'Agentic Readiness', icon: Bot },
{ id: 'roadmap', label: 'Roadmap', icon: Map },
{ id: 'law10', label: 'Ley 10/2025', icon: Scale },
];
export function DashboardHeader({
title = 'CLIENTE DEMO - Beyond CX Analytics',
activeTab,
onTabChange,
onMetodologiaClick
}: DashboardHeaderProps) {
const { t } = useTranslation();
const TABS: TabConfig[] = [
{ id: 'executive', label: t('tabs.executive'), icon: LayoutDashboard },
{ id: 'dimensions', label: t('tabs.dimensions'), icon: Layers },
{ id: 'readiness', label: t('tabs.agenticReadiness'), icon: Bot },
{ id: 'roadmap', label: t('tabs.roadmap'), icon: Map },
{ id: 'law10', label: t('tabs.law10'), icon: Scale },
];
return (
<header className="sticky top-0 z-50 bg-white border-b border-slate-200 shadow-sm">
{/* Top row: Title and Metodología Badge */}
{/* Top row: Title and Badges */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-3 sm:py-4">
<div className="flex items-center justify-between gap-2">
<h1 className="text-base sm:text-xl font-bold text-slate-800 truncate">{title}</h1>
{onMetodologiaClick && (
<button
onClick={onMetodologiaClick}
className="inline-flex items-center gap-1 sm:gap-1.5 px-2 sm:px-3 py-1 sm:py-1.5 bg-green-100 text-green-800 rounded-full text-[10px] sm:text-xs font-medium hover:bg-green-200 transition-colors cursor-pointer flex-shrink-0"
>
<ShieldCheck className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
<span className="hidden md:inline">Metodología de Transformación de Datos aplicada</span>
<span className="md:hidden">Metodología</span>
<Info className="w-2.5 h-2.5 sm:w-3 sm:h-3 opacity-60" />
</button>
)}
<div className="flex items-center gap-2">
<LanguageSelector />
{onMetodologiaClick && (
<button
onClick={onMetodologiaClick}
className="inline-flex items-center gap-1 sm:gap-1.5 px-2 sm:px-3 py-1 sm:py-1.5 bg-green-100 text-green-800 rounded-full text-[10px] sm:text-xs font-medium hover:bg-green-200 transition-colors cursor-pointer flex-shrink-0"
>
<ShieldCheck className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
<span className="hidden md:inline">{t('methodology.appliedBadge')}</span>
<span className="md:hidden">{t('methodology.appliedBadgeShort')}</span>
<Info className="w-2.5 h-2.5 sm:w-3 sm:h-3 opacity-60" />
</button>
)}
</div>
</div>
</div>