2 namespace TYPO3\CMS\Core\Localization
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\Utility\ArrayUtility
;
18 use TYPO3\CMS\Core\Utility\GeneralUtility
;
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
31 * This is set to the language that is currently running for the user
35 public $lang = 'default';
38 * If TRUE, will show the key/location of labels in the backend.
42 public $debugKey = false
;
45 * Internal cache for read LL-files
49 public $LL_files_cache = [];
52 * Internal cache for ll-labels (filled as labels are requested)
56 public $LL_labels_cache = [];
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.
64 protected $languageDependencies = [];
67 * An internal cache for storing loaded files, see readLLfile()
71 protected $languageFileCache = [];
74 * LanguageService constructor.
76 public function __construct()
78 $this->debugKey
= (bool
)$GLOBALS['TYPO3_CONF_VARS']['BE']['languageDebug'];
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']);
87 * @throws \RuntimeException
88 * @param string $languageKey The language key (two character string from backend users profile)
90 public function init($languageKey)
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;
107 * Debugs localization key.
109 * @param string $value value to debug
112 public function debugLL($value)
114 return $this->debugKey ?
'[' . $value . ']' : '';
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.
121 * @param string $index Label key
124 public function getLL($index)
126 return $this->getLLL($index, $GLOBALS['LOCAL_LANG']);
130 * Returns the label with key $index from the $LOCAL_LANG array used as the second argument
132 * @param string $index Label key
133 * @param array $localLanguage $LOCAL_LANG array to get label key from
136 public function getLLL($index, $localLanguage)
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'];
151 return $value . $this->debugLL($index);
155 * splitLabel function
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
161 * @param string $input Label key/reference
164 public function sL($input)
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];
170 if (strpos($input, 'LLL:') === 0) {
171 $restStr = trim(substr($input, 4));
173 // ll-file referred to is found in an extension.
174 if (strpos($restStr, 'EXT:') === 0) {
175 $restStr = trim(substr($restStr, 4));
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]);
184 $output = $this->getLLL($parts[1], $this->LL_files_cache
[$parts[0]]);
186 // Use a constant non-localizable label
189 $output .= $this->debugLL($input);
190 $this->LL_labels_cache
[$this->lang
][$identifier] = $output;
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
199 * @param string $table Table name found as key in global array $TCA_DESCR
201 public function loadSingleTableDescription($table)
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
);
212 if (is_array($localLanguage['default'])) {
213 foreach ($localLanguage['default'] as $lkey => $lVal) {
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);
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);
233 $fieldName = $keyParts[0];
234 $type = $keyParts[1];
236 // Detecting 'hidden' labels, converting to normal fieldname
237 if ($fieldName === '_') {
240 if ($fieldName !== '' && $fieldName[0] === '_') {
241 $fieldName = substr($fieldName, 1);
244 $label = $lVal[0]['target'] ?
: $lVal[0]['source'];
245 if ($specialInstruction) {
246 $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] .= LF
. $label;
249 $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] = $label;
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).
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
266 public function includeLLFile($fileRef, $setGlobal = true
, $mergeLocalOntoDefault = false
)
268 $globalLanguage = [];
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
274 $globalLanguage = (array)($GLOBALS['LOCAL_LANG'] ??
[]);
275 ArrayUtility
::mergeRecursiveWithOverrule($globalLanguage, $localLanguage);
277 $globalLanguage = $localLanguage;
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
]);
287 // Return value if not global is set.
289 return $globalLanguage;
291 $GLOBALS['LOCAL_LANG'] = $globalLanguage;
296 * Includes a locallang file and returns the $LOCAL_LANG array found inside.
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
301 protected function readLLfile($fileRef)
303 if (isset($this->languageFileCache
[$fileRef . $this->lang
])) {
304 return $this->languageFileCache
[$fileRef . $this->lang
];
307 /** @var LocalizationFactory $languageFactory */
308 $languageFactory = GeneralUtility
::makeInstance(LocalizationFactory
::class);
310 if ($this->lang
!== 'default') {
311 $languages = array_reverse($this->languageDependencies
);
313 $languages = ['default'];
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'];
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
);
328 $this->languageFileCache
[$fileRef . $this->lang
] = $localLanguage;
330 return $localLanguage;
334 * Gets labels with a specific fetched from the current locallang file.
335 * This is useful for e.g gathering javascript labels.
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
341 public function getLabelsWithPrefix($prefix, $strip = '')
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;