Localization outside the frontend context
When rendering a typical frontend request, TYPO3 resolves the active site and language aspect before any Extbase code executes. However, backend modules, CLI commands, and custom middleware operate outside this standard pipeline and frequently lack a language context. This guide outlines how localization behaves in non-frontend environments and how to explicitly manage languages in your Extbase code.
Note
For a broader look at establishing execution environments outside the frontend (such as configuring storage PIDs), see Using Extbase outside a frontend request.
Understanding the default behavior
If no language context is explicitly provided, Extbase queries the Context API and falls back to a default fallback state:
- Fallback Target: An initialized
Languagewith an ID of 0.Aspect - Overlay Mode: OVERLAYS_ON_WITH_FLOATING.
The Impact: Your backend modules or CLI commands will behave as if they are
executing against a site configured with fallback in the default
language. Only default language records will be returned.
While acceptable for simple tasks, this default behavior breaks features like CLI commands that send localized email alerts, generate localized reports, or export multi-language datasets.
Setting the language explicitly
To query records in a specific locale, you must manually pass the language aspect to your query settings.
The following CLI command demonstrates how to fetch a frontend user's preferred language from their record, resolve it against the site configuration, and query the repository using that specific context. The command sends a reminder about upcoming conferences to all frontend users, in the language stored on their user record.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Command;
use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use MyVendor\MyExtension\Domain\Repository\FrontendUserRepository;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
use TYPO3\CMS\Core\Site\SiteFinder;
#[AsCommand(
name: 'myextension:conferencereminder',
description: 'Mail all frontend users a reminder in their own language',
)]
class ConferenceReminderCommand extends Command
{
public function __construct(
protected readonly ConferenceRepository $conferenceRepository,
protected readonly FrontendUserRepository $frontendUserRepository,
protected readonly SiteFinder $siteFinder,
) {
parent::__construct();
}
protected function execute(
InputInterface $input,
OutputInterface $output,
): int {
// There is no site in a command, so the site the mails belong to has
// to be named explicitly.
$site = $this->siteFinder->getSiteByIdentifier('my-site');
foreach ($this->frontendUserRepository->findAll() as $user) {
// for sake of this example the user record provides a chosen language
// in line with the site configuration
$siteLanguage = $site->getLanguageById($user->getLanguageId());
// The language's own configuration, exactly as the frontend of
// this site would apply it.
$languageAspect = LanguageAspectFactory::createFromSiteLanguage(
$siteLanguage,
);
$conferences = $this->conferenceRepository
->findAllForLanguageAspect($languageAspect);
// Send the mail, using $conferences and $siteLanguage->getLocale()
}
return Command::SUCCESS;
}
}
Because this method relies on
find, the exact same
repository logic remains reusable across both the frontend (where the aspect is
provided natively) and CLI commands (where you supply it manually). See also
Deciding the language per query.
Tip
UI Text vs. Database Records: Query settings only affect data records. If you need to translate email subject lines or template text strings (labels) in your command, use the Core Language Service instead. See Translating labels in PHP.
Backend modules
How you manage localization in backend modules depends entirely on whether your module interacts with the page tree.
Global modules (no page tree)
Modules that manage global records independently of specific pages (for
example, global settings, system reports, or job queues) do not have a page
and so do not have a site configuration to take a fallback from. They
default to the primary language aspect. You must explicitly specify target
languages in your query settings.
Page-bound modules (with page tree)
If your module utilizes the backend page tree navigation component, TYPO3 knows exactly which page the editor is viewing. Because pages map directly to sites, you can automatically inherit the site's full localization rules—including the site's languages, language titles for the language selector, and fallback types.
To match the exact translation behavior your visitors see on the live frontend,
resolve the site language from the active page and generate the aspect using
the factory. Then hand the aspect to
find (see
Setting a language aspect)
to add it to the query settings and execute the query.
<?php
namespace MyVendor\MyExtension\Controller;
use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class ConferenceModuleController extends ActionController
{
public function __construct(
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
protected readonly ConferenceRepository $conferenceRepository,
protected readonly SiteFinder $siteFinder,
) {}
public function indexAction(
int $pageUid = 0,
int $languageId = 0,
): ResponseInterface {
// The page selected in the page tree determines the site, and with it
// the available languages and their fallback configuration.
$site = $this->siteFinder->getSiteByPageId($pageUid);
$siteLanguage = $site->getLanguageById($languageId);
// The same aspect the frontend would use for this site language.
$languageAspect = LanguageAspectFactory::createFromSiteLanguage(
$siteLanguage,
);
$moduleTemplate = $this->moduleTemplateFactory->create($this->request);
$moduleTemplate->assign('languages', $site->getLanguages());
$moduleTemplate->assign('selectedLanguage', $siteLanguage);
$moduleTemplate->assign(
'conferences',
$this->conferenceRepository->findAllForLanguageAspect($languageAspect),
);
return $moduleTemplate->renderResponse('Conference/Index');
}
}
Using
Language ensures your
backend module's data listings stay perfectly synchronized with the frontend
site configuration.
See also
- Extbase in backend modules for more general information on using Extbase in backend modules.
- Resolving the storagePid in a backend module describes where backend modules look for records, which is also resolved differently without a site.
How relations are handled
Relational fields follow the exact same translation mapping rules used throughout TYPO3. They are automatically resolved based on the specific language aspect assigned to the parent query.
If you rely on the fallback environment (the default language aspect), any relational child records fetched inside your commands will resolve to the default language. If you explicitly fetch a parent record in a localized language, its underlying relations will automatically match that localized language context.