Skip to content

Changelog

Changelog

[1.4.5]

Upgrade: no database changes; cache:flush is enough (new JavaScript).

Fixed

  • Slider content switched back to the first foreign language. The observer that translates nodes added by scripts kept the language it was started with. A looping slider re-appends its slides, so after es → it the slider headings returned to Spanish within seconds (seen live on the start page). Added nodes now follow the language selected last, and a late answer for a language the visitor has already left is cached but no longer written into the page.

[1.4.4]

Upgrade: no database changes; cache:flush is enough (new JavaScript).

Fixed

  • Switching between two foreign languages left segments in the previous one. A segment without a translation for the new target — not cached in cache-only mode (HTTP 429), or a failed request — kept the text it showed, so after es → it uncached headings stayed Spanish between Italian ones. Such segments, attributes and the document title now fall back to the source text. Translations from the browser cache still apply.

Added

  • Browser test Tests/JavaScript/dom-switch.cjs (Playwright).

[1.4.3]

Upgrade: no database changes; cache:flush is enough. Ukrainian and Serbian results translated by the LLM before this release are wrong in many places and stay in the cache, because its key does not include the prompt. After the update, purge and warm them again:

vendor/bin/typo3 ntlingua:cache-purge --target=uk,sr --model=gpt-4o-mini
vendor/bin/typo3 ntlingua:warmup-dom --target=uk,sr

Leave out --model to purge them regardless of the model (DeepL results included); --dry-run only counts.

Fixed

  • The LLM prompt named languages only by code. "Übersetze … von de nach uk" was read as United Kingdom: on one site about 566 of ~790 cached Ukrainian entries were English, and Serbian came back in either script. Source and target language are now named in German, in their own language and by code, e.g. "nach Ukrainisch (Українська, Sprachcode uk)"; for Serbian the script is fixed to Cyrillic, matching the menu label "Српски" (also: Bosnian Latin, Chinese simplified characters). Codes outside the catalog are named via \Locale::getDisplayLanguage() when intl is installed, otherwise by code.
  • The loading overlay and the status line were translated as page text. Both show "Traduction en français …" while a language loads; the next switch collected that text and sent it for translation — a 429 each time in cache-only mode. Both now carry data-nt-notranslate; the status line is marked by the script as well, since sites bring their own switcher markup.

Added

  • ntlingua:cache-purge --target=<codes> [--model=<model>] [--dry-run] removes cached translations of the given target languages, for results that are wrong although the call succeeded.

Changed

  • One language catalog (Localization\LanguageCatalog) for the DOM languages of the menu, the choice in the site configuration and the names in the LLM prompt, instead of two copies; it adds the German name of every language.

[1.4.2]

Upgrade: no database changes; cache:flush is enough.

Fixed

  • LLM chunks with a wrong-length answer stayed untranslated for good. The model merges neighbouring fragments (list items, sentence parts split by a line break), so the answer for a chunk of 20 strings had fewer elements; the whole chunk became null and the identical resend in the next warmup failed the same way every night (seen on a statute page with 18 segments missing in nearly all target languages).
  • A chunk whose answer arrived but does not fit — no JSON array, another length, or an element that is not text — is now halved and the halves are sent again, until the answer fits or a single string is left. A single string that still fails stays null and sets lastError() (e.g. "answer count mismatch (2 for 1)"), so the reason shows in the warmup log.
  • Only the chunks that miscounted are split; the halves run through completeMany() when the provider supports it.
  • Transport failures (exception, empty answer) are not split, so an outage does not multiply the number of requests.
  • Each additional request is reserved on its own with the characters of its half. When the budget refuses, splitting stops, translations already received are kept and nothing is thrown.
  • An element that is an array no longer reaches a (string) cast, which raised "Array to string conversion" (an exception in the Development context).
  • The system prompt now asks to translate every element on its own, sentence fragments and list items included, and never to merge, split or drop elements.

[1.4.1]

Upgrade: no database changes; cache:flush is enough.

Fixed

  • The page uid never reached the browser. page.jsFooterInline is rendered through cObjGetSeparated(), which only takes numeric keys; the named key ntLinguaPageUid was silently dropped. The DOM translation then answered "invalid page" on every site whose template does not render data-page-uid itself. Now rendered under the numeric key 73810 (reported by Stadt Villingen-Schwenningen).
  • lib.ntLinguaExclude had no effect, for the same reason (now key 73811), and because the widget read the value while loading — before the inline script after it had set it. It is now read when translating, like the page uid.

[1.4.0]

Upgrade: no database changes; cache:flush is enough. Two new extension settings (triggerMode, botProtection), both backwards compatible; bots no longer trigger Einfache/Leichte Sprache by default.

Findings reported by Stadt Villingen-Schwenningen after going live.

Fixed

  • Error page on parallel conversions. TextCacheRepository::put() deleted and re-inserted the entry; when two requests converted the same text at the same time, the second insert hit the unique key and the visitor got an error page. The conflict now updates the entry.
  • One failing passage broke the whole page. The Einfache-Sprache middleware and TransformProcessor now keep a passage in its original wording on any error (logged), instead of only on an exhausted budget.
  • Single text nodes added by scripts were never translated — the DOM walker only visited the descendants of an added node.

Added

  • Crawler protection for Einfache/Leichte Sprache. Search and AI crawlers, HTTP libraries and requests without a browser user agent never trigger a conversion (botProtection, on by default); in DOM translation they get cache hits only. A page with ?ntlingua= is sent with X-Robots-Tag: noindex, nofollow, a converted page additionally with Cache-Control: private, no-store.
  • triggerMode: any (parameter or cookie, as before), cookie (followed ?ntlingua= links do nothing) or signed (only a cookie the widget obtained from the server; it is signed with the encryption key and expires after 30 days). The widget now always fetches the cookie through the API ({"setMode": "einfache"}).
  • Exclusions by configuration: lib.ntLinguaExclude.value = .social-wall, #chat (CSS selectors) keeps areas out of the DOM translation, in addition to translate="no" in the markup.
  • Cache hits in the log: one row per day and language (action = cache, chars_in characters, chars_out segments).

Changed

  • Fewer DeepL characters. Text is sent with whitespace folded (template indentation had made up about 42 % of the billed characters at the reporting site); the node keeps its own outer whitespace. Text without letters (phone numbers, dates, page numbers) is not sent. The log now records the characters actually sent.
  • Content loaded later (sliders, lazy loading) is collected for 150 ms and sent in one request; a text already requested is not requested again while the first request runs; copies of translated nodes (slider clones) are recognised as known translations and not sent as "German" source again.

[1.3.0]

Fixed

  • Ein fehlgeschlagener Übersetzungsversuch wird nicht mehr als Ergebnis zwischengespeichert. Lieferte ein Chunk keine oder eine unpassend lange Antwort, gab LlmTranslator dafür den Quelltext zurück; DeepLTranslator tat dasselbe bei fehlendem Schlüssel und bei abweichender Trefferzahl. Für den Aufrufer war das von einer korrekten Übersetzung nicht zu unterscheiden — er schrieb den deutschen Originaltext in den Cache, und von da an beantwortete der Cache jede weitere Anfrage damit. Der Versuch wurde nie wiederholt, die Stelle blieb dauerhaft deutsch. Auf einer betroffenen Installation waren das 300 Segmente: ganze Absätze auf Türkisch und Arabisch, auf Französisch die gesamte Hauptnavigation. Besonders tückisch, weil der Endpunkt Erfolg meldete und die Seite vollständig aussah.

Das ergänzt die mit 1.2.0 eingeführte $partial-Behandlung: Jene erkennt an einem unverändert zurückgekommenen Text, dass er nach einer Budget-Ablehnung vermutlich nie gesendet wurde. Ein gescheiterter oder unpassend langer Chunk wird jetzt nicht mehr vermutet, sondern vom Übersetzer als solcher gemeldet.

  • Die Extension lief auf TYPO3 12.4 und 13.4 überhaupt nicht. Der EventListener ProtectSourceTerms griff mit getContent()/setContent() auf Methoden zu, die AfterCacheableContentIsGeneratedEvent erst in TYPO3 14 hat. Auf einer 13.4 brach damit jede Seitenauslieferung mit „Call to undefined method" ab, sobald die Extension installiert war — obwohl composer.json ^12.4 || ^13.4 || ^14.0 angibt. Der Listener unterscheidet die Fälle jetzt und holt den Inhalt auf 12.4/13.4 über den TypoScriptFrontendController.

Added

  • Ein Übersetzungsausfall ist von außen erkennbar. Bisher antwortete /nt-lingua/transform auch bei vollständigem Ausfall mit 200 und lieferte die Quelltexte aus — weder eine Überwachung noch das Log im Backend konnten die Störung bemerken. Die Antwort enthält nun untranslated und degraded, dazu die Kopfzeile X-NtLingua-Untranslated für eine Prüfung ohne JSON-Auswertung. Ein vollständiger Ausfall antwortet mit 503, ein Teilausfall bleibt 200 — der übersetzte Teil ist ja brauchbar. Für die Anzeige ändert sich nichts: Das Client-Skript wertet den Status nicht aus, die Seite bleibt in der Ausgangssprache. Jeder Ausfall wird zusätzlich im Übersetzungs-Log vermerkt.
  • Der Log nennt den Grund eines Ausfalls. LlmTranslator fing jede Ausnahme des Anbieters ab und verwarf sie; im Log stand danach nur, dass Segmente fehlen. Ein nicht hinterlegter Schlüssel, ein widerrufener Schlüssel und ein nicht erreichbarer Anbieter sahen gleich aus. Die Meldung wird jetzt an den Log-Eintrag angehängt, etwa „(Anthropic API key is not configured.)". In die API-Antwort gelangt sie bewusst nicht — Anbietermeldungen sind für den Betreiber, nicht für anonyme Aufrufer.
  • TextTransformationService::translateStrings() gibt die Anzahl über den optionalen Parameter &$untranslated zurück. Bestehende Aufrufe bleiben gültig.

Changed

  • TranslatorInterface::translateBatch meldet ein nicht übersetztes Segment als null statt als Quelltext. Der Quelltext wird weiterhin ausgeliefert — Seite und Datensatz bleiben vollständig —, aber er wird nicht gespeichert, sodass der nächste Aufruf es erneut versucht. Eigene Implementierungen der Schnittstelle müssen angepasst werden.
  • Bereits zwischengespeicherte Fehlschläge lassen sich einmalig entfernen mit DELETE FROM tx_ntlingua_text_cache WHERE result_text = source_text;

[1.2.1]

Fixed

  • %env(NAME)% for the DeepL API key now works. The documentation has listed the placeholder for deepLApiKey, but the value was never resolved: the placeholder text itself was sent to DeepL as the key, every request was rejected and translation fell back to the original text. The placeholder is now replaced by the environment variable. A variable that is not set counts as "no key configured" (LLM translator is used) instead of producing failing requests. Keys entered directly keep working; surrounding whitespace is trimmed.

[1.2.0]

Security

  • Every resend of a provider request is paid from the public budget. TextTransformationService reserved the budget once before handing a batch to the translator. LlmTranslator splits batches into chunks of 20, sends them concurrently and resends every chunk whose answer came back empty — those resends went out without a reservation (an in-memory reproduction: 21 strings produced two concurrent calls plus two unbudgeted resends). The same applied to DeepL's resends after HTTP 429 and to the LLM fallback after DeepL HTTP 456. The budget is now passed down to the translators (BudgetAwareTranslatorInterface), which reserve right before each send — every chunk, every resend. Without budget left nothing is resent (and DeepL does not wait out its back-off); entries left untranslated are neither returned nor cached. Direct LLM use and the DeepL fallback both stay within the per-client and global caps; cache hits still cost nothing. Backend, CLI and scheduler pass no budget and keep the previous behaviour.
  • Limit for concurrently running public provider requests. Character budgets capped the cost but not how many workers and provider connections anonymous requests could tie up at once. A public request now takes one of maxConcurrentProviderRequests installation-wide slots on its first reservation and frees it when it is done. Slots are rows in tx_ntlingua_ratelimit, taken only by a conditional UPDATE (or the INSERT creating them), and expire after concurrencySlotTimeout seconds so a crashed request cannot block a slot for good. When all slots are taken the API answers HTTP 503 with Retry-After and still returns cache hits; the GET path keeps the original wording. Requests served entirely from the cache never need a slot. Race test against MariaDB with 30–40 parallel processes: exactly the configured number of slots granted, every run.

Added

  • Extension settings maxConcurrentProviderRequests (default 4, 0 = no limit) and concurrencySlotTimeout (default 120 seconds).
  • BudgetAwareTranslatorInterface for translators that send more than once per batch; plain TranslatorInterface implementations keep working and are reserved once per batch.
  • Tests for budgeted resends (direct LLM and DeepL fallback, with and without budget left, global caps, cache hits), DeepL 429 resends, the concurrency slots and the 503 answer; race test Tests/Concurrency/concurrency-slots-race.php.

Changed

  • BudgetExceededException is no longer final; ConcurrencyLimitExceededException extends it so all existing refusal paths apply.
  • PublicUsageLimits::budgetFor() returns RateLimitedProviderBudget; callers call release() when their provider work is done (the destructor does it as a fallback).

Upgrade notes

  • No schema change: the slots use the existing table tx_ntlingua_ratelimit. Flush caches after the update (new classes and settings).
  • Accepted limitation, unchanged: domTranslationCacheOnly stays off by default. As long as it is off, DOM translation translates arbitrary rendered text within the budgets, and a caller can use up the global budget for all visitors until the window ends. Budgets and slots bound cost and load; they do not bind the text to the site's content. This is documented as an accepted limitation, not as a fix.

[1.1.0]

Security

  • The transform API honours page visibility. POST /nt-lingua/transform loaded tt_content fields by uid with frontend restrictions on the record only. A visible element on a hidden, scheduled or group-protected page, in a branch closed with "extend to subpages", on a folder or in another site could be read and sent to the provider. The new ContentAccessGuard applies the frontend's own rules (PageRepository, RootlineUtility, RecordAccessVoter) to the page and its rootline, checks site and language, and runs before any text reaches a provider. The middleware now runs after frontend authentication so group access of logged-in users keeps working.
  • Provider answers can no longer inject markup. Whether an answer was sanitized depended on the source containing <; for plain sources it was only entity-decoded — encoded markup became real markup — and then inserted via innerHTML (field mode) or parsed as HTML (?ntlingua= GET path). Now: HTML sources are always sanitized against the allowlist, plain sources yield plain text with tags removed, the API states the output format, nt-lingua.js assigns text via textContent, and the GET path escapes the model's text before restoring the page's own tags. Existing cache entries are sanitized again on every output instead of being invalidated — no provider cost, and safe entries come out unchanged.
  • Cost limits cover every public provider call and hold under parallel load. The GET path (?ntlingua=einfache|leichte or cookie) and the TransformProcessor ran without any limit; the field mode only estimated costs. A budget is now reserved right before each provider call — POST, GET, and the LLM fallback after DeepL HTTP 456 — with a per-client allowance and new installation-wide hourly and daily caps that cannot be bypassed by switching IP addresses. IPv6 clients are counted per /64. The counters moved from a read-compare-write cache entry to the table tx_ntlingua_ratelimit, updated by a single conditional UPDATE; in a test with 20 parallel processes the old logic granted 160–487 reservations against a limit of 100, the new one exactly 100.
  • DOM translation is bound to a page of the site. Requests must carry the uid of a page the visitor can open in the current site (pageUid, sent by nt-lingua.js automatically); otherwise HTTP 400.

Added

  • Extension settings rateLimitGlobalCharsPerHour (default 500000) and rateLimitGlobalCharsPerDay (default 2000000).
  • Extension setting domTranslationCacheOnly (default off): public DOM translation is served from the cache only, new segments are translated by the DOM warmup task. This is the only mode in which anonymous callers cannot have arbitrary text translated.
  • Functional test suite (SQLite, in-process fake providers) and a race test for the counters against MariaDB (Tests/Concurrency/rate-limiter-race.php).

Changed

  • Exhausted budgets answer with HTTP 429 but still return cache hits; the GET path keeps paragraphs it cannot simplify in their original wording.
  • Field mode accepts at most 100 items per request (was 600).

Upgrade notes

  • Run vendor/bin/typo3 extension:setup (creates tx_ntlingua_ratelimit) and flush caches. The former cache ntlingua_ratelimit is no longer used; its cache_ntlingua_ratelimit* tables can be dropped via the database analyzer.
  • Known limitation: DOM translation translates rendered segments that do not exist as records, so without domTranslationCacheOnly arbitrary text can still be translated within the budgets. Pages that echo user input inside <main> (e.g. search results) should mark that area with data-nt-notranslate.

[1.0.21]

Fixed

  • A failing translator no longer aborts saving the record. The DataHandler hook wrapped its translation loop in try … finally with no catch, so any exception from the translator escaped into DataHandler and ended the whole write. On live this surfaced as a 403 Forbidden from DeepL (revoked key) that broke an impexp:import and left half-created records behind, but it applies to every outage — exhausted quota, network failure, provider down. A translation that does not happen is a missing translation, never a reason to lose the record: the failure is now logged, reported to the editor as a flash message, and saving continues.
  • An invalid API key is reported as such. DeepL answers 401/403 when a key is revoked, expired or disabled. Instead of passing the raw HTTP error on, the translator now raises a message naming the cause and the remedy. These codes are deliberately not retried — a second attempt does not make a key valid; only 429 (throttling) is retried, 456 remains the quota exception.

Added

  • Extension setting autoTranslateOnSave (default on) to switch off translate-on-save entirely. Bulk operations — imports, migrations, mass editing — write hundreds of records, where one translation request per record is slow and burns quota. Until now the only way out was the per-record field tx_ntlingua_exclude. The per-site toggle in the site configuration keeps working as before; this switch sits above it.

[1.0.20]

Fixed

  • Interne Links überlebten die Übersetzung nicht. RTE-Inhalte speichern Verweise als t3://page?uid=…. Der HTML-Sanitizer, der die Antwort des Übersetzungsanbieters säubert, lässt in href nur http, https, mailto, tel und lokale Pfade zu — jedes andere Schema verliert das Attribut. Aus <a href="t3://page?uid=508">Ziel</a> wurde damit in jeder Übersetzung <a>Ziel</a>: Der Text stand da, das Ziel war weg. Betroffen waren native Overlays ebenso wie Einfache/Leichte Sprache — unbemerkt, weil ein Link ohne href im Frontend wie Fließtext aussieht.

Der URI wird jetzt vor dem Übersetzen auf eine reservierte .invalid-Adresse abgebildet und danach zurückgesetzt (Typo3UriProtector). Der Sanitizer bleibt eng eingestellt — er säubert fremde Ausgabe und sollte keine zusätzlichen Schemata erlauben. Nebeneffekt: Der Anbieter bekommt den URI URL-kodiert und übersetzt keine Wörter mehr darin.

Bereits zwischengespeicherte Übersetzungen aus der Zeit davor werden erkannt und verworfen, statt weiter ausgeliefert zu werden: Enthält die Quelle mehr verlinkte Anker als der Cache-Treffer, wird neu übersetzt.

[1.0.19]

Fixed

  • Batch-Übersetzungs-Modul zeigte Zielsprachen mehrfach (z. B. „English (en)" dreimal): getTranslationLanguages() sammelte die Sprachen über ALLE Sites (getAllSites()), sodass in einer Multi-Site-Installation dieselbe Sprach-ID je Site einmal erschien. Die Liste wird jetzt nach Sprach-ID dedupliziert — jede Zielsprache erscheint genau einmal.

[1.0.18]

Added

  • Öffentliche API NtLingua.translateNode(node): übersetzt einen dynamisch gerenderten Teilbaum (z. B. die Suchergebnisse von nt-search) in die aktuell aktive DOM-Sprache; no-op ohne aktive Sprache.

Fixed

  • Leerzeichen an Inline-Element-Grenzen blieben erhalten: Der Server trimmt Segmente — bei Textknoten neben <strong>/<mark> u. ä. verschwand das Leerzeichen an der Naht („TYPO3 Hosting" → „TYPO3Hébergement"). apply() stellt führendes/abschließendes Whitespace des Quellknotens wieder her.

[1.0.17]

Fixed

  • ntlingua:warmup-dom: Bei leerem Site-Feld nt_lingua_dom_languages greift jetzt derselbe Fallback wie im Frontend (alle mode:'dom'-Einträge aus Languages.php) — vorher wurde die Site übersprungen, obwohl die Sprachbuttons angezeigt werden.

[1.0.16]

Added

  • Neuer CLI-Command ntlingua:warmup-dom (Scheduler-fähig): wärmt den DOM-Übersetzungs-Cache mit den echten Frontend-Segmenten — er rendert jede Seite aus der XML-Sitemap wie ein Besucher und extrahiert Textknoten + title/alt/placeholder/aria-label + Seitentitel nach denselben Regeln wie nt-lingua.js collect() (inkl. translate="no"-/notranslate-Ausschluss). Damit treffen Browser-Anfragen den Cache wirklich — der bestehende ntlingua:warmup wärmt nur rohe tt_content-Felder und erzeugt für den DOM-Pfad unbrauchbare Schlüssel (andere Granularität, Navigation/Footer/ ContentBlocks fehlen). Optionen: --target (Standard: Site-Feld nt_lingua_dom_languages), --site, --sitemap (explizite URL bei Redirect-Setups), --limit, --sleep, --dry-run. Bereits gecachte Segmente kosten nichts — geplante Läufe übersetzen nur Neues/Geändertes.

[1.0.15]

Added

  • DOM-Übersetzung: Der Seitentitel (document.title) wird jetzt mitübersetzt — Browser-Tab und Verlaufseintrag erscheinen in der gewählten Sprache; beim Zurücksetzen wird der Originaltitel wiederhergestellt. Läuft über denselben Cache wie die übrigen Segmente (Server + Client), kostet also nach dem ersten Mal keine weiteren Tokens.

[1.0.14]

Changed

  • DOM-Übersetzung: Das Fortschritts-Overlay („Übersetze …") erscheint jetzt in der Zielsprache (fr/es/it/hu/en/pt/nl) statt fest auf Deutsch. Fallback auf die deutsche Phrase mit Sprachnamen für nicht gelistete Codes. einfache/leichte bleiben auf Deutsch.

[1.0.13]

Fixed

  • DOM-Übersetzung: Kaskaden-Bug behoben. Ein Wechsel Sprache A → B sammelte den bereits übersetzten DOM (Sprache A) und schickte ihn als Quelle → jeder Sprache-zu-Sprache-Wechsel war ein garantierter Cache-Miss (A→B war nie gewärmt) und traf live das LLM (8–10 s je Umschaltung). Es wird jetzt immer aus dem gespeicherten deutschen Original übersetzt (origOf() statt des aktuell angezeigten Textes).

[1.0.12]

Added

  • Client-Cache (localStorage) pro Zielsprache, Schlüssel = Quelltext (nicht URL): geteilte Chrome (Navigation/Footer) und wiederkehrende Inhalte werden einmal für die ganze Site gecacht und bei jeder Folgeseite ohne Roundtrip angewandt. Selbstinvalidierend (Schlüssel = Quelltext) plus Deploy-Stempel und 14-Tage-TTL.
  • LLM-Fallback: Der Batch wird in 20er-Chunks zerlegt und — sofern der Provider es unterstützt — nebenläufig ausgeführt (nt-ai completeMany() via curl_multi). Rund 2× schneller.

Fixed

  • LLM-Fallback: robuster Per-Chunk-Fallback statt „all or nothing". Bisher wurde bei einer Zähl-Diskrepanz der GESAMTE Batch als Originaltext zurückgegeben und so unübersetzt gecacht (ganze Sprachen „vergiftet", z. B. Ungarisch). Jetzt betrifft ein Fehler nur einen 20er-Chunk, mit sequentiellem Retry pro leerem Slot.
  • Whitespace-Trim der LLM-Ausgabe — kein \n…-Müll mehr im Cache.
  • normalize() faltet jetzt auch nbsp (U+00A0) und weitere Unicode-Leerzeichen; \s matcht in PCRE nur ASCII → sonst entstanden Fast-Duplikate im Cache. Beide normalize()-Kopien (TextTransformationService + GlossaryRepository) angeglichen.

[1.0.11]

Fixed

  • Performance: translateStrings() bündelt die Cache-Lookups in einen Batch-Query (TextCacheRepository::getMany(), content_hash IN (…)) statt einem Query pro Segment. Gegen die entfernte Live-DB waren das hunderte sequentielle Roundtrips → sekundenlanger, „hängender" Spinner bei Volltext-Übersetzung (128 gecachte Segmente: 12 s → ~1 s).

[1.0.10]

Fixed

  • Glossar: rootLevel = -1 ergänzt — Einträge lassen sich jetzt auch auf Root-Level (pid=0) anlegen, nicht nur auf Seiten.

[1.0.9]

Fixed

  • Container-Übersetzung (Korrektur zu 1.0.8): Das tx_container_parent der Kind-Overlays zeigt jetzt auf das Default-Sprach-Container-Record statt auf das übersetzte Container-Overlay — so verlangt es b13/container im Connected-Mode (vgl. container:fixContainerParentForConnectedMode). 1.0.8 hatte auf das Overlay gezeigt, was pro Kind einen ChildInTranslatedContainerError und im Backend „doppelte/verwaiste" Elemente erzeugte. Frontend war in beiden Fällen ok.

[1.0.8]

Fixed

  • Native Übersetzung von Seiten mit EXT:container-Struktur (Spalten/Grids): Container werden jetzt in Hierarchie-Reihenfolge lokalisiert (Container vor Kindern) und tx_container_parent der Overlays auf die Container-Overlays umgebogen. Vorher blieben die Kind-Overlays verwaist (colPos ohne Container → „unbenutzte Elemente"), Spalten/Bilder fehlten und beim Lokalisieren eines Kindes vor seinem Container trat eine Exception auf.
  • Glossar-Records lassen sich jetzt auf jedem Seitentyp anlegen (ctrl.security.ignorePageTypeRestriction), inkl. Root-Seite. Zielsprache-Feld mit Hinweis ergänzt, dass * für alle Sprachen gilt. Doku: Ablageort ist pid-unabhängig (global gelesen).

[1.0.7]

Fixed

  • Einfache/Leichte Sprache: Inline-HTML (<img>, <span>, <a> …) in Textblöcken wurde nach der KI-Umschreibung als literaler Text ausgegeben. Inline-Markup wird jetzt vor dem KI-Call durch Platzhalter-Token geschützt und danach 1:1 wieder eingesetzt (analog zum DeepL-Pfad tag_handling=html).

[1.0.6]

Fixed

  • DOM-Übersetzung: Der Transform-Endpoint akzeptiert jetzt die im Site-Setting nt_lingua_dom_languages konfigurierten Sprachen (nicht nur die aus Languages.php).

[Unreleased]

Added

  • Initial release