Files
BeyondCXAnalytics-Demo/backend/beyond_api/i18n.py
Claude f719d181c0 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
2026-02-06 17:46:01 +00:00

129 lines
5.4 KiB
Python

"""
Módulo de internacionalización para Beyond API.
Proporciona traducciones para mensajes de error, validaciones y clasificaciones.
"""
from typing import Literal
Language = Literal["es", "en"]
# Diccionario de traducciones
MESSAGES = {
"es": {
# Errores de autenticación
"auth.credentials_required": "Credenciales requeridas",
"auth.incorrect_credentials": "Credenciales incorrectas",
# Errores de análisis
"analysis.invalid_type": "analysis debe ser 'basic' o 'premium'.",
"analysis.invalid_economy_json": "economy_json no es un JSON válido.",
"analysis.no_cached_file": "No hay archivo cacheado en el servidor. Sube un archivo primero.",
"analysis.execution_error": "Error ejecutando análisis: {error}",
# Errores de validación
"validation.field_not_numeric": "El campo '{field}' debe ser numérico (float). Valor recibido: {value}",
"validation.segments_not_dict": "customer_segments debe ser un diccionario {segment: level}",
"validation.segment_value_not_str": "El valor de customer_segments['{key}'] debe ser str. Valor recibido: {value}",
"validation.csv_not_found": "El CSV no existe: {path}",
"validation.not_csv_file": "La ruta no apunta a un fichero CSV: {path}",
"validation.missing_columns": "Faltan columnas obligatorias para {metric}: {missing}",
# Clasificaciones Agentic Readiness
"agentic.ready_for_copilot": "Listo para Copilot",
"agentic.ready_for_copilot_desc": "Procesos con predictibilidad y simplicidad suficientes para asistencia IA (sugerencias en tiempo real, autocompletado).",
"agentic.optimize_first": "Optimizar primero",
"agentic.optimize_first_desc": "Estandarizar procesos y reducir variabilidad antes de implementar asistencia IA.",
"agentic.requires_human": "Requiere gestión humana",
"agentic.requires_human_desc": "Procesos complejos o variables que necesitan intervención humana antes de considerar automatización.",
},
"en": {
# Authentication errors
"auth.credentials_required": "Credentials required",
"auth.incorrect_credentials": "Incorrect credentials",
# Analysis errors
"analysis.invalid_type": "analysis must be 'basic' or 'premium'.",
"analysis.invalid_economy_json": "economy_json is not valid JSON.",
"analysis.no_cached_file": "No cached file on server. Upload a file first.",
"analysis.execution_error": "Error executing analysis: {error}",
# Validation errors
"validation.field_not_numeric": "Field '{field}' must be numeric (float). Received value: {value}",
"validation.segments_not_dict": "customer_segments must be a dictionary {segment: level}",
"validation.segment_value_not_str": "Value of customer_segments['{key}'] must be str. Received value: {value}",
"validation.csv_not_found": "CSV does not exist: {path}",
"validation.not_csv_file": "Path does not point to a CSV file: {path}",
"validation.missing_columns": "Missing required columns for {metric}: {missing}",
# Agentic Readiness classifications
"agentic.ready_for_copilot": "Ready for Copilot",
"agentic.ready_for_copilot_desc": "Processes with sufficient predictability and simplicity for AI assistance (real-time suggestions, autocomplete).",
"agentic.optimize_first": "Optimize first",
"agentic.optimize_first_desc": "Standardize processes and reduce variability before implementing AI assistance.",
"agentic.requires_human": "Requires human management",
"agentic.requires_human_desc": "Complex or variable processes that need human intervention before considering automation.",
}
}
def t(key: str, lang: Language = "es", **kwargs) -> str:
"""
Traduce un mensaje al idioma especificado.
Args:
key: Clave del mensaje (ej: 'auth.credentials_required')
lang: Idioma ('es' o 'en'). Por defecto 'es'
**kwargs: Variables para interpolación (ej: field='nombre', value='123')
Returns:
Mensaje traducido con variables interpoladas
Examples:
>>> t('auth.credentials_required', 'en')
'Credentials required'
>>> t('validation.field_not_numeric', 'en', field='age', value='abc')
"Field 'age' must be numeric (float). Received value: abc"
"""
messages = MESSAGES.get(lang, MESSAGES["es"])
message = messages.get(key, key)
# Interpolar variables si se proporcionan
if kwargs:
try:
return message.format(**kwargs)
except KeyError:
# Si falta alguna variable, devolver el mensaje sin interpolar
return message
return message
def get_language_from_header(accept_language: str | None) -> Language:
"""
Extrae el idioma preferido del header Accept-Language.
Args:
accept_language: Valor del header Accept-Language (ej: 'en-US,en;q=0.9,es;q=0.8')
Returns:
'en' si el idioma preferido es inglés, 'es' en caso contrario
Examples:
>>> get_language_from_header('en-US,en;q=0.9')
'en'
>>> get_language_from_header('es-ES,es;q=0.9')
'es'
>>> get_language_from_header(None)
'es'
"""
if not accept_language:
return "es"
# Extraer el primer idioma del header
primary_lang = accept_language.split(',')[0].split('-')[0].strip().lower()
return "en" if primary_lang == "en" else "es"