+
+
diff --git a/js/dnd.js b/js/dnd.js
index e40e7b2..67b1271 100644
--- a/js/dnd.js
+++ b/js/dnd.js
@@ -4,6 +4,39 @@ document.addEventListener('DOMContentLoaded', () => {
const panelTop = document.getElementById('panel-top'); // Todo el panel "Secciones" (incluye los botones "+ tipo"), usado como zona de detección al reordenar.
const sectionsContainer = document.getElementById('panel-top-sections'); // Contiene las secciones que el usuario va añadiendo.
const draggableItemsSource = document.getElementById('draggable-items-source'); // Lista oculta con los elementos disponibles para arrastrar: fuente de datos única, se clona (visible) en cada sección de tipo "arrastrar y soltar".
+ const languageSelect = document.getElementById('language-select'); // Idioma del contenido (no de la interfaz): controla qué versión de los textos de título/subtítulo/texto se muestra y edita.
+
+ // Idioma actualmente seleccionado para el contenido. La estructura de
+ // secciones (cuáles hay, su orden, su tipo) es la misma para todos los
+ // idiomas; lo único que cambia es qué texto se muestra en cada campo de
+ // título/subtítulo/texto libre.
+ let currentLanguage = languageSelect.value;
+
+ // ---------------------------------------------------------------------
+ // Multi-idioma del contenido: título, subtítulo y texto libre guardan un
+ // texto por idioma en vez de uno solo, para poder editarlos y asociarlos
+ // al idioma seleccionado sin perder lo ya escrito en los demás.
+ // ---------------------------------------------------------------------
+
+ // Cada input/textarea traducible guarda su propio mapa { idioma: texto }
+ // serializado en el propio elemento (data-translations), así viaja solo
+ // con guardar/cargar la sección (ver serializeSection/addTitleSection...),
+ // sin necesidad de un almacén aparte que haya que mantener sincronizado.
+ function getTranslations(el) {
+ try {
+ return JSON.parse(el.dataset.translations || '{}');
+ } catch {
+ return {};
+ }
+ }
+
+ // Actualiza, dentro del mapa ya guardado en el elemento, el texto del
+ // idioma indicado (por defecto el actual), sin tocar el resto de idiomas.
+ function setTranslation(el, text, lang = currentLanguage) {
+ const translations = getTranslations(el);
+ translations[lang] = text;
+ el.dataset.translations = JSON.stringify(translations);
+ }
// ---------------------------------------------------------------------
// Reordenar secciones (arrastrando toda la cabecera de cada una)
@@ -407,10 +440,31 @@ document.addEventListener('DOMContentLoaded', () => {
// añadir/quitar/reordenar secciones o elementos soltados (MutationObserver).
// Al estar en #panel-top-sections y usar subtree:true, esto ya cubre
// también los cambios dentro de las secciones anidadas en un "Grupo".
- sectionsContainer.addEventListener('input', renderOutput);
+ // Si el campo es traducible (título/subtítulo/texto), cada pulsación de
+ // tecla también se guarda en su mapa de traducciones para el idioma
+ // actual, así no hace falta un paso aparte de "guardar" al cambiar de
+ // idioma o al serializar la sección.
+ sectionsContainer.addEventListener('input', (e) => {
+ if (e.target.matches('.title-input, .subtitle-input, textarea')) {
+ setTranslation(e.target, e.target.value);
+ }
+ renderOutput();
+ });
sectionsContainer.addEventListener('change', renderOutput);
new MutationObserver(renderOutput).observe(sectionsContainer, { childList: true, subtree: true });
+ // Al cambiar el idioma del contenido, se sustituye en cada campo
+ // traducible (a cualquier nivel de anidamiento) el texto mostrado por el
+ // que tenga guardado para el nuevo idioma (vacío si aún no se ha escrito
+ // nada en ese idioma), y se repinta la vista previa.
+ languageSelect.addEventListener('change', () => {
+ currentLanguage = languageSelect.value;
+ sectionsContainer.querySelectorAll('.title-input, .subtitle-input, textarea').forEach(el => {
+ el.value = getTranslations(el)[currentLanguage] || '';
+ });
+ renderOutput();
+ });
+
// ---------------------------------------------------------------------
// Creación de secciones (título, texto, imagen, drag & drop, grupo)
// ---------------------------------------------------------------------
@@ -544,17 +598,20 @@ document.addEventListener('DOMContentLoaded', () => {
// Sección de título: un input de una línea que se muestra como
en el
// resultado. `data` es el dataset del botón "+ Título" (data-type-label,
- // data-placeholder). `text`, si se pasa, precarga el valor (se usa al
- // restaurar un patrón guardado). `container` es dónde se añade la
- // sección: #panel-top-sections por defecto, o el de un grupo.
- function addTitleSection(data, text = '', container = sectionsContainer) {
+ // data-placeholder). `translations`, si se pasa, es el mapa { idioma:
+ // texto } completo (se usa al restaurar un patrón guardado o al
+ // duplicar una sección); se muestra el que corresponda al idioma actual.
+ // `container` es dónde se añade la sección: #panel-top-sections por
+ // defecto, o el de un grupo.
+ function addTitleSection(data, translations = {}, container = sectionsContainer) {
const { section, body } = createSectionShell(data.typeLabel);
const input = document.createElement('input');
input.type = 'text';
input.className = 'title-input input input-bordered w-full';
input.placeholder = data.placeholder;
- input.value = text;
+ input.dataset.translations = JSON.stringify(translations);
+ input.value = translations[currentLanguage] || '';
body.appendChild(input);
container.appendChild(section);
@@ -562,27 +619,29 @@ document.addEventListener('DOMContentLoaded', () => {
// Sección de subtítulo: igual que la de título pero se muestra como
// (más pequeño) en el resultado, para marcar un encabezado secundario.
- function addSubtitleSection(data, text = '', container = sectionsContainer) {
+ function addSubtitleSection(data, translations = {}, container = sectionsContainer) {
const { section, body } = createSectionShell(data.typeLabel);
const input = document.createElement('input');
input.type = 'text';
input.className = 'subtitle-input input input-bordered w-full';
input.placeholder = data.placeholder;
- input.value = text;
+ input.dataset.translations = JSON.stringify(translations);
+ input.value = translations[currentLanguage] || '';
body.appendChild(input);
container.appendChild(section);
}
// Sección de texto libre.
- function addTextSection(data, text = '', container = sectionsContainer) {
+ function addTextSection(data, translations = {}, container = sectionsContainer) {
const { section, body } = createSectionShell(data.typeLabel);
const textarea = document.createElement('textarea');
textarea.className = 'textarea textarea-bordered w-full';
textarea.placeholder = data.placeholder;
- textarea.value = text;
+ textarea.dataset.translations = JSON.stringify(translations);
+ textarea.value = translations[currentLanguage] || '';
body.appendChild(textarea);
container.appendChild(section);
@@ -809,14 +868,17 @@ document.addEventListener('DOMContentLoaded', () => {
return { type: 'group', children: Array.from(groupContainer.children).map(serializeSection).filter(Boolean) };
}
+ // Título/subtítulo/texto libre: se serializa el mapa de traducciones
+ // completo (todos los idiomas ya escritos), no solo el texto que se ve
+ // ahora mismo, para no perder lo escrito en otros idiomas al guardar.
const titleInput = section.querySelector('.title-input');
- if (titleInput) return { type: 'title', text: titleInput.value };
+ if (titleInput) return { type: 'title', text: getTranslations(titleInput) };
const subtitleInput = section.querySelector('.subtitle-input');
- if (subtitleInput) return { type: 'subtitle', text: subtitleInput.value };
+ if (subtitleInput) return { type: 'subtitle', text: getTranslations(subtitleInput) };
const textarea = section.querySelector('textarea');
- if (textarea) return { type: 'text', text: textarea.value };
+ if (textarea) return { type: 'text', text: getTranslations(textarea) };
const dndCanvas = section.querySelector('.dnd-canvas');
if (dndCanvas) {