Traduccion parte de la piscina
This commit is contained in:
parent
dc8bc854f9
commit
56cba7f7ab
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
@ -128,7 +128,6 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
if (user?.name) {
|
if (user?.name) {
|
||||||
document.getElementById("nombreUsuarioHeader").textContent = user.name;
|
document.getElementById("nombreUsuarioHeader").textContent = user.name;
|
||||||
document.getElementById("nombreUsuarioDropdown").textContent = "Coach " + user.name;
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("❌ Error al obtener datos del usuario:", err);
|
console.error("❌ Error al obtener datos del usuario:", err);
|
||||||
|
|
|
@ -1,4 +1,52 @@
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const userId = sessionStorage.getItem("userId");
|
||||||
|
if (!userId) {
|
||||||
|
alert("Sesión expirada, inicia sesión de nuevo.");
|
||||||
|
window.location.href = "index.html";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/users/${userId}`);
|
||||||
|
const user = await res.json();
|
||||||
|
|
||||||
|
if (user?.name) {
|
||||||
|
document.getElementById("nombreUsuarioHeader").textContent = user.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lang = 'es'; // valor por defecto
|
||||||
|
|
||||||
|
// 1. Determinar idioma
|
||||||
|
if (sessionStorage.getItem('langJustChanged') === 'true') {
|
||||||
|
lang = sessionStorage.getItem('lang') || user.language || 'es';
|
||||||
|
} else if (sessionStorage.getItem('lang')) {
|
||||||
|
lang = sessionStorage.getItem('lang');
|
||||||
|
} else if (user.language) {
|
||||||
|
lang = user.language;
|
||||||
|
sessionStorage.setItem('lang', lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.tLang = lang;
|
||||||
|
localStorage.setItem('lang', lang); // opcional si usas localStorage
|
||||||
|
|
||||||
|
// 🌀 Recarga solo una vez con delay para evitar parpadeo inmediato
|
||||||
|
if (!sessionStorage.getItem('langLoadedOnce')) {
|
||||||
|
sessionStorage.setItem('langLoadedOnce', 'true');
|
||||||
|
setTimeout(() => location.reload(), 100); //
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Aplicar traducciones después de confirmar idioma
|
||||||
|
const selector = document.getElementById("langSelector");
|
||||||
|
if (selector) selector.value = lang;
|
||||||
|
|
||||||
|
aplicarTraducciones();
|
||||||
|
actualizarTextosDinamicos();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Error al obtener datos del usuario:", err);
|
||||||
|
}
|
||||||
|
|
||||||
const rutinaId = new URLSearchParams(window.location.search).get('routineId');
|
const rutinaId = new URLSearchParams(window.location.search).get('routineId');
|
||||||
if (!rutinaId) return alert('No se proporcionó ID de rutina.');
|
if (!rutinaId) return alert('No se proporcionó ID de rutina.');
|
||||||
|
|
||||||
|
@ -38,68 +86,94 @@ atletas.forEach(a => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let figuraActual = null; // 🔁 Guardar la figura seleccionada
|
||||||
|
|
||||||
select.addEventListener('change', () => {
|
select.addEventListener('change', () => {
|
||||||
const datos = JSON.parse(select.value || '{}');
|
const datos = JSON.parse(select.value || '{}');
|
||||||
select.dataset.id = datos.id; // << GUARDAS el ObjectId real
|
select.dataset.id = datos.id;
|
||||||
|
|
||||||
|
const idInput = document.getElementById('idPersonalizado');
|
||||||
if (datos.idPers) {
|
if (datos.idPers) {
|
||||||
document.getElementById('idPersonalizado').value = datos.idPers;
|
idInput.value = datos.idPers;
|
||||||
document.getElementById('idPersonalizado').disabled = true;
|
idInput.disabled = true;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('idPersonalizado').value = '';
|
idInput.value = '';
|
||||||
document.getElementById('idPersonalizado').disabled = false;
|
idInput.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let figurasFINA = [];
|
let figurasFINA = [];
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/catalog/figurasFINA.json');
|
const res = await fetch('/catalog/figurasFINA.json');
|
||||||
figurasFINA = await res.json();
|
figurasFINA = await res.json();
|
||||||
|
|
||||||
const sugerenciasBox = document.getElementById('sugerenciasFigura');
|
const sugerenciasBox = document.getElementById('sugerenciasFigura');
|
||||||
|
|
||||||
inputFigura.addEventListener('input', () => {
|
inputFigura.addEventListener('input', () => {
|
||||||
const val = inputFigura.value.trim().toLowerCase();
|
const val = inputFigura.value.trim().toLowerCase();
|
||||||
sugerenciasBox.innerHTML = '';
|
sugerenciasBox.innerHTML = '';
|
||||||
if (val.length < 1) return;
|
if (val.length < 1) return;
|
||||||
|
|
||||||
const matches = figurasFINA.filter(f => f.nombre.toLowerCase().includes(val));
|
const matches = figurasFINA.filter(f => f.nombre.toLowerCase().includes(val));
|
||||||
matches.forEach(figura => {
|
matches.forEach(figura => {
|
||||||
const item = document.createElement('button');
|
const item = document.createElement('button');
|
||||||
item.type = 'button';
|
item.type = 'button';
|
||||||
item.textContent = figura.nombre;
|
item.className = 'list-group-item list-group-item-action';
|
||||||
item.onclick = () => {
|
item.textContent = figura.nombre;
|
||||||
|
item.onclick = () => {
|
||||||
inputFigura.value = figura.nombre;
|
inputFigura.value = figura.nombre;
|
||||||
inputFigura.title = `${figura.descripcion} (${figura.categoria})`;
|
inputFigura.title = `${figura.descripcion} (${figura.categoria})`;
|
||||||
sugerenciasBox.innerHTML = '';
|
sugerenciasBox.innerHTML = '';
|
||||||
|
figuraActual = figura; // guardamos figura activa
|
||||||
mostrarPreview(figura);
|
mostrarPreview(figura);
|
||||||
|
|
||||||
const codigoInput = document.getElementById("codigoElemento");
|
const codigoInput = document.getElementById("codigoElemento");
|
||||||
if (codigoInput && figura.codigoElemento) {
|
if (codigoInput && figura.codigoElemento) {
|
||||||
codigoInput.value = figura.codigoElemento;
|
codigoInput.value = figura.codigoElemento;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sugerenciasBox.appendChild(item);
|
sugerenciasBox.appendChild(item);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
inputFigura.addEventListener('blur', () => {
|
inputFigura.addEventListener('blur', () => {
|
||||||
setTimeout(() => sugerenciasBox.innerHTML = '', 200);
|
setTimeout(() => sugerenciasBox.innerHTML = '', 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
function mostrarPreview(figura) {
|
function mostrarPreview(figura) {
|
||||||
document.getElementById('previewFigura').innerHTML = `
|
const nombreTrad = window.figurasTraducidas?.[figura.nombre]?.[window.tLang] || figura.nombre;
|
||||||
<img src="catalog/figuras/${figura.imagen}" alt="${figura.nombre}" class="img-fluid rounded border" style="max-height:150px;" />
|
const descripcionTrad = window.descripcionesTraducidas?.[figura.descripcion]?.[window.tLang] || figura.descripcion;
|
||||||
<p class="mt-2">${figura.descripcion} <span class="badge bg-info">${figura.categoria}</span></p>
|
|
||||||
`;
|
// Traducción visual adicional debajo del input
|
||||||
const codigoInput = document.getElementById("codigoElemento");
|
const nombreTraducidoVisual = document.getElementById("nombreFiguraTraducido");
|
||||||
if (codigoInput && figura.codigoElemento) {
|
if (nombreTraducidoVisual) {
|
||||||
codigoInput.value = figura.codigoElemento;
|
nombreTraducidoVisual.textContent = nombreTrad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById('previewFigura').innerHTML = `
|
||||||
|
<img src="catalog/figuras/${figura.imagen}" alt="${nombreTrad}" class="img-fluid rounded border" style="max-height:150px;" />
|
||||||
|
<p class="mt-2"><strong>${nombreTrad}</strong><br>${descripcionTrad} <span class="badge bg-info">${figura.categoria}</span></p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const codigoInput = document.getElementById("codigoElemento");
|
||||||
|
if (codigoInput && figura.codigoElemento) {
|
||||||
|
codigoInput.value = figura.codigoElemento;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.warn('❌ No se pudo cargar el catálogo FINA:', err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Esto es clave: vuelve a mostrar la figura al cambiar idioma
|
||||||
|
document.getElementById('langSelector').addEventListener('change', () => {
|
||||||
|
window.tLang = sessionStorage.getItem('lang') || user.language || 'es';
|
||||||
|
|
||||||
|
if (figuraActual) {
|
||||||
|
mostrarPreview(figuraActual); // 🔁 refresca con idioma nuevo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('❌ No se pudo cargar el catálogo FINA:', err);
|
||||||
|
}
|
||||||
|
|
||||||
const tipoPiscinaSelect = document.getElementById('tipoPiscina');
|
const tipoPiscinaSelect = document.getElementById('tipoPiscina');
|
||||||
|
|
||||||
const medidasPiscina = {
|
const medidasPiscina = {
|
||||||
|
@ -652,9 +726,9 @@ formaciones.forEach((f, i) => {
|
||||||
inputFigura.title = '';
|
inputFigura.title = '';
|
||||||
|
|
||||||
btnEditarFormacion.className = 'btn btn-warning btn-sm d-inline-flex align-items-center gap-2';
|
btnEditarFormacion.className = 'btn btn-warning btn-sm d-inline-flex align-items-center gap-2';
|
||||||
btnEditarFormacion.innerHTML = '🟡 En edición activa';
|
btnEditarFormacion.innerHTML = t('editor.edicionActiva');
|
||||||
|
|
||||||
btnGuardarFormacion.textContent = 'Actualizar Formación';
|
btnGuardarFormacion.textContent = t('editor.actualizar');
|
||||||
btnGuardarFormacion.classList.remove('btn-primary');
|
btnGuardarFormacion.classList.remove('btn-primary');
|
||||||
btnGuardarFormacion.classList.add('btn-warning');
|
btnGuardarFormacion.classList.add('btn-warning');
|
||||||
} else {
|
} else {
|
||||||
|
@ -764,3 +838,31 @@ wave.on('audioprocess', (currentTime) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('langSelector').addEventListener('change', async (e) => {
|
||||||
|
const selectedLang = e.target.value;
|
||||||
|
sessionStorage.setItem('lang', selectedLang);
|
||||||
|
window.tLang = selectedLang;
|
||||||
|
|
||||||
|
const userId = sessionStorage.getItem("userId");
|
||||||
|
if (userId) {
|
||||||
|
await fetch(`/api/users/${userId}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ language: selectedLang })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🟡 Recargar solo 1 vez
|
||||||
|
sessionStorage.setItem('langJustChanged', 'true');
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
sessionStorage.clear();
|
||||||
|
localStorage.removeItem('lang'); // ✅ Limpia persistencia de idioma
|
||||||
|
alert("Sesión cerrada");
|
||||||
|
window.location.href = "../index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
window.logout = logout;
|
|
@ -0,0 +1,355 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
|
@ -66,47 +66,48 @@
|
||||||
|
|
||||||
<main class="container-xl px-4 my-4">
|
<main class="container-xl px-4 my-4">
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<h2 id="tituloRutina" class="fw-bold fs-2 mb-2">Editor de Formación</h2>
|
<h2 id="tituloRutina" class="fw-bold fs-2 mb-2" data-i18n="editor.titulo">Editor de Formación</h2>
|
||||||
<div class="d-flex flex-wrap gap-3 small text-muted">
|
<div class="d-flex flex-wrap gap-3 small text-muted">
|
||||||
<div><strong>Tipo:</strong> <span id="tipoRutina">Cargando...</span></div>
|
<div><strong data-i18n="editor.tipo">Tipo:</strong> <span id="tipoRutina">Cargando...</span></div>
|
||||||
<div><strong>Modalidad:</strong> <span id="modalidadRutina">Cargando...</span></div>
|
<div><strong data-i18n="editor.modalidad">Modalidad:</strong> <span id="modalidadRutina">Cargando...</span></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="row gx-4 gy-4">
|
<div class="row gx-4 gy-4">
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="card p-4 h-100 d-flex flex-column">
|
<div class="card p-4 h-100 d-flex flex-column">
|
||||||
<h5 class="fw-semibold mb-3 text-primary">Agregar Atleta</h5>
|
<h5 class="fw-semibold mb-3 text-primary" data-i18n="editor.agregar">Agregar Atleta</h5>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="selectAtleta" class="form-label">Seleccionar atleta</label>
|
<label for="selectAtleta" class="form-label" data-i18n="editor.seleccionarAtleta">Seleccionar atleta</label>
|
||||||
<select id="selectAtleta" class="form-select"></select>
|
<select id="selectAtleta" class="form-select"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="rolAtleta" class="form-label">Rol</label>
|
<label for="rolAtleta" class="form-label" data-i18n="editor.rol">Rol</label>
|
||||||
<select id="rolAtleta" class="form-select">
|
<select id="rolAtleta" class="form-select">
|
||||||
<option value="">Seleccionar rol</option>
|
<option value="" data-i18n="editor.rolSeleccionar">Seleccionar rol</option>
|
||||||
<option value="volador">Volador</option>
|
<option value="volador" data-i18n="editor.volador">Volador</option>
|
||||||
<option value="pilar">Pilar</option>
|
<option value="pilar" data-i18n="editor.pilar">Pilar</option>
|
||||||
<option value="otro">Otro</option>
|
<option value="otro" data-i18n="editor.otro">Otro</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="idPersonalizado" class="form-label">ID personalizado</label>
|
<label for="idPersonalizado" class="form-label" data-i18n="editor.idPers">ID personalizado</label>
|
||||||
<input type="text" id="idPersonalizado" class="form-control" placeholder="Ej: A, 1, V2" readonly>
|
<input type="text" id="idPersonalizado" class="form-control" placeholder="Ej: A, 1, V2" data-i18n-placeholder="editor.idEj" readonly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="figura" class="form-label">Figura técnica</label>
|
<label for="figura" class="form-label" data-i18n="editor.figura">Figura técnica</label>
|
||||||
<input type="text" id="figura" class="form-control" placeholder="Ej: Barracuda, Flamingo..." autocomplete="off">
|
<input type="text" id="figura" class="form-control" placeholder="Ej: Barracuda, Flamingo..." data-i18n-placeholder="editor.figuraEj" autocomplete="off">
|
||||||
|
<small id="nombreFiguraTraducido" class="form-text text-muted"></small>
|
||||||
<div id="sugerenciasFigura" class="list-group position-relative"></div>
|
<div id="sugerenciasFigura" class="list-group position-relative"></div>
|
||||||
<div id="previewFigura" class="text-center mt-3 mb-3"></div>
|
<div id="previewFigura" class="text-center mt-3 mb-3"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="tipoElemento" class="form-label">Tipo de elemento</label>
|
<label for="tipoElemento" class="form-label" data-i18n="editor.tipoElemento">Tipo de elemento</label>
|
||||||
<select id="tipoElemento" class="form-select">
|
<select id="tipoElemento" class="form-select">
|
||||||
<option value="TRE">TRE</option>
|
<option value="TRE">TRE</option>
|
||||||
<option value="híbrido">Híbrido</option>
|
<option value="híbrido">Híbrido</option>
|
||||||
|
@ -115,23 +116,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="codigoElemento" class="form-label">Código del elemento</label>
|
<label for="codigoElemento" class="form-label" data-i18n="editor.codigoElemento">Código del elemento</label>
|
||||||
<input type="text" id="codigoElemento" class="form-control" placeholder="Ej: TRE 1A, H4...">
|
<input type="text" id="codigoElemento" class="form-control" placeholder="Ej: TRE 1A, H4..." data-i18n-placeholder="editor.codigoEj">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="btnAgregarAtleta" class="btn btn-primary mt-auto">Añadir Atleta</button>
|
<button id="btnAgregarAtleta" class="btn btn-primary mt-auto" data-i18n="editor.btnAgregar">Añadir Atleta</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-8 d-flex flex-column gap-3">
|
<div class="col-lg-8 d-flex flex-column gap-3">
|
||||||
<div class="card p-4">
|
<div class="card p-4">
|
||||||
<h5 class="fw-semibold mb-3 text-primary">Piscina</h5>
|
<h5 class="fw-semibold mb-3 text-primary" data-i18n="editor.piscina">Piscina</h5>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="tipoPiscina" class="form-label">Tipo de piscina</label>
|
<label for="tipoPiscina" class="form-label" data-i18n="editor.tipoPiscina">Tipo de piscina</label>
|
||||||
<select id="tipoPiscina" class="form-select form-select-sm w-100" style="max-width: 280px;">
|
<select id="tipoPiscina" class="form-select form-select-sm w-100" style="max-width: 280px;">
|
||||||
<option value="olimpica">Olímpica (50m x 25m)</option>
|
<option value="olimpica" data-i18n="editor.olimpica">Olímpica (50m x 25m)</option>
|
||||||
<option value="semiolimpica">Semiolímpica (25m x 12.5m)</option>
|
<option value="semiolimpica" data-i18n="editor.semiolimpica">Semiolímpica (25m x 12.5m)</option>
|
||||||
<option value="fosa">Fosa/Poza (20m x 20m)</option>
|
<option value="fosa" data-i18n="editor.fosa">Fosa/Poza (20m x 20m)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -144,14 +145,14 @@
|
||||||
<button id="btnEliminarFormacion" class="btn btn-outline-danger btn-sm">🗑 Eliminar Formación</button>
|
<button id="btnEliminarFormacion" class="btn btn-outline-danger btn-sm">🗑 Eliminar Formación</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="form-label fw-semibold">Línea de Tiempo</label>
|
<label class="form-label fw-semibold" data-i18n="editor.linea">Línea de Tiempo</label>
|
||||||
<div id="lineaTiempo" class="timeline-placeholder d-flex flex-wrap gap-2 mb-3"></div>
|
<div id="lineaTiempo" class="timeline-placeholder d-flex flex-wrap gap-2 mb-3"></div>
|
||||||
|
|
||||||
<!-- Reproductor abajo -->
|
<!-- Reproductor abajo -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div id="waveform" style="height: 80px;"></div>
|
<div id="waveform" style="height: 80px;"></div>
|
||||||
<div class="text-end mt-2">
|
<div class="text-end mt-2">
|
||||||
<button id="playPauseBtn" class="btn btn-outline-primary btn-sm">▶ Reproducir</button>
|
<button id="playPauseBtn" class="btn btn-outline-primary btn-sm" data-i18n="editor.play">▶ Reproducir</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -159,26 +160,26 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="card p-4 mt-4">
|
<div class="card p-4 mt-4">
|
||||||
<h5 class="fw-semibold mb-3 text-primary">Datos de Formación</h5>
|
<h5 class="fw-semibold mb-3 text-primary" data-i18n="editor.datos">Datos de Formación</h5>
|
||||||
<div class="row gy-3">
|
<div class="row gy-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="nombreFormacion" class="form-label">Nombre</label>
|
<label for="nombreFormacion" class="form-label" data-i18n="editor.nombre">Nombre</label>
|
||||||
<input type="text" id="nombreFormacion" class="form-control" placeholder="Nombre de la formación">
|
<input type="text" id="nombreFormacion" class="form-control" placeholder="Nombre de la formación" data-i18n-placeholder="editor.nombreEj">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="duracionFormacion" class="form-label">Duración (segundos)</label>
|
<label for="duracionFormacion" class="form-label" data-i18n="editor.duracion">Duración (segundos)</label>
|
||||||
<input type="number" id="duracionFormacion" class="form-control" min="1" placeholder="Ej: 5">
|
<input type="number" id="duracionFormacion" class="form-control" min="1" placeholder="Ej: 5" data-i18n-placeholder="editor.duracionEj">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="notasFormacion" class="form-label">Notas tácticas</label>
|
<label for="notasFormacion" class="form-label" data-i18n="editor.notas">Notas tácticas</label>
|
||||||
<textarea id="notasFormacion" class="form-control" rows="1" placeholder="Instrucciones, marcajes, etc."></textarea>
|
<textarea id="notasFormacion" class="form-control" rows="1" placeholder="Instrucciones, marcajes, etc." data-i18n-placeholder="editor.notasEj"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-end mt-4">
|
<div class="text-end mt-4">
|
||||||
<button id="btnGuardarFormacion" class="btn btn-primary">Guardar Formación</button>
|
<button id="btnGuardarFormacion" class="btn btn-primary" data-i18n="editor.guardar">Guardar Formación</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -186,6 +187,8 @@
|
||||||
<audio id="audioPlayer" class="d-none" controls></audio>
|
<audio id="audioPlayer" class="d-none" controls></audio>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||||||
<script src="js/piscina.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="js/traduccionPiscina.js"></script>
|
||||||
|
<script type="module" src="js/piscina.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue