diff --git a/composer.json b/composer.json index eb78ab966f34d42a240431c63677008e4a18955a..318fbbc04062e5ac94f381bec0c9687b0e8e0e3b 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 02edccecf330684433b0a9ff3e9bb12270b3a579..19cd958f577b47e80a6d9d918ebd803bc99e78c6 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 ff0fa3ce45ea1d55d83e352605572b657bad4846..a697fa8b8fbb3213bf4fe663eb2a9ae4a3cadcfb 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 898cd06a41562e7989c1a7d15dd82ac819abe447..688af821132c94ee3214256f91c4fb6293152da3 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 321ac7fc4604724c5ff31576c7a5b8cd1f0a6148..ee46efdd15c2a28570ff7cace5592f1b7e9e97dd 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 28096b7c840ed19d7b467dbf5d2d6be17bccabce..fba4f2c5248838d4b9fb2d126550fa489f9b0e80 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 0000000000000000000000000000000000000000..127e766517767481a21772fbf0fbc5ac1560a56a --- /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 0000000000000000000000000000000000000000..f0428618f6fed2738831dc2a7e0e6f868dbc76b6 --- /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 0000000000000000000000000000000000000000..c4b9dea991c5d90b9f234e9dbb4d02d9f89526be --- /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 49b7b149054412ad1957faaa98822542348658df..d856e6037424da7964071bfed060873930670bcd 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 0000000000000000000000000000000000000000..7f4f1c4a37ecd3c262c94e10d96db2085db23b8f --- /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 1670bd5d6f25401fe53fefb4311116929fdfe1c1..6598db095cd9aa0abd5e5f6b53ba2f0bebc37be0 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 9ebde00602920b15dee086007821ef3ef8b6df3d..3b9aee7fbe729ce21d23f82fd7e776aa5b1c2601 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()); + + } }