256d0759abf557dbb8fc3316ba284edf95d966d0
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Utility / LocalizationUtility.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Utility;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Localization\Locales;
18 use TYPO3\CMS\Core\Localization\LocalizationFactory;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
21 use TYPO3\CMS\Extbase\Object\ObjectManager;
22
23 /**
24 * Localization helper which should be used to fetch localized labels.
25 *
26 * @api
27 */
28 class LocalizationUtility
29 {
30 /**
31 * @var string
32 */
33 protected static $locallangPath = 'Resources/Private/Language/';
34
35 /**
36 * Local Language content
37 *
38 * @var array
39 */
40 protected static $LOCAL_LANG = [];
41
42 /**
43 * Contains those LL keys, which have been set to (empty) in TypoScript.
44 * This is necessary, as we cannot distinguish between a nonexisting
45 * translation and a label that has been cleared by TS.
46 * In both cases ['key'][0]['target'] is "".
47 *
48 * @var array
49 */
50 protected static $LOCAL_LANG_UNSET = [];
51
52 /**
53 * Key of the language to use
54 *
55 * @var string
56 */
57 protected static $languageKey = 'default';
58
59 /**
60 * Pointer to alternative fall-back language to use
61 *
62 * @var array
63 */
64 protected static $alternativeLanguageKeys = [];
65
66 /**
67 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
68 */
69 protected static $configurationManager = null;
70
71 /**
72 * Returns the localized label of the LOCAL_LANG key, $key.
73 *
74 * @param string $key The key from the LOCAL_LANG array for which to return the value.
75 * @param string|null $extensionName The name of the extension
76 * @param array $arguments the arguments of the extension, being passed over to vsprintf
77 * @return string|NULL The value from LOCAL_LANG or NULL if no translation was found.
78 * @api
79 * @todo : If vsprintf gets a malformed string, it returns FALSE! Should we throw an exception there?
80 */
81 public static function translate($key, $extensionName = null, $arguments = null)
82 {
83 $value = null;
84 if (GeneralUtility::isFirstPartOfStr($key, 'LLL:')) {
85 $value = self::translateFileReference($key);
86 } else {
87 if (empty($extensionName)) {
88 throw new \InvalidArgumentException(
89 'Parameter $extensionName cannot be empty if a fully-qualified key is not specified.',
90 1498144052
91 );
92 }
93 self::initializeLocalization($extensionName);
94 // The "from" charset of csConv() is only set for strings from TypoScript via _LOCAL_LANG
95 if (!empty(self::$LOCAL_LANG[$extensionName][self::$languageKey][$key][0]['target'])
96 || isset(self::$LOCAL_LANG_UNSET[$extensionName][self::$languageKey][$key])
97 ) {
98 // Local language translation for key exists
99 $value = self::$LOCAL_LANG[$extensionName][self::$languageKey][$key][0]['target'];
100 } elseif (!empty(self::$alternativeLanguageKeys)) {
101 $languages = array_reverse(self::$alternativeLanguageKeys);
102 foreach ($languages as $language) {
103 if (!empty(self::$LOCAL_LANG[$extensionName][$language][$key][0]['target'])
104 || isset(self::$LOCAL_LANG_UNSET[$extensionName][$language][$key])
105 ) {
106 // Alternative language translation for key exists
107 $value = self::$LOCAL_LANG[$extensionName][$language][$key][0]['target'];
108 break;
109 }
110 }
111 }
112 if ($value === null && (!empty(self::$LOCAL_LANG[$extensionName]['default'][$key][0]['target'])
113 || isset(self::$LOCAL_LANG_UNSET[$extensionName]['default'][$key]))
114 ) {
115 // Default language translation for key exists
116 // No charset conversion because default is English and thereby ASCII
117 $value = self::$LOCAL_LANG[$extensionName]['default'][$key][0]['target'];
118 }
119 }
120 if (is_array($arguments) && $value !== null) {
121 return vsprintf($value, $arguments);
122 } else {
123 return $value;
124 }
125 }
126
127 /**
128 * Returns the localized label of the LOCAL_LANG key, $key.
129 *
130 * @param string $key The language key including the path to a custom locallang file ("LLL:path:key").
131 * @return string The value from LOCAL_LANG or NULL if no translation was found.
132 * @see language::sL()
133 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::sL()
134 */
135 protected static function translateFileReference($key)
136 {
137 if (TYPO3_MODE === 'FE') {
138 $value = self::getTypoScriptFrontendController()->sL($key);
139 return $value !== false ? $value : null;
140 } elseif (is_object($GLOBALS['LANG'])) {
141 $value = self::getLanguageService()->sL($key);
142 return $value !== '' ? $value : null;
143 } else {
144 return $key;
145 }
146 }
147
148 /**
149 * Loads local-language values by looking for a "locallang.xlf" (or "locallang.xml") file in the plugin resources directory and if found includes it.
150 * Also locallang values set in the TypoScript property "_LOCAL_LANG" are merged onto the values found in the "locallang.xlf" file.
151 *
152 * @param string $extensionName
153 */
154 protected static function initializeLocalization($extensionName)
155 {
156 if (isset(self::$LOCAL_LANG[$extensionName])) {
157 return;
158 }
159 $locallangPathAndFilename = 'EXT:' . GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName) . '/' . self::$locallangPath . 'locallang.xlf';
160 self::setLanguageKeys();
161
162 /** @var $languageFactory LocalizationFactory */
163 $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
164
165 self::$LOCAL_LANG[$extensionName] = $languageFactory->getParsedData($locallangPathAndFilename, self::$languageKey);
166 foreach (self::$alternativeLanguageKeys as $language) {
167 $tempLL = $languageFactory->getParsedData($locallangPathAndFilename, $language);
168 if (self::$languageKey !== 'default' && isset($tempLL[$language])) {
169 self::$LOCAL_LANG[$extensionName][$language] = $tempLL[$language];
170 }
171 }
172 self::loadTypoScriptLabels($extensionName);
173 }
174
175 /**
176 * Sets the currently active language/language_alt keys.
177 * Default values are "default" for language key and "" for language_alt key.
178 */
179 protected static function setLanguageKeys()
180 {
181 self::$languageKey = 'default';
182 self::$alternativeLanguageKeys = [];
183 if (TYPO3_MODE === 'FE') {
184 if (isset(self::getTypoScriptFrontendController()->config['config']['language'])) {
185 self::$languageKey = self::getTypoScriptFrontendController()->config['config']['language'];
186 if (isset(self::getTypoScriptFrontendController()->config['config']['language_alt'])) {
187 self::$alternativeLanguageKeys[] = self::getTypoScriptFrontendController()->config['config']['language_alt'];
188 } else {
189 /** @var $locales \TYPO3\CMS\Core\Localization\Locales */
190 $locales = GeneralUtility::makeInstance(Locales::class);
191 if (in_array(self::$languageKey, $locales->getLocales())) {
192 foreach ($locales->getLocaleDependencies(self::$languageKey) as $language) {
193 self::$alternativeLanguageKeys[] = $language;
194 }
195 }
196 }
197 }
198 } elseif (!empty($GLOBALS['BE_USER']->uc['lang'])) {
199 self::$languageKey = $GLOBALS['BE_USER']->uc['lang'];
200 } elseif (!empty(self::getLanguageService()->lang)) {
201 self::$languageKey = self::getLanguageService()->lang;
202 }
203 }
204
205 /**
206 * Overwrites labels that are set via TypoScript.
207 * TS locallang labels have to be configured like:
208 * plugin.tx_myextension._LOCAL_LANG.languageKey.key = value
209 *
210 * @param string $extensionName
211 */
212 protected static function loadTypoScriptLabels($extensionName)
213 {
214 $configurationManager = static::getConfigurationManager();
215 $frameworkConfiguration = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, $extensionName);
216 if (!is_array($frameworkConfiguration['_LOCAL_LANG'])) {
217 return;
218 }
219 self::$LOCAL_LANG_UNSET[$extensionName] = [];
220 foreach ($frameworkConfiguration['_LOCAL_LANG'] as $languageKey => $labels) {
221 if (!(is_array($labels) && isset(self::$LOCAL_LANG[$extensionName][$languageKey]))) {
222 continue;
223 }
224 foreach ($labels as $labelKey => $labelValue) {
225 if (is_string($labelValue)) {
226 self::$LOCAL_LANG[$extensionName][$languageKey][$labelKey][0]['target'] = $labelValue;
227 if ($labelValue === '') {
228 self::$LOCAL_LANG_UNSET[$extensionName][$languageKey][$labelKey] = '';
229 }
230 } elseif (is_array($labelValue)) {
231 $labelValue = self::flattenTypoScriptLabelArray($labelValue, $labelKey);
232 foreach ($labelValue as $key => $value) {
233 self::$LOCAL_LANG[$extensionName][$languageKey][$key][0]['target'] = $value;
234 if ($value === '') {
235 self::$LOCAL_LANG_UNSET[$extensionName][$languageKey][$key] = '';
236 }
237 }
238 }
239 }
240 }
241 }
242
243 /**
244 * Flatten TypoScript label array; converting a hierarchical array into a flat
245 * array with the keys separated by dots.
246 *
247 * Example Input: array('k1' => array('subkey1' => 'val1'))
248 * Example Output: array('k1.subkey1' => 'val1')
249 *
250 * @param array $labelValues Hierarchical array of labels
251 * @param string $parentKey the name of the parent key in the recursion; is only needed for recursion.
252 * @return array flattened array of labels.
253 */
254 protected static function flattenTypoScriptLabelArray(array $labelValues, $parentKey = '')
255 {
256 $result = [];
257 foreach ($labelValues as $key => $labelValue) {
258 if (!empty($parentKey)) {
259 if ($key === '_typoScriptNodeValue') {
260 $key = $parentKey;
261 } else {
262 $key = $parentKey . '.' . $key;
263 }
264 }
265 if (is_array($labelValue)) {
266 $labelValue = self::flattenTypoScriptLabelArray($labelValue, $key);
267 $result = array_merge($result, $labelValue);
268 } else {
269 $result[$key] = $labelValue;
270 }
271 }
272 return $result;
273 }
274
275 /**
276 * Returns instance of the configuration manager
277 *
278 * @return \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
279 */
280 protected static function getConfigurationManager()
281 {
282 if (!is_null(static::$configurationManager)) {
283 return static::$configurationManager;
284 }
285 $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
286 $configurationManager = $objectManager->get(ConfigurationManagerInterface::class);
287 static::$configurationManager = $configurationManager;
288 return $configurationManager;
289 }
290
291 /**
292 * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
293 */
294 protected static function getTypoScriptFrontendController()
295 {
296 return $GLOBALS['TSFE'];
297 }
298
299 /**
300 * @return \TYPO3\CMS\Core\Localization\LanguageService
301 */
302 protected static function getLanguageService()
303 {
304 return $GLOBALS['LANG'];
305 }
306 }