356 lines
13 KiB
JavaScript
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;
|
|
|
|
|