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,
like "backend" for "EXT:backend".
This synax is designed to improve readability, remove a clear reference
to the used file extension and to add convenience for new developers and
integrators: The previous locallang. convention as well as the
name has been removed in favor of a more generic "messages" resource name,
which is common for localization systems or Symfony-based applications.
This is also where the term "translation domain" stems from.
Example:
// Domain-based reference
$languageService->sL('backend.toolbar:save');
// Equivalent file-based reference (existing syntax, still supported)
$languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_toolbar.xlf:save');
Note
The existing syntax and naming
(LLL:)
will be around for a long while without a deprecation notice.
Translation Domain Format
The format defines two parts: The package part (extension key) and the resource part. These are separated by a dot.
As mentioned, the resource part will leave out previous historical
namings, especially locallang. and the locallang_ prefix.
The actual identifier within the resource 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 discoveredPrivate/ 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 domain names always correspond to existing files and prevents 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 omittedPrivate/ 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 irrelevant for the resource identifier resolving. These prefixes will be properly evaluated internally for later locale-based 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:
$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 expose less implementation detail compared to full file paths.
CLI Command
The command
bin/ lists all available translation domains
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.
PSR-14 Event
The event
Before is dispatched after domain
generation, allowing customization of domain names.
Event:
\TYPO3\
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 file system operations.
All existing LLL: file references continue to work. Translation domains
are optional and can be adopted incrementally. Both syntaxes can be mixed
within the same codebase. This affects TypoScript, Fluid
<f:
usages, TCA configuration, and PHP code using the
Language API.
TYPO3 Core will slowly migrate internal references to use translation domains over time, as this increases readability, especially in Fluid templates, or TCA references.
Technical components:
Translation- Maps domains to file paths, manages cacheDomain Mapper Label- Discovers label files and handles locale resolutionFile Resolver Localization- Integrates domain resolution transparentlyFactory
The
Translation automatically detects file references
(EXT: prefix) and passes them through unchanged.