swimmingArt/public/js/traduccionPiscina.js

356 lines
13 KiB
JavaScript

const traducciones = {
es: {
editor: {
titulo: "Editor de Formación",
tipo: "Tipo",
modalidad: "Modalidad",
agregar: "Agregar Atleta",
seleccionarAtleta: "Seleccionar atleta",
rol: "Rol",
rolSeleccionar: "Seleccionar rol",
volador: "Volador",
pilar: "Pilar",
otro: "Otro",
idPers: "ID personalizado",
idEj: "Ej: A, 1, V2",
figura: "Figura técnica",
figuraEj: "Ej: Barracuda, Flamingo...",
tipoElemento: "Tipo de elemento",
codigoElemento: "Código del elemento",
codigoEj: "Ej: TRE 1A, H4...",
hibrido: "Híbrido",
transicion: "Transición",
btnAgregar: "Añadir Atleta",
editar: "Editar formación",
eliminar: "Eliminar Formación",
guardar: "Guardar Formación",
linea: "Línea de Tiempo",
piscina: "Piscina",
tipoPiscina: "Tipo de piscina",
olimpica: "Olímpica (50m x 25m)",
semiolimpica: "Semiolímpica (25m x 12.5m)",
fosa: "Fosa/Poza (20m x 20m)",
duracion: "Duración (segundos)",
duracionEj: "Ej: 5",
nombre: "Nombre",
nombreEj: "Nombre de la formación",
notas: "Notas tácticas",
notasEj: "Instrucciones, marcajes, etc.",
datos: "Datos de Formación",
play: "▶ Reproducir",
pause: "⏸︎ Pausar",
actualizar: "Actualizar Formación",
sinFigura: "Sin figura",
direccion: "Añadir Dirección",
eliminarDir: "Eliminar Dirección",
edicionActiva: "🟡 En edición activa",
alertaFaltanDatos: "Faltan datos del atleta",
alertaMaxAtletas: "Modalidad \"{MOD}\" permite un máximo de {MAX} atletas.",
alertaClickPiscina: "Haz clic en la piscina para colocarlo",
alertaNombreFaltante: "Agrega nombre de formación y al menos un atleta",
alertaSeleccionaFormacion: "Selecciona una formación desde la línea de tiempo primero.",
confirmEliminar: {
titulo: "¿Eliminar formación?",
texto: "Esta acción no se puede deshacer",
confirmar: "Sí, eliminar",
cancelar: "Cancelar",
eliminado: "La formación ha sido eliminada.",
error: "No se pudo eliminar la formación."
}
},
nav: {
init: "Inicializar Rutina",
equip: "Equipos Disponibles"
},
logout: "Salir"
},
en: {
editor: {
titulo: "Formation Editor",
tipo: "Type",
modalidad: "Modality",
agregar: "Add Athlete",
seleccionarAtleta: "Select athlete",
rol: "Role",
rolSeleccionar: "Select role",
volador: "Flyer",
pilar: "Base",
otro: "Other",
idPers: "Custom ID",
idEj: "E.g.: A, 1, V2",
figura: "Technical figure",
figuraEj: "E.g.: Barracuda, Flamingo...",
tipoElemento: "Element type",
codigoElemento: "Element code",
codigoEj: "E.g.: TRE 1A, H4...",
hibrido: "Hybrid",
transicion: "Transition",
btnAgregar: "Add Athlete",
editar: "Edit formation",
eliminar: "Delete Formation",
guardar: "Save Formation",
linea: "Timeline",
piscina: "Pool",
tipoPiscina: "Pool type",
olimpica: "Olympic (50m x 25m)",
semiolimpica: "Semi-Olympic (25m x 12.5m)",
fosa: "Pit (20m x 20m)",
duracion: "Duration (seconds)",
duracionEj: "E.g.: 5",
nombre: "Name",
nombreEj: "Formation name",
notas: "Tactical notes",
notasEj: "Instructions, markings, etc.",
datos: "Formation Data",
play: "▶ Play",
pause: "⏸︎ Pause",
actualizar: "Update Formation",
sinFigura: "No figure",
direccion: "Add Direction",
eliminarDir: "Remove Direction",
edicionActiva: "🟡 Editing active",
alertaFaltanDatos: "Missing athlete data",
alertaMaxAtletas: "Modality \"{MOD}\" allows a maximum of {MAX} athletes.",
alertaClickPiscina: "Click on the pool to place the athlete",
alertaNombreFaltante: "Add formation name and at least one athlete",
alertaSeleccionaFormacion: "Select a formation from the timeline first.",
confirmEliminar: {
titulo: "Delete formation?",
texto: "This action cannot be undone",
confirmar: "Yes, delete",
cancelar: "Cancel",
eliminado: "Formation has been deleted.",
error: "Failed to delete formation."
}
},
nav: {
init: "Initialize Routine",
equip: "Available Teams"
},
logout: "Logout"
},
fr: {
editor: {
titulo: "Éditeur de Formation",
tipo: "Type",
modalidad: "Modalité",
agregar: "Ajouter un Athlète",
seleccionarAtleta: "Sélectionner un athlète",
rol: "Rôle",
rolSeleccionar: "Sélectionner un rôle",
volador: "Voltigeur",
pilar: "Pilier",
otro: "Autre",
idPers: "ID personnalisé",
idEj: "Ex. : A, 1, V2",
figura: "Figure technique",
figuraEj: "Ex. : Barracuda, Flamant...",
tipoElemento: "Type d'élément",
codigoElemento: "Code de l'élément",
codigoEj: "Ex. : TRE 1A, H4...",
hibrido: "Hybride",
transicion: "Transition",
btnAgregar: "Ajouter un Athlète",
editar: "Modifier la formation",
eliminar: "Supprimer la Formation",
guardar: "Enregistrer la Formation",
linea: "Chronologie",
piscina: "Piscine",
tipoPiscina: "Type de piscine",
olimpica: "Olympique (50m x 25m)",
semiolimpica: "Semi-olympique (25m x 12,5m)",
fosa: "Fosse (20m x 20m)",
duracion: "Durée (secondes)",
duracionEj: "Ex. : 5",
nombre: "Nom",
nombreEj: "Nom de la formation",
notas: "Notes tactiques",
notasEj: "Instructions, repères, etc.",
datos: "Données de la Formation",
play: "▶ Lire",
pause: "⏸︎ Pause",
actualizar: "Mettre à jour la Formation",
sinFigura: "Pas de figure",
direccion: "Ajouter une Direction",
eliminarDir: "Supprimer la Direction",
edicionActiva: "🟡 Édition active",
alertaFaltanDatos: "Données de l'athlète manquantes",
alertaMaxAtletas: "La modalité \"{MOD}\" permet un maximum de {MAX} athlètes.",
alertaClickPiscina: "Cliquez sur la piscine pour placer l'athlète",
alertaNombreFaltante: "Ajoutez un nom de formation et au moins un athlète",
alertaSeleccionaFormacion: "Sélectionnez d'abord une formation depuis la chronologie.",
confirmEliminar: {
titulo: "Supprimer la formation ?",
texto: "Cette action est irréversible",
confirmer: "Oui, supprimer",
cancelar: "Annuler",
eliminado: "La formation a été supprimée.",
error: "Échec de la suppression de la formation."
}
},
nav: {
init: "Démarrer la routine",
equip: "Équipes disponibles"
},
logout: "Se déconnecter"
}
};
window.figurasTraducidas = {
"Pez Volador con Giro": {
en: "Flying Fish with Twist",
fr: "Poisson Volant avec Torsion"
},
"Pez Volador": {
en: "Flying Fish",
fr: "Poisson Volant"
},
"Rodilla Flexionada con Giro Completo": {
en: "Bent Knee with Full Twist",
fr: "Genou Fléchi avec Rotation Complète"
},
"Rodilla Flexionada con Medio Giro": {
en: "Bent Knee with Half Twist",
fr: "Genou Fléchi avec Demi-Tour"
},
"Fouetté con Giro 720°": {
en: "Fouetté with 720° Turn",
fr: "Fouetté avec Rotation de 720°"
},
"Fouetté con Giro 360°": {
en: "Fouetté with 360° Turn",
fr: "Fouetté avec Rotation de 360°"
},
"Mariposa": {
en: "Butterfly",
fr: "Papillon"
},
"Split con Giro 180°": {
en: "Split with 180° Turn",
fr: "Grand Écart avec Rotation de 180°"
},
"Split a Rodilla Flexionada": {
en: "Split to Bent Knee",
fr: "Grand Écart vers Genou Fléchi"
}
};
window.descripcionesTraducidas = {
"Impulso a vertical desde pike, transición a cola de pez aérea, regreso a vertical y giro de 180°.": {
en: "Thrust to vertical from pike, transition to aerial fish tail, return to vertical and 180° twist.",
fr: "Poussée en vertical depuis pike, transition vers queue de poisson aérienne, retour en vertical et torsion de 180°."
},
"Impulso a vertical desde pike, transición a cola de pez aérea, regreso a vertical y descenso.": {
en: "Thrust to vertical from pike, transition to aerial fish tail, return to vertical and descent.",
fr: "Poussée en vertical depuis pike, transition vers queue de poisson aérienne, retour en vertical et descente."
},
"Vertical con giro completo a rodilla flexionada, otro giro a vertical, apertura 180° a split y walkout.": {
en: "Vertical with full twist to bent knee, another twist to vertical, 180° split and walkout.",
fr: "Vertical avec rotation complète vers genou fléchi, nouvelle rotation vers vertical, ouverture à 180° en écart et sortie."
},
"Vertical con medio giro a rodilla flexionada, otro medio giro a vertical, split y walkout.": {
en: "Vertical with half twist to bent knee, another half twist to vertical, split and walkout.",
fr: "Vertical avec demi-tour vers genou fléchi, autre demi-tour vers vertical, écart et sortie."
},
"Desde cola de pez, dos giros Fouetté, paso a vertical y giro continuo de 720°.": {
en: "From fish tail, two Fouetté turns, transition to vertical and continuous 720° turn.",
fr: "Depuis queue de poisson, deux rotations Fouetté, passage en vertical et rotation continue de 720°."
},
"Desde cola de pez, dos giros Fouetté, paso a vertical y giro rápido de 360°.": {
en: "From fish tail, two Fouetté turns, transition to vertical and quick 360° turn.",
fr: "Depuis queue de poisson, deux rotations Fouetté, passage en vertical et rotation rapide de 360°."
},
"Secuencia dinámica con pike, cola de pez, split, giros, arco superficial y salida en layout.": {
en: "Dynamic sequence with pike, fish tail, split, turns, surface arch and layout exit.",
fr: "Séquence dynamique avec pike, queue de poisson, écart, rotations, arc en surface et sortie en layout."
},
"Impulso a vertical, split aéreo, giro 180° a rodilla flexionada y descenso vertical.": {
en: "Thrust to vertical, aerial split, 180° turn to bent knee and vertical descent.",
fr: "Poussée en vertical, grand écart aérien, rotation de 180° vers genou fléchi et descente verticale."
},
"Impulso a vertical, split aéreo, transición a rodilla flexionada y descenso vertical.": {
en: "Thrust to vertical, aerial split, transition to bent knee and vertical descent.",
fr: "Poussée en vertical, grand écart aérien, transition vers genou fléchi et descente verticale."
}
};
let lang = window.tLang || localStorage.getItem('lang') || 'es';
window.tLang = lang;
const t = (key, replacements = {}) => {
const keys = key.split('.');
let value = keys.reduce((obj, k) => obj?.[k], traducciones[lang]) ?? key;
for (const [k, v] of Object.entries(replacements)) {
value = value.replace(`{${k}}`, v);
}
return value;
};
function aplicarTraducciones() {
// Traduce elementos con data-i18n
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.dataset.i18n;
el.textContent = t(key);
});
// Traduce placeholders
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.dataset.i18nPlaceholder;
el.placeholder = t(key);
});
// Traducción de <option> del select tipoElemento
const tipoElementoSelect = document.getElementById('tipoElemento');
if (tipoElementoSelect) {
tipoElementoSelect.querySelectorAll('option').forEach(option => {
const val = option.value.toLowerCase();
const text = option.textContent.toLowerCase();
if (val === 'tre') {
option.textContent = 'TRE'; // no traducir
} else if (val === 'híbrido' || text.includes('hybrid') || val === 'hybride') {
option.textContent = t('editor.hibrido') || 'Híbrido';
} else if (val === 'transición' || text.includes('transition') || val === 'transition') {
option.textContent = t('editor.transicion') || 'Transición';
}
});
}
}
document.addEventListener('DOMContentLoaded', () => {
aplicarTraducciones();
actualizarTextosDinamicos();
});
document.getElementById('langSelector').addEventListener('change', (e) => {
const selectedLang = e.target.value;
lang = selectedLang;
window.tLang = selectedLang; // ✅ sincroniza con global
localStorage.setItem('lang', selectedLang); // opcional, si quieres persistencia local
aplicarTraducciones();
actualizarTextosDinamicos();
});
function actualizarTextosDinamicos() {
document.getElementById("btnGuardarFormacion").textContent = `💾 ${t('editor.guardar')}`;
document.getElementById("btnEditarFormacion").innerHTML = `${t('editor.editar')}`;
document.getElementById("btnEliminarFormacion").innerHTML = `${t('editor.eliminar')}`;
if (btnEditarFormacion.classList.contains('btn-warning')) {
btnEditarFormacion.innerHTML = t('editor.edicionActiva'); // refresca si está en modo edición
}
}
// Expone variables globales para otros scripts
window.traducciones = traducciones;
window.figurasTraducidas = figurasTraducidas;
window.descripcionesTraducidas = descripcionesTraducidas;
window.tLang = lang;
window.aplicarTraducciones = aplicarTraducciones;
window.actualizarTextosDinamicos = actualizarTextosDinamicos;