Feature: #93334 - Translation Domain Mapping
See forge#93334
Description
Translation domains have been introduced as a shorter alternative to
file-based references for label resources (.xlf XLIFF files). The
syntax uses the format package and is fully backward
compatible with existing LLL: references. Package refers to the
extension key, such as "backend" for "EXT:backend".
This syntax is designed to improve readability, remove explicit references to
file extensions, and provide convenience for new developers and integrators.
The previous locallang. convention has been replaced with a more
generic "messages" resource name, following common conventions in other
localization systems (for example Symfony). This is also where the term
translation domain originates.
Example:
// Domain-based reference
$languageService->sL('backend.toolbar:save');
// Equivalent file-based reference (still supported)
$languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_toolbar.xlf:save'
);
Note
The existing syntax and naming
(LLL:)
will remain available without deprecation for a long time.
Translation Domain Format
The format defines two parts: the package part (extension key) and the resource part, separated by a dot.
The resource part omits historical namings such as locallang. and the
locallang_ prefix. The actual label identifier is separated by a colon.
Format
$languageService->sL('backend.toolbar:save');
// Resolves to: EXT:backend/Resources/Private/Language/locallang_toolbar.xlf
// and returns the translated "save" identifier.
Domain Resolution
Deterministic File-Based Mapping
Translation domains are resolved deterministically by scanning the file system. When a domain is first requested for a package:
- All label files in
Resources/are discovered.Private/ Language/ - A domain name is generated from each file name.
- The domain-to-file mapping is cached in
cache..l10n - Subsequent requests use the cached mapping.
This ensures that domain names always correspond to existing files and avoids speculative file system lookups.
When there are filename conflicts such as locallang_ and
db., then locallang_ will be ignored.
Performance Characteristics
The implementation reduces file system operations compared to traditional file-based lookups, as all label files within an extension are discovered once.
Domain Generation Rules
Domain names are generated from file paths using these transformation rules:
- The base path
Resources/is omitted.Private/ Language/ -
Standard filename patterns:
locallang.→xlf .messageslocallang_→toolbar. xlf .toolbarlocallang_→sudo_ mode. xlf .sudo_mode
-
Subdirectories use dot notation:
Form/→locallang_ tabs. xlf .form.tabs
-
Site Set labels receive the
.setsprefix:Configuration/→Sets/ Felogin/ labels. xlf .sets.felogin
-
Case conversion:
- UpperCamelCase → snake_case (
Sudo→Mode sudo_)mode - snake_case → preserved (
sudo_→mode sudo_)mode
- UpperCamelCase → snake_case (
-
Locale prefixes are ignored for domain name generation but properly evaluated for locale-specific translations:
- (
de.→locallang. xlf messages) - (
de-→AT. tabs. xlf tabs)
- (
Examples:
File Path → Domain
────────────────────────────────────────────────────────────
EXT:backend/.../locallang.xlf → backend.messages
EXT:backend/.../locallang_toolbar.xlf → backend.toolbar
EXT:core/.../Form/locallang_tabs.xlf → core.form.tabs
EXT:felogin/Configuration/Sets/.../labels.xlf → felogin.sets.felogin
Usage
The translation domain system integrates with the existing
\TYPO3\ API. Both domain-based and
file-based references are supported:
use TYPO3\CMS\Core\Localization\LanguageService;
$languageService = $this->languageServiceFactory->createFromSiteLanguage(
$request->getAttribute('language')
);
// Domain-based reference
$label = $languageService->sL('backend.toolbar:menu.item');
// Another domain-based reference
$label = $languageService->sL('backend.messages:button.save');
// Traditional file reference (still supported)
$label = $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:button.save'
);
Domain-based references are shorter and reveal less implementation detail than full file paths.
CLI Command
The development command
bin/ lists all available
translation domains along with their available translations and label counts:
# List domains in active extensions
php bin/typo3 language:domain:list
# Filter by extension
php bin/typo3 language:domain:list --extension=backend
Output:
+--------------------+---------------------------------------+----------+
| Translation Domain | Label Resource | # Labels |
+--------------------+---------------------------------------+----------+
| backend.messages | EXT:backend/.../locallang.xlf | 84 |
| backend.toolbar | EXT:backend/.../locallang_toolbar.xlf | 42 |
+--------------------+---------------------------------------+----------+
The Labels column displays the number of translatable labels within the English source file.
On top of this, the development command
bin/
can be used to search for specific label contents. Both commands are provided
in the EXT: extension.
PSR-14 Event
The event
\TYPO3\
is dispatched after domain generation, allowing customization of domain names.
The event provides these public properties:
$package— The extension key (read-only).Key $domains— An associative array mapping domain names to label files (modifiable):array<string, string>.
Example
Event listener implementation:
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Localization\Event\BeforeLabelResourceResolvedEvent;
final readonly class CustomTranslationDomainResolver
{
#[AsEventListener(identifier: 'my-extension/custom-domain-names')]
public function __invoke(BeforeLabelResourceResolvedEvent $event): void
{
if ($event->packageKey !== 'my_extension') {
return;
}
// Use file my_messages.xlf even if locallang.xlf is found
$event->domains['my_extension.messages'] =
'EXT:my_extension/Resources/Private/Language/my_messages.xlf';
}
}
Backend modules
Previously, backend module labels (including their title and description) were defined in a file like this:
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="EXT:my_extension/Resources/Private/Language/locallang_mod.xlf" date="2038-10-28T13:37:37Z" product-name="mymodule">
<header/>
<body>
<trans-unit id="mlang_labels_tablabel">
<source>My module</source>
</trans-unit>
<trans-unit id="mlang_labels_tabdescr">
<source>Shows my module.</source>
</trans-unit>
<trans-unit id="mlang_tabs_tab">
<source>My label</source>
</trans-unit>
</body>
</file>
</xliff>
and utilized via the module definition:
<?php
return [
'my_module' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
'access' => 'user',
'path' => '/module/my-module',
'iconIdentifier' => 'my-module-icon',
'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf',
'aliases' => ['web_MyModule'],
'routes' => [
'_default' => [
'target' => MyController::class . '::handleRequest',
],
],
],
];
Now, labels can use more speaking identifiers:
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="EXT:my_extension/Resources/Private/Language/Modules/mymodule.xlf" date="2026-11-05T16:22:37Z" product-name="mymodule">
<header/>
<body>
<trans-unit id="short_description">
<source>My module</source>
</trans-unit>
<trans-unit id="description">
<source>Shows my module.</source>
</trans-unit>
<trans-unit id="title">
<source>My label</source>
</trans-unit>
</body>
</file>
</xliff>
<?php
return [
'my_module' => [
'parent' => 'web',
'position' => ['after' => 'web_list'],
'access' => 'user',
'path' => '/module/my-module',
'iconIdentifier' => 'my-module-icon',
'labels' => 'my_extension.modules.my_module',
'aliases' => ['web_MyModule'],
'routes' => [
'_default' => [
'target' => MyController::class . '::handleRequest',
],
],
],
];
The naming for the short-hand translation domain for modules should follow the following pattern as best practice:
<extensionkey>.- when multiple modules exist for an extension. Bothmodules.<modulename> extensionandKey modulenameshould use lower snake case ("some_long_module_name"), ideally without underscores (qrcode.is more readable thanmodules. generator qrcode.for example). Files are put intomodules. backend_ image_ generator EXT:.extensionkey/ Resources/ Private/ Languages/ Modules/ modulename. xlf <extensionkey>.- single backend module only The file is saved asmodule EXT:.extensionkey/ Resources/ Private/ Languages/ module. xlf
To summarize, the key changes are:
- Use a speaking XLIFF file inside
/Resources/(best practice, could be any sub-directory)Private/ Languages/ Modules - Use understandable XLIFF identifiers: - "title" instead of "mlang_tabs_tab" - "short_description" instead of "mlang_labels_tablabel" - "description" instead of "mlang_labels_tabdescr"
- Use short-form identifiers ("my_extension.modules.my_module" instead of "LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf")
inside the
Backend/registration.Modules. php
All TYPO3 Core backend modules that used the old label identifiers have been migrated to the new syntax, the utilized
files are now deprecated, see deprecation. TYPO3 Core also uses
singular module language containers like workspaces. instead of workspaces..
Impact
Translation domains provide a shorter, more readable alternative to file-based label references. The implementation uses deterministic file-system scanning with per-package caching to reduce lookups.
All existing LLL: file references continue to work. Translation domains
are optional and can be adopted incrementally. Both syntaxes can coexist in
the same codebase. This affects TypoScript, Fluid
<f: usages, TCA configuration, and PHP code using the
Language API.
TYPO3 Core will gradually migrate internal references to translation domains over time, increasing readability—especially in Fluid templates or TCA definitions.
Technical components:
\TYPO3\CMS\ Core\ Localization\ Translation Domain Mapper - Maps domains to file paths and manages the cache.
\TYPO3\CMS\ Core\ Localization\ Label File Resolver - Discovers label files and handles locale resolution.
\TYPO3\CMS\ Core\ Localization\ Localization Factory - Integrates domain resolution transparently.
The
Translation
automatically detects EXT: file references and passes them through unchanged.