feat: improved i18n and l10n
This commit is contained in:
@@ -0,0 +1,183 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: crochet\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"Language: en\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
# crochet/templates/crochet/pattern.html
|
||||||
|
|
||||||
|
msgid "No se ha podido guardar. Inténtalo de nuevo."
|
||||||
|
msgstr "Couldn't save. Please try again."
|
||||||
|
|
||||||
|
msgid "Guardar"
|
||||||
|
msgstr "Save"
|
||||||
|
|
||||||
|
msgid "Añadir sección"
|
||||||
|
msgstr "Add section"
|
||||||
|
|
||||||
|
msgid "¿Seguro que quieres cargar el patrón guardado? Se perderán los cambios que no hayas guardado."
|
||||||
|
msgstr "Are you sure you want to load the saved pattern? Any unsaved changes will be lost."
|
||||||
|
|
||||||
|
msgid "Cargar"
|
||||||
|
msgstr "Load"
|
||||||
|
|
||||||
|
msgid "Exportar a PDF"
|
||||||
|
msgstr "Export to PDF"
|
||||||
|
|
||||||
|
msgid "Guardado"
|
||||||
|
msgstr "Saved"
|
||||||
|
|
||||||
|
msgid "Personalización de página"
|
||||||
|
msgstr "Page customization"
|
||||||
|
|
||||||
|
msgid "Título del patrón"
|
||||||
|
msgstr "Pattern title"
|
||||||
|
|
||||||
|
msgid "Título del patrón..."
|
||||||
|
msgstr "Pattern title..."
|
||||||
|
|
||||||
|
msgid "Autor"
|
||||||
|
msgstr "Author"
|
||||||
|
|
||||||
|
msgid "Autor..."
|
||||||
|
msgstr "Author..."
|
||||||
|
|
||||||
|
msgid "Color del texto"
|
||||||
|
msgstr "Text color"
|
||||||
|
|
||||||
|
msgid "Color de acento (títulos)"
|
||||||
|
msgstr "Accent color (headings)"
|
||||||
|
|
||||||
|
msgid "Color de fondo"
|
||||||
|
msgstr "Background color"
|
||||||
|
|
||||||
|
msgid "Tipografía"
|
||||||
|
msgstr "Font"
|
||||||
|
|
||||||
|
msgid "Monoespaciada"
|
||||||
|
msgstr "Monospace"
|
||||||
|
|
||||||
|
msgid "Tamaño de letra"
|
||||||
|
msgstr "Font size"
|
||||||
|
|
||||||
|
msgid "Pequeño"
|
||||||
|
msgstr "Small"
|
||||||
|
|
||||||
|
msgid "Normal"
|
||||||
|
msgstr "Normal"
|
||||||
|
|
||||||
|
msgid "Grande"
|
||||||
|
msgstr "Large"
|
||||||
|
|
||||||
|
msgid "Alineación del texto"
|
||||||
|
msgstr "Text alignment"
|
||||||
|
|
||||||
|
msgid "Izquierda"
|
||||||
|
msgstr "Left"
|
||||||
|
|
||||||
|
msgid "Centrado"
|
||||||
|
msgstr "Center"
|
||||||
|
|
||||||
|
msgid "Justificado"
|
||||||
|
msgstr "Justify"
|
||||||
|
|
||||||
|
msgid "Tamaño de página"
|
||||||
|
msgstr "Page size"
|
||||||
|
|
||||||
|
msgid "Carta (Letter)"
|
||||||
|
msgstr "Letter"
|
||||||
|
|
||||||
|
msgid "Orientación"
|
||||||
|
msgstr "Orientation"
|
||||||
|
|
||||||
|
msgid "Vertical"
|
||||||
|
msgstr "Portrait"
|
||||||
|
|
||||||
|
msgid "Horizontal"
|
||||||
|
msgstr "Landscape"
|
||||||
|
|
||||||
|
msgid "Secciones"
|
||||||
|
msgstr "Sections"
|
||||||
|
|
||||||
|
msgid "Vista previa"
|
||||||
|
msgstr "Preview"
|
||||||
|
|
||||||
|
msgid "Colapsar todo"
|
||||||
|
msgstr "Collapse all"
|
||||||
|
|
||||||
|
msgid "Expandir todo"
|
||||||
|
msgstr "Expand all"
|
||||||
|
|
||||||
|
msgid "Título"
|
||||||
|
msgstr "Title"
|
||||||
|
|
||||||
|
msgid "Título..."
|
||||||
|
msgstr "Title..."
|
||||||
|
|
||||||
|
msgid "Subtítulo"
|
||||||
|
msgstr "Subtitle"
|
||||||
|
|
||||||
|
msgid "Subtítulo..."
|
||||||
|
msgstr "Subtitle..."
|
||||||
|
|
||||||
|
msgid "Texto"
|
||||||
|
msgstr "Text"
|
||||||
|
|
||||||
|
msgid "Escribe aquí..."
|
||||||
|
msgstr "Write here..."
|
||||||
|
|
||||||
|
msgid "Nota"
|
||||||
|
msgstr "Note"
|
||||||
|
|
||||||
|
msgid "Escribe una nota o consejo..."
|
||||||
|
msgstr "Write a note or tip..."
|
||||||
|
|
||||||
|
msgid "Materiales"
|
||||||
|
msgstr "Materials"
|
||||||
|
|
||||||
|
msgid "Añadir material..."
|
||||||
|
msgstr "Add material..."
|
||||||
|
|
||||||
|
msgid "Añadir línea"
|
||||||
|
msgstr "Add line"
|
||||||
|
|
||||||
|
msgid "Imagen"
|
||||||
|
msgstr "Image"
|
||||||
|
|
||||||
|
msgid "No se ha podido subir la imagen. Inténtalo de nuevo."
|
||||||
|
msgstr "Couldn't upload the image. Please try again."
|
||||||
|
|
||||||
|
msgid "Patrón"
|
||||||
|
msgstr "Pattern"
|
||||||
|
|
||||||
|
msgid "Elementos"
|
||||||
|
msgstr "Elements"
|
||||||
|
|
||||||
|
msgid "Grupo"
|
||||||
|
msgstr "Group"
|
||||||
|
|
||||||
|
msgid "Arrastrar para reordenar"
|
||||||
|
msgstr "Drag to reorder"
|
||||||
|
|
||||||
|
msgid "Colapsar / expandir"
|
||||||
|
msgstr "Collapse / expand"
|
||||||
|
|
||||||
|
msgid "Duplicar sección"
|
||||||
|
msgstr "Duplicate section"
|
||||||
|
|
||||||
|
msgid "Eliminar sección"
|
||||||
|
msgstr "Delete section"
|
||||||
|
|
||||||
|
msgid "¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las secciones que contiene."
|
||||||
|
msgstr "Are you sure you want to delete this group? All sections inside it will also be deleted."
|
||||||
|
|
||||||
|
msgid "Añade una sección para ver aquí el resultado."
|
||||||
|
msgstr "Add a section to see the result here."
|
||||||
|
|
||||||
|
# crochet/views.py (PatternDetailView.EMPTY_MESSAGE)
|
||||||
|
|
||||||
|
msgid "Este patrón todavía no tiene contenido."
|
||||||
|
msgstr "This pattern doesn't have any content yet."
|
||||||
@@ -5,9 +5,13 @@
|
|||||||
import { renderOutput } from './render.js';
|
import { renderOutput } from './render.js';
|
||||||
import { renderPageMeta, applyPageSettings } from './page-settings.js';
|
import { renderPageMeta, applyPageSettings } from './page-settings.js';
|
||||||
import { applyDefaultSkeleton, buildSectionsFrom } from './section-types.js';
|
import { applyDefaultSkeleton, buildSectionsFrom } from './section-types.js';
|
||||||
|
import { setAllSectionsCollapsed } from './sections.js';
|
||||||
import './tabs.js';
|
import './tabs.js';
|
||||||
import './storage.js';
|
import './storage.js';
|
||||||
|
|
||||||
|
// Mismo punto de corte que .panel-hidden-mobile (ver css/main.css).
|
||||||
|
const MOBILE_BREAKPOINT_QUERY = '(max-width: 767px)';
|
||||||
|
|
||||||
// El propio patrón (sections/pageSettings ya guardados) viaja embebido en la
|
// El propio patrón (sections/pageSettings ya guardados) viaja embebido en la
|
||||||
// plantilla Django, ver {{ pattern_data|json_script:"pattern-data" }} en
|
// plantilla Django, ver {{ pattern_data|json_script:"pattern-data" }} en
|
||||||
// pattern.html. Si todavía no tiene ninguna sección (patrón recién creado),
|
// pattern.html. Si todavía no tiene ninguna sección (patrón recién creado),
|
||||||
@@ -25,5 +29,14 @@ function loadInitialPattern() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadInitialPattern();
|
loadInitialPattern();
|
||||||
|
|
||||||
|
// En pantallas estrechas arranca con todas las secciones colapsadas (solo
|
||||||
|
// se ve el badge de cada una) en vez de todos sus campos editables a la
|
||||||
|
// vez: con un patrón ya guardado (varias secciones o grupos) ese "ruido"
|
||||||
|
// inicial era mucho en móvil.
|
||||||
|
if (window.matchMedia(MOBILE_BREAKPOINT_QUERY).matches) {
|
||||||
|
setAllSectionsCollapsed(true);
|
||||||
|
}
|
||||||
|
|
||||||
renderPageMeta();
|
renderPageMeta();
|
||||||
renderOutput();
|
renderOutput();
|
||||||
|
|||||||
@@ -54,17 +54,38 @@ function addGroupSection(data, children = [], container = sectionsContainer) {
|
|||||||
const groupContainer = document.createElement('div');
|
const groupContainer = document.createElement('div');
|
||||||
groupContainer.className = 'group-container flex flex-col gap-4';
|
groupContainer.className = 'group-container flex flex-col gap-4';
|
||||||
|
|
||||||
const toolbar = document.createElement('div');
|
// Mismo menú desplegable que la barra superior (ver pattern.html), no una
|
||||||
toolbar.className = 'flex flex-wrap gap-2 mb-4';
|
// fila de botones sueltos: un grupo anidado multiplicaba ese ruido visual
|
||||||
|
// en vez de reducirlo.
|
||||||
|
const dropdown = document.createElement('div');
|
||||||
|
dropdown.className = 'dropdown mb-2';
|
||||||
|
|
||||||
|
const dropdownBtn = document.createElement('button');
|
||||||
|
dropdownBtn.type = 'button';
|
||||||
|
dropdownBtn.className = 'btn btn-sm';
|
||||||
|
dropdownBtn.tabIndex = 0;
|
||||||
|
dropdownBtn.textContent = `+ ${addSectionLabel}`;
|
||||||
|
|
||||||
|
const menu = document.createElement('ul');
|
||||||
|
menu.tabIndex = 0;
|
||||||
|
menu.className = 'dropdown-content menu menu-sm bg-base-100 border border-base-300 rounded-box z-10 w-52 p-2 shadow';
|
||||||
|
|
||||||
SECTION_TYPES.forEach(({ button, create }) => {
|
SECTION_TYPES.forEach(({ button, create }) => {
|
||||||
const btn = button.cloneNode(true);
|
const btn = button.cloneNode(true);
|
||||||
btn.removeAttribute('id'); // Puede haber varios grupos, cada uno con su propia copia de estos botones.
|
btn.removeAttribute('id'); // Puede haber varios grupos, cada uno con su propia copia de estos botones.
|
||||||
btn.addEventListener('click', () => create(undefined, groupContainer));
|
btn.addEventListener('click', () => {
|
||||||
toolbar.appendChild(btn);
|
create(undefined, groupContainer);
|
||||||
|
btn.blur(); // Cierra el menú (daisyUI lo abre con :focus-within) tras elegir.
|
||||||
|
});
|
||||||
|
const item = document.createElement('li');
|
||||||
|
item.appendChild(btn);
|
||||||
|
menu.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
body.appendChild(toolbar);
|
dropdown.appendChild(dropdownBtn);
|
||||||
|
dropdown.appendChild(menu);
|
||||||
|
|
||||||
|
body.appendChild(dropdown);
|
||||||
body.appendChild(groupContainer);
|
body.appendChild(groupContainer);
|
||||||
container.appendChild(section);
|
container.appendChild(section);
|
||||||
|
|
||||||
@@ -73,6 +94,11 @@ function addGroupSection(data, children = [], container = sectionsContainer) {
|
|||||||
buildSectionsFrom(children, groupContainer);
|
buildSectionsFrom(children, groupContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Etiqueta de "+ Añadir sección" compartida por la barra superior y por el
|
||||||
|
// menú propio de cada "Grupo" (ver addGroupSection): un solo dato en el
|
||||||
|
// HTML (ver pattern.html), traducible por la plantilla Django.
|
||||||
|
const addSectionLabel = document.getElementById('add-section-toggle').dataset.label;
|
||||||
|
|
||||||
const addTitleBtn = document.getElementById('add-title-section');
|
const addTitleBtn = document.getElementById('add-title-section');
|
||||||
const addSubtitleBtn = document.getElementById('add-subtitle-section');
|
const addSubtitleBtn = document.getElementById('add-subtitle-section');
|
||||||
const addTextBtn = document.getElementById('add-text-section');
|
const addTextBtn = document.getElementById('add-text-section');
|
||||||
@@ -432,9 +458,12 @@ sectionsContainer.addEventListener('section:duplicate', (e) => duplicateSection(
|
|||||||
// addGroupSection() para el de dentro de un grupo).
|
// addGroupSection() para el de dentro de un grupo).
|
||||||
setupSectionsReorder(sectionsContainer, panelTop);
|
setupSectionsReorder(sectionsContainer, panelTop);
|
||||||
|
|
||||||
// Botones de la barra superior para añadir cada tipo de sección. Los textos
|
// Botones del menú "+ Añadir sección" de la barra superior. Los textos
|
||||||
// (etiqueta de tipo, placeholders...) se leen de sus data-* en el HTML, no
|
// (etiqueta de tipo, placeholders...) se leen de sus data-* en el HTML, no
|
||||||
// están escritos aquí, para que la plantilla Django pueda traducirlos.
|
// están escritos aquí, para que la plantilla Django pueda traducirlos.
|
||||||
SECTION_TYPES.forEach(({ button, create }) => {
|
SECTION_TYPES.forEach(({ button, create }) => {
|
||||||
button.addEventListener('click', () => create(undefined, undefined));
|
button.addEventListener('click', () => {
|
||||||
|
create(undefined, undefined);
|
||||||
|
button.blur(); // Cierra el menú (daisyUI lo abre con :focus-within) tras elegir.
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -380,8 +380,24 @@ export function addImageSection(data, image = null, container = sectionsContaine
|
|||||||
export function addDndSection(data, items = [], container = sectionsContainer) {
|
export function addDndSection(data, items = [], container = sectionsContainer) {
|
||||||
const { section, body } = createSectionShell(data.typeLabel);
|
const { section, body } = createSectionShell(data.typeLabel);
|
||||||
|
|
||||||
const row = document.createElement('div');
|
// Siempre en columna (antes cambiaba a fila a partir de "sm", con los
|
||||||
row.className = 'flex flex-col sm:flex-row gap-4';
|
// elementos arrastrables a la derecha): con el catálogo ya grande, esa
|
||||||
|
// barra lateral quedaba demasiado estrecha para envolver los tipos de
|
||||||
|
// punto con comodidad, así que ahora va arriba a todo lo ancho.
|
||||||
|
const column = document.createElement('div');
|
||||||
|
column.className = 'flex flex-col gap-4';
|
||||||
|
|
||||||
|
const elementsPanel = document.createElement('div');
|
||||||
|
elementsPanel.className = 'w-full border border-base-300 rounded-box p-4';
|
||||||
|
|
||||||
|
const elementsTitle = document.createElement('h3');
|
||||||
|
elementsTitle.className = 'font-semibold mb-2';
|
||||||
|
elementsTitle.textContent = data.elementsLabel;
|
||||||
|
|
||||||
|
const list = createElementsList();
|
||||||
|
|
||||||
|
elementsPanel.appendChild(elementsTitle);
|
||||||
|
elementsPanel.appendChild(list);
|
||||||
|
|
||||||
const dndCanvas = document.createElement('div');
|
const dndCanvas = document.createElement('div');
|
||||||
// content-start (align-content: flex-start): sin esto, un flex-wrap con
|
// content-start (align-content: flex-start): sin esto, un flex-wrap con
|
||||||
@@ -393,21 +409,9 @@ export function addDndSection(data, items = [], container = sectionsContainer) {
|
|||||||
// debajo, no repartido.
|
// debajo, no repartido.
|
||||||
dndCanvas.className = 'dnd-canvas flex-1 flex flex-wrap content-start items-center gap-2 min-h-24 py-2';
|
dndCanvas.className = 'dnd-canvas flex-1 flex flex-wrap content-start items-center gap-2 min-h-24 py-2';
|
||||||
|
|
||||||
const elementsPanel = document.createElement('aside');
|
column.appendChild(elementsPanel);
|
||||||
elementsPanel.className = 'w-full sm:w-48 sm:shrink-0 border border-base-300 rounded-box p-4';
|
column.appendChild(dndCanvas);
|
||||||
|
body.appendChild(column);
|
||||||
const elementsTitle = document.createElement('h3');
|
|
||||||
elementsTitle.className = 'font-semibold mb-2';
|
|
||||||
elementsTitle.textContent = data.elementsLabel;
|
|
||||||
|
|
||||||
const list = createElementsList();
|
|
||||||
|
|
||||||
elementsPanel.appendChild(elementsTitle);
|
|
||||||
elementsPanel.appendChild(list);
|
|
||||||
|
|
||||||
row.appendChild(dndCanvas);
|
|
||||||
row.appendChild(elementsPanel);
|
|
||||||
body.appendChild(row);
|
|
||||||
container.appendChild(section);
|
container.appendChild(section);
|
||||||
setupDropZone(dndCanvas, undefined, section);
|
setupDropZone(dndCanvas, undefined, section);
|
||||||
|
|
||||||
@@ -418,21 +422,26 @@ export function addDndSection(data, items = [], container = sectionsContainer) {
|
|||||||
// Colapsar / expandir todas las secciones de una vez
|
// Colapsar / expandir todas las secciones de una vez
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
const toggleCollapseAllBtn = document.getElementById('toggle-collapse-all');
|
||||||
|
|
||||||
// Colapsa o expande a la vez todas las secciones que haya, sin importar su
|
// Colapsa o expande a la vez todas las secciones que haya, sin importar su
|
||||||
// nivel de anidamiento (querySelectorAll recorre también las que están
|
// nivel de anidamiento (querySelectorAll recorre también las que están
|
||||||
// dentro de un "Grupo"), sincronizando tanto su cuerpo como el icono de su
|
// dentro de un "Grupo"), sincronizando tanto su cuerpo como el icono de su
|
||||||
// propio botón de colapsar individual.
|
// propio botón de colapsar individual y la etiqueta de "Colapsar todo" /
|
||||||
function setAllSectionsCollapsed(collapsed) {
|
// "Expandir todo". Exportada para poder arrancar ya colapsado en móvil (ver
|
||||||
|
// main.js), no solo desde el propio botón.
|
||||||
|
export function setAllSectionsCollapsed(collapsed) {
|
||||||
sectionsContainer.querySelectorAll('.section-body').forEach(body => {
|
sectionsContainer.querySelectorAll('.section-body').forEach(body => {
|
||||||
body.classList.toggle('hidden', collapsed);
|
body.classList.toggle('hidden', collapsed);
|
||||||
});
|
});
|
||||||
sectionsContainer.querySelectorAll('.collapse-btn').forEach(btn => {
|
sectionsContainer.querySelectorAll('.collapse-btn').forEach(btn => {
|
||||||
btn.textContent = collapsed ? '▸' : '▾';
|
btn.textContent = collapsed ? '▸' : '▾';
|
||||||
});
|
});
|
||||||
|
toggleCollapseAllBtn.textContent = collapsed
|
||||||
|
? toggleCollapseAllBtn.dataset.expandAllLabel
|
||||||
|
: toggleCollapseAllBtn.dataset.collapseAllLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleCollapseAllBtn = document.getElementById('toggle-collapse-all');
|
|
||||||
|
|
||||||
toggleCollapseAllBtn.addEventListener('click', () => {
|
toggleCollapseAllBtn.addEventListener('click', () => {
|
||||||
// Se decide según el estado actual (en vez de recordar uno propio) para
|
// Se decide según el estado actual (en vez de recordar uno propio) para
|
||||||
// que el botón siga teniendo sentido aunque el usuario haya colapsado o
|
// que el botón siga teniendo sentido aunque el usuario haya colapsado o
|
||||||
@@ -441,7 +450,4 @@ toggleCollapseAllBtn.addEventListener('click', () => {
|
|||||||
const anyExpanded = Array.from(sectionsContainer.querySelectorAll('.section-body'))
|
const anyExpanded = Array.from(sectionsContainer.querySelectorAll('.section-body'))
|
||||||
.some(body => !body.classList.contains('hidden'));
|
.some(body => !body.classList.contains('hidden'));
|
||||||
setAllSectionsCollapsed(anyExpanded);
|
setAllSectionsCollapsed(anyExpanded);
|
||||||
toggleCollapseAllBtn.textContent = anyExpanded
|
|
||||||
? toggleCollapseAllBtn.dataset.expandAllLabel
|
|
||||||
: toggleCollapseAllBtn.dataset.collapseAllLabel;
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% load static %}
|
{% load static i18n %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ lang }}">
|
<html lang="{{ lang }}">
|
||||||
|
|
||||||
@@ -34,10 +34,10 @@
|
|||||||
</select>
|
</select>
|
||||||
<button id="save-pattern" type="button" class="btn btn-sm btn-outline"
|
<button id="save-pattern" type="button" class="btn btn-sm btn-outline"
|
||||||
data-save-url="{% url 'crochet:pattern_save' pattern.uuid %}"
|
data-save-url="{% url 'crochet:pattern_save' pattern.uuid %}"
|
||||||
data-error-message="No se ha podido guardar. Inténtalo de nuevo.">Guardar</button>
|
data-error-message="{% trans 'No se ha podido guardar. Inténtalo de nuevo.' %}">{% trans 'Guardar' %}</button>
|
||||||
<button id="load-pattern" type="button" class="btn btn-sm btn-outline"
|
<button id="load-pattern" type="button" class="btn btn-sm btn-outline"
|
||||||
data-confirm-message="¿Seguro que quieres cargar el patrón guardado? Se perderán los cambios que no hayas guardado.">Cargar</button>
|
data-confirm-message="{% trans '¿Seguro que quieres cargar el patrón guardado? Se perderán los cambios que no hayas guardado.' %}">{% trans 'Cargar' %}</button>
|
||||||
<button id="export-pdf" type="button" class="btn btn-sm">Exportar a PDF</button>
|
<button id="export-pdf" type="button" class="btn btn-sm">{% trans 'Exportar a PDF' %}</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
localStorage no da ninguna señal de que ha ocurrido algo. -->
|
localStorage no da ninguna señal de que ha ocurrido algo. -->
|
||||||
<div id="save-toast" class="toast toast-top toast-end no-print hidden">
|
<div id="save-toast" class="toast toast-top toast-end no-print hidden">
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<span data-message="Guardado">Guardado</span>
|
<span data-message="{% trans 'Guardado' %}">{% trans 'Guardado' %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -56,72 +56,72 @@
|
|||||||
de #panel-top-sections. <details> nativo para no necesitar JS
|
de #panel-top-sections. <details> nativo para no necesitar JS
|
||||||
solo para plegarla/desplegarla. -->
|
solo para plegarla/desplegarla. -->
|
||||||
<details class="border border-base-300 rounded-box p-4 mb-4 no-print">
|
<details class="border border-base-300 rounded-box p-4 mb-4 no-print">
|
||||||
<summary class="text-lg font-semibold cursor-pointer select-none">Personalización de página</summary>
|
<summary class="text-lg font-semibold cursor-pointer select-none">{% trans 'Personalización de página' %}</summary>
|
||||||
<div class="flex flex-col gap-4 mt-4">
|
<div class="flex flex-col gap-4 mt-4">
|
||||||
<div class="flex flex-col sm:flex-row gap-4">
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
<label class="flex flex-col gap-1 flex-1">
|
<label class="flex flex-col gap-1 flex-1">
|
||||||
<span class="text-sm">Título del patrón</span>
|
<span class="text-sm">{% trans 'Título del patrón' %}</span>
|
||||||
<input id="page-title-input" type="text" class="input input-bordered" placeholder="Título del patrón...">
|
<input id="page-title-input" type="text" class="input input-bordered" placeholder="{% trans 'Título del patrón...' %}">
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1 flex-1">
|
<label class="flex flex-col gap-1 flex-1">
|
||||||
<span class="text-sm">Autor</span>
|
<span class="text-sm">{% trans 'Autor' %}</span>
|
||||||
<input id="page-author-input" type="text" class="input input-bordered" placeholder="Autor...">
|
<input id="page-author-input" type="text" class="input input-bordered" placeholder="{% trans 'Autor...' %}">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-end gap-4">
|
<div class="flex flex-wrap items-end gap-4">
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Color del texto</span>
|
<span class="text-sm">{% trans 'Color del texto' %}</span>
|
||||||
<input id="page-text-color" type="color" value="#000000">
|
<input id="page-text-color" type="color" value="#000000">
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Color de acento (títulos)</span>
|
<span class="text-sm">{% trans 'Color de acento (títulos)' %}</span>
|
||||||
<input id="page-accent-color" type="color" value="#1d4ed8">
|
<input id="page-accent-color" type="color" value="#1d4ed8">
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Color de fondo</span>
|
<span class="text-sm">{% trans 'Color de fondo' %}</span>
|
||||||
<input id="page-bg-color" type="color" value="#ffffff">
|
<input id="page-bg-color" type="color" value="#ffffff">
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Tipografía</span>
|
<span class="text-sm">{% trans 'Tipografía' %}</span>
|
||||||
<select id="page-font-select" class="select select-bordered select-sm">
|
<select id="page-font-select" class="select select-bordered select-sm">
|
||||||
<option value="sans-serif" selected>Sans-serif</option>
|
<option value="sans-serif" selected>Sans-serif</option>
|
||||||
<option value="serif">Serif</option>
|
<option value="serif">Serif</option>
|
||||||
<option value="Georgia, serif">Georgia</option>
|
<option value="Georgia, serif">Georgia</option>
|
||||||
<option value="'Trebuchet MS', sans-serif">Trebuchet MS</option>
|
<option value="'Trebuchet MS', sans-serif">Trebuchet MS</option>
|
||||||
<option value="'Courier New', monospace">Monoespaciada</option>
|
<option value="'Courier New', monospace">{% trans 'Monoespaciada' %}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Tamaño de letra</span>
|
<span class="text-sm">{% trans 'Tamaño de letra' %}</span>
|
||||||
<select id="page-font-size-select" class="select select-bordered select-sm">
|
<select id="page-font-size-select" class="select select-bordered select-sm">
|
||||||
<option value="0.875rem">Pequeño</option>
|
<option value="0.875rem">{% trans 'Pequeño' %}</option>
|
||||||
<option value="1rem" selected>Normal</option>
|
<option value="1rem" selected>{% trans 'Normal' %}</option>
|
||||||
<option value="1.25rem">Grande</option>
|
<option value="1.25rem">{% trans 'Grande' %}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Alineación del texto</span>
|
<span class="text-sm">{% trans 'Alineación del texto' %}</span>
|
||||||
<select id="page-align-select" class="select select-bordered select-sm">
|
<select id="page-align-select" class="select select-bordered select-sm">
|
||||||
<option value="left" selected>Izquierda</option>
|
<option value="left" selected>{% trans 'Izquierda' %}</option>
|
||||||
<option value="center">Centrado</option>
|
<option value="center">{% trans 'Centrado' %}</option>
|
||||||
<option value="justify">Justificado</option>
|
<option value="justify">{% trans 'Justificado' %}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Tamaño de página</span>
|
<span class="text-sm">{% trans 'Tamaño de página' %}</span>
|
||||||
<select id="page-size-select" class="select select-bordered select-sm">
|
<select id="page-size-select" class="select select-bordered select-sm">
|
||||||
<option value="A4" selected>A4</option>
|
<option value="A4" selected>A4</option>
|
||||||
<option value="A3">A3</option>
|
<option value="A3">A3</option>
|
||||||
<option value="A5">A5</option>
|
<option value="A5">A5</option>
|
||||||
<option value="letter">Carta (Letter)</option>
|
<option value="letter">{% trans 'Carta (Letter)' %}</option>
|
||||||
<option value="legal">Legal</option>
|
<option value="legal">Legal</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
<span class="text-sm">Orientación</span>
|
<span class="text-sm">{% trans 'Orientación' %}</span>
|
||||||
<select id="page-orientation-select" class="select select-bordered select-sm">
|
<select id="page-orientation-select" class="select select-bordered select-sm">
|
||||||
<option value="portrait" selected>Vertical</option>
|
<option value="portrait" selected>{% trans 'Vertical' %}</option>
|
||||||
<option value="landscape">Horizontal</option>
|
<option value="landscape">{% trans 'Horizontal' %}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,51 +129,62 @@
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<div class="tabs tabs-boxed w-fit mb-4 md:hidden no-print">
|
<div class="tabs tabs-boxed w-fit mb-4 md:hidden no-print">
|
||||||
<button id="tab-sections" type="button" class="tab tab-active">Secciones</button>
|
<button id="tab-sections" type="button" class="tab tab-active">{% trans 'Secciones' %}</button>
|
||||||
<button id="tab-output" type="button" class="tab">Vista previa</button>
|
<button id="tab-output" type="button" class="tab">{% trans 'Vista previa' %}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
|
|
||||||
<section id="panel-top" class="w-full md:w-1/2 border border-base-300 rounded-box p-4 no-print">
|
<section id="panel-top" class="w-full md:w-1/2 border border-base-300 rounded-box p-4 no-print">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h2 class="text-lg font-semibold">Secciones</h2>
|
<h2 class="text-lg font-semibold">{% trans 'Secciones' %}</h2>
|
||||||
<button id="toggle-collapse-all" type="button" class="btn btn-xs btn-outline"
|
<button id="toggle-collapse-all" type="button" class="btn btn-xs btn-outline"
|
||||||
data-collapse-all-label="Colapsar todo" data-expand-all-label="Expandir todo">Colapsar todo</button>
|
data-collapse-all-label="{% trans 'Colapsar todo' %}" data-expand-all-label="{% trans 'Expandir todo' %}">{% trans 'Colapsar todo' %}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 mb-4">
|
<!-- Un único botón con menú (en vez de 7-8 botones sueltos que se
|
||||||
<button id="add-title-section" type="button" class="btn btn-sm"
|
partían en varias filas en pantallas estrechas): menos ruido
|
||||||
data-type-label="Título" data-placeholder="Título...">+ Título</button>
|
visual antes de llegar al contenido real. daisyUI abre
|
||||||
<button id="add-subtitle-section" type="button" class="btn btn-sm"
|
.dropdown-content con :focus-within, sin JS propio; el mismo
|
||||||
data-type-label="Subtítulo" data-placeholder="Subtítulo...">+ Subtítulo</button>
|
botón (clonado) se reutiliza dentro de cada "Grupo" (ver
|
||||||
<button id="add-text-section" type="button" class="btn btn-sm"
|
addGroupSection en section-types.js), así que su
|
||||||
data-type-label="Texto" data-placeholder="Escribe aquí...">+ Texto</button>
|
data-label también viaja con él. -->
|
||||||
<button id="add-note-section" type="button" class="btn btn-sm"
|
<div class="dropdown mb-4">
|
||||||
data-type-label="Nota" data-placeholder="Escribe una nota o consejo..."
|
<button id="add-section-toggle" type="button" class="btn btn-sm" tabindex="0"
|
||||||
data-text-color-label="Color del texto" data-bg-color-label="Color de fondo">+ Nota</button>
|
data-label="{% trans 'Añadir sección' %}">+ {% trans 'Añadir sección' %}</button>
|
||||||
<button id="add-materials-section" type="button" class="btn btn-sm"
|
<ul tabindex="0" class="dropdown-content menu menu-sm bg-base-100 border border-base-300 rounded-box z-10 w-52 p-2 shadow">
|
||||||
data-type-label="Materiales" data-placeholder="Añadir material..." data-add-line-label="+ Añadir línea">+ Materiales</button>
|
<li><button id="add-title-section" type="button"
|
||||||
<button id="add-image-section" type="button" class="btn btn-sm"
|
data-type-label="{% trans 'Título' %}" data-placeholder="{% trans 'Título...' %}">{% trans 'Título' %}</button></li>
|
||||||
data-type-label="Imagen"
|
<li><button id="add-subtitle-section" type="button"
|
||||||
data-upload-url="{% url 'crochet:pattern_image_upload' pattern.uuid %}"
|
data-type-label="{% trans 'Subtítulo' %}" data-placeholder="{% trans 'Subtítulo...' %}">{% trans 'Subtítulo' %}</button></li>
|
||||||
data-upload-error-message="No se ha podido subir la imagen. Inténtalo de nuevo.">+ Imagen</button>
|
<li><button id="add-text-section" type="button"
|
||||||
<button id="add-dnd-section" type="button" class="btn btn-sm"
|
data-type-label="{% trans 'Texto' %}" data-placeholder="{% trans 'Escribe aquí...' %}">{% trans 'Texto' %}</button></li>
|
||||||
data-type-label="Patrón" data-elements-label="Elementos">+ Patrón</button>
|
<li><button id="add-note-section" type="button"
|
||||||
<button id="add-group-section" type="button" class="btn btn-sm"
|
data-type-label="{% trans 'Nota' %}" data-placeholder="{% trans 'Escribe una nota o consejo...' %}"
|
||||||
data-type-label="Grupo">+ Grupo</button>
|
data-text-color-label="{% trans 'Color del texto' %}" data-bg-color-label="{% trans 'Color de fondo' %}">{% trans 'Nota' %}</button></li>
|
||||||
|
<li><button id="add-materials-section" type="button"
|
||||||
|
data-type-label="{% trans 'Materiales' %}" data-placeholder="{% trans 'Añadir material...' %}" data-add-line-label="+ {% trans 'Añadir línea' %}">{% trans 'Materiales' %}</button></li>
|
||||||
|
<li><button id="add-image-section" type="button"
|
||||||
|
data-type-label="{% trans 'Imagen' %}"
|
||||||
|
data-upload-url="{% url 'crochet:pattern_image_upload' pattern.uuid %}"
|
||||||
|
data-upload-error-message="{% trans 'No se ha podido subir la imagen. Inténtalo de nuevo.' %}">{% trans 'Imagen' %}</button></li>
|
||||||
|
<li><button id="add-dnd-section" type="button"
|
||||||
|
data-type-label="{% trans 'Patrón' %}" data-elements-label="{% trans 'Elementos' %}">{% trans 'Patrón' %}</button></li>
|
||||||
|
<li><button id="add-group-section" type="button"
|
||||||
|
data-type-label="{% trans 'Grupo' %}">{% trans 'Grupo' %}</button></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel-top-sections" class="flex flex-col gap-4"
|
<div id="panel-top-sections" class="flex flex-col gap-4"
|
||||||
data-drag-title="Arrastrar para reordenar"
|
data-drag-title="{% trans 'Arrastrar para reordenar' %}"
|
||||||
data-collapse-title="Colapsar / expandir"
|
data-collapse-title="{% trans 'Colapsar / expandir' %}"
|
||||||
data-duplicate-title="Duplicar sección"
|
data-duplicate-title="{% trans 'Duplicar sección' %}"
|
||||||
data-remove-title="Eliminar sección"
|
data-remove-title="{% trans 'Eliminar sección' %}"
|
||||||
data-remove-confirm="¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las secciones que contiene."></div>
|
data-remove-confirm="{% trans '¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las secciones que contiene.' %}"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="panel-output" class="w-full md:w-1/2 border border-base-300 rounded-box p-4 panel-hidden-mobile">
|
<section id="panel-output" class="w-full md:w-1/2 border border-base-300 rounded-box p-4 panel-hidden-mobile">
|
||||||
<h2 id="preview-title" class="text-lg font-semibold mb-2 no-print">Vista previa</h2>
|
<h2 id="preview-title" class="text-lg font-semibold mb-2 no-print">{% trans 'Vista previa' %}</h2>
|
||||||
<div id="panel-output-text"
|
<div id="panel-output-text"
|
||||||
data-empty-message="Añade una sección para ver aquí el resultado."></div>
|
data-empty-message="{% trans 'Añade una sección para ver aquí el resultado.' %}"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+6
-2
@@ -2,7 +2,7 @@ import json
|
|||||||
|
|
||||||
from django.http import HttpResponseBadRequest, JsonResponse
|
from django.http import HttpResponseBadRequest, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language, gettext_lazy as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import DetailView
|
from django.views.generic import DetailView
|
||||||
|
|
||||||
@@ -57,7 +57,11 @@ class PatternDetailView(DetailView):
|
|||||||
slug_field = 'uuid'
|
slug_field = 'uuid'
|
||||||
slug_url_kwarg = 'uuid'
|
slug_url_kwarg = 'uuid'
|
||||||
|
|
||||||
EMPTY_MESSAGE = 'Este patrón todavía no tiene contenido.'
|
# gettext_lazy (no gettext): se evalúa al construir el HTML de salida
|
||||||
|
# (dentro de render_pattern_html, vía format_html), no aquí en la
|
||||||
|
# definición de la clase, así que respeta el idioma activo de cada
|
||||||
|
# petición aunque este atributo se cree una sola vez al importar el módulo.
|
||||||
|
EMPTY_MESSAGE = _('Este patrón todavía no tiene contenido.')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|||||||
Reference in New Issue
Block a user