Algroveon-Mini-SSG – Technische Architektur des Generators
Wie Algroveon-Mini-SSG intern funktioniert: Template-System, Mehrsprachigkeit, Build-Pipeline und die technischen Entscheidungen hinter dem statischen Site-Generator.
Zusammenfassung
Die technische Architektur des Algroveon-Mini-SSG basiert auf einer modularen Trennung zwischen zentralen Themes und seitenindividuellen Konfigurationen. Durch den Einsatz von Jinja2 und einem speziellen ChoiceLoader ermöglicht das System flexible Template-Overrides sowie eine einfache Verwaltung von Layouts via Frontmatter. Die Struktur integriert zudem eine dedizierte Build-Engine und eine LLM-gestützte Übersetzungs-Engine.
Diese Zusammenfassung wurde mit KI-Unterstützung erstellt.
Einleitung
Der Entwicklertagebuch-Artikel hat die Geschichte hinter Algroveon-Mini-SSG erzählt. Hier geht es nicht mehr um die Entstehung, sondern um den technischen Unterbau: Wie ist das System aufgebaut? Warum wurde das Template-System genau so entworfen? Wie funktioniert die Übersetzungs-Engine im Detail? Und warum ist die Admin-UI serverseitig gerendert statt als React-App gebaut? Genau darum geht es hier.
Die Architektur im Überblick
sites/<site>/
site.yaml ← Konfiguration, Navigation, Theme-Zuweisung
content/<lang>/ ← Markdown-Inhalte, nach Sprache sortiert
static/ ← CSS, Bilder, Fonts
templates/ ← site-spezifische Template-Overrides (optional)
_site/ ← Build-Output (reines HTML)
themes/<theme>/ ← geteilte Jinja2-Templates (site-übergreifend)
build.py ← Build-Engine
admin.py ← Flask-Admin-Server
translate.py ← LLM-Übersetzungs-Engine
Die Trennung zwischen themes/ und sites/ ist bewusst so angelegt. Ein Theme kann von mehreren Sites gemeinsam genutzt werden. Site-spezifische Anpassungen liegen in sites/<site>/templates/ und überschreiben das Theme, ohne dass das Original angefasst werden muss. Aufgelöst wird das über den Jinja2-ChoiceLoader: Zuerst wird im site-spezifischen Ordner gesucht, danach im Theme.
Das Template-System
Layouts per Frontmatter
Jede Markdown-Datei hat ein layout:-Feld im Frontmatter:
---
layout: post
title: "Mein Artikel"
---
build.py sucht das passende Template (post.html) über den ChoiceLoader. So lassen sich pro Site beliebig viele Layouts verwenden – post, project, page, home, blog-index, 404 und weitere – jeweils als einfache Jinja2-HTML-Dateien. Das ist absichtlich schlicht gehalten und genau deshalb flexibel.
Template-Kontext
Jedes Template bekommt zwei Objekte:
page– das gerenderte Dokument:page.title,page.content(HTML),page.date,page.stack, alle Frontmatter-Feldersite– die Site-Konfiguration:site.title,site.nav,site.url,site.author
Zusätzlich stehen all_pages (alle gebauten Seiten als Liste) und collections (nach Tags gruppierte Seiten) zur Verfügung. Das ist vor allem für Übersichtsseiten, Archivseiten oder Sitemaps nützlich.
Site-spezifische Overrides über den Admin
Im Design-Tab der Admin-UI lassen sich Templates direkt im Browser bearbeiten. Beim Speichern schreibt die Admin-UI die geänderte Datei nach sites/<site>/templates/<template>.html. Beim nächsten Build hat diese Datei Vorrang vor dem Theme-Original. Dahinter steckt keine besondere Magie, sondern schlicht der ChoiceLoader, der site-lokale Pfade zuerst durchsucht.
Mehrsprachigkeit: URL-Struktur und Fallback
URL-Schema
Die URLs folgen dem Schema /<lang>/pfad.html, also zum Beispiel /de/blog/mein-artikel.html und /en/blog/my-article.html. Das ist keine bloße Konvention, sondern eine bewusste Architekturentscheidung: Beide Sprachversionen existieren als eigenständige HTML-Dateien. Es gibt keine clientseitige Sprachumschaltung und kein JavaScript-Routing. Genau das hält das System klar, nachvollziehbar und robust.
Fallback-Logik
Fehlt eine Übersetzung, greift der Build auf die Ausgangsdatei zurück, statt eine 404-Seite zu erzeugen. Gesteuert wird das über ein source_hash-Feld im Frontmatter, das aus dem Übersetzungsprozess stammt.
Datum-Parsing
Dateinamen nach dem Muster 2024-11-17-artikel.md werden automatisch als Datum erkannt. Frontmatter-Datumsfelder haben dabei Vorrang. Das parse_date-Utility unterstützt unter anderem:
- ISO:
2024-11-17 - Europäisch:
17.11.2024,17-11-2024 - Britisch/Amerikanisch:
17/11/2024,11/17/2024 - Langtext DE:
17. November 2024 - Langtext EN:
November 17, 2024/17 November 2024
Die Monatsnamen werden aus locales/*.yaml aufgebaut. Neue Sprachen lassen sich dadurch vergleichsweise einfach ergänzen: Locale-Datei hinzufügen, fertig.
Die Übersetzungs-Engine
Provider-Erkennung
translate.py unterstützt drei Provider, die anhand der konfigurierten API-URL automatisch erkannt werden:
- Ollama (lokal) – Port 11434 oder keine URL → native Ollama API (
/api/chat,/api/generate) - OpenAI –
openai.comin der URL → openai-Paket, direkte API - OpenAI-kompatibel – alles andere → openai-Paket mit eigener
base_url(zum Beispiel Mistral, LM Studio oder lokale Endpoints)
Die Konfiguration liegt in configuration.yaml (versioniert) und configuration.local.yaml (gitignored, für API-Keys und interne URLs). Das lokale File überschreibt dabei die Basis-Konfiguration.
Blockweise Übersetzung
Der Inhalt wird nicht als Ganzes an das Modell geschickt. Stattdessen wird der Markdown-Body in Blöcke zerlegt und blockweise übersetzt. Das ist in der Praxis deutlich kontrollierbarer und hat mehrere Vorteile:
- der Kontext bleibt überschaubar, was bei längeren Artikeln meist zu stabileren Ergebnissen führt
- Codeblöcke (umschlossen von
```) werden übersprungen und nicht übersetzt - Frontmatter-Felder (
title,descriptionund alle intranslate_fieldsdefinierten Felder) werden separat verarbeitet KEEP_FIELDS(zum Beispiellayout,status,stack,date) bleiben unverändert erhalten
Veralterungs-Erkennung via SHA-256
Jede übersetzte Datei bekommt ein source_hash-Feld im Frontmatter eingetragen:
source_hash: a3f7c12e4b8d9e01
Dabei handelt es sich um einen SHA-256-Hash über den Body der Quelldatei plus alle übersetzten Frontmatter-Felder, gespeichert in Form der ersten 16 Hex-Zeichen. Ändert sich die Quelldatei, erkennt translate.py beim nächsten Lauf die Übersetzung als veraltet und übersetzt nur diese Datei neu. Dateien mit unverändertem Hash werden übersprungen. Genau dadurch bleibt der Prozess effizient, ohne unübersichtlich zu werden.
Das --rehash-Flag schreibt den Hash in bestehende Dateien, ohne einen LLM-Aufruf auszulösen. Das ist vor allem nach manuellen Korrekturen nützlich, wenn eine Übersetzung bewusst als aktuell markiert werden soll.
No-Thinking-Modus
Reasoning-fähige Modelle wie Qwen3, QwQ oder DeepSeek-R1 erzeugen vor der eigentlichen Antwort oft einen <think>-Block. Für reine Übersetzungsaufgaben bringt das in der Praxis keinen wirklichen Mehrwert, verlängert aber die Laufzeit teils deutlich. Das --no-thinking-Flag sendet deshalb einen Systemhinweis, der dieses Verhalten unterbindet. Online-APIs, die damit nichts anfangen können, ignorieren die Option einfach.
VRAM-Management bei Ollama
Wenn auf einer lokalen Ollama-Instanz mehrere Modelle geladen sind, fordert translate.py vor dem ersten Übersetzungsaufruf eine VRAM-Bereinigung an: Alle Modelle außer dem konfigurierten Übersetzungsmodell werden entladen (keep_alive: 0). Gerade auf Systemen mit begrenztem VRAM hilft das, Out-of-Memory-Fehler zu vermeiden.
Live-Output via SSE
Der Übersetzungsprozess läuft aus der Admin-UI heraus als Subprocess. Der Output wird per Server-Sent Events (SSE) zeilenweise an den Browser gestreamt. Ein Keepalive-Ping hält die Verbindung offen, während das Modell arbeitet. Der praktische Effekt ist simpel: Der Browser zeigt den Fortschritt und die übersetzten Abschnitte direkt an, ohne Polling. Gerade bei längeren Läufen macht das einen spürbaren Unterschied.
Die Admin-UI: SSR statt SPA
Die Entscheidung
Die Admin-Oberfläche ist serverseitig gerendert, auf Basis von Flask, ergänzt um Vanilla-JavaScript für die interaktiven Teile. Also keine React-App, kein Vue und kein zusätzlicher Frontend-Build-Step.
Das war eine bewusste Entscheidung. Eine SSR-Anwendung ist in so einem Projekt einfacher zu debuggen, schneller weiterzuentwickeln und robuster gegen den typischen Overhead moderner Frontend-Frameworks, die regelmäßig neue Abhängigkeiten und Breaking Changes mitbringen. Ein Admin-Interface, das ich selbst nutze, braucht keine vollwertige SPA-Architektur.
Der Trade-off ist klar: Das Interface reagiert nicht ganz so direkt wie eine SPA. In der Praxis reicht die Kombination aus fetch und gezielten DOM-Updates für die wichtigen Bereiche aber völlig aus – etwa beim Datei-Explorer, beim Editor-Inhalt oder beim Build-Output-Stream.
Sicherheits-Header
Die Admin-UI ist nicht für den öffentlichen Internetzugang gedacht, sondern für die Nutzung im lokalen Netzwerk. Trotzdem werden konservative HTTP-Security-Header gesetzt:
X-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-Policy– nur eigene Ressourcen, keine externen CDNs. Fonts und Icons liegen lokal vor.
Path-Traversal-Schutz
Alle Dateioperationen – also Lesen, Schreiben und Löschen – laufen durch safe_path(). Diese Funktion löst den Pfad auf und prüft per relative_to(), ob er innerhalb des Projektordners liegt. Versuche, per ../../ aus dem Projektverzeichnis auszubrechen, werden damit abgefangen.
Deploy: MD5-Sync, FTP und SFTP
Der Deploy-Prozess arbeitet inkrementell. Vor dem Upload wird für jede lokale Datei ein MD5-Hash berechnet. Auf dem Remote-Server werden die vorhandenen Dateien ebenfalls gehasht. Hochgeladen werden nur Dateien, deren Hash abweicht. Das reduziert Upload-Zeit und Datenmenge spürbar, vor allem bei Sites mit wenigen Änderungen.
SFTP läuft über paramiko. Das Passwort wird im OS-Schlüsselbund gespeichert (keyring) – unter macOS in der Keychain, unter Linux im Secret Service. Es liegt damit nicht im Klartext in einer Konfigurationsdatei.
FTP ist zusätzlich über die Standardbibliothek (ftplib) verfügbar, also ohne weitere Abhängigkeiten. Das ist vor allem für Hoster relevant, die bis heute kein SFTP anbieten.
Ein Verbindungstest-Button in den Einstellungen prüft Erreichbarkeit und Zugangsdaten vor dem eigentlichen Deploy. Das spart unnötige Fehlersuche, wenn am Ende schlicht die Zugangsdaten nicht stimmen.
Fazit
Algroveon-Mini-SSG ist an vielen Stellen bewusst schlicht gehalten, an anderen Stellen aber technischer, als es von außen vielleicht wirkt. Die Übersetzungs-Engine mit Hash-basierter Veralterungs-Erkennung, automatischer Provider-Erkennung und VRAM-Management ist mehr als nur ein kleines Hilfsskript. Auch das Template-System mit ChoiceLoader und site-spezifischen Overrides ist kein Zufallsprodukt, sondern bewusst so gebaut. Die Entscheidung für SSR in der Admin-UI war aus meiner Sicht ebenfalls richtig.