`;
+ return html + (need ? ghost : '');
}
function renderChildren(node, warnings){
@@ -624,7 +644,7 @@ function renderChildren(node, warnings){
if(!kids.length) return '';
const types = new Set(kids.map(k => k.type));
if(types.size > 1){
- warnings.push(`Zeile ${kids[0].line}: Unter „${esc(node.label)}“ sind - und | gemischt — dargestellt nach dem ersten Kind.`);
+ warnings.push(t('mixedWarn', {line: kids[0].line, label: esc(node.label)}));
}
const gate = gateOf(kids);
const items = kids.map(k => {
@@ -646,7 +666,7 @@ function render(){
}
if(!roots.length){
- out.innerHTML = '
Noch keine Struktur — einfach lostippen.
';
+ out.innerHTML = `
${esc(t('empty'))}
`;
warnBox.innerHTML = '';
return;
}
@@ -808,14 +828,158 @@ copyBtn.addEventListener('click', async () => {
src.setSelectionRange(0, 0);
src.blur();
}
- copyBtn.textContent = 'kopiert ✓';
+ copyBtn.textContent = t('copyDone');
copyBtn.classList.add('done');
- setTimeout(() => { copyBtn.textContent = 'kopieren'; copyBtn.classList.remove('done'); }, 1500);
+ setTimeout(() => { copyBtn.textContent = t('copy'); copyBtn.classList.remove('done'); }, 1500);
+});
+
+/* ---------- Internationalisierung (DE/EN/ES/FR) ---------- */
+const I18N = {
+ de: {
+ subtitle:"Werkbaum – Ein Text-basierter WBS/Projektstrukturplan/Feature-Tree Diagramm-Editor.",
+ editorTitle:"Struktur (Text)", diagramTitle:"Diagramm",
+ copy:"kopieren", copyDone:"kopiert ✓", copyTitle:"Text in die Zwischenablage kopieren",
+ minimize:"minimieren", normal:"normal", maximize:"maximieren",
+ agenda:"Agenda", discarded:"verworfene",
+ modeHorizontal:"Horizontal – Organigramm, Diagramm über dem Editor",
+ modeKompakt:"Kompakt – alles nach unten, platzsparend",
+ modeVertikal:"Vertikal – Baum nach rechts, Diagramm neben dem Editor",
+ zoomOut:"verkleinern", zoomReset:"zurücksetzen", zoomIn:"vergrößern",
+ zoomAria:"Zoom (Strg/Cmd + Mausrad)",
+ empty:"Noch keine Struktur — einfach lostippen.", ghost:"Untergliederung fehlt",
+ mixedWarn:"Zeile {line}: Unter „{label}“ sind - und | gemischt — dargestellt nach dem ersten Kind.",
+ st_idee:"Idee", st_geplant:"geplant", st_arbeit:"in Arbeit", st_durchstich:"Durchstich",
+ st_fertig:"fertig", st_prod:"in Produktion", st_verworfen:"verworfen",
+ hint_indent:"Einrückung (2 Leerzeichen oder Tab) definiert die Hierarchie.",
+ hint_all:"Teilpaket, alle erforderlich", hint_any:"Alternative, eine wählen",
+ hint_root:"Zeile ohne Zeichen = Wurzelknoten. Geschwister sollten dasselbe Zeichen tragen.",
+ hint_status:"Status als Kästchen nach dem Zeichen, z. B.",
+ hint_size:"Aufwand als T-Shirt-Größe in Klammern, Link einfach als URL anhängen:",
+ hint_break:"Ab (M) gilt: weiter untergliedern — fehlt die Untergliederung, erscheint ein Platzhalter im Diagramm.",
+ hint_comment:"Kommentare mit %% — als ganze Zeile oder am Zeilenende.",
+ hint_people:"Personen mit @name — erscheinen unten rechts am Knoten."
+ },
+ en: {
+ subtitle:"Werkbaum – A text-based WBS / work breakdown structure / feature-tree diagram editor.",
+ editorTitle:"Structure (text)", diagramTitle:"Diagram",
+ copy:"copy", copyDone:"copied ✓", copyTitle:"Copy text to clipboard",
+ minimize:"minimize", normal:"normal", maximize:"maximize",
+ agenda:"Legend", discarded:"discarded",
+ modeHorizontal:"Horizontal – org chart, diagram above the editor",
+ modeKompakt:"Compact – everything downward, space-saving",
+ modeVertikal:"Vertical – tree to the right, diagram beside the editor",
+ zoomOut:"zoom out", zoomReset:"reset", zoomIn:"zoom in",
+ zoomAria:"Zoom (Ctrl/Cmd + mouse wheel)",
+ empty:"No structure yet — just start typing.", ghost:"Breakdown missing",
+ mixedWarn:"Line {line}: under “{label}”, - and | are mixed — rendered by the first child.",
+ st_idee:"idea", st_geplant:"planned", st_arbeit:"in progress", st_durchstich:"walking skeleton",
+ st_fertig:"done", st_prod:"in production", st_verworfen:"discarded",
+ hint_indent:"Indentation (2 spaces or a tab) defines the hierarchy.",
+ hint_all:"sub-task, all required", hint_any:"alternative, choose one",
+ hint_root:"Line without a marker = root node. Siblings should share the same marker.",
+ hint_status:"Status as a checkbox after the marker, e.g.",
+ hint_size:"Effort as a T-shirt size in parentheses; add a link simply as a URL:",
+ hint_break:"From (M) on: break it down further — if the breakdown is missing, a placeholder appears in the diagram.",
+ hint_comment:"Comments with %% — whole line or at the end of a line.",
+ hint_people:"People with @name — shown at the bottom-right of the node."
+ },
+ es: {
+ subtitle:"Werkbaum – Un editor de diagramas basado en texto para EDT / estructura de desglose del trabajo / árbol de características.",
+ editorTitle:"Estructura (texto)", diagramTitle:"Diagrama",
+ copy:"copiar", copyDone:"copiado ✓", copyTitle:"Copiar el texto al portapapeles",
+ minimize:"minimizar", normal:"normal", maximize:"maximizar",
+ agenda:"Leyenda", discarded:"descartados",
+ modeHorizontal:"Horizontal – organigrama, diagrama sobre el editor",
+ modeKompakt:"Compacto – todo hacia abajo, ahorra espacio",
+ modeVertikal:"Vertical – árbol hacia la derecha, diagrama junto al editor",
+ zoomOut:"alejar", zoomReset:"restablecer", zoomIn:"acercar",
+ zoomAria:"Zoom (Ctrl/Cmd + rueda del ratón)",
+ empty:"Aún no hay estructura — simplemente empieza a escribir.", ghost:"Falta el desglose",
+ mixedWarn:"Línea {line}: bajo «{label}» se mezclan - y | — se representa según el primer hijo.",
+ st_idee:"idea", st_geplant:"planificado", st_arbeit:"en curso", st_durchstich:"prototipo funcional",
+ st_fertig:"terminado", st_prod:"en producción", st_verworfen:"descartado",
+ hint_indent:"La sangría (2 espacios o un tabulador) define la jerarquía.",
+ hint_all:"subtarea, todas obligatorias", hint_any:"alternativa, elige una",
+ hint_root:"Línea sin marcador = nodo raíz. Los hermanos deberían llevar el mismo marcador.",
+ hint_status:"Estado como casilla tras el marcador, p. ej.",
+ hint_size:"Esfuerzo como talla de camiseta entre paréntesis; añade un enlace simplemente como URL:",
+ hint_break:"A partir de (M): sigue desglosando — si falta el desglose, aparece un marcador de posición en el diagrama.",
+ hint_comment:"Comentarios con %% — línea completa o al final de la línea.",
+ hint_people:"Personas con @nombre — aparecen abajo a la derecha del nodo."
+ },
+ fr: {
+ subtitle:"Werkbaum – Un éditeur de diagrammes basé sur le texte pour WBS / organigramme des tâches / arbre de fonctionnalités.",
+ editorTitle:"Structure (texte)", diagramTitle:"Diagramme",
+ copy:"copier", copyDone:"copié ✓", copyTitle:"Copier le texte dans le presse-papiers",
+ minimize:"réduire", normal:"normal", maximize:"agrandir",
+ agenda:"Légende", discarded:"abandonnés",
+ modeHorizontal:"Horizontal – organigramme, diagramme au-dessus de l'éditeur",
+ modeKompakt:"Compact – tout vers le bas, gain de place",
+ modeVertikal:"Vertical – arbre vers la droite, diagramme à côté de l'éditeur",
+ zoomOut:"dézoomer", zoomReset:"réinitialiser", zoomIn:"zoomer",
+ zoomAria:"Zoom (Ctrl/Cmd + molette)",
+ empty:"Pas encore de structure — commencez à taper.", ghost:"Décomposition manquante",
+ mixedWarn:"Ligne {line} : sous « {label} », - et | sont mélangés — rendu selon le premier enfant.",
+ st_idee:"idée", st_geplant:"planifié", st_arbeit:"en cours", st_durchstich:"squelette fonctionnel",
+ st_fertig:"terminé", st_prod:"en production", st_verworfen:"abandonné",
+ hint_indent:"L'indentation (2 espaces ou une tabulation) définit la hiérarchie.",
+ hint_all:"sous-tâche, toutes requises", hint_any:"alternative, en choisir une",
+ hint_root:"Ligne sans marqueur = nœud racine. Les frères devraient porter le même marqueur.",
+ hint_status:"Statut sous forme de case après le marqueur, p. ex.",
+ hint_size:"Effort en taille de T-shirt entre parenthèses ; ajoutez un lien simplement comme URL :",
+ hint_break:"À partir de (M) : décomposer davantage — si la décomposition manque, un espace réservé apparaît dans le diagramme.",
+ hint_comment:"Commentaires avec %% — ligne entière ou en fin de ligne.",
+ hint_people:"Personnes avec @nom — affichées en bas à droite du nœud."
+ }
+};
+let lang = 'de';
+function t(key, vars){
+ let s = (I18N[lang] && I18N[lang][key]) ?? I18N.de[key] ?? key;
+ if(vars) for(const k in vars) s = s.split('{'+k+'}').join(vars[k]);
+ return s;
+}
+function buildHint(){
+ const chip = (key, code) => `${code} ${esc(t('st_'+key))}`;
+ return `${esc(t('hint_indent'))}
+ - ${esc(t('hint_all'))} (all of)
+ | ${esc(t('hint_any'))} (any of)
+ ${esc(t('hint_root'))}
+ ${esc(t('hint_status'))} - [~] Frontend:
+