From craft-workspace-webconsulting-skills
Guides creation, editing, auditing, migrating, and validating TYPO3 translation files including locallang.xlf, labels.xlf, XLIFF 1.2/2.0, LLL references, Content Blocks labels, backend labels, and ICU MessageFormat.
npx claudepluginhub dirnbauer/webconsulting-skillsThis skill uses the workspace's default tool permissions.
> Source: https://github.com/dirnbauer/webconsulting-skills
Implements Lokalise translation data lifecycle, key metadata, exports, PII management, and compliance patterns for secure Node.js integrations.
Audits and implements WCAG 2.2 AA accessibility in TYPO3 v14 using Fluid templates, PHP middleware, JavaScript enhancements, content elements, forms, and go-live checklist.
Safely extracts and mass-converts hardcoded strings to i18n t() calls in frontend codebases using multi-pass batching, parallel language dispatch, 8 audit gates, and HTML integrity checks. For >10 strings.
Share bugs, ideas, or general feedback.
Use this skill for TYPO3 translation files, localization keys, XLIFF format
choices, LLL: references, and editor-facing labels. Other TYPO3 skills own
their domain logic; this skill owns how strings are named, localized, validated,
and stored.
Resources/Private/Language/*.xlf.ContentBlocks/**/language/*.xlf.LLL:EXT:
references.Inventory the translation surface.
Resources/Private/Language/*.xlf and
ContentBlocks/**/language/*.xlf.Choose the XLIFF format intentionally.
Name keys for the editor or runtime intent.
title, description,
field.cta_text.label, or wizard.group.hero.label.Localize, do not just mirror.
Validate before finishing.
.xlf as XML.LLL: reference resolves to a real key.Use TYPO3's localization APIs instead of custom XML parsers or hand-rolled label resolution.
Use LanguageServiceFactory when you need a context-aware language service.
Do not rely on $GLOBALS['LANG'] in frontend or CLI code; it is not reliably
available there.
<?php
declare(strict_types=1);
namespace Vendor\Extension\Controller;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
final readonly class ExampleController
{
public function __construct(
private LanguageServiceFactory $languageServiceFactory,
) {}
public function label(ServerRequestInterface $request): string
{
$languageService = $this->languageServiceFactory->createFromSiteLanguage(
$request->getAttribute('language'),
);
return $languageService->sL(
'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:button.save',
);
}
}
For backend-only code, $GLOBALS['LANG'] is often already initialized by the
backend bootstrap. Prefer a helper so the dependency is obvious:
private function getLanguageService(): \TYPO3\CMS\Core\Localization\LanguageService
{
return $GLOBALS['LANG'];
}
sL() returns the plain translated string. Escape it yourself before output in
a web context.
Use LanguageService::translate() with named arguments for ICU messages. Named
arguments trigger ICU handling; positional arguments continue to behave like
classic sprintf placeholders.
$label = $languageService->translate(
'file_count',
'my_extension.messages',
['count' => 5],
);
In Extbase-only code, LocalizationUtility::translate() is acceptable for
convenience:
$label = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate(
'file_count',
'MyExtension',
['count' => 1],
);
Use <f:translate> and pass named arguments for ICU strings:
<f:translate key="file_count" arguments="{count: numberOfFiles}" />
<f:translate key="greeting" arguments="{name: userName, count: messageCount}" />
Outside an Extbase template context, use the full LLL:EXT: reference:
<f:translate key="LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:key1" />
Use complete LLL:EXT: paths for TCA labels and descriptions:
'ctrl' => [
'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_my_table',
],
'columns' => [
'title' => [
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_my_table.title',
],
],
TYPO3 v14 can resolve translation domains such as my_extension.messages:key
in addition to traditional file references. Domain references are shorter and
can be adopted incrementally.
$label = $languageService->sL('my_extension.messages:button.save');
Use the development commands from EXT:lowlevel to inspect domains:
php bin/typo3 language:domain:list
php bin/typo3 language:domain:list --extension=my_extension
php bin/typo3 language:domain:search save
Avoid filename conflicts such as locallang.xlf and messages.xlf containing
different labels for the same generated messages domain.
Use this shape for new generated TYPO3 v14 source labels:
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
<file id="messages">
<unit id="title">
<segment>
<source>Text & Media</source>
</segment>
</unit>
<unit id="description">
<segment>
<source>Combine editorial copy, media, and calls to action in one balanced content section.</source>
</segment>
</unit>
</file>
</xliff>
Use this shape for approved German targets:
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en" trgLang="de">
<file id="messages">
<unit id="title">
<segment state="final">
<source>Text & Media</source>
<target>Text & Media</target>
</segment>
</unit>
<unit id="description">
<segment state="final">
<source>Combine editorial copy, media, and calls to action in one balanced content section.</source>
<target>Kombiniert redaktionelle Texte, Medien und Handlungsaufrufe in einem ausgewogenen Inhaltsbereich.</target>
</segment>
</unit>
</file>
</xliff>
source and target text.&, <, and >.translate() API or Fluid named arguments for ICU runtime
interpolation. sL() only resolves the label string.Example:
<unit id="items.count">
<segment>
<source>{count, plural, one {# item} other {# items}}</source>
</segment>
</unit>
German target:
<unit id="items.count">
<segment state="final">
<source>{count, plural, one {# item} other {# items}}</source>
<target>{count, plural, one {# Eintrag} other {# Eintraege}}</target>
</segment>
</unit>
LLL:EXT:extension_key/Resources/Private/Language/labels.xlf:key for
shared labels.LLL: references. Do not expose raw machine IDs such as hero, data, or
team as editor-facing labels.ExtensionManagementUtility::addTcaSelectItemGroup(), pass the
visible label as an LLL: reference and keep the group identifier stable.Content Blocks keeps labels beside the block, commonly in
ContentBlocks/<Type>/<name>/language/labels.xlf. The English labels.xlf is
the source file, and translations use locale prefixes such as
de.labels.xlf.
Put editor-facing title and description in the block language file.
Use Content Blocks key conventions for fields:
titledescription<field>.label<field>.description<collection>.<child>.labelRun the generator to see what Content Blocks expects:
vendor/bin/typo3 content-blocks:language:generate vendor/block --print
vendor/bin/typo3 content-blocks:language:generate vendor/block --extension=my_extension
Remember that labels.xlf entries take precedence over inline labels in
config.yaml. If an inline YAML label appears ignored, check the XLIFF file.
For generated TYPO3 v14-only catalogs, convert or emit the generated labels as XLIFF 2.0 after the Content Blocks key list is known.
| Problem | Common Cause | First Steps |
|---|---|---|
| German target is ignored | XLIFF 2 segment has state="translated" while approved-only loading is active | Change reviewed translations to state="reviewed" or state="final". Only relax requireApprovedLocalizations for a deliberate preview/debugging workflow. |
| English fallback appears | Target file is missing, has the wrong prefix, or is not next to the source file | Verify de.locallang.xlf or de.labels.xlf sits beside locallang.xlf or labels.xlf and has trgLang="de" for XLIFF 2.0. |
en.locallang.xlf is not loaded | TYPO3 expects the unprefixed source file to be English | Rename the source to locallang.xlf or labels.xlf; do not create an en.*.xlf source file. |
| XLIFF 2.0 breaks an older project | TYPO3 v13 does not support XLIFF 2.x | Keep XLIFF 1.2 until the extension is v14-only and translation tools support 2.x. |
Raw LLL:EXT:... appears | Wrong path, extension key, filename, or unit ID | Copy the exact path, parse the file, verify the unit ID, clear caches, and check domain/list commands if domains are used. |
| Label shows unescaped HTML | sL() returns raw text | Escape in the output context or keep markup out of labels unless the renderer intentionally handles it. |
| ICU plural does not interpolate | Label was fetched with sL() or positional arguments | Use LanguageService::translate() or <f:translate arguments="{count: value}"> with named arguments. |
| Translation domain resolves the wrong file | Conflicting locallang.xlf and messages.xlf or stale cache.l10n | Avoid conflicting files, flush caches, then run language:domain:list. |
| Content Blocks label differs from YAML | labels.xlf has precedence over inline config.yaml labels | Regenerate or print labels with content-blocks:language:generate, then edit the XLIFF source. |
| Target file has missing labels | Source and target unit IDs drifted | Diff unit IDs from source and target, add missing units, and keep source text in target files for translator context. |
| Multiple languages in one file | XLIFF file tries to contain more than one target language | Split into one target file per language prefix, for example de.locallang.xlf and fr.locallang.xlf. |
Multiple <file> elements are used | External tooling exported a multi-file XLIFF document | Split into one TYPO3 label file with exactly one <file> element. |
LLL: stringLLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:key.composer.json / TYPO3 extension key.language:domain:list.de. prefix.trgLang="de" for XLIFF 2.0 or target-language="de" for XLIFF 1.2.target.state="reviewed" or
state="final".content-blocks:language:generate ... --print.labels.xlf.de.labels.xlf with matching unit IDs and German targets.source string.LanguageService::translate() and named arguments, or with
Fluid <f:translate arguments="{count: value}">.0, 1, and multiple values.version="2.0", XLIFF 2 namespace, and
srcLang="en".trans-unit entries to unit plus segment.trgLang="<locale>" and approved
segment state="reviewed" or state="final".# XML well-formedness
find Resources ContentBlocks -name '*.xlf' -print0 2>/dev/null | xargs -0 -n1 xmllint --noout
# Find unresolved labels or raw machine-looking wizard labels
rg -n "LLL:|group:|label:|description:" Configuration ContentBlocks Resources -S
# Check XLIFF versions in a project
rg -n '<xliff version=' Resources ContentBlocks -S
# Inspect TYPO3 v14 translation domains, when EXT:lowlevel is installed
php bin/typo3 language:domain:list
If xmllint is unavailable, use the project's PHP runtime with DOMDocument to
parse each changed file.
LLL: targets remain intact.LanguageService, LocalizationUtility,
<f:translate>, TCA LLL: references), not custom label loaders.Should I use XLIFF 2.0 or 1.2?
Use XLIFF 2.0 for new TYPO3 v14-only work. Keep XLIFF 1.2 when the extension still supports TYPO3 v13 or when the team's translation tools cannot handle XLIFF 2.x yet.
Should I create en.locallang.xlf?
No. The unprefixed source file is English. Add target files with locale
prefixes, for example de.locallang.xlf.
Can one XLIFF file contain multiple target languages?
No. Keep one source file and one additional file per target locale.
Why is state="translated" not visible?
TYPO3 normally loads approved translations. In XLIFF 2.x, use
state="reviewed" or state="final" for approved targets.
When do I use sL() versus translate()?
Use sL() to resolve a plain LLL: or domain label. Use translate() when
you need arguments, especially named ICU MessageFormat arguments.
Can I use ICU MessageFormat in TYPO3 labels?
Yes in TYPO3 14.2 and newer. Store ICU strings as regular XLIFF text and pass
named arguments through LanguageService::translate(), LocalizationUtility,
or Fluid <f:translate>.
Are translation domains required?
No. Existing LLL:EXT: references continue to work. Domains are a TYPO3 v14
option for shorter references and can be adopted gradually.
Why did my Content Blocks inline label stop showing?
Content Blocks gives labels.xlf precedence over inline YAML labels. Check the
block language file first.
Are XLIFF labels for editorial content?
No. XLIFF files are for labels and short UI messages. Editorial content is localized through TYPO3 content and record localization.
labels.xlf
https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/Definition/Language/Index.html