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

@@ -2,8 +2,11 @@ from __future__ import annotations
import os
import secrets
from fastapi import Depends, HTTPException, status
from fastapi import Depends, HTTPException, status, Header
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from typing import Optional
from .i18n import t, get_language_from_header
# auto_error=False para que no dispare el popup nativo del navegador automáticamente
security = HTTPBasic(auto_error=False)
@@ -13,16 +16,21 @@ BASIC_USER = os.getenv("BASIC_AUTH_USERNAME", "beyond")
BASIC_PASS = os.getenv("BASIC_AUTH_PASSWORD", "beyond2026")
def get_current_user(credentials: HTTPBasicCredentials | None = Depends(security)) -> str:
def get_current_user(
credentials: HTTPBasicCredentials | None = Depends(security),
accept_language: Optional[str] = Header(None)
) -> str:
"""
Valida el usuario/contraseña vía HTTP Basic.
NO envía WWW-Authenticate para evitar el popup nativo del navegador
(el frontend tiene su propio formulario de login).
"""
lang = get_language_from_header(accept_language)
if credentials is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Credenciales requeridas",
detail=t("auth.credentials_required", lang),
)
correct_username = secrets.compare_digest(credentials.username, BASIC_USER)
@@ -31,7 +39,7 @@ def get_current_user(credentials: HTTPBasicCredentials | None = Depends(security
if not (correct_username and correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Credenciales incorrectas",
detail=t("auth.incorrect_credentials", lang),
)
return credentials.username