Skip to content

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.ntLinguaPageUid or data-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:

# .ddev/config.yaml
web_environment:
  - NTLINGUA_DEEPL_KEY=your-deepl-api-key

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 Sprache
  • Resources/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.