[TASK] Do not use ObjectManager to instantiate PageTitleProviderManager
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Localization / LanguageService.php
1 <?php
2 namespace TYPO3\CMS\Core\Localization;
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\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Contains the TYPO3 Backend Language class
22 * For detailed information about how localization is handled,
23 * please refer to the 'Inside TYPO3' document which describes this.
24 * This class is normally instantiated as the global variable $GLOBALS['LANG']
25 * It's only available in the backend and under certain circumstances in the frontend
26 * @see \TYPO3\CMS\Backend\Template\DocumentTemplate
27 */
28 class LanguageService
29 {
30 /**
31 * This is set to the language that is currently running for the user
32 *
33 * @var string
34 */
35 public $lang = 'default';
36
37 /**
38 * If TRUE, will show the key/location of labels in the backend.
39 *
40 * @var bool
41 */
42 public $debugKey = false;
43
44 /**
45 * Internal cache for read LL-files
46 *
47 * @var array
48 */
49 public $LL_files_cache = [];
50
51 /**
52 * Internal cache for ll-labels (filled as labels are requested)
53 *
54 * @var array
55 */
56 public $LL_labels_cache = [];
57
58 /**
59 * List of language dependencies for actual language. This is used for local variants of a language
60 * that depend on their "main" language, like Brazilian Portuguese or Canadian French.
61 *
62 * @var array
63 */
64 protected $languageDependencies = [];
65
66 /**
67 * An internal cache for storing loaded files, see readLLfile()
68 *
69 * @var array
70 */
71 protected $languageFileCache = [];
72
73 /**
74 * LanguageService constructor.
75 */
76 public function __construct()
77 {
78 $this->debugKey = (bool)$GLOBALS['TYPO3_CONF_VARS']['BE']['languageDebug'];
79 }
80
81 /**
82 * Initializes the backend language.
83 * This is for example done in \TYPO3\CMS\Backend\Template\DocumentTemplate with lines like these:
84 * $LANG = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
85 * $LANG->init($GLOBALS['BE_USER']->uc['lang']);
86 *
87 * @throws \RuntimeException
88 * @param string $languageKey The language key (two character string from backend users profile)
89 */
90 public function init($languageKey)
91 {
92 // Find the requested language in this list based on the $languageKey
93 /** @var \TYPO3\CMS\Core\Localization\Locales $locales */
94 $locales = GeneralUtility::makeInstance(Locales::class);
95 // Language is found. Configure it:
96 if (in_array($languageKey, $locales->getLocales())) {
97 // The current language key
98 $this->lang = $languageKey;
99 $this->languageDependencies[] = $languageKey;
100 foreach ($locales->getLocaleDependencies($languageKey) as $language) {
101 $this->languageDependencies[] = $language;
102 }
103 }
104 }
105
106 /**
107 * Debugs localization key.
108 *
109 * @param string $value value to debug
110 * @return string
111 */
112 public function debugLL($value)
113 {
114 return $this->debugKey ? '[' . $value . ']' : '';
115 }
116
117 /**
118 * Returns the label with key $index from the globally loaded $LOCAL_LANG array.
119 * Mostly used from modules with only one LOCAL_LANG file loaded into the global space.
120 *
121 * @param string $index Label key
122 * @return string
123 */
124 public function getLL($index)
125 {
126 return $this->getLLL($index, $GLOBALS['LOCAL_LANG']);
127 }
128
129 /**
130 * Returns the label with key $index from the $LOCAL_LANG array used as the second argument
131 *
132 * @param string $index Label key
133 * @param array $localLanguage $LOCAL_LANG array to get label key from
134 * @return string
135 */
136 public function getLLL($index, $localLanguage)
137 {
138 // Get Local Language. Special handling for all extensions that
139 // read PHP LL files and pass arrays here directly.
140 if (isset($localLanguage[$this->lang][$index])) {
141 $value = is_string($localLanguage[$this->lang][$index])
142 ? $localLanguage[$this->lang][$index]
143 : $localLanguage[$this->lang][$index][0]['target'];
144 } elseif (isset($localLanguage['default'][$index])) {
145 $value = is_string($localLanguage['default'][$index])
146 ? $localLanguage['default'][$index]
147 : $localLanguage['default'][$index][0]['target'];
148 } else {
149 $value = '';
150 }
151 return $value . $this->debugLL($index);
152 }
153
154 /**
155 * splitLabel function
156 *
157 * All translations are based on $LOCAL_LANG variables.
158 * 'language-splitted' labels can therefore refer to a local-lang file + index.
159 * Refer to 'Inside TYPO3' for more details
160 *
161 * @param string $input Label key/reference
162 * @return string
163 */
164 public function sL($input)
165 {
166 $identifier = $input . '_' . (int)$this->debugKey;
167 if (isset($this->LL_labels_cache[$this->lang][$identifier])) {
168 return $this->LL_labels_cache[$this->lang][$identifier];
169 }
170 if (strpos($input, 'LLL:') === 0) {
171 $restStr = trim(substr($input, 4));
172 $extPrfx = '';
173 // ll-file referred to is found in an extension.
174 if (strpos($restStr, 'EXT:') === 0) {
175 $restStr = trim(substr($restStr, 4));
176 $extPrfx = 'EXT:';
177 }
178 $parts = explode(':', $restStr);
179 $parts[0] = $extPrfx . $parts[0];
180 // Getting data if not cached
181 if (!isset($this->LL_files_cache[$parts[0]])) {
182 $this->LL_files_cache[$parts[0]] = $this->readLLfile($parts[0]);
183 }
184 $output = $this->getLLL($parts[1], $this->LL_files_cache[$parts[0]]);
185 } else {
186 // Use a constant non-localizable label
187 $output = $input;
188 }
189 $output .= $this->debugLL($input);
190 $this->LL_labels_cache[$this->lang][$identifier] = $output;
191 return $output;
192 }
193
194 /**
195 * Loading $TCA_DESCR[$table]['columns'] with content from locallang files
196 * as defined in $TCA_DESCR[$table]['refs']
197 * $TCA_DESCR is a global var
198 *
199 * @param string $table Table name found as key in global array $TCA_DESCR
200 */
201 public function loadSingleTableDescription($table)
202 {
203 // First the 'table' cannot already be loaded in [columns]
204 // and secondly there must be a references to locallang files available in [refs]
205 if (is_array($GLOBALS['TCA_DESCR'][$table]) && !isset($GLOBALS['TCA_DESCR'][$table]['columns']) && is_array($GLOBALS['TCA_DESCR'][$table]['refs'])) {
206 // Init $TCA_DESCR for $table-key
207 $GLOBALS['TCA_DESCR'][$table]['columns'] = [];
208 // Get local-lang for each file in $TCA_DESCR[$table]['refs'] as they are ordered.
209 foreach ($GLOBALS['TCA_DESCR'][$table]['refs'] as $llfile) {
210 $localLanguage = $this->includeLLFile($llfile, false, true);
211 // Traverse all keys
212 if (is_array($localLanguage['default'])) {
213 foreach ($localLanguage['default'] as $lkey => $lVal) {
214 // Exploding by '.':
215 // 0-n => fieldname,
216 // n+1 => type from (alttitle, description, details, syntax, image_descr,image,seeAlso),
217 // n+2 => special instruction, if any
218 $keyParts = explode('.', $lkey);
219 $keyPartsCount = count($keyParts);
220 // Check if last part is special instruction
221 // Only "+" is currently supported
222 $specialInstruction = $keyParts[$keyPartsCount - 1] === '+';
223 if ($specialInstruction) {
224 array_pop($keyParts);
225 }
226 // If there are more than 2 parts, get the type from the last part
227 // and merge back the other parts with a dot (.)
228 // Otherwise just get type and field name straightaway
229 if ($keyPartsCount > 2) {
230 $type = array_pop($keyParts);
231 $fieldName = implode('.', $keyParts);
232 } else {
233 $fieldName = $keyParts[0];
234 $type = $keyParts[1];
235 }
236 // Detecting 'hidden' labels, converting to normal fieldname
237 if ($fieldName === '_') {
238 $fieldName = '';
239 }
240 if ($fieldName !== '' && $fieldName[0] === '_') {
241 $fieldName = substr($fieldName, 1);
242 }
243 // Append label
244 $label = $lVal[0]['target'] ?: $lVal[0]['source'];
245 if ($specialInstruction) {
246 $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] .= LF . $label;
247 } else {
248 // Substitute label
249 $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] = $label;
250 }
251 }
252 }
253 }
254 }
255 }
256
257 /**
258 * Includes locallang file (and possibly additional localized version if configured for)
259 * Read language labels will be merged with $LOCAL_LANG (if $setGlobal = TRUE).
260 *
261 * @param string $fileRef $fileRef is a file-reference
262 * @param bool $setGlobal Setting in global variable $LOCAL_LANG (or returning the variable)
263 * @param bool $mergeLocalOntoDefault
264 * @return mixed if $setGlobal===TRUE, LL-files set $LOCAL_LANG in global scope, or array is returned from function
265 */
266 public function includeLLFile($fileRef, $setGlobal = true, $mergeLocalOntoDefault = false)
267 {
268 $globalLanguage = [];
269 // Get default file
270 $localLanguage = $this->readLLfile($fileRef);
271 if (is_array($localLanguage) && !empty($localLanguage)) {
272 // it depends on, whether we should return the result or set it in the global $LOCAL_LANG array
273 if ($setGlobal) {
274 $globalLanguage = (array)($GLOBALS['LOCAL_LANG'] ?? []);
275 ArrayUtility::mergeRecursiveWithOverrule($globalLanguage, $localLanguage);
276 } else {
277 $globalLanguage = $localLanguage;
278 }
279 // Merge local onto default
280 if ($mergeLocalOntoDefault && $this->lang !== 'default' && is_array($globalLanguage[$this->lang]) && is_array($globalLanguage['default'])) {
281 // array_merge can be used so far the keys are not
282 // numeric - which we assume they are not...
283 $globalLanguage['default'] = array_merge($globalLanguage['default'], $globalLanguage[$this->lang]);
284 unset($globalLanguage[$this->lang]);
285 }
286 }
287 // Return value if not global is set.
288 if (!$setGlobal) {
289 return $globalLanguage;
290 }
291 $GLOBALS['LOCAL_LANG'] = $globalLanguage;
292 return null;
293 }
294
295 /**
296 * Includes a locallang file and returns the $LOCAL_LANG array found inside.
297 *
298 * @param string $fileRef Input is a file-reference to be a 'local_lang' file containing a $LOCAL_LANG array
299 * @return array value of $LOCAL_LANG found in the included file, empty if non found
300 */
301 protected function readLLfile($fileRef)
302 {
303 if (isset($this->languageFileCache[$fileRef . $this->lang])) {
304 return $this->languageFileCache[$fileRef . $this->lang];
305 }
306
307 /** @var LocalizationFactory $languageFactory */
308 $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
309
310 if ($this->lang !== 'default') {
311 $languages = array_reverse($this->languageDependencies);
312 } else {
313 $languages = ['default'];
314 }
315 $localLanguage = [];
316 foreach ($languages as $language) {
317 $tempLL = $languageFactory->getParsedData($fileRef, $language);
318 $localLanguage['default'] = $tempLL['default'];
319 if (!isset($localLanguage[$this->lang])) {
320 $localLanguage[$this->lang] = $localLanguage['default'];
321 }
322 if ($this->lang !== 'default' && isset($tempLL[$language])) {
323 // Merge current language labels onto labels from previous language
324 // This way we have a labels with fall back applied
325 ArrayUtility::mergeRecursiveWithOverrule($localLanguage[$this->lang], $tempLL[$language], true, false);
326 }
327 }
328 $this->languageFileCache[$fileRef . $this->lang] = $localLanguage;
329
330 return $localLanguage;
331 }
332
333 /**
334 * Gets labels with a specific fetched from the current locallang file.
335 * This is useful for e.g gathering javascript labels.
336 *
337 * @param string $prefix Prefix to select the correct labels
338 * @param string $strip Sub-prefix to be removed from label names in the result
339 * @return array Processed labels
340 */
341 public function getLabelsWithPrefix($prefix, $strip = '')
342 {
343 $extraction = [];
344 $labels = array_merge((array)$GLOBALS['LOCAL_LANG']['default'], (array)$GLOBALS['LOCAL_LANG'][$this->lang]);
345 // Regular expression to strip the selection prefix and possibly something from the label name:
346 $labelPattern = '#^' . preg_quote($prefix, '#') . '(' . preg_quote($strip, '#') . ')?#';
347 // Iterate through all locallang labels:
348 foreach ($labels as $label => $value) {
349 if (strpos($label, $prefix) === 0) {
350 $key = preg_replace($labelPattern, '', $label);
351 $extraction[$key] = $value;
352 }
353 }
354 return $extraction;
355 }
356 }