.. include:: /Includes.rst.txt .. _feature-93334-1729000000: ============================================ Feature: #93334 - Translation Domain Mapping ============================================ See :issue:`93334` Description =========== Translation domains have been introduced as a shorter alternative to file-based references for label resources (:file:`.xlf` XLIFF files). The syntax uses the format `package[.subdomain...].resource` and is fully backward compatible with existing `LLL:EXT:` 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 :file:`locallang.xlf` 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: .. code-block:: php // 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:EXT:extension/Resources/Private/Language/locallang.xlf:label`) will remain available without deprecation for a long time. .. _feature-93334-translation-domain-format: 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.xlf` and the `locallang_` prefix. The actual label identifier is separated by a colon. Format ------ .. code-block:: php :caption: Example usage of "package.resource:identifier" $languageService->sL('backend.toolbar:save'); // Resolves to: EXT:backend/Resources/Private/Language/locallang_toolbar.xlf // and returns the translated "save" identifier. .. _feature-93334-domain-resolution: Domain Resolution ================= .. _feature-93334-deterministic-mapping: Deterministic File-Based Mapping -------------------------------- Translation domains are resolved deterministically by scanning the file system. When a domain is first requested for a package: 1. All label files in :directory:`Resources/Private/Language/` are discovered. 2. A domain name is generated from each file name. 3. The domain-to-file mapping is cached in `cache.l10n`. 4. 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 :file:`locallang_db.xlf` and :file:`db.xlf`, then :file:`locallang_db.xlf` will be ignored. .. _feature-93334-performance: Performance Characteristics --------------------------- The implementation reduces file system operations compared to traditional file-based lookups, as all label files within an extension are discovered once. .. _feature-93334-domain-rules: Domain Generation Rules ----------------------- Domain names are generated from file paths using these transformation rules: 1. The base path :directory:`Resources/Private/Language/` is omitted. 2. Standard filename patterns: * :file:`locallang.xlf` → `.messages` * :file:`locallang_toolbar.xlf` → `.toolbar` * :file:`locallang_sudo_mode.xlf` → `.sudo_mode` 3. Subdirectories use dot notation: * :file:`Form/locallang_tabs.xlf` → `.form.tabs` 4. Site Set labels receive the `.sets` prefix: * :file:`Configuration/Sets/Felogin/labels.xlf` → `.sets.felogin` 5. Case conversion: * UpperCamelCase → snake_case (`SudoMode` → `sudo_mode`) * snake_case → preserved (`sudo_mode` → `sudo_mode`) 6. 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: .. code-block:: text 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 .. important:: The domain name `messages` is currently evaluated for both the legacy file name :file:`locallang.xlf` but also for new files :file:`messages.xlf`. If a file `messages.xlf` is present, this means the `locallang.xlf` will never be automatically evaluated for the resulting `messages` domain. It is recommended to avoid having both files in the same directory, unless both contain the same label contents, as no merging of these two files is performed. .. _feature-93334-usage: Usage ===== The translation domain system integrates with the existing :php:`\TYPO3\CMS\Core\Localization\LanguageService` API. Both domain-based and file-based references are supported: .. code-block:: php 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. .. _feature-93334-cli: CLI Command =========== The development command :bash:`bin/typo3 language:domain:list` lists all available translation domains along with their available translations and label counts: .. code-block:: bash # List domains in active extensions php bin/typo3 language:domain:list # Filter by extension php bin/typo3 language:domain:list --extension=backend Output: .. code-block:: text +--------------------+---------------------------------------+----------+ | 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 :bash:`bin/typo3 language:domain:search` can be used to search for specific label contents. Both commands are provided in the `EXT:lowlevel` extension. .. _feature-93334-psr14: PSR-14 Event ============ The event :php:`\TYPO3\CMS\Core\Localization\Event\BeforeLabelResourceResolvedEvent` is dispatched after domain generation, allowing customization of domain names. The event provides these public properties: * :php:`$packageKey` — The extension key (read-only). * :php:`$domains` — An associative array mapping domain names to label files (modifiable): :php:`array`. Example ------- Event listener implementation: .. code-block:: php 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: .. code-block:: xml :caption: EXT:my_extension/Resources/Private/Language/locallang_mod.xlf :emphasize-lines: 6,9,12
My module Shows my module. My label and utilized via the module definition: .. code-block:: php :caption: EXT:my_extension/Configuration/Backend/Modules.php :emphasize-lines: 9 [ '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: .. code-block:: xml :caption: EXT:my_extension/Resources/Private/Language/Module/mymodule.xlf
My module Shows my module. My label .. code-block:: php :caption: EXT:my_extension/Configuration/Backend/Modules.php :emphasize-lines: 10 [ '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: * `.modules.` - **when multiple modules exist for an extension**. Both `extensionKey` and `modulename` should use lower snake case ("some_long_module_name"), ideally without underscores (`qrcode.modules.generator` is more readable than `qrcode.modules.backend_image_generator` for example). Files are put into :file:`EXT:extensionkey/Resources/Private/Languages/Modules/modulename.xlf`. * `.module` - **single backend module only** The file is saved as :file:`EXT:extensionkey/Resources/Private/Languages/module.xlf`. To summarize, the key changes are: #. Use a speaking XLIFF file inside :directory:`/Resources/Private/Languages/Modules` (best practice, could be any sub-directory) #. 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 :file:`Backend/Modules.php` registration. 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 :ref:`deprecation `. TYPO3 Core also uses singular module language containers like `workspaces.module` instead of `workspaces.modules.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:EXT:` 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 :fluid:`` usages, TCA configuration, and PHP code using the :php-short:`\TYPO3\CMS\Core\Localization\LanguageService` API. TYPO3 Core will gradually migrate internal references to translation domains over time, increasing readability—especially in Fluid templates or TCA definitions. Technical components: :php:`\TYPO3\CMS\Core\Localization\TranslationDomainMapper` Maps domains to file paths and manages the cache. :php:`\TYPO3\CMS\Core\Localization\LabelFileResolver` Discovers label files and handles locale resolution. :php:`\TYPO3\CMS\Core\Localization\LocalizationFactory` Integrates domain resolution transparently. The :php-short:`\TYPO3\CMS\Core\Localization\TranslationDomainMapper` automatically detects `EXT:` file references and passes them through unchanged. .. index:: PHP-API, Localization, ext:core