---
title: "Localization in Extbase"
manual: "TYPO3 Explained"
version: "14.3"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-localisation@14.3"
source: "ExtensionArchitecture/Extbase/Localization/Index.rst"
rendered: "2026-09-29T12:57:07+00:00"
---

# Localization in Extbase {#extbase-localisation}

An Extbase plugin that lists records will show different records in different
languages — and sometimes a different number of them. This chapter explains
what decides that, starting from the setting you are most likely to have
inherited and working towards the cases you control in code.

**On this page**

-   [How the site configuration decides the language of Extbase records](https://docs.typo3.org/permalink/t3coreapi:how-the-site-configuration-decides-the-language-of-extbase-records@14.3)
-   [Setting the language for a single Extbase query](https://docs.typo3.org/permalink/t3coreapi:setting-the-language-for-a-single-extbase-query@14.3)
-   [Localization beyond reading in the frontend](https://docs.typo3.org/permalink/t3coreapi:localization-beyond-reading-in-the-frontend@14.3)
-   [Translating labels in Extbase extensions](https://docs.typo3.org/permalink/t3coreapi:translating-labels-in-extbase-extensions@14.3)

> [!NOTE]
> Whatever this chapter says about an object does not automatically hold for
> the objects it relates to. Relations follow different rules almost
> everywhere, so each section states separately what happens to them.

## How the site configuration decides the language of Extbase records {#extbase-localisation-site-configuration}

Extbase does not decide which language your records are fetched in. It reads
the [language aspect](https://docs.typo3.org/permalink/t3coreapi:context-api-aspects-language@14.3) from the
[Context API](https://docs.typo3.org/permalink/t3coreapi:context-api@14.3) and follows it. In the frontend that
aspect is built from the site configuration.

The setting that decides it is
[fallbackType](https://docs.typo3.org/permalink/t3coreapi:confval-sitehandling-addinglanguages-fallbacktype@14.3), which
each language of a site carries:

| `fallbackType` | A visitor requesting Polish sees |
| --- | --- |
| `free` | Only records actually stored as Polish records. No translation handling at all. |
| `fallback` | Polish conferences, plus the English originals of those that have no Polish translation. |
| `strict` | Only conferences that exist in Polish: translations of an English original, and conferences created directly in Polish without one. Untranslated ones disappear from the list. |

If a site language sets no `fallbackType`, it is `strict`.

Records marked as *all languages* (`sys_language_uid = -1`) are returned
in every language, whichever setting is in use.

### Language overlay types behind fallbackType {#extbase-localisation-overlay-types}

Internally each of those settings becomes an *overlay type* on the language
aspect. The names appear in query settings, in
`\TYPO3\CMS\Core\Context\LanguageAspect` and in most discussions of the
topic, so it is worth knowing which is which:

| `fallbackType` | Overlay type |
| --- | --- |
| `free` | `LanguageAspect::OVERLAYS_OFF` |
| `fallback` | `LanguageAspect::OVERLAYS_MIXED` |
| `strict` | `LanguageAspect::OVERLAYS_ON_WITH_FLOATING` |

A fourth overlay type, `LanguageAspect::OVERLAYS_ON`, exists in the class
but no site configuration produces it. It appears only where an aspect is
constructed in PHP, as described in
[Deciding the language per query](https://docs.typo3.org/permalink/t3coreapi:extbase-localisation-query-settings@14.3).

> [!NOTE]
> **See also**
>
> [Overlay types](https://docs.typo3.org/permalink/t3coreapi:context-api-aspects-language-overlay-types@14.3)
> — what each of the four does, independently of Extbase.

<!-- TODO: no Markdown rendering for "versionchanged" -->

Extbase previously ignored fallbackType when fetching records and always
behaved like fallback. It now follows the site configuration, so a
strict site probably returns fewer records than before:
findByUid() on an  untranslated record returns null rather
than the default language record, and untranslated related records are
dropped from relations. See Important: #88886 Extbase persistence
respects the language overlay type.
To keep a single query behaving as before, set an aspect with
OVERLAYS_MIXED on it as shown in Deciding the language per
query.

**What this means for relations**

The site configuration governs the records a repository fetches directly. It
does not fully govern their relations.

On a `free` site, a query performs no translation handling — but the relations
of the records it returns still do. A conference fetched without translation
handling can still come back with its categories translated. This is
deliberate: relations are usually stored against the default language record,
so Extbase would otherwise return nothing for them.

The practical consequence is that `free` mode is not "translation handling
switched off" for your whole object graph, only for its roots.

## Setting the language for a single Extbase query {#extbase-localisation-query-settings}

When you maintain the extension, a single query can depart from the site
configuration. Every query carries
[query settings](https://docs.typo3.org/permalink/t3coreapi:extbase-persistence-queries-querysettings@14.3), and the
language aspect is one of them.

### Setting a language aspect on Extbase query settings {#extbase-localisation-query-settings-aspect}

Put a `\TYPO3\CMS\Core\Context\LanguageAspect` on the query
settings. This is the supported way to fetch records in a language, or with a
translation behavior, that differs from the one the site asked for.

Give the repository one method that accepts a finished aspect, rather than one
method per way of choosing a language:

**EXT:my_extension/Classes/Domain/Repository/ConferenceRepository.php**

```php
<?php

namespace MyVendor\MyExtension\Domain\Repository;

use MyVendor\MyExtension\Domain\Model\Conference;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;

class ConferenceRepository extends Repository
{
  /**
   * Takes a ready-made language aspect, so the caller decides the language.
   * Usable from the frontend, a backend module and the command line alike.
   *
   * @return QueryResultInterface<Conference>
   */
  public function findAllForLanguageAspect(
    LanguageAspect $languageAspect,
  ): QueryResultInterface {
    $query = $this->createQuery();
    $query->getQuerySettings()->setLanguageAspect($languageAspect);

    return $query->execute();
  }
}

```

The repository then makes no assumption about where the language came from, and
the same method serves a frontend plugin, a backend module and a command alike.
Choosing the language is a decision for the caller, and there are two ways to
make it.

### Deriving the language aspect from a site language {#extbase-localisation-query-settings-from-site}

If your site configuration declares the language configuration you want, create
the language aspect from it. If there is none, you can create the aspect
manually.

**EXT:my_extension/Classes/Controller/ConferenceController.php**

```php
<?php

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class ConferenceController extends ActionController
{
  public function __construct(
    protected readonly ConferenceRepository $conferenceRepository,
  ) {}

  /**
   * Lists conferences in a chosen language with that language's own
   * translation behavior.
   */
  public function listInLanguageAction(int $languageId): ResponseInterface
  {
    $site = $this->request->getAttribute('site');
    $languageAspect = LanguageAspectFactory::createFromSiteLanguage(
      $site->getLanguageById($languageId),
    );

    $this->view->assign(
      'conferences',
      $this->conferenceRepository->findAllForLanguageAspect($languageAspect),
    );
    return $this->htmlResponse();
  }
}

```

`LanguageAspectFactory::createFromSiteLanguage()` is the same call the
frontend uses internally. It reads the language UID, the `fallbackType` and
the fallback chain from that one site language, so the three values stay
consistent with each other.

### Building a custom language aspect in PHP {#extbase-localisation-query-settings-custom}

Constructing the aspect by hand, as the action `translatedOnlyAction` does,
is the right move only when you deliberately want behavior outside what
a site language can express. `LanguageAspect::OVERLAYS_ON` is the
clearest case: it returns translations that have a default language original,
and leaves out records that exist only in the requested language without
a valid default language parent.

**EXT:my_extension/Classes/Controller/ConferenceController.php**

```php
<?php

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class ConferenceController extends ActionController
{
  public function __construct(
    protected readonly ConferenceRepository $conferenceRepository,
  ) {}

  /**
   * Lists only genuine translations: conferences that have a default
   * language original, leaving out those that exist without a default
   * language parent. No site configuration produces OVERLAYS_ON, so the
   * aspect is built here rather than derived from a site language.
   */
  public function translatedOnlyAction(int $languageId): ResponseInterface
  {
    $languageAspect = new LanguageAspect(
      $languageId, // language id
      $languageId, // content id
      LanguageAspect::OVERLAYS_ON,
      [],
    );

    $this->view->assign(
      'conferences',
      $this->conferenceRepository->findAllForLanguageAspect($languageAspect),
    );
    return $this->htmlResponse();
  }
}

```

The constructor takes four parameters, each documented as a property of the
aspect in
[the Context API reference](https://docs.typo3.org/permalink/t3coreapi:context-api-aspects-language-properties@14.3): the
requested language, the language records are fetched in, the overlay type and
the fallback chain. The first two differ only when a page requested in one
language should show the content of another.

Records marked as *all languages* are returned whichever overlay type you set,
so this is not a way to exclude them.

> [!WARNING]
> Constructing a `\TYPO3\CMS\Core\Context\LanguageAspect` manually
> with languageUid, overlay type and fallback chain in a combination not
> declared by any site configuration can lead to unexpected results in the
> delivered record set. Make sure to verify especially the fallback chain in
> such a case, as it relies on languageUids that can change in the site
> configuration without the code ever learning about it.

Both routes are used elsewhere in this manual: from [outside the frontend](https://docs.typo3.org/permalink/t3coreapi:extbase-localisation-no-frontend@14.3), where no site implies the language, and
when [reading records that belong to another site](https://docs.typo3.org/permalink/t3coreapi:extbase-cross-site-locales@14.3).

### uid and \_localizedUid of localized Extbase objects {#extbase-model-localizeduid}

Once translation handling is involved, the `uid` of a domain object is no
longer simply the `uid` of the row it came from. Extbase keeps both, in
two properties every domain object has:

-   **`uid`**

    The identifier of the default language record. This is the one to use when
    building links or storing a reference, because it stays the same in every
    language.

-   **`_localizedUid`**

    The identifier of the record the values actually came from — the
    translation, where one was used.

For a conference stored as `uid:2` in the default language and
`uid:11` in its Polish translation, a query in Polish returns:

| Translation handling | Default language record | Translated record |
| --- | --- | --- |
| On (`strict`, `fallback`) | `uid: 2`, `_localizedUid: 2` | `uid: 2`, `_localizedUid: 11` |
| Off (`free`) | `uid: 2`, `_localizedUid: 2` | `uid: 11`, `_localizedUid: 11` |

The difference matters when a record is passed back into a query or a link: on
a `free` site the object carries the translated record's own identifier, while
everywhere else it carries the default language one.

A third property, `_languageUid`, holds the language the record belongs
to. It is the property to set when [writing a record in a specific
language](https://docs.typo3.org/permalink/t3coreapi:extbase-localisation-writing-default@14.3).

> [!TIP]
> **Hint**
>
> If your project uses [`typo3/cms-workspaces`](https://packagist.org/packages/typo3/cms-workspaces) there is yet another
> additional property, `_versionedUid`. Refer to
> [Versioning in workspaces](https://docs.typo3.org/c/typo3/cms-workspaces/14.3/en-us/Administration/Versioning/Index.html#versioning) for details on
> workspace overlays.

### Fetching Extbase records in all languages {#extbase-localisation-query-settings-all-languages}

`setRespectSysLanguage(false)` removes the language restriction from the
query. Records of every language are returned side by side, so a conference
existing in three languages is returned three times.

This is what you want for a listing that deliberately spans languages, such as
a backend overview of all translations of a record. In a frontend list it looks
like duplicates.

**What this means for relations**

An aspect set on a query applies to the records that query returns. Relations
are fetched by separate queries, which Extbase configures itself, and it
overrules parts of what you set:

-   `setRespectStoragePage(false)` and `setRespectSysLanguage(false)`
    are always applied to relation queries, whatever the parent query said.
-   `OVERLAYS_OFF` is replaced by `OVERLAYS_MIXED`, so relations are
    translation-handled even when the parent query is not.
-   The language of the parent record is passed down, so relations are fetched
    in the language of the record holding them rather than the language of the
    request. Records marked as all languages are exempt.

The overlay type and fallback chain you set are otherwise carried over. In
practice this means you can influence relations, but you cannot switch
translation handling off for them.

## Localization beyond reading in the frontend {#extbase-localisation-beyond}

The rules above describe a plugin reading records in a rendered frontend
request. Three situations depart from that, each on its own page:

-   **[Reading localized records across sites](https://docs.typo3.org/permalink/t3coreapi:extbase-cross-site@14.3)**

    Reading records that belong to another site, where language IDs and
    fallback settings may no longer be the same as for the site you are
    currently handling.

-   **[Writing records from the frontend](https://docs.typo3.org/permalink/t3coreapi:extbase-localisation-writing@14.3)**

    Creating records from frontend forms, where Extbase decides the language
    itself and cannot produce translations.

-   **[Localization outside the frontend](https://docs.typo3.org/permalink/t3coreapi:extbase-localisation-no-frontend@14.3)**

    Backend modules, command line commands and middlewares, where the site and
    language the frontend would have supplied are missing.

## Translating labels in Extbase extensions {#extbase-localisation-translate}

This chapter covers how Extbase handles *records* across languages. Translating
the *labels* of an extension — button captions, flash messages, validation
errors — is a separate topic and handled on dedicated pages:

> [!NOTE]
> **See also**
>
> -   [Translating labels in Extbase](https://docs.typo3.org/permalink/t3coreapi:extension-localization-extbase@14.3) —
>     in controllers and other PHP code
> -   [LocalizationUtility API reference](https://docs.typo3.org/permalink/t3coreapi:extbase-localization-utility-api@14.3)
>     — all parameters of `translate()`
> -   [Translating labels in Fluid](https://docs.typo3.org/permalink/t3coreapi:extension-localization-fluid@14.3) —
>     the `<f:translate>` ViewHelper

-   [Writing records](https://docs.typo3.org/permalink/t3coreapi:writing-records-from-the-frontend@14.3)
-   [Without frontend context](https://docs.typo3.org/permalink/t3coreapi:localization-outside-the-frontend-context@14.3)
