Zum Inhalt

Entwicklerhandbuch

Architektur

Frontend-Request
    ├─ POST /nt-lingua/transform ─→ TransformApiMiddleware
    │        │
    │        ├─ isLanguageTarget(target) = true  ─→ TextTransformationService::translateStrings()
    │        └─ isLanguageTarget(target) = false ─→ TextTransformationService::transformField()
    └─ GET Seitenaufruf (Cookie/Param)
             └─ TransformProcessor (DataProcessing) ─→ transformField() [Server-Fallback]

TextTransformationService
    ├─ einfache / leichte  ─→ AiProviderInterface (nt_ai)    + Prompts
    └─ Sprachcodes          ─→ TranslatorInterface (LLM/DeepL) + Glossar

TextCacheRepository
    └─ tx_ntlingua_text_cache (UNIQUE: content_hash + target)

Zwei Kopplungspunkte:

  • Frontend → Server: POST /nt-lingua/transform wird von TransformApiMiddleware verarbeitet
  • Server → KI/Übersetzung: TranslatorInterface (LLM oder DeepL) + AiProviderInterface (nt-ai)

nt_lingua kennt keine konkreten KI-Provider — der einzige externe Kopplungspunkt ist Netthinks\NtAi\Provider\AiProviderInterface.

Datenmodell

tx_ntlingua_text_cache

Spalte Typ Beschreibung
content_hash varchar(64) sha256(normalize(source) \| target \| model)
target varchar(20) einfache, leichte oder Sprachcode (z. B. en)
source_lang varchar(10) Quellsprache (Standard: de)
source_text mediumtext Ursprünglicher Text
result_text mediumtext Transformierter/übersetzter Text
provider varchar(30) KI-Anbieter oder deepl
model varchar(80) Verwendetes Modell
tokens_used int Verbrauchte Token
readability decimal(5,2) Ø Wörter/Satz (niedriger = einfacher)
source_uid int UID des Quell-Datensatzes
source_table varchar(80) Tabelle des Quell-Datensatzes
source_field varchar(80) Feld des Quell-Datensatzes
crdate / tstamp int Unix-Timestamps

Unique-Key: (content_hash, target) — Einfache, Leichte und alle Sprachcodes teilen sich dieselbe Tabelle kollisionsfrei.

Cache-Invalidierung: AfterDatabaseOperationsEventInvalidateTextCache löscht alle Einträge zum betroffenen Datensatz, wenn header oder bodytext gespeichert werden.

tx_ntlingua_glossary

Spalte Beschreibung
source_term Quellbegriff (vor Vergleich normalisiert)
target_lang Zielsprache (ISO-Code) oder * für alle Sprachen
target_term Festgelegte Übersetzung (bei ignore-Modus leer lassen)
mode fixed (erzwingen) oder ignore (unverändert lassen)

Glossar-Einträge werden vor Cache und Provider ausgewertet. target_lang='*' gilt sprachunabhängig für alle DOM-Übersetzungen.

tt_content / pages (Erweiterungsfelder)

Feld Beschreibung
tx_ntlingua_mt 1 = maschinell übersetzt (readonly im Backend)
tx_ntlingua_srchash Hash der Quellfelder zum Zeitpunkt der Overlay-Erzeugung — ermöglicht Delta-Erkennung

CLI-Referenz

ntlingua:warmup

vendor/bin/typo3 ntlingua:warmup [--target=<ziel>] [--limit=<n>]
Option Standard Beschreibung
--target einfache einfache, leichte oder ISO-Sprachcode
--limit 0 Max. Datensätze (0 = alle)

Verarbeitet alle tt_content-Datensätze mit sys_language_uid=0, deleted=0, hidden=0. Cache-Hits werden übersprungen — der Befehl ist idempotent und kann als nächtlicher Scheduler-Task ausgeführt werden.

# Einfache Sprache für alle Content-Elemente vorwärmen:
vendor/bin/typo3 ntlingua:warmup --target=einfache

# Englische Übersetzung, maximal 500 Datensätze:
vendor/bin/typo3 ntlingua:warmup --target=en --limit=500

ntlingua:overlays

vendor/bin/typo3 ntlingua:overlays --language=<uid> --code=<iso>
Option Beschreibung
--language sys_language_uid der Zielsprache (muss in TYPO3-Site-Konfiguration vorhanden sein)
--code ISO-Sprachcode (z. B. en, fr)

Erzeugt verbundene l10n-Overlays (connected mode). Bei Wiederholung werden nur geänderte Datensätze verarbeitet (Delta-Erkennung via tx_ntlingua_srchash).

Übersetzte Felder:

Tabelle Felder
pages title, nav_title, subtitle, seo_title, description
tt_content header, subheader, bodytext

Warning

ntlingua:overlays im Live-Workspace ausführen, nicht in Draft-Workspaces.

JavaScript-API

// Sprache oder Modus wechseln:
NtLingua.transform('fr', buttonElement);   // DOM-Übersetzung
NtLingua.transform('einfache', btn);       // Einfache Sprache
NtLingua.transform('de');                  // Reset zur Ausgangssprache

// Aktuellen Zustand abfragen:
NtLingua.getCurrent();   // z. B. 'de', 'fr', 'einfache'

Custom Events

document.addEventListener('NtLingua:ready', (e) => {
    console.log('Initialisiert, aktives Ziel:', e.detail.target);
});
document.addEventListener('NtLingua:done', (e) => {
    console.log('Umschaltung abgeschlossen:', e.detail.target);
});

Eigenen Übersetzer hinzufügen

TranslatorInterface implementieren und in Services.yaml als Alias registrieren:

namespace MyVendor\MyExt\Translator;

use Netthinks\NtLingua\Translator\TranslatorInterface;

final class MyTranslator implements TranslatorInterface
{
    public function translateBatch(array $texts, string $sourceLang, string $targetLang): array { … }
    public function getName(): string  { return 'my-translator'; }
    public function getModel(): string { return 'my-model'; }
}
# Services.yaml der eigenen Extension:
Netthinks\NtLingua\Translator\TranslatorInterface:
  alias: MyVendor\MyExt\Translator\MyTranslator

Fluid-Wrapper (Pflicht für JS-Feldtausch)

Content-Felder müssen in FSC-Template-Overrides mit data-ntlingua-*-Attributen umschlossen werden:

<!-- bodytext (z. B. in einem EXT:fluid_styled_content Override): -->
<div data-ntlingua-uid="{data.uid}" data-ntlingua-field="bodytext">
    <f:format.html>{data.bodytext}</f:format.html>
</div>

<!-- header: -->
<div data-ntlingua-uid="{data.uid}" data-ntlingua-field="header">
    {data.header}
</div>

Fluid-Overrides unter packages/netthinks/Resources/Private/Extensions/fluid_styled_content/ anlegen.

Barrierefreiheit

  • <html lang> und dir werden bei jedem Umschalten gesetzt (WCAG 3.1.1 / 3.1.2)
  • Sprachnamen als Autonyme mit lang-Attribut je Button/Link
  • Dropdown trägt data-nt-notranslate — Sprachnamen werden beim Übersetzen der Seite nicht selbst übersetzt
  • role="status" / aria-live="polite" für Fehlermeldungen im Navbar-Bereich
  • aria-live="polite" auf dem Disclaimer-Banner
  • aria-busy="true" auf <main> während der Übersetzung (Overlay-Spinner)
  • aria-pressed auf Buttons für den aktiven Zustand; native Sprachen als Links mit aria-current
  • Native Sprachen als Links (semantisch korrekt, ohne JS nutzbar, kein WCAG-3.2.2-Problem)
  • DOM-Sprachen und Vereinfachung als Buttons (kein Seitenaufruf, reines Client-Verhalten)
  • data-ntlingua-native auf nativen Links → localStorage-Cleanup vor der Navigation
  • Voll tastaturbedienbar, sichtbarer Fokus; Widget schließt bei Escape

Bekannte Eigenheiten

PHP: Numerische String-Keys in Arrays

PHP konvertiert Array-Keys, die wie Ganzzahlen aussehen ("1999", "42"), automatisch zu int. In TextTransformationService::translateStrings() kann das im foreach ($missing as $orig => $_)-Loop dazu führen, dass $orig ein int ist, obwohl normalize(string $s) einen string erwartet. Fix: $orig = (string)$orig; am Beginn des Loops.

DeepL: source_lang akzeptiert keine Locale-Varianten

DeepLs source_lang-Parameter akzeptiert nur Basiscodes (EN, DE), keine Locale-Varianten (EN-US, DE-AT). Das <html lang>-Attribut kann Locale-Varianten enthalten (z. B. en-US bei locale: en-US in der TYPO3-Site-Konfiguration). DeepLTranslator kürzt den Source-Code mit explode('-', $sourceLang)[0].

Chrome Autotranslate blockiert den TreeWalker

Chromes eingebauter Übersetzer setzt class="notranslate" auf das <html>-Element. Die ignored()-Funktion stoppt explizit bei document.documentElement und prüft das <html>-Element selbst nicht — andernfalls würde der gesamte Seiteninhalt als ausgeschlossen gewertet und der TreeWalker lieferte einen leeren Text-Pool.

Zwei Sprachwähler-Instanzen (Mobile + Desktop)

Das Partial wird zweimal gerendert (Mobile und Desktop, CSS-gesteuert). setToggleLabel() nutzt querySelectorAll('.nt-lingua-active-label'), um beide Toggle-Buttons synchron zu halten.