Skip to content

Commit 490f126

Browse files
lolli42bmack
authored andcommitted
[BUGFIX] Allow access to TypoScript overrides for labels in _LOCAL_LANG
This bugfix enables the possibility to access _LOCAL_LANG values from TypoScript properly again via Extbase's LocalizationUtility, and thus for <f:translate> ViewHelpers as well again. This is what has changed under-the-hood: The TranslateViewHelper is now only a thin layer to Extbase's LocalizationUtility (as before), and only checks if a current request or Locale/languageKey is given, if a locale can be resolved. Everything else is then dispatched to the LocalizationUtility. <f:translate> is very clean now and has almost no further responsibility than to call LocalizationUtility::translate Instead of adding further LocalizationUtility magic, overriding of TypoScript is now enabled for any kind of plugin which hands in $extensionName. This is achieved by building proper Locale objects from the request which are then used to build the respective LanguageService. As it turned out after the 12.4.0 release, the "Locales" class is indeed the factory for creating a Locale, which is decoupled from the actual LanguageService (= label magic), the Locales factory receives a few create methods to make life easier for usage, which both f:translate AND LocalizationUtility receive, making their parts much smaller. Further work will dissolve the usage of the Configuration Manager of Extbase, but this won't happen in v12 anymore. Resolves: #100759 Releases: main, 12.4 Change-Id: Ifcad2ec590746e96066a96f314500bd50e9b4695 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/82023 Tested-by: core-ci <typo3@b13.com> Reviewed-by: Benni Mack <benni@typo3.org> Tested-by: Benni Mack <benni@typo3.org>
1 parent 7224dc1 commit 490f126

File tree

14 files changed

+266
-119
lines changed

14 files changed

+266
-119
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@
304304
"TYPO3Tests\\ParentChildTranslation\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/parent_child_translation/Classes/",
305305
"TYPO3Tests\\TestEid\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_eid/Classes/",
306306
"TYPO3Tests\\FluidTest\\": "typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/fluid_test/Classes/",
307+
"TYPO3Tests\\TestTranslate\\": "typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Classes/",
307308
"TYPO3Tests\\FormCachingTests\\" : "typo3/sysext/form/Tests/Functional/RequestHandling/Fixtures/Extensions/form_caching_tests/Classes/",
308309
"TYPO3Tests\\IrreTutorial\\" : "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Classes/",
309310
"TYPO3Tests\\RequestMirror\\": "typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Classes/",

typo3/sysext/core/Classes/Localization/LanguageServiceFactory.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,7 @@ public function create(Locale|string $locale): LanguageService
5151

5252
public function createFromUserPreferences(?AbstractUserAuthentication $user): LanguageService
5353
{
54-
if ($user && ($user->user['lang'] ?? false)) {
55-
return $this->create($this->locales->createLocale($user->user['lang']));
56-
}
57-
return $this->create('en');
54+
return $this->create($this->locales->createLocaleFromUserPreferences($user));
5855
}
5956

6057
public function createFromSiteLanguage(SiteLanguage $language): LanguageService

typo3/sysext/core/Classes/Localization/Locale.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,18 @@ public function __construct(
105105
$this->dependencies = array_map(fn($dep) => $this->normalize($dep), $dependencies);
106106
}
107107

108+
/**
109+
* @internal not part of TYPO3 public API
110+
*/
111+
public function setDependencies(array $dependencies, bool $raw = false): void
112+
{
113+
if ($raw) {
114+
$this->dependencies = $dependencies;
115+
} else {
116+
$this->dependencies = array_map(fn($dep) => $this->normalize($dep), $dependencies);
117+
}
118+
}
119+
108120
public function getName(): string
109121
{
110122
return $this->locale;

typo3/sysext/core/Classes/Localization/Locales.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717

1818
namespace TYPO3\CMS\Core\Localization;
1919

20+
use Psr\Http\Message\ServerRequestInterface;
21+
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
2022
use TYPO3\CMS\Core\Core\Environment;
23+
use TYPO3\CMS\Core\Http\ApplicationType;
2124
use TYPO3\CMS\Core\Log\LogManager;
2225
use TYPO3\CMS\Core\SingletonInterface;
2326
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
@@ -401,4 +404,25 @@ protected static function setLocale(string $locale, string $localeStringForTrigg
401404
}
402405
return true;
403406
}
407+
408+
public function createLocaleFromRequest(?ServerRequestInterface $request): Locale
409+
{
410+
$languageServiceFactory = GeneralUtility::makeInstance(LanguageServiceFactory::class);
411+
if ($request !== null && ApplicationType::fromRequest($request)->isFrontend()) {
412+
// @todo: the string conversion is needed for the time being, as long as SiteLanguage does not contain
413+
// the full locale with all fallbacks, then getTypo3Language() also needs to be removed.
414+
$localeString = (string)($request->getAttribute('language')?->getTypo3Language()
415+
?? $request->getAttribute('site')->getDefaultLanguage()->getTypo3Language());
416+
return $this->createLocale($localeString);
417+
}
418+
return $languageServiceFactory->createFromUserPreferences($GLOBALS['BE_USER'] ?? null)->getLocale();
419+
}
420+
421+
public function createLocaleFromUserPreferences(?AbstractUserAuthentication $user): Locale
422+
{
423+
if ($user && ($user->user['lang'] ?? false)) {
424+
return $this->createLocale($user->user['lang']);
425+
}
426+
return $this->createLocale('en');
427+
}
404428
}

typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php

Lines changed: 26 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
2626
use TYPO3\CMS\Core\Localization\Locale;
2727
use TYPO3\CMS\Core\Localization\Locales;
28-
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
2928
use TYPO3\CMS\Core\Utility\GeneralUtility;
3029
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
3130

@@ -67,25 +66,18 @@ public static function translate(string $key, ?string $extensionName = null, arr
6766
}
6867
$languageFilePath = static::getLanguageFilePath($extensionName);
6968
}
70-
if ($languageKey === null) {
71-
$languageKey = static::getLanguageKey();
72-
}
7369
if ($alternativeLanguageKeys !== null && $alternativeLanguageKeys !== []) {
7470
trigger_error('Calling LocalizationUtility::translate() with the argument $alternativeLanguageKeys will be removed in TYPO3 v13.0. Use Locales instead.', E_USER_DEPRECATED);
7571
}
76-
if ($languageKey instanceof Locale) {
77-
if ($alternativeLanguageKeys !== null && $alternativeLanguageKeys !== []) {
78-
$alternativeLanguageKeys = $languageKey->getDependencies();
79-
}
80-
$languageKey = (string)$languageKey;
81-
}
82-
$languageService = static::initializeLocalization($languageFilePath, $languageKey, $alternativeLanguageKeys, $extensionName);
72+
$locale = self::getLocale($languageKey, $alternativeLanguageKeys);
73+
$languageService = static::initializeLocalization($languageFilePath, $locale, $extensionName);
8374
$resolvedLabel = $languageService->sL('LLL:' . $languageFilePath . ':' . $key);
8475
$value = $resolvedLabel !== '' ? $resolvedLabel : null;
8576

8677
// Check if a value was explicitly set to "" via TypoScript, if so, we need to ensure that this is "" and not null
8778
if ($extensionName) {
8879
$overrideLabels = static::loadTypoScriptLabels($extensionName);
80+
$languageKey = $locale->getName();
8981
// @todo: probably cannot handle "de-DE" and "de" fallbacks
9082
if ($value === null && isset($overrideLabels[$languageKey])) {
9183
$value = '';
@@ -104,12 +96,10 @@ public static function translate(string $key, ?string $extensionName = null, arr
10496
/**
10597
* Loads local-language values by looking for a "locallang.xlf" file in the plugin resources directory and if found includes it.
10698
* Locallang values set in the TypoScript property "_LOCAL_LANG" are merged onto the values found in the "locallang.xlf" file.
107-
*
108-
* @param string[]|null $alternativeLanguageKeys
10999
*/
110-
protected static function initializeLocalization(string $languageFilePath, string $languageKey, ?array $alternativeLanguageKeys, ?string $extensionName): LanguageService
100+
protected static function initializeLocalization(string $languageFilePath, Locale $locale, ?string $extensionName): LanguageService
111101
{
112-
$languageService = self::buildLanguageService($languageKey, $alternativeLanguageKeys, $languageFilePath);
102+
$languageService = self::buildLanguageService($locale, $languageFilePath);
113103
if (!empty($extensionName)) {
114104
$overrideLabels = static::loadTypoScriptLabels($extensionName);
115105
if ($overrideLabels !== []) {
@@ -119,17 +109,11 @@ protected static function initializeLocalization(string $languageFilePath, strin
119109
return $languageService;
120110
}
121111

122-
protected static function buildLanguageService(string $languageKey, ?array $alternativeLanguageKeys, $languageFilePath): LanguageService
112+
protected static function buildLanguageService(Locale $locale, string $languageFilePath): LanguageService
123113
{
124-
$languageKeyHash = sha1(json_encode(array_merge([$languageKey], $alternativeLanguageKeys ?? [], [$languageFilePath])));
114+
$languageKeyHash = sha1(json_encode(array_merge([(string)$locale], $locale->getDependencies(), [$languageFilePath])));
125115
$cache = self::getRuntimeCache();
126116
if (!$cache->get($languageKeyHash)) {
127-
if ($alternativeLanguageKeys === [] || $alternativeLanguageKeys === null) {
128-
// Using the Locales factory, as it handles dependencies (e.g. "de-AT" falls back to "de")
129-
$locale = GeneralUtility::makeInstance(Locales::class)->createLocale($languageKey);
130-
} else {
131-
$locale = new Locale($languageKey, $alternativeLanguageKeys);
132-
}
133117
$languageService = GeneralUtility::makeInstance(LanguageServiceFactory::class)->create($locale);
134118
$languageService->includeLLFile($languageFilePath);
135119
$cache->set($languageKeyHash, $languageService);
@@ -146,24 +130,23 @@ protected static function getLanguageFilePath(string $extensionName): string
146130
}
147131

148132
/**
149-
* Resolves the currently active language key.
133+
* Resolves the currently active locale.
134+
* Using the Locales factory, as it handles dependencies (e.g. "de-AT" falls back to "de").
150135
*/
151-
protected static function getLanguageKey(): string
136+
protected static function getLocale(Locale|string|null $localeOrLanguageKey, ?array $alternativeLanguageKeys): Locale
152137
{
153-
if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
154-
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
155-
) {
156-
// Frontend application
157-
$siteLanguage = self::getCurrentSiteLanguage();
158-
159-
// Get values from site language
160-
if ($siteLanguage !== null) {
161-
return $siteLanguage->getTypo3Language();
162-
}
163-
} elseif (!empty($GLOBALS['BE_USER']->user['lang'])) {
164-
return $GLOBALS['BE_USER']->user['lang'];
138+
$localeFactory = GeneralUtility::makeInstance(Locales::class);
139+
if ($localeOrLanguageKey instanceof Locale) {
140+
$locale = $localeOrLanguageKey;
141+
} elseif (is_string($localeOrLanguageKey) && $localeOrLanguageKey !== '') {
142+
$locale = $localeFactory->createLocale($localeOrLanguageKey);
143+
} else {
144+
$locale = $localeFactory->createLocaleFromRequest($GLOBALS['TYPO3_REQUEST'] ?? null);
165145
}
166-
return 'default';
146+
if (!empty($alternativeLanguageKeys)) {
147+
$locale->setDependencies($alternativeLanguageKeys);
148+
}
149+
return $locale;
167150
}
168151

169152
/**
@@ -173,6 +156,11 @@ protected static function getLanguageKey(): string
173156
*/
174157
protected static function loadTypoScriptLabels(string $extensionName): array
175158
{
159+
// Only allow overrides in Frontend Context
160+
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
161+
if (!$request instanceof ServerRequestInterface || !ApplicationType::fromRequest($request)->isFrontend()) {
162+
return [];
163+
}
176164
$configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class);
177165
$frameworkConfiguration = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, $extensionName);
178166
if (!is_array($frameworkConfiguration['_LOCAL_LANG'] ?? false)) {
@@ -229,18 +217,6 @@ protected static function flattenTypoScriptLabelArray(array $labelValues, string
229217
return $result;
230218
}
231219

232-
/**
233-
* Returns the currently configured "site language" if a site is configured (= resolved)
234-
* in the current request.
235-
*/
236-
protected static function getCurrentSiteLanguage(): ?SiteLanguage
237-
{
238-
if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
239-
return $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
240-
}
241-
return null;
242-
}
243-
244220
protected static function getRuntimeCache(): FrontendInterface
245221
{
246222
return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');

typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
namespace TYPO3\CMS\Extbase\Tests\Functional\Utility;
1919

2020
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21+
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
22+
use TYPO3\CMS\Core\Http\ServerRequest;
2123
use TYPO3\CMS\Core\Utility\GeneralUtility;
2224
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
2325
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
@@ -144,6 +146,9 @@ public function translateTestWithExplicitLanguageParameters(
144146
*/
145147
public function loadTypoScriptLabels(): void
146148
{
149+
$GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
150+
$GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']
151+
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE);
147152
$configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
148153
$configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class);
149154
$configurationManagerInterfaceMock
@@ -181,6 +186,9 @@ public function loadTypoScriptLabels(): void
181186
*/
182187
public function clearLabelWithTypoScript(): void
183188
{
189+
$GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
190+
$GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']
191+
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE);
184192
$configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
185193
$configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class);
186194
$configurationManagerInterfaceMock
@@ -216,6 +224,9 @@ public function translateThrowsExceptionWithEmptyExtensionNameIfKeyIsNotPrefixed
216224
*/
217225
public function translateWillReturnLabelsFromTsEvenIfNoXlfFileExists(): void
218226
{
227+
$GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
228+
$GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']
229+
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE);
219230
$typoScriptLocalLang = [
220231
'_LOCAL_LANG' => [
221232
'da' => [
@@ -233,7 +244,7 @@ public function translateWillReturnLabelsFromTsEvenIfNoXlfFileExists(): void
233244
->willReturn($typoScriptLocalLang);
234245
GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock);
235246

236-
$result = LocalizationUtility::translate('key1', 'core', languageKey: 'da');
247+
$result = LocalizationUtility::translate('key1', 'core', [], 'da');
237248

238249
self::assertSame('I am a new key and there is no xlf file', $result);
239250
}

0 commit comments

Comments
 (0)