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:
@@ -7,11 +7,12 @@ import math
|
||||
from uuid import uuid4
|
||||
from typing import Optional, Any, Literal
|
||||
|
||||
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends
|
||||
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends, Header
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from beyond_api.security import get_current_user
|
||||
from beyond_api.services.analysis_service import run_analysis_collect_json
|
||||
from beyond_api.i18n import t, get_language_from_header
|
||||
|
||||
# Cache paths - same as in cache.py
|
||||
CACHE_DIR = Path(os.getenv("CACHE_DIR", "/data/cache"))
|
||||
@@ -52,6 +53,7 @@ async def analysis_endpoint(
|
||||
economy_json: Optional[str] = Form(default=None),
|
||||
analysis: Literal["basic", "premium"] = Form(default="premium"),
|
||||
current_user: str = Depends(get_current_user),
|
||||
accept_language: Optional[str] = Header(None),
|
||||
):
|
||||
"""
|
||||
Ejecuta el pipeline sobre un CSV subido (multipart/form-data) y devuelve
|
||||
@@ -62,12 +64,13 @@ async def analysis_endpoint(
|
||||
- "premium": usa la configuración completa por defecto
|
||||
(p.ej. beyond_metrics_config.json), sin romper lo existente.
|
||||
"""
|
||||
lang = get_language_from_header(accept_language)
|
||||
|
||||
# Validar `analysis` (por si llega algo raro)
|
||||
if analysis not in {"basic", "premium"}:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="analysis debe ser 'basic' o 'premium'.",
|
||||
detail=t("analysis.invalid_type", lang),
|
||||
)
|
||||
|
||||
# 1) Parseo de economía (si viene)
|
||||
@@ -78,7 +81,7 @@ async def analysis_endpoint(
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="economy_json no es un JSON válido.",
|
||||
detail=t("analysis.invalid_economy_json", lang),
|
||||
)
|
||||
|
||||
# 2) Guardar el CSV subido en una carpeta de trabajo
|
||||
@@ -159,23 +162,26 @@ async def analysis_cached_endpoint(
|
||||
economy_json: Optional[str] = Form(default=None),
|
||||
analysis: Literal["basic", "premium"] = Form(default="premium"),
|
||||
current_user: str = Depends(get_current_user),
|
||||
accept_language: Optional[str] = Header(None),
|
||||
):
|
||||
"""
|
||||
Ejecuta el pipeline sobre el archivo CSV cacheado en el servidor.
|
||||
Útil para re-analizar sin tener que subir el archivo de nuevo.
|
||||
"""
|
||||
lang = get_language_from_header(accept_language)
|
||||
|
||||
# Validar que existe el archivo cacheado
|
||||
if not CACHED_FILE.exists():
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="No hay archivo cacheado en el servidor. Sube un archivo primero.",
|
||||
detail=t("analysis.no_cached_file", lang),
|
||||
)
|
||||
|
||||
# Validar `analysis`
|
||||
if analysis not in {"basic", "premium"}:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="analysis debe ser 'basic' o 'premium'.",
|
||||
detail=t("analysis.invalid_type", lang),
|
||||
)
|
||||
|
||||
# Parseo de economía (si viene)
|
||||
@@ -186,7 +192,7 @@ async def analysis_cached_endpoint(
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="economy_json no es un JSON válido.",
|
||||
detail=t("analysis.invalid_economy_json", lang),
|
||||
)
|
||||
|
||||
# Extraer metadatos del CSV
|
||||
@@ -204,7 +210,7 @@ async def analysis_cached_endpoint(
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error ejecutando análisis: {str(e)}",
|
||||
detail=t("analysis.execution_error", lang, error=str(e)),
|
||||
)
|
||||
|
||||
# Limpiar NaN/inf para que el JSON sea válido
|
||||
|
||||
Reference in New Issue
Block a user