From ed503f9019e4dc7e7fe842a17fceeb90197422cc Mon Sep 17 00:00:00 2001 From: Christian Kuhn <lolli@schwarzbu.ch> Date: Mon, 28 Aug 2023 16:08:24 +0200 Subject: [PATCH] [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 disolve 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/+/80732 Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: core-ci <typo3@b13.com> Reviewed-by: Benni Mack <benni@typo3.org> --- composer.json | 1 + .../Localization/LanguageServiceFactory.php | 5 +- .../core/Classes/Localization/Locales.php | 24 +++++ .../Classes/Utility/LocalizationUtility.php | 63 +++++------- .../Utility/LocalizationUtilityTest.php | 13 ++- .../ViewHelpers/TranslateViewHelper.php | 95 ++++++++----------- .../Controller/TranslateController.php | 29 ++++++ .../Configuration/Services.yaml | 8 ++ .../Private/Templates/Translate.html | 8 ++ .../Extensions/test_translate/composer.json | 5 + .../test_translate/ext_localconf.php | 18 ++++ .../fluid/Tests/Functional/Fixtures/pages.csv | 19 ++-- .../ViewHelpers/TranslateViewHelperTest.php | 72 ++++++++++++++ 13 files changed, 250 insertions(+), 110 deletions(-) create mode 100644 typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Classes/Controller/TranslateController.php create mode 100644 typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Configuration/Services.yaml create mode 100644 typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Resources/Private/Templates/Translate.html create mode 100644 typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/ext_localconf.php diff --git a/composer.json b/composer.json index eb78ab966f34..318fbbc04062 100644 --- a/composer.json +++ b/composer.json @@ -301,6 +301,7 @@ "TYPO3Tests\\ParentChildTranslation\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/parent_child_translation/Classes/", "TYPO3Tests\\TestEid\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_eid/Classes/", "TYPO3Tests\\FluidTest\\": "typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/fluid_test/Classes/", + "TYPO3Tests\\TestTranslate\\": "typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Classes/", "TYPO3Tests\\FormCachingTests\\" : "typo3/sysext/form/Tests/Functional/RequestHandling/Fixtures/Extensions/form_caching_tests/Classes/", "TYPO3Tests\\RequestMirror\\": "typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Classes/", "TYPO3Tests\\TestValidators\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_validators/Classes/", diff --git a/typo3/sysext/core/Classes/Localization/LanguageServiceFactory.php b/typo3/sysext/core/Classes/Localization/LanguageServiceFactory.php index 02edccecf330..19cd958f577b 100644 --- a/typo3/sysext/core/Classes/Localization/LanguageServiceFactory.php +++ b/typo3/sysext/core/Classes/Localization/LanguageServiceFactory.php @@ -51,10 +51,7 @@ class LanguageServiceFactory public function createFromUserPreferences(?AbstractUserAuthentication $user): LanguageService { - if ($user && ($user->user['lang'] ?? false)) { - return $this->create($this->locales->createLocale($user->user['lang'])); - } - return $this->create('en'); + return $this->create($this->locales->createLocaleFromUserPreferences($user)); } public function createFromSiteLanguage(SiteLanguage $language): LanguageService diff --git a/typo3/sysext/core/Classes/Localization/Locales.php b/typo3/sysext/core/Classes/Localization/Locales.php index ff0fa3ce45ea..a697fa8b8fbb 100644 --- a/typo3/sysext/core/Classes/Localization/Locales.php +++ b/typo3/sysext/core/Classes/Localization/Locales.php @@ -17,7 +17,10 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Localization; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication; use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Http\ApplicationType; use TYPO3\CMS\Core\Log\LogManager; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; @@ -353,4 +356,25 @@ class Locales implements SingletonInterface } return true; } + + public function createLocaleFromRequest(?ServerRequestInterface $request): Locale + { + $languageServiceFactory = GeneralUtility::makeInstance(LanguageServiceFactory::class); + if ($request !== null && ApplicationType::fromRequest($request)->isFrontend()) { + // @todo: the string conversion is needed for the time being, as long as SiteLanguage does not contain + // the full locale with all fallbacks, then getTypo3Language() also needs to be removed. + $localeString = (string)($request->getAttribute('language')?->getTypo3Language() + ?? $request->getAttribute('site')->getDefaultLanguage()->getTypo3Language()); + return $this->createLocale($localeString); + } + return $languageServiceFactory->createFromUserPreferences($GLOBALS['BE_USER'] ?? null)->getLocale(); + } + + public function createLocaleFromUserPreferences(?AbstractUserAuthentication $user): Locale + { + if ($user && ($user->user['lang'] ?? false)) { + return $this->createLocale($user->user['lang']); + } + return $this->createLocale('en'); + } } diff --git a/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php b/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php index 898cd06a4156..688af821132c 100644 --- a/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php +++ b/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php @@ -25,7 +25,6 @@ use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; use TYPO3\CMS\Core\Localization\Locale; use TYPO3\CMS\Core\Localization\Locales; -use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; @@ -66,19 +65,15 @@ class LocalizationUtility } $languageFilePath = static::getLanguageFilePath($extensionName); } - if ($languageKey === null) { - $languageKey = static::getLanguageKey(); - } - if ($languageKey instanceof Locale) { - $languageKey = (string)$languageKey; - } - $languageService = static::initializeLocalization($languageFilePath, $languageKey, $extensionName); + $locale = self::getLocale($languageKey); + $languageService = static::initializeLocalization($languageFilePath, $locale, $extensionName); $resolvedLabel = $languageService->sL('LLL:' . $languageFilePath . ':' . $key); $value = $resolvedLabel !== '' ? $resolvedLabel : null; // Check if a value was explicitly set to "" via TypoScript, if so, we need to ensure that this is "" and not null if ($extensionName) { $overrideLabels = static::loadTypoScriptLabels($extensionName); + $languageKey = $locale->getName(); // @todo: probably cannot handle "de-DE" and "de" fallbacks if ($value === null && isset($overrideLabels[$languageKey])) { $value = ''; @@ -98,9 +93,9 @@ class LocalizationUtility * Loads local-language values by looking for a "locallang.xlf" file in the plugin resources directory and if found includes it. * Locallang values set in the TypoScript property "_LOCAL_LANG" are merged onto the values found in the "locallang.xlf" file. */ - protected static function initializeLocalization(string $languageFilePath, string $languageKey, ?string $extensionName): LanguageService + protected static function initializeLocalization(string $languageFilePath, Locale $locale, ?string $extensionName): LanguageService { - $languageService = self::buildLanguageService($languageKey, $languageFilePath); + $languageService = self::buildLanguageService($locale, $languageFilePath); if (!empty($extensionName)) { $overrideLabels = static::loadTypoScriptLabels($extensionName); if ($overrideLabels !== []) { @@ -110,13 +105,11 @@ class LocalizationUtility return $languageService; } - protected static function buildLanguageService(string $languageKey, string $languageFilePath): LanguageService + protected static function buildLanguageService(Locale $locale, string $languageFilePath): LanguageService { - $languageKeyHash = sha1(json_encode(array_merge([$languageKey], [$languageFilePath]))); + $languageKeyHash = sha1(json_encode(array_merge([(string)$locale], $locale->getDependencies(), [$languageFilePath]))); $cache = self::getRuntimeCache(); if (!$cache->get($languageKeyHash)) { - // Using the Locales factory, as it handles dependencies (e.g. "de-AT" falls back to "de") - $locale = GeneralUtility::makeInstance(Locales::class)->createLocale($languageKey); $languageService = GeneralUtility::makeInstance(LanguageServiceFactory::class)->create($locale); $languageService->includeLLFile($languageFilePath); $cache->set($languageKeyHash, $languageService); @@ -133,24 +126,19 @@ class LocalizationUtility } /** - * Resolves the currently active language key. + * Resolves the currently active locale. + * Using the Locales factory, as it handles dependencies (e.g. "de-AT" falls back to "de"). */ - protected static function getLanguageKey(): string + protected static function getLocale(Locale|string|null $localeOrLanguageKey): Locale { - if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface - && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() - ) { - // Frontend application - $siteLanguage = self::getCurrentSiteLanguage(); - - // Get values from site language - if ($siteLanguage !== null) { - return $siteLanguage->getTypo3Language(); - } - } elseif (!empty($GLOBALS['BE_USER']->user['lang'])) { - return $GLOBALS['BE_USER']->user['lang']; + if ($localeOrLanguageKey instanceof Locale) { + return $localeOrLanguageKey; } - return 'default'; + $localeFactory = GeneralUtility::makeInstance(Locales::class); + if (is_string($localeOrLanguageKey)) { + return $localeFactory->createLocale($localeOrLanguageKey); + } + return $localeFactory->createLocaleFromRequest($GLOBALS['TYPO3_REQUEST'] ?? null); } /** @@ -160,6 +148,11 @@ class LocalizationUtility */ protected static function loadTypoScriptLabels(string $extensionName): array { + // Only allow overrides in Frontend Context + $request = $GLOBALS['TYPO3_REQUEST'] ?? null; + if (!$request instanceof ServerRequestInterface || !ApplicationType::fromRequest($request)->isFrontend()) { + return []; + } $configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class); $frameworkConfiguration = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, $extensionName); if (!is_array($frameworkConfiguration['_LOCAL_LANG'] ?? false)) { @@ -216,18 +209,6 @@ class LocalizationUtility return $result; } - /** - * Returns the currently configured "site language" if a site is configured (= resolved) - * in the current request. - */ - protected static function getCurrentSiteLanguage(): ?SiteLanguage - { - if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) { - return $GLOBALS['TYPO3_REQUEST']->getAttribute('language'); - } - return null; - } - protected static function getRuntimeCache(): FrontendInterface { return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); diff --git a/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php b/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php index 321ac7fc4604..ee46efdd15c2 100644 --- a/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php @@ -18,6 +18,8 @@ declare(strict_types=1); namespace TYPO3\CMS\Extbase\Tests\Functional\Utility; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; +use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; @@ -143,6 +145,9 @@ final class LocalizationUtilityTest extends FunctionalTestCase */ public function loadTypoScriptLabels(): void { + $GLOBALS['TYPO3_REQUEST'] = new ServerRequest(); + $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST'] + ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK; $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); $configurationManagerInterfaceMock @@ -180,6 +185,9 @@ final class LocalizationUtilityTest extends FunctionalTestCase */ public function clearLabelWithTypoScript(): void { + $GLOBALS['TYPO3_REQUEST'] = new ServerRequest(); + $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST'] + ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK; $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); $configurationManagerInterfaceMock @@ -215,6 +223,9 @@ final class LocalizationUtilityTest extends FunctionalTestCase */ public function translateWillReturnLabelsFromTsEvenIfNoXlfFileExists(): void { + $GLOBALS['TYPO3_REQUEST'] = new ServerRequest(); + $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST'] + ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); $typoScriptLocalLang = [ '_LOCAL_LANG' => [ 'da' => [ @@ -232,7 +243,7 @@ final class LocalizationUtilityTest extends FunctionalTestCase ->willReturn($typoScriptLocalLang); GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); - $result = LocalizationUtility::translate('key1', 'core', languageKey: 'da'); + $result = LocalizationUtility::translate('key1', 'core', [], 'da'); self::assertSame('I am a new key and there is no xlf file', $result); } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php index 28096b7c840e..fba4f2c52488 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php @@ -18,10 +18,8 @@ declare(strict_types=1); namespace TYPO3\CMS\Fluid\ViewHelpers; use Psr\Http\Message\ServerRequestInterface; -use TYPO3\CMS\Core\Http\ApplicationType; -use TYPO3\CMS\Core\Localization\LanguageService; -use TYPO3\CMS\Core\Localization\LanguageServiceFactory; use TYPO3\CMS\Core\Localization\Locale; +use TYPO3\CMS\Core\Localization\Locales; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\RequestInterface as ExtbaseRequestInterface; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; @@ -123,7 +121,7 @@ final class TranslateViewHelper extends AbstractViewHelper $this->registerArgument('default', 'string', 'If the given locallang key could not be found, this value is used. If this argument is not set, child nodes will be used to render the default'); $this->registerArgument('arguments', 'array', 'Arguments to be replaced in the resulting string'); $this->registerArgument('extensionName', 'string', 'UpperCamelCased extension key (for example BlogExample)'); - $this->registerArgument('languageKey', 'string', 'Language key ("da" for example) or "default" to use. If empty, use current language.'); + $this->registerArgument('languageKey', 'string', 'Language key ("da" for example) or "default" to use. Also a Locale object is possible. If empty, use current locale from the request.'); } /** @@ -155,71 +153,58 @@ final class TranslateViewHelper extends AbstractViewHelper $request = $renderingContext->getRequest(); } - if (!$request instanceof ExtbaseRequestInterface) { - // Straight resolving via core LanguageService in non-extbase context - if (!str_starts_with($id, 'LLL:EXT:')) { - // Resolve "short key" without LLL:EXT: syntax given, if an extension name is given. - // @todo: We could consider to deprecate this case. It is mostly implemented for a more - // smooth transition when (backend) controllers no longer feed an extbase request. - if (!empty($extensionName)) { - $id = 'LLL:EXT:' . GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName) . '/Resources/Private/Language/locallang.xlf:' . $id; - } elseif (empty($default)) { - // Throw exception in case neither an extension key nor a default value - // are given, since the "short key" shouldn't be considered as a label. - throw new \RuntimeException( - 'ViewHelper f:translate in non-extbase context needs attribute "extensionName" to resolve' - . ' key="' . $id . '" without path. Either set attribute "extensionName" together with the short' - . ' key "yourKey" to result in a lookup "LLL:EXT:your_extension/Resources/Private/Language/locallang.xlf:yourKey",' - . ' or (better) use a full LLL reference like key="LLL:EXT:your_extension/Resources/Private/Language/yourFile.xlf:yourKey".' - . ' Alternatively, you can also define a default value.', - 1639828178 - ); - } + if (empty($extensionName)) { + if ($request instanceof ExtbaseRequestInterface) { + $extensionName = $request->getControllerExtensionName(); + } elseif (str_starts_with($id, 'LLL:EXT:')) { + $extensionName = substr($id, 8, strpos($id, '/', 8) - 8); + } elseif ($default) { + return self::handleDefaultValue($default, $translateArguments); + } else { + // Throw exception in case neither an extension key nor a extbase request + // are given, since the "short key" shouldn't be considered as a label. + throw new \RuntimeException( + 'ViewHelper f:translate in non-extbase context needs attribute "extensionName" to resolve' + . ' key="' . $id . '" without path. Either set attribute "extensionName" together with the short' + . ' key "yourKey" to result in a lookup "LLL:EXT:your_extension/Resources/Private/Language/locallang.xlf:yourKey",' + . ' or (better) use a full LLL reference like key="LLL:EXT:your_extension/Resources/Private/Language/yourFile.xlf:yourKey".' + . ' Alternatively, you can also define a default value.', + 1639828178 + ); } - $value = self::getLanguageService($request, $arguments['languageKey'])->sL($id); - if (empty($value) || (!str_starts_with($id, 'LLL:EXT:') && $value === $id)) { - // In case $value is empty (LLL: could not be resolved) or $value - // is the same as $id and is no "LLL:", fall back to the default. - $value = $default; - } - if (!empty($translateArguments)) { - $value = vsprintf($value, $translateArguments); - } - return $value; } - - /** @var ExtbaseRequestInterface $request */ - $extensionName = $extensionName ?? $request->getControllerExtensionName(); try { - // Trigger full extbase magic: "<f:translate key="key1" />" will look up - // "LLL:EXT:current_extension/Resources/Private/Language/locallang.xlf:key1" AND - // overloads from _LOCAL_LANG extbase TypoScript settings if specified. - // Not this triggers TypoScript parsing via extbase ConfigurationManager - // and should be avoided in backend context! - $value = LocalizationUtility::translate($id, $extensionName, $translateArguments, $arguments['languageKey']); + $locale = self::getUsedLocale($arguments['languageKey'], $request); + $value = LocalizationUtility::translate($id, $extensionName, $translateArguments, $locale); } catch (\InvalidArgumentException) { // @todo: Switch to more specific Exceptions here - for instance those thrown when a package was not found, see #95957 $value = null; } if ($value === null) { - $value = $default; - if (!empty($translateArguments)) { - $value = vsprintf($value, $translateArguments); - } + return self::handleDefaultValue($default, $translateArguments); } return $value; } - protected static function getLanguageService(ServerRequestInterface $request = null, string|Locale $languageKey = null): LanguageService + /** + * Ensure that a string is returned, if the underlying logic returns null, or cannot handle a translation + */ + protected static function handleDefaultValue(string $default, ?array $translateArguments): string + { + if (!empty($translateArguments)) { + return vsprintf($default, $translateArguments); + } + return $default; + } + + protected static function getUsedLocale(Locale|string|null $languageKey, ?ServerRequestInterface $request): Locale|string|null { - $languageServiceFactory = GeneralUtility::makeInstance(LanguageServiceFactory::class); - if ($languageKey) { - return $languageServiceFactory->create($languageKey); + if ($languageKey !== null && $languageKey !== '') { + return $languageKey; } - if ($request !== null && ApplicationType::fromRequest($request)->isFrontend()) { - return $languageServiceFactory->createFromSiteLanguage($request->getAttribute('language') - ?? $request->getAttribute('site')->getDefaultLanguage()); + if ($request) { + return GeneralUtility::makeInstance(Locales::class)->createLocaleFromRequest($request); } - return $languageServiceFactory->createFromUserPreferences($GLOBALS['BE_USER'] ?? null); + return null; } } diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Classes/Controller/TranslateController.php b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Classes/Controller/TranslateController.php new file mode 100644 index 000000000000..127e76651776 --- /dev/null +++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Classes/Controller/TranslateController.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3Tests\TestTranslate\Controller; + +use Psr\Http\Message\ResponseInterface; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class TranslateController extends ActionController +{ + public function translateAction(): ResponseInterface + { + return $this->htmlResponse($this->view->render()); + } +} diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Configuration/Services.yaml b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Configuration/Services.yaml new file mode 100644 index 000000000000..f0428618f6fe --- /dev/null +++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Configuration/Services.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + TYPO3Tests\TestTranslate\: + resource: '../Classes/*' diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Resources/Private/Templates/Translate.html b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Resources/Private/Templates/Translate.html new file mode 100644 index 000000000000..c4b9dea991c5 --- /dev/null +++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/Resources/Private/Templates/Translate.html @@ -0,0 +1,8 @@ +<html + xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" + data-namespace-typo3-fluid="true" +> + +<f:translate key="localized.to.de" /> + +</html> diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/composer.json b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/composer.json index 49b7b1490544..d856e6037424 100644 --- a/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/composer.json +++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/composer.json @@ -6,6 +6,11 @@ "require": { "typo3/cms-core": "13.0.*@dev" }, + "autoload": { + "psr-4": { + "TYPO3Tests\\TestTranslate\\": "Classes" + } + }, "extra": { "typo3/cms": { "extension-key": "test_translate" diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/ext_localconf.php b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/ext_localconf.php new file mode 100644 index 000000000000..7f4f1c4a37ec --- /dev/null +++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate/ext_localconf.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +use TYPO3\CMS\Extbase\Utility\ExtensionUtility; +use TYPO3Tests\TestTranslate\Controller\TranslateController; + +defined('TYPO3') or die(); + +ExtensionUtility::configurePlugin( + 'TestTranslate', + 'Test', + [ + TranslateController::class => 'translate', + ], + [ + ] +); diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/pages.csv b/typo3/sysext/fluid/Tests/Functional/Fixtures/pages.csv index 1670bd5d6f25..6598db095cd9 100644 --- a/typo3/sysext/fluid/Tests/Functional/Fixtures/pages.csv +++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/pages.csv @@ -1,9 +1,10 @@ -"pages",,,,,,, -,"uid","pid","title","sorting","deleted","perms_everybody","slug" -,1,0,"Root",128,0,15,"/" -,2,1,"Dummy 1-2",128,0,15,"/dummy-1-2" -,3,2,"Dummy 1-2-3",128,0,15,"/dummy-1-2/dummy-1-2-3" -,4,3,"Dummy 1-2-3-4",128,0,15,"/dummy-1-2/dummy-1-2-3/dummy-1-2-3-4" -,5,1,"Dummy 1-5",128,0,15,"/dummy-1-5" -,6,5,"Dummy 1-5-6",128,0,15,"/dummy-1-5/dummy-1-5-6" -,7,0,"Root 2",128,0,15,"/" +"pages" +,"uid","pid","title","sorting","deleted","sys_language_uid","l10n_parent","perms_everybody","slug" +,1,0,"Root",128,0,0,0,15,"/" +,2,1,"Dummy 1-2",128,0,0,0,15,"/dummy-1-2" +,3,2,"Dummy 1-2-3",128,0,0,0,15,"/dummy-1-2/dummy-1-2-3" +,4,3,"Dummy 1-2-3-4",128,0,0,0,15,"/dummy-1-2/dummy-1-2-3/dummy-1-2-3-4" +,5,1,"Dummy 1-5",256,0,0,15,0,"/dummy-1-5" +,6,5,"Dummy 1-5-6",128,0,0,0,15,"/dummy-1-5/dummy-1-5-6" +,7,0,"Root 2",128,0,0,0,15,"/" +,8,1,"Dummy 1-2 DE",129,0,1,2,15,"/de/dummy-1-2" diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php index 9ebde0060292..3b9aee7fbe72 100644 --- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php @@ -18,19 +18,29 @@ declare(strict_types=1); namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers; use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; +use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Http\Uri; use TYPO3\CMS\Core\Localization\Locales; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; +use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait; use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters; use TYPO3\CMS\Extbase\Mvc\Request; use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory; +use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; use TYPO3Fluid\Fluid\Core\ViewHelper\Exception; use TYPO3Fluid\Fluid\View\TemplateView; final class TranslateViewHelperTest extends FunctionalTestCase { + use SiteBasedTestTrait; + + protected const LANGUAGE_PRESETS = [ + 'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'], + 'DE' => ['id' => 1, 'title' => 'Deutsch', 'locale' => 'de_DE.UTF8'], + ]; + protected array $testExtensionsToLoad = [ 'typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/test_translate', ]; @@ -418,4 +428,66 @@ final class TranslateViewHelperTest extends FunctionalTestCase $templateView->assign('myLocale', (new Locales())->createLocale('de_at')); self::assertSame('DE_AT label', $templateView->render()); } + + /** + * @test + */ + public function renderInExtbaseFrontendContextHandlesLabelOverrideWithTypoScriptInDefaultLanguage(): void + { + $this->importCSVDataSet(__DIR__ . '/../Fixtures/pages.csv'); + $this->writeSiteConfiguration( + 'test', + $this->buildSiteConfiguration(1, '/'), + ); + (new ConnectionPool())->getConnectionForTable('sys_template')->insert('sys_template', [ + 'pid' => 1, + 'root' => 1, + 'clear' => 1, + 'config' => <<<EOT +page = PAGE +page.10 = EXTBASEPLUGIN +page.10 { + extensionName = TestTranslate + pluginName = Test +} +plugin.tx_testtranslate_test._LOCAL_LANG.default.localized\.to\.de = TypoScript default label +EOT + ]); + $response = $this->executeFrontendSubRequest((new InternalRequest())->withPageId(2)); + self::assertStringContainsString('TypoScript default label', (string)$response->getBody()); + + } + + /** + * @test + */ + public function renderInExtbaseFrontendContextHandlesLabelOverrideWithTypoScriptInLocalizedPage(): void + { + $this->importCSVDataSet(__DIR__ . '/../Fixtures/pages.csv'); + $this->writeSiteConfiguration( + 'test', + $this->buildSiteConfiguration(1, '/'), + [ + $this->buildDefaultLanguageConfiguration('EN', '/'), + $this->buildLanguageConfiguration('DE', '/de/', ['EN']), + ] + ); + (new ConnectionPool())->getConnectionForTable('sys_template')->insert('sys_template', [ + 'pid' => 1, + 'root' => 1, + 'clear' => 1, + 'config' => <<<EOT +page = PAGE +page.10 = EXTBASEPLUGIN +page.10 { + extensionName = TestTranslate + pluginName = Test +} +plugin.tx_testtranslate_test._LOCAL_LANG.de-DE.localized\.to\.de = TypoScript de label +EOT + ]); + $response = $this->executeFrontendSubRequest((new InternalRequest())->withPageId(2)->withLanguageId(1)); + self::assertStringContainsString('TypoScript de label', (string)$response->getBody()); + + } } -- GitLab