From 4dbe5bdc87e56ea8da764d2cbf039b4f917b3085 Mon Sep 17 00:00:00 2001
From: Markus Hoelzle <typo3@markus-hoelzle.de>
Date: Thu, 7 Sep 2017 16:45:39 +0200
Subject: [PATCH] [FEATURE] Add possibility to get a label in a specific
 language

Add possibility to get a label in a specific language in
LocalizationUtility::translate() and the TranslateViewHelper

Change-Id: I8589e2b155e57eed3124ed48b0d859fe7796ff3b
Resolves: #82354
Related: #81834
Releases: master
Reviewed-on: https://review.typo3.org/53967
Tested-by: TYPO3com <no-reply@typo3.com>
Tested-by: Joerg Kummer <typo3@enobe.de>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
Tested-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
---
 ...sibilityToGetALabelInASpecificLanguage.rst |  40 +++
 .../Classes/Utility/LocalizationUtility.php   | 200 ++++++-----
 .../Unit/Utility/LocalizationUtilityTest.php  | 336 +++++++++---------
 .../ViewHelpers/TranslateViewHelper.php       |  18 +-
 ...anslateViewHelperFixtureForEmptyString.php |   4 +-
 5 files changed, 334 insertions(+), 264 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-82354-AddPossibilityToGetALabelInASpecificLanguage.rst

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-82354-AddPossibilityToGetALabelInASpecificLanguage.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-82354-AddPossibilityToGetALabelInASpecificLanguage.rst
new file mode 100644
index 000000000000..51bb6de04dc1
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-82354-AddPossibilityToGetALabelInASpecificLanguage.rst
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+=======================================================================
+Feature: #82354 - Add possibility to get a label in a specific language
+=======================================================================
+
+See :issue:`82354`
+
+Description
+===========
+
+The extbase related LocalizationUtility now supports retrieving the localization
+of a key in a different language than the initialized language of the given user.
+This allows for instance rendering a text in french while the users language is
+german. This feature works in the frontend and backend of TYPO3 and supports
+to retrieve the labels via an extension name or an explicit specified
+`LLL:path/locallang.xlf:label` key.
+
+The ViewHelper `<f:translate />` and the utility `LocalizationUtility::translate()`
+do support now two new optional Parameters `languageKey` and `alternativeLanguageKeys`
+to control the output language.
+
+Hint: The `alternativeLanguageKeys` will be used "reversed" (this behaviour was not changed here but could be confusing).
+So if the `alternativeLanguageKeys` is defined with "fr,de", then "de" will used before "fr".
+
+
+Basic Usage
+===========
+
+.. code-block:: php
+
+    \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('someKey', 'extensionName', [], 'dk');
+
+
+.. code-block:: html
+
+   <f:translate key="someKey" languageKey="dk" />
+
+
+.. index:: Fluid, PHP-API
diff --git a/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php b/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php
index 1dd951a214cd..404dd911bbe7 100644
--- a/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php
+++ b/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php
@@ -49,20 +49,6 @@ class LocalizationUtility
      */
     protected static $LOCAL_LANG_UNSET = [];
 
-    /**
-     * Key of the language to use
-     *
-     * @var string
-     */
-    protected static $languageKey = 'default';
-
-    /**
-     * Pointer to alternative fall-back language to use
-     *
-     * @var array
-     */
-    protected static $alternativeLanguageKeys = [];
-
     /**
      * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
      */
@@ -73,16 +59,21 @@ class LocalizationUtility
      *
      * @param string $key The key from the LOCAL_LANG array for which to return the value.
      * @param string|null $extensionName The name of the extension
-     * @param array $arguments the arguments of the extension, being passed over to vsprintf
-     * @return string|NULL The value from LOCAL_LANG or NULL if no translation was found.
+     * @param array $arguments The arguments of the extension, being passed over to vsprintf
+     * @param string $languageKey The language key or null for using the current language from the system
+     * @param string[] $alternativeLanguageKeys The alternative language keys if no translation was found. If null and we are in the frontend, then the language_alt from TypoScript setup will be used
+     * @return string|null The value from LOCAL_LANG or null if no translation was found.
      * @api
      * @todo : If vsprintf gets a malformed string, it returns FALSE! Should we throw an exception there?
      */
-    public static function translate($key, $extensionName = null, $arguments = null)
+    public static function translate($key, $extensionName = null, $arguments = null, string $languageKey = null, array $alternativeLanguageKeys = null)
     {
         $value = null;
         if (GeneralUtility::isFirstPartOfStr($key, 'LLL:')) {
-            $value = self::translateFileReference($key);
+            $keyParts = explode(':', $key);
+            unset($keyParts[0]);
+            $key = array_pop($keyParts);
+            $languageFilePath = implode(':', $keyParts);
         } else {
             if (empty($extensionName)) {
                 throw new \InvalidArgumentException(
@@ -90,33 +81,44 @@ class LocalizationUtility
                     1498144052
                 );
             }
-            self::initializeLocalization($extensionName);
-            // The "from" charset of csConv() is only set for strings from TypoScript via _LOCAL_LANG
-            if (!empty(self::$LOCAL_LANG[$extensionName][self::$languageKey][$key][0]['target'])
-                || isset(self::$LOCAL_LANG_UNSET[$extensionName][self::$languageKey][$key])
-            ) {
-                // Local language translation for key exists
-                $value = self::$LOCAL_LANG[$extensionName][self::$languageKey][$key][0]['target'];
-            } elseif (!empty(self::$alternativeLanguageKeys)) {
-                $languages = array_reverse(self::$alternativeLanguageKeys);
-                foreach ($languages as $language) {
-                    if (!empty(self::$LOCAL_LANG[$extensionName][$language][$key][0]['target'])
-                        || isset(self::$LOCAL_LANG_UNSET[$extensionName][$language][$key])
-                    ) {
-                        // Alternative language translation for key exists
-                        $value = self::$LOCAL_LANG[$extensionName][$language][$key][0]['target'];
-                        break;
-                    }
+            $languageFilePath = static::getLanguageFilePath($extensionName);
+        }
+        $languageFilePath = GeneralUtility::getFileAbsFileName($languageFilePath);
+        $languageKeys = static::getLanguageKeys();
+        if ($languageKey === null) {
+            $languageKey = $languageKeys['languageKey'];
+        }
+        if (empty($alternativeLanguageKeys)) {
+            $alternativeLanguageKeys = $languageKeys['alternativeLanguageKeys'];
+        }
+        static::initializeLocalization($languageFilePath, $languageKey, $alternativeLanguageKeys, $extensionName);
+
+        // The "from" charset of csConv() is only set for strings from TypoScript via _LOCAL_LANG
+        if (!empty(self::$LOCAL_LANG[$languageFilePath][$languageKey][$key][0]['target'])
+            || isset(self::$LOCAL_LANG_UNSET[$languageFilePath][$languageKey][$key])
+        ) {
+            // Local language translation for key exists
+            $value = self::$LOCAL_LANG[$languageFilePath][$languageKey][$key][0]['target'];
+        } elseif (!empty($alternativeLanguageKeys)) {
+            $languages = array_reverse($alternativeLanguageKeys);
+            foreach ($languages as $language) {
+                if (!empty(self::$LOCAL_LANG[$languageFilePath][$language][$key][0]['target'])
+                    || isset(self::$LOCAL_LANG_UNSET[$languageFilePath][$language][$key])
+                ) {
+                    // Alternative language translation for key exists
+                    $value = self::$LOCAL_LANG[$languageFilePath][$language][$key][0]['target'];
+                    break;
                 }
             }
-            if ($value === null && (!empty(self::$LOCAL_LANG[$extensionName]['default'][$key][0]['target'])
-                || isset(self::$LOCAL_LANG_UNSET[$extensionName]['default'][$key]))
-            ) {
-                // Default language translation for key exists
-                // No charset conversion because default is English and thereby ASCII
-                $value = self::$LOCAL_LANG[$extensionName]['default'][$key][0]['target'];
-            }
         }
+        if ($value === null && (!empty(self::$LOCAL_LANG[$languageFilePath]['default'][$key][0]['target'])
+            || isset(self::$LOCAL_LANG_UNSET[$languageFilePath]['default'][$key]))
+        ) {
+            // Default language translation for key exists
+            // No charset conversion because default is English and thereby ASCII
+            $value = self::$LOCAL_LANG[$languageFilePath]['default'][$key][0]['target'];
+        }
+
         if (is_array($arguments) && $value !== null) {
             return vsprintf($value, $arguments);
         }
@@ -124,81 +126,86 @@ class LocalizationUtility
     }
 
     /**
-     * Returns the localized label of the LOCAL_LANG key, $key.
+     * Loads local-language values by looking for a "locallang.xlf" (or "locallang.xml") file in the plugin resources directory and if found includes it.
+     * Also locallang values set in the TypoScript property "_LOCAL_LANG" are merged onto the values found in the "locallang.xlf" file.
      *
-     * @param string $key The language key including the path to a custom locallang file ("LLL:path:key").
-     * @return string The value from LOCAL_LANG or NULL if no translation was found.
-     * @see language::sL()
-     * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::sL()
+     * @param string $languageFilePath
+     * @param string $languageKey
+     * @param string[] $alternativeLanguageKeys
+     * @param string $extensionName
      */
-    protected static function translateFileReference($key)
+    protected static function initializeLocalization(string $languageFilePath, string $languageKey, array $alternativeLanguageKeys, string $extensionName = null)
     {
-        if (TYPO3_MODE === 'FE') {
-            $value = self::getTypoScriptFrontendController()->sL($key);
-            return $value !== false ? $value : null;
+        $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
+
+        if (empty(self::$LOCAL_LANG[$languageFilePath][$languageKey])) {
+            $parsedData = $languageFactory->getParsedData($languageFilePath, $languageKey);
+            foreach ($parsedData as $tempLanguageKey => $data) {
+                if (!empty($data)) {
+                    self::$LOCAL_LANG[$languageFilePath][$tempLanguageKey] = $data;
+                }
+            }
+        }
+        if ($languageKey !== 'default') {
+            foreach ($alternativeLanguageKeys as $alternativeLanguageKey) {
+                if (empty(self::$LOCAL_LANG[$languageFilePath][$alternativeLanguageKey])) {
+                    $tempLL = $languageFactory->getParsedData($languageFilePath, $alternativeLanguageKey);
+                    if (isset($tempLL[$alternativeLanguageKey])) {
+                        self::$LOCAL_LANG[$languageFilePath][$alternativeLanguageKey] = $tempLL[$alternativeLanguageKey];
+                    }
+                }
+            }
         }
-        if (is_object($GLOBALS['LANG'])) {
-            $value = self::getLanguageService()->sL($key);
-            return $value !== '' ? $value : null;
+        if (!empty($extensionName)) {
+            static::loadTypoScriptLabels($extensionName, $languageFilePath);
         }
-        return $key;
     }
 
     /**
-     * Loads local-language values by looking for a "locallang.xlf" (or "locallang.xml") file in the plugin resources directory and if found includes it.
-     * Also locallang values set in the TypoScript property "_LOCAL_LANG" are merged onto the values found in the "locallang.xlf" file.
+     * Returns the default path and filename for an extension
      *
      * @param string $extensionName
+     * @return string
      */
-    protected static function initializeLocalization($extensionName)
+    protected static function getLanguageFilePath(string $extensionName): string
     {
-        if (isset(self::$LOCAL_LANG[$extensionName])) {
-            return;
-        }
-        $locallangPathAndFilename = 'EXT:' . GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName) . '/' . self::$locallangPath . 'locallang.xlf';
-        self::setLanguageKeys();
-
-        /** @var $languageFactory LocalizationFactory */
-        $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
-
-        self::$LOCAL_LANG[$extensionName] = $languageFactory->getParsedData($locallangPathAndFilename, self::$languageKey);
-        foreach (self::$alternativeLanguageKeys as $language) {
-            $tempLL = $languageFactory->getParsedData($locallangPathAndFilename, $language);
-            if (self::$languageKey !== 'default' && isset($tempLL[$language])) {
-                self::$LOCAL_LANG[$extensionName][$language] = $tempLL[$language];
-            }
-        }
-        self::loadTypoScriptLabels($extensionName);
+        return 'EXT:' . GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName) . '/' . self::$locallangPath . 'locallang.xlf';
     }
 
     /**
      * Sets the currently active language/language_alt keys.
-     * Default values are "default" for language key and "" for language_alt key.
+     * Default values are "default" for language key and an empty array for language_alt key.
+     *
+     * @return array
      */
-    protected static function setLanguageKeys()
+    protected static function getLanguageKeys(): array
     {
-        self::$languageKey = 'default';
-        self::$alternativeLanguageKeys = [];
+        $languageKeys = [
+            'languageKey' => 'default',
+            'alternativeLanguageKeys' => [],
+        ];
         if (TYPO3_MODE === 'FE') {
-            if (isset(self::getTypoScriptFrontendController()->config['config']['language'])) {
-                self::$languageKey = self::getTypoScriptFrontendController()->config['config']['language'];
-                if (isset(self::getTypoScriptFrontendController()->config['config']['language_alt'])) {
-                    self::$alternativeLanguageKeys[] = self::getTypoScriptFrontendController()->config['config']['language_alt'];
+            $tsfe = static::getTypoScriptFrontendController();
+            if (isset($tsfe->config['config']['language'])) {
+                $languageKeys['languageKey'] = $tsfe->config['config']['language'];
+                if (isset($tsfe->config['config']['language_alt'])) {
+                    $languageKeys['alternativeLanguageKeys'] = $tsfe->config['config']['language_alt'];
                 } else {
                     /** @var $locales \TYPO3\CMS\Core\Localization\Locales */
                     $locales = GeneralUtility::makeInstance(Locales::class);
-                    if (in_array(self::$languageKey, $locales->getLocales())) {
-                        foreach ($locales->getLocaleDependencies(self::$languageKey) as $language) {
-                            self::$alternativeLanguageKeys[] = $language;
+                    if (in_array($languageKeys['languageKey'], $locales->getLocales())) {
+                        foreach ($locales->getLocaleDependencies($languageKeys['languageKey']) as $language) {
+                            $languageKeys['alternativeLanguageKeys'] = $language;
                         }
                     }
                 }
             }
         } elseif (!empty($GLOBALS['BE_USER']->uc['lang'])) {
-            self::$languageKey = $GLOBALS['BE_USER']->uc['lang'];
-        } elseif (!empty(self::getLanguageService()->lang)) {
-            self::$languageKey = self::getLanguageService()->lang;
+            $languageKeys['languageKey'] = $GLOBALS['BE_USER']->uc['lang'];
+        } elseif (!empty(static::getLanguageService()->lang)) {
+            $languageKeys['languageKey'] = static::getLanguageService()->lang;
         }
+        return $languageKeys;
     }
 
     /**
@@ -207,31 +214,32 @@ class LocalizationUtility
      * plugin.tx_myextension._LOCAL_LANG.languageKey.key = value
      *
      * @param string $extensionName
+     * @param string $languageFilePath
      */
-    protected static function loadTypoScriptLabels($extensionName)
+    protected static function loadTypoScriptLabels($extensionName, $languageFilePath)
     {
         $configurationManager = static::getConfigurationManager();
         $frameworkConfiguration = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, $extensionName);
         if (!is_array($frameworkConfiguration['_LOCAL_LANG'])) {
             return;
         }
-        self::$LOCAL_LANG_UNSET[$extensionName] = [];
+        self::$LOCAL_LANG_UNSET[$languageFilePath] = [];
         foreach ($frameworkConfiguration['_LOCAL_LANG'] as $languageKey => $labels) {
-            if (!(is_array($labels) && isset(self::$LOCAL_LANG[$extensionName][$languageKey]))) {
+            if (!(is_array($labels) && isset(self::$LOCAL_LANG[$languageFilePath][$languageKey]))) {
                 continue;
             }
             foreach ($labels as $labelKey => $labelValue) {
                 if (is_string($labelValue)) {
-                    self::$LOCAL_LANG[$extensionName][$languageKey][$labelKey][0]['target'] = $labelValue;
+                    self::$LOCAL_LANG[$languageFilePath][$languageKey][$labelKey][0]['target'] = $labelValue;
                     if ($labelValue === '') {
-                        self::$LOCAL_LANG_UNSET[$extensionName][$languageKey][$labelKey] = '';
+                        self::$LOCAL_LANG_UNSET[$languageFilePath][$languageKey][$labelKey] = '';
                     }
                 } elseif (is_array($labelValue)) {
                     $labelValue = self::flattenTypoScriptLabelArray($labelValue, $labelKey);
                     foreach ($labelValue as $key => $value) {
-                        self::$LOCAL_LANG[$extensionName][$languageKey][$key][0]['target'] = $value;
+                        self::$LOCAL_LANG[$languageFilePath][$languageKey][$key][0]['target'] = $value;
                         if ($value === '') {
-                            self::$LOCAL_LANG_UNSET[$extensionName][$languageKey][$key] = '';
+                            self::$LOCAL_LANG_UNSET[$languageFilePath][$languageKey][$key] = '';
                         }
                     }
                 }
diff --git a/typo3/sysext/extbase/Tests/Unit/Utility/LocalizationUtilityTest.php b/typo3/sysext/extbase/Tests/Unit/Utility/LocalizationUtilityTest.php
index f1719e8f4309..0a0c05c6dfd5 100644
--- a/typo3/sysext/extbase/Tests/Unit/Utility/LocalizationUtilityTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Utility/LocalizationUtilityTest.php
@@ -33,124 +33,133 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
      *
      * @var array
      */
-    protected $LOCAL_LANG = [
-        'extensionKey' => [
-            'default' => [
-                'key1' => [
-                    [
-                        'source' => 'English label for key1',
-                        'target' => 'English label for key1',
-                    ]
-                ],
-                'key2' => [
-                    [
-                        'source' => 'English label for key2',
-                        'target' => 'English label for key2',
-                    ]
-                ],
-                'key3' => [
-                    [
-                        'source' => 'English label for key3',
-                        'target' => 'English label for key3',
-                    ]
-                ],
-                'key4' => [
-                    [
-                        'source' => 'English label for key4',
-                        'target' => 'English label for key4',
-                    ]
-                ],
-                'keyWithPlaceholder' => [
-                    [
-                        'source' => 'English label with number %d',
-                        'target' => 'English label with number %d',
-                    ]
-                ],
-            ],
-            'dk' => [
-                'key1' => [
-                    [
-                        'source' => 'English label for key1',
-                        'target' => 'Dansk label for key1',
-                    ]
-                ],
-                // not translated in dk => no target (llxml)
-                'key2' => [
-                    [
-                        'source' => 'English label for key2',
-                    ]
-                ],
-                'key3' => [
-                    [
-                        'source' => 'English label for key3',
-                    ]
-                ],
-                // not translated in dk => empty target (xliff)
-                'key4' => [
-                    [
-                        'source' => 'English label for key4',
-                        'target' => '',
-                    ]
-                ],
-                // not translated in dk => empty target (xliff)
-                'key5' => [
-                    [
-                        'source' => 'English label for key5',
-                        'target' => '',
-                    ]
-                ],
-                'keyWithPlaceholder' => [
-                    [
-                        'source' => 'English label with number %d',
-                    ]
-                ],
-            ],
-            // fallback language for labels which are not translated in dk
-            'dk_alt' => [
-                'key1' => [
-                    [
-                        'source' => 'English label for key1',
-                    ]
-                ],
-                'key2' => [
-                    [
-                        'source' => 'English label for key2',
-                        'target' => 'Dansk alternative label for key2',
-                    ]
-                ],
-                'key3' => [
-                    [
-                        'source' => 'English label for key3',
-                    ]
-                ],
-                // not translated in dk_alt => empty target (xliff)
-                'key4' => [
-                    [
-                        'source' => 'English label for key4',
-                        'target' => '',
-                    ]
-                ],
-                'key5' => [
-                    [
-                        'source' => 'English label for key5',
-                        'target' => 'Dansk alternative label for key5',
-                    ]
-                ],
-                'keyWithPlaceholder' => [
-                    [
-                        'source' => 'English label with number %d',
-                    ]
-                ],
-            ],
+    protected $LOCAL_LANG = [];
 
-        ],
-    ];
+    /**
+     * File path of locallang for extension "core"
+     * @var string
+     */
+    protected $languageFilePath = '';
 
     /**
      * Prepare class mocking some dependencies
      */
     protected function setUp()
     {
+        $this->languageFilePath = $this->getLanguageFilePath('core');
+        $this->LOCAL_LANG = [
+            $this->languageFilePath => [
+                'default' => [
+                    'key1' => [
+                        [
+                            'source' => 'English label for key1',
+                            'target' => 'English label for key1',
+                        ]
+                    ],
+                    'key2' => [
+                        [
+                            'source' => 'English label for key2',
+                            'target' => 'English label for key2',
+                        ]
+                    ],
+                    'key3' => [
+                        [
+                            'source' => 'English label for key3',
+                            'target' => 'English label for key3',
+                        ]
+                    ],
+                    'key4' => [
+                        [
+                            'source' => 'English label for key4',
+                            'target' => 'English label for key4',
+                        ]
+                    ],
+                    'keyWithPlaceholder' => [
+                        [
+                            'source' => 'English label with number %d',
+                            'target' => 'English label with number %d',
+                        ]
+                    ],
+                ],
+                'dk' => [
+                    'key1' => [
+                        [
+                            'source' => 'English label for key1',
+                            'target' => 'Dansk label for key1',
+                        ]
+                    ],
+                    // not translated in dk => no target (llxml)
+                    'key2' => [
+                        [
+                            'source' => 'English label for key2',
+                        ]
+                    ],
+                    'key3' => [
+                        [
+                            'source' => 'English label for key3',
+                        ]
+                    ],
+                    // not translated in dk => empty target (xliff)
+                    'key4' => [
+                        [
+                            'source' => 'English label for key4',
+                            'target' => '',
+                        ]
+                    ],
+                    // not translated in dk => empty target (xliff)
+                    'key5' => [
+                        [
+                            'source' => 'English label for key5',
+                            'target' => '',
+                        ]
+                    ],
+                    'keyWithPlaceholder' => [
+                        [
+                            'source' => 'English label with number %d',
+                        ]
+                    ],
+                ],
+                // fallback language for labels which are not translated in dk
+                'dk_alt' => [
+                    'key1' => [
+                        [
+                            'source' => 'English label for key1',
+                        ]
+                    ],
+                    'key2' => [
+                        [
+                            'source' => 'English label for key2',
+                            'target' => 'Dansk alternative label for key2',
+                        ]
+                    ],
+                    'key3' => [
+                        [
+                            'source' => 'English label for key3',
+                        ]
+                    ],
+                    // not translated in dk_alt => empty target (xliff)
+                    'key4' => [
+                        [
+                            'source' => 'English label for key4',
+                            'target' => '',
+                        ]
+                    ],
+                    'key5' => [
+                        [
+                            'source' => 'English label for key5',
+                            'target' => 'Dansk alternative label for key5',
+                        ]
+                    ],
+                    'keyWithPlaceholder' => [
+                        [
+                            'source' => 'English label with number %d',
+                        ]
+                    ],
+                ],
+
+            ],
+        ];
+
         $reflectionClass = new \ReflectionClass(\TYPO3\CMS\Extbase\Utility\LocalizationUtility::class);
 
         $this->configurationManager = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Configuration\ConfigurationManager::class, ['getConfiguration']);
@@ -173,14 +182,15 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
         $property = $reflectionClass->getProperty('LOCAL_LANG');
         $property->setAccessible(true);
         $property->setValue([]);
+    }
 
-        $property = $reflectionClass->getProperty('languageKey');
-        $property->setAccessible(true);
-        $property->setValue('default');
-
-        $property = $reflectionClass->getProperty('alternativeLanguageKeys');
-        $property->setAccessible(true);
-        $property->setValue([]);
+    /**
+     * @param string $extensionName
+     * @return string
+     */
+    protected function getLanguageFilePath(string $extensionName): string
+    {
+        return PATH_typo3 . 'sysext/' . $extensionName . '/Resources/Private/Language/locallang.xlf';
     }
 
     /**
@@ -237,37 +247,36 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
     {
         return [
             'get translated key' =>
-            ['key1', $this->LOCAL_LANG, 'dk', 'Dansk label for key1'],
+            ['key1', 'dk', 'Dansk label for key1'],
 
             'fallback to English when translation is missing for key' =>
-            ['key2', $this->LOCAL_LANG, 'dk', 'English label for key2'],
+            ['key2', 'dk', 'English label for key2'],
 
             'fallback to English for non existing language' =>
-            ['key2', $this->LOCAL_LANG, 'xx', 'English label for key2'],
+            ['key2', 'xx', 'English label for key2'],
 
             'replace placeholder with argument' =>
-            ['keyWithPlaceholder', $this->LOCAL_LANG, 'en', 'English label with number 100', [], [100]],
+            ['keyWithPlaceholder', 'default', 'English label with number 100', [], [100]],
 
             'get translated key from primary language' =>
-            ['key1', $this->LOCAL_LANG, 'dk', 'Dansk label for key1', ['dk_alt']],
+            ['key1', 'dk', 'Dansk label for key1', ['dk_alt']],
 
             'fallback to alternative language if translation is missing(llxml)' =>
-            ['key2', $this->LOCAL_LANG, 'dk', 'Dansk alternative label for key2', ['dk_alt']],
+            ['key2', 'dk', 'Dansk alternative label for key2', ['dk_alt']],
 
             'fallback to alternative language if translation is missing(xlif)' =>
-            ['key5', $this->LOCAL_LANG, 'dk', 'Dansk alternative label for key5', ['dk_alt']],
+            ['key5', 'dk', 'Dansk alternative label for key5', ['dk_alt']],
 
             'fallback to English for label not translated in dk and dk_alt(llxml)' =>
-            ['key3', $this->LOCAL_LANG, 'dk', 'English label for key3', ['dk_alt']],
+            ['key3', 'dk', 'English label for key3', ['dk_alt']],
 
             'fallback to English for label not translated in dk and dk_alt(xlif)' =>
-            ['key4', $this->LOCAL_LANG, 'dk', 'English label for key4', ['dk_alt']],
+            ['key4', 'dk', 'English label for key4', ['dk_alt']],
         ];
     }
 
     /**
      * @param string $key
-     * @param array $LOCAL_LANG
      * @param string $languageKey
      * @param string $expected
      * @param array $altLanguageKeys
@@ -275,23 +284,38 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
      * @dataProvider translateDataProvider
      * @test
      */
-    public function translateTest($key, array $LOCAL_LANG, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
+    public function translateTestWithBackendUserLanguage($key, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
     {
         $reflectionClass = new \ReflectionClass(\TYPO3\CMS\Extbase\Utility\LocalizationUtility::class);
 
         $property = $reflectionClass->getProperty('LOCAL_LANG');
         $property->setAccessible(true);
-        $property->setValue($LOCAL_LANG);
+        $property->setValue($this->LOCAL_LANG);
 
-        $property = $reflectionClass->getProperty('languageKey');
-        $property->setAccessible(true);
-        $property->setValue($languageKey);
+        $oldBackendUserLanguage = $GLOBALS['BE_USER']->uc['lang'];
+        $GLOBALS['BE_USER']->uc['lang'] = $languageKey;
+        $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, null, $altLanguageKeys));
+        $GLOBALS['BE_USER']->uc['lang'] = $oldBackendUserLanguage;
+    }
 
-        $property = $reflectionClass->getProperty('alternativeLanguageKeys');
+    /**
+     * @param string $key
+     * @param string $languageKey
+     * @param string $expected
+     * @param array $altLanguageKeys
+     * @param array $arguments
+     * @dataProvider translateDataProvider
+     * @test
+     */
+    public function translateTestWithExplicitLanguageParameters($key, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
+    {
+        $reflectionClass = new \ReflectionClass(\TYPO3\CMS\Extbase\Utility\LocalizationUtility::class);
+
+        $property = $reflectionClass->getProperty('LOCAL_LANG');
         $property->setAccessible(true);
-        $property->setValue($altLanguageKeys);
+        $property->setValue($this->LOCAL_LANG);
 
-        $this->assertEquals($expected, LocalizationUtility::translate($key, 'extensionKey', $arguments));
+        $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, $languageKey, $altLanguageKeys));
     }
 
     /**
@@ -302,12 +326,12 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
         return [
             'override labels with typoscript' => [
                 'LOCAL_LANG' => [
-                    'extensionKey' => [
+                    $this->getLanguageFilePath('core') => [
                         'dk' => [
                             'key1' => [
                                 [
                                     'source' => 'English label for key1',
-                                    'target' => 'Dansk label for key1 extensionKey',
+                                    'target' => 'Dansk label for key1 core',
                                 ]
                             ],
                             'key2' => [
@@ -322,12 +346,12 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
                             ],
                         ],
                     ],
-                    'extensionKey1' => [
+                    $this->getLanguageFilePath('backend') => [
                         'dk' => [
                             'key1' => [
                                 [
                                     'source' => 'English label for key1',
-                                    'target' => 'Dansk label for key1 extensionKey1',
+                                    'target' => 'Dansk label for key1 backend',
                                 ]
                             ],
                             'key2' => [
@@ -346,12 +370,12 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
                 'typoscript LOCAL_LANG' => [
                     '_LOCAL_LANG' => [
                         'dk' => [
-                            'key1' => 'key1 value from TS extensionKey',
+                            'key1' => 'key1 value from TS core',
                             'key3' => [
-                                'subkey1' => 'key3.subkey1 value from TS extensionKey',
+                                'subkey1' => 'key3.subkey1 value from TS core',
                                 // this key doesn't exist in xml files
                                 'subkey2' => [
-                                    'subsubkey' => 'key3.subkey2.subsubkey value from TS extensionKey'
+                                    'subsubkey' => 'key3.subkey2.subsubkey value from TS core'
                                 ]
                             ]
                         ]
@@ -362,7 +386,7 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
                     'key1' => [
                         [
                             'source' => 'English label for key1',
-                            'target' => 'key1 value from TS extensionKey',
+                            'target' => 'key1 value from TS core',
                         ]
                     ],
                     'key2' => [
@@ -373,12 +397,12 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
                     'key3.subkey1' => [
                         [
                             'source' => 'English label for key3',
-                            'target' => 'key3.subkey1 value from TS extensionKey',
+                            'target' => 'key3.subkey1 value from TS core',
                         ]
                     ],
                     'key3.subkey2.subsubkey' => [
                         [
-                            'target' => 'key3.subkey2.subsubkey value from TS extensionKey',
+                            'target' => 'key3.subkey2.subsubkey value from TS core',
                         ]
                     ],
                 ],
@@ -404,22 +428,18 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
         $property->setAccessible(true);
         $property->setValue($LOCAL_LANG);
 
-        $property = $reflectionClass->getProperty('languageKey');
-        $property->setAccessible(true);
-        $property->setValue($languageKey);
-
         $configurationType = \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
-        $this->configurationManager->expects($this->at(0))->method('getConfiguration')->with($configurationType, 'extensionKey', null)->will($this->returnValue($typoScriptLocalLang));
+        $this->configurationManager->expects($this->at(0))->method('getConfiguration')->with($configurationType, 'core', null)->will($this->returnValue($typoScriptLocalLang));
 
         $method = $reflectionClass->getMethod('loadTypoScriptLabels');
         $method->setAccessible(true);
-        $method->invoke(null, 'extensionKey');
+        $method->invoke(null, 'core', $this->languageFilePath);
 
         $property = $reflectionClass->getProperty('LOCAL_LANG');
         $property->setAccessible(true);
         $result = $property->getValue();
 
-        $this->assertEquals($expected, $result['extensionKey'][$languageKey]);
+        $this->assertEquals($expected, $result[$this->languageFilePath][$languageKey]);
     }
 
     /**
@@ -433,10 +453,6 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
         $property->setAccessible(true);
         $property->setValue($this->LOCAL_LANG);
 
-        $property = $reflectionClass->getProperty('languageKey');
-        $property->setAccessible(true);
-        $property->setValue('dk');
-
         $typoScriptLocalLang = [
             '_LOCAL_LANG' => [
                 'dk' => [
@@ -446,13 +462,13 @@ class LocalizationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
         ];
 
         $configurationType = \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
-        $this->configurationManager->expects($this->at(0))->method('getConfiguration')->with($configurationType, 'extensionKey', null)->will($this->returnValue($typoScriptLocalLang));
+        $this->configurationManager->expects($this->at(0))->method('getConfiguration')->with($configurationType, 'core', null)->will($this->returnValue($typoScriptLocalLang));
 
         $method = $reflectionClass->getMethod('loadTypoScriptLabels');
         $method->setAccessible(true);
-        $method->invoke(null, 'extensionKey');
+        $method->invoke(null, 'core', $this->languageFilePath);
 
-        $result = LocalizationUtility::translate('key1', 'extensionKey');
+        $result = LocalizationUtility::translate('key1', 'core', null, 'dk');
         $this->assertNotNull($result);
         $this->assertEquals('', $result);
     }
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php
index ea2d9c488bc6..70dab1757b6f 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php
@@ -94,6 +94,8 @@ 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 ("dk" for example) or "default" to use for this translation. If this argument is empty, we use the current language');
+        $this->registerArgument('alternativeLanguageKeys', 'array', 'Alternative language keys if no translation does exist');
     }
 
     /**
@@ -111,7 +113,7 @@ class TranslateViewHelper extends AbstractViewHelper
         $id = $arguments['id'];
         $default = $arguments['default'];
         $extensionName = $arguments['extensionName'];
-        $arguments = $arguments['arguments'];
+        $translateArguments = $arguments['arguments'];
 
         // Wrapper including a compatibility layer for TYPO3 Flow Translation
         if ($id === null) {
@@ -125,14 +127,14 @@ class TranslateViewHelper extends AbstractViewHelper
         $request = $renderingContext->getControllerContext()->getRequest();
         $extensionName = $extensionName === null ? $request->getControllerExtensionName() : $extensionName;
         try {
-            $value = static::translate($id, $extensionName, $arguments);
+            $value = static::translate($id, $extensionName, $translateArguments, $arguments['languageKey'], $arguments['alternativeLanguageKeys']);
         } catch (\InvalidArgumentException $e) {
             $value = null;
         }
         if ($value === null) {
             $value = $default !== null ? $default : $renderChildrenClosure();
-            if (!empty($arguments)) {
-                $value = vsprintf($value, $arguments);
+            if (!empty($translateArguments)) {
+                $value = vsprintf($value, $translateArguments);
             }
         }
         return $value;
@@ -144,11 +146,13 @@ class TranslateViewHelper extends AbstractViewHelper
      * @param string $id Translation Key compatible to TYPO3 Flow
      * @param string $extensionName UpperCamelCased extension key (for example BlogExample)
      * @param array $arguments Arguments to be replaced in the resulting string
+     * @param string $languageKey Language key to use for this translation
+     * @param string[] $alternativeLanguageKeys Alternative language keys if no translation does exist
      *
-     * @return NULL|string
+     * @return null|string
      */
-    protected static function translate($id, $extensionName, $arguments)
+    protected static function translate($id, $extensionName, $arguments, $languageKey, $alternativeLanguageKeys)
     {
-        return LocalizationUtility::translate($id, $extensionName, $arguments);
+        return LocalizationUtility::translate($id, $extensionName, $arguments, $languageKey, $alternativeLanguageKeys);
     }
 }
diff --git a/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Fixtures/TranslateViewHelperFixtureForEmptyString.php b/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Fixtures/TranslateViewHelperFixtureForEmptyString.php
index 7a810001c757..543302cd084a 100644
--- a/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Fixtures/TranslateViewHelperFixtureForEmptyString.php
+++ b/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Fixtures/TranslateViewHelperFixtureForEmptyString.php
@@ -26,10 +26,12 @@ class TranslateViewHelperFixtureForEmptyString extends TranslateViewHelper
      * @param string $id Translation Key compatible to TYPO3 Flow
      * @param string $extensionName UpperCamelCased extension key (for example BlogExample)
      * @param array $arguments Arguments to be replaced in the resulting string
+     * @param string $languageKey Language key to use for this translation
+     * @param string[] $alternativeLanguageKeys Alternative language keys if no translation does exist
      *
      * @return NULL
      */
-    protected static function translate($id, $extensionName, $arguments)
+    protected static function translate($id, $extensionName, $arguments, $languageKey, $alternativeLanguageKeys)
     {
         return null;
     }
-- 
GitLab