Languages
TCA_API is language-aware. For TYPO3 sites that declare multiple languages, the
extension resolves the requested locale per request, applies the matching
sys_ constraint to queries, overlays translations on the
default-language rows according to the site's fallback, and isolates
cache entries per locale.
Resolution order
For every API request the dispatcher resolves a single Site using
this precedence:
- The URL — if the path starts with a language base segment declared on
the site (e.g.
/de/matches the German language whoseapi/... base: /), that language is selected. This piggybacks on TYPO3's ownde/ typo3/middleware, which has already populatedcms- frontend/ site $request->getbefore TCA_API runs.Attribute ('language') -
The ``X-Locale`` header — if set, overrides the URL-derived language. The value is the TYPO3
languageinteger (not a locale string).Id GET /api/articles X-Locale: 1Copied!is equivalent to:
GET /de/api/articlesCopied! - The site's default language — when neither the URL nor the header resolves a language.
Invalid X- values (non-integer, unknown id, or pointing at a disabled
language) return 400 Bad Request with a hydra: body listing the
available enabled language ids:
{
"@context": "http://www.w3.org/ns/hydra/context.jsonld",
"@type": "hydra:Error",
"hydra:title": "Invalid language",
"hydra:description": "Invalid language \"99\". Available enabled language ids: 0, 1"
}
Query and overlay behaviour
For tables whose TCA declares a ctrl. (almost every
translatable TYPO3 table — pages, tt_, sys_,
sys_, and any extension table marked translatable), the
repository:
- Restricts the base query to
sys_— default-language rows plus "all-languages" sentinel rows.language_ uid IN (0, - 1) - Bulk-loads matching translation rows (
sys_,language_ uid = <requested> l10n_).parent IN <base uids> - Applies an overlay row-by-row.
The overlay honours the site's fallback:
fallback |
Behaviour for a base row that has no translation |
|---|---|
strict | Row is omitted from the response. |
fallback | Default-language row is returned unchanged (English fallback). |
free | Same as fallback for the overlay path. |
Rows flagged sys_ ("visible in all languages") are
always returned untouched, regardless of fallback — they are by
definition language-agnostic.
IRI stability
The Hydra @id for a record always uses the default-language UID, even
when the response payload contains the translated values. A German consumer of
/de/ sees:
{
"@id": "/api/articles/1",
"@type": "Article",
"title": "Hallo Welt"
}
The translation row's own UID (e.g. 51) is never exposed.
Opt-out per resource
A resource can disable language filtering entirely with the
general. key:
return [
'general' => [
'table' => 'sys_category',
'resourceName' => 'categories-all',
'resourceType' => 'CategoryAll',
'language' => ['mode' => 'ignore'],
],
// …
];
mode |
Default | Behaviour |
|---|---|---|
auto | ✓ | Apply language constraint + overlay when the table's TCA declares a
language (the standard case). |
ignore | Skip the language constraint and the overlay. Every row in every language is returned as a distinct member. Useful for admin listings or for tables that are language-agnostic by design. |
Tables whose TCA does not declare ctrl. (rare —
non-translatable extension tables) are already exempt; the ignore mode is
only needed when you want to opt out of filtering on a normally-translatable
table.
Response headers
Every API response carries:
| Header | Value |
|---|---|
Content- | The resolved language's hreflang (falls back to the locale's
primary language code, e.g. de). |
Vary | Includes X- so shared caches (Varnish, Cloudflare, etc.)
isolate entries per locale. When CORS is enabled the value is
Origin, X-. |
CORS preflight (OPTIONS) responses also advertise X- in
Access- so browsers can send the override.
Cache key isolation
When response caching is enabled, the resolved language id is
part of the cache key composition. A request to /api/ and one to
/de/ (or the same path with X-) produce distinct
cache entries and cannot return each other's payload.
Site fixture example
A typical multi-language site set-up:
# config/sites/main/config.yaml
base: 'https://example.com/'
languages:
-
title: English
languageId: 0
locale: en_US
base: /
hreflang: en
fallbackType: strict
-
title: German
languageId: 1
locale: de_DE
base: /de/
hreflang: de
fallbackType: fallback
fallbacks: '0'
settings:
tca_api:
enabled: true
apiPrefix: '/api/'
With this configuration:
GET /→ English rows only.api/ articles GET /→ German overlays where translations exist, English fallback otherwise (becausede/ api/ articles fallback).Type: fallback GET /with headerapi/ articles X-→ same as the previous.Locale: 1 GET /with headerapi/ articles X-→Locale: 99 400 Bad Request.
What TCA_API does not do
- Accept-Language header. Browser language preferences are deliberately
not consulted. Locale selection is either explicit (URL base segment or
X-header) or falls back to the site default — never silently derived from the user agent.Locale - Region/country subtags.
X-is the TYPO3Locale languageinteger, not an IETF tag — there is noId en-vsUS en-matching.GB - Per-locale IRIs.
/api/is the canonical IRI regardless of language; locale is a request-time dimension, not part of the resource identity.articles/ 1 - Mass-edit translations through the API. The standard CRUD operations always target default-language rows. Creating or updating translation rows is currently the TYPO3 backend's job.