Configuration¶
Extension Configuration¶
Open Admin Tools → Settings → Extension Configuration → nt_lingua.
| Setting | Default | Description |
|---|---|---|
translator |
LLM (nt_ai) | Translation backend for DOM translations: LLM (nt_ai) uses the provider configured in nt-ai; DeepL uses the DeepL API directly. |
deepLApiKey |
(empty) | DeepL API key. Leave empty to use LLM. Supports %env(VAR)%. |
deepLEndpoint |
https://api-free.deepl.com/v2 |
DeepL API endpoint. Use https://api-free.deepl.com/v2 for the Free tier or https://api.deepl.com/v2 for a Pro subscription. |
autoTranslateOnSave |
on | Translate automatically on save. Switch off for bulk operations (imports, migrations). |
Securing the public endpoints¶
Anonymous visitors can trigger provider calls in two ways: through the POST API /nt-lingua/transform (DOM translation, Plain Language per content element) and through a page request with ?ntlingua=einfache|leichte or the cookie of the same name. Both share the same budgets. Only real provider calls are counted, and each one is reserved right before it is sent: the LLM fallback after DeepL HTTP 456, every chunk of a batch and every resend (an LLM chunk resent after an empty answer, each half of an LLM chunk whose answer had the wrong length, a DeepL request resent after HTTP 429). Without budget left, nothing is resent. Cache hits are free.
| Setting | Default | Description |
|---|---|---|
rateLimitRequestsPerMinute |
60 | Requests per minute and client on the POST API. |
rateLimitCharsPerHour |
200000 | Characters per hour and client (IPv4 address or IPv6 /64 network). |
rateLimitGlobalCharsPerHour |
500000 | Characters per hour for all anonymous calls together. Cannot be bypassed by switching IP addresses. |
rateLimitGlobalCharsPerDay |
2000000 | Characters per day for all anonymous calls together. |
maxCharsPerRequest |
50000 | Maximum characters per DOM translation request. |
maxCharsPerString |
8000 | Maximum characters per text segment; longer segments are dropped. |
maxConcurrentProviderRequests |
4 | Anonymous requests that may talk to DeepL or the language model at the same time, installation-wide. Further requests get HTTP 503 with Retry-After; cache hits need no slot. 0 switches the limit off. |
concurrencySlotTimeout |
120 | Seconds after which a slot that was never released (crashed or killed request) counts as free again. Choose longer than the slowest expected translation. |
domTranslationCacheOnly |
off | Serve public DOM translations from the cache only. New segments are then translated exclusively by the DOM warmup (ntlingua:warmup-dom), which fetches the real website. Recommended once the warmup task runs regularly. |
triggerMode |
any |
What triggers Einfache/Leichte Sprache on the server: any = parameter ?ntlingua= or cookie (as before), cookie = the cookie only (followed links do nothing), signed = only a signed cookie the widget obtained from the server. |
botProtection |
on | Search and AI crawlers, HTTP libraries and requests without a browser user agent never trigger a conversion and get cache hits only in DOM translation. |
When a budget is exhausted the API answers with HTTP 429, when all slots are busy with HTTP 503; both still return available cache hits. The GET path shows paragraphs that could not be simplified in their original wording. Backend, CLI and scheduler are not subject to these limits.
Accepted limitation while domTranslationCacheOnly is off
DOM translation accepts rendered segments (text nodes, attributes, titles, content loaded later) that do not exist as records. With domTranslationCacheOnly off — the default — any caller can have arbitrary text translated at the operator's expense, up to the budgets above. The budgets cap the cost, the slots cap the parallel load, but neither binds the text to the website's content. In addition, a caller who deliberately uses up the global budget leaves other visitors without new translations until the window ends (cache hits keep working). This is an accepted limitation of the default configuration, not a fix: only domTranslationCacheOnly closes it, at the price that new texts appear in translation only after the next DOM warmup run.
Crawlers (since 1.4.0): A page with ?ntlingua= is sent with X-Robots-Tag: noindex, nofollow, a converted page additionally with Cache-Control: private, no-store. If your site appends the parameter to links (e.g. via config.linkVars), remove it there — nt-lingua itself does not. Recommended for public websites: triggerMode = signed.
Excluding areas (since 1.4.0): Besides translate="no" in the markup, lib.ntLinguaExclude.value = .social-wall, #chat-widget (CSS selectors, in the site's setup) keeps areas out of the DOM translation.
Log: Cache hits appear per day and language as action cache (chars_in = characters, chars_out = segments). Translation characters are what actually went to the provider — whitespace folded, text without letters not sent at all.
Further safeguards:
- Frontend access rules: The POST API only reads a content element if the frontend would show its page to the visitor — the page and its rootline are checked for hidden, start/stop time, access groups and "extend to subpages", as well as membership in the requested site and language. Content on folders (sysfolders) is not served.
- Page binding: A DOM translation must send the uid of a page of the site that the visitor can open (
window.ntLinguaPageUidordata-page-uid), otherwise HTTP 400. - Output: Provider answers are treated as untrusted. HTML sources are always sanitized against an allowlist (typo3/html-sanitizer); plain-text sources yield plain text without tags. This also applies to existing cache entries, which are sanitized again on every output.
TypoScript settings¶
plugin.tx_ntlingua.settings {
sourceLanguage = de
translator = llm
offerEinfache = 1
offerLeichte = 0
}
| Setting | Default | Values | Description |
|---|---|---|---|
sourceLanguage |
de |
ISO 639-1 | Language of the original page content. Sent as the source language in every translation request. |
translator |
llm |
llm, deepl |
Translation backend for DOM languages. Must match the TranslatorInterface alias in Services.yaml. |
offerEinfache |
1 |
0 / 1 |
Show the "Einfache Sprache" option in the language switcher. |
offerLeichte |
0 |
0 / 1 |
Show the "Leichte Sprache" option. Only enable when a disclaimer is shown — Leichte Sprache is an AI draft and has not been verified by a certified expert. |
Languages configuration¶
Configuration/Languages.php defines available languages and their mode:
return [
'en' => [
'name' => 'English',
'rtl' => false,
'mode' => 'native',
'sysLanguageUid' => 1,
'url' => '/en/',
],
'fr' => ['name' => 'Français', 'rtl' => false, 'mode' => 'dom'],
'ar' => ['name' => 'العربية', 'rtl' => true, 'mode' => 'dom'],
];
Field reference:
| Field | Required | Description |
|---|---|---|
name |
Yes | Display label in the language switcher. |
rtl |
Yes | true for right-to-left scripts (Arabic, Hebrew, Persian). Adds dir="rtl" and mirrors the layout. |
mode |
Yes | native or dom — see table below. |
sysLanguageUid |
native only |
UID of the TYPO3 site language. Open Sites → your site → Languages to look it up. |
url |
native only |
URL prefix matching the base field of the TYPO3 site language (e.g. /en/). |
Mode:
| Mode | Description |
|---|---|
native |
Real TYPO3 l10n overlay — server-rendered, indexable, own URL, hreflang |
dom |
Client-side DOM replacement — instant, not indexed, no page reload |
Site configuration (Backend)¶
Under Sites → Configuration → NT Lingua tab, you can select which DOM languages are offered per site — without editing Languages.php. If the selection is empty, all mode: dom entries from Languages.php are used.
Translator backend¶
LLM (default)¶
Uses the provider configured in netthinks/nt-ai. No additional API key needed.
In Configuration/Services.yaml:
Netthinks\NtLingua\Translator\TranslatorInterface:
alias: Netthinks\NtLingua\Translator\LlmTranslator
DeepL¶
Netthinks\NtLingua\Translator\TranslatorInterface:
alias: Netthinks\NtLingua\Translator\DeepLTranslator
Netthinks\NtLingua\Translator\DeepLTranslator:
arguments:
$apiKey: '%env(NTLINGUA_DEEPL_KEY)%'
Set the environment variable:
DeepL Free API limits
The free tier allows 500,000 characters/month. When the quota is exceeded (HTTP 456), the extension automatically falls back to the LLM translator (via nt-ai) for the remainder of the month. Rate-limit responses (HTTP 429) are retried up to three times with exponential backoff (5 / 10 / 20 s) before giving up. For anonymous requests every resend is reserved against the public budget first; without budget left there is no resend and no pause.
Glossary¶
Manage entries in the TYPO3 list view, record type NT Lingua – Glossar. The storage location is arbitrary — the glossary is read globally (no pid filter) and entries may live at root level (pid=0) and on any page type. Keep all entries in one place for tidiness (e.g. a sysfolder or the root page).
target_lang |
mode |
Effect |
|---|---|---|
fr |
ignore |
Leave term unchanged for French |
* |
ignore |
Exclude term from all languages |
fr |
fixed |
Always replace with target_term |
* |
fixed |
Replace for all languages |
Tip
For brand names in running text, <span translate="no">Brand</span> is more reliable than glossary entries, because the glossary matches whole text nodes only.
Excluding content from translation¶
<!-- Exclude element and all children: -->
<span translate="no">Net.Thinks</span>
<div data-nt-notranslate>This block stays untranslated</div>
Excluded HTML tags (always): <script>, <style>, <code>, <pre>, <noscript>.
AI prompts¶
Customize Plain Language output by editing:
Resources/Private/Prompts/einfache.txt— Einfache SpracheResources/Private/Prompts/leichte.txt— Leichte Sprache
Prompts must output only HTML using <p>, <ul>/<li>, <h2>, <strong>, <a href>. Facts, numbers and names must remain unchanged.