[BUGFIX] Render FlashMessages at ClearCache
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Utility / LocalizationUtilityTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\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 Prophecy\Argument;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Localization\LocalizationFactory;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
22 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
23 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
24
25 /**
26 * Test case
27 */
28 class LocalizationUtilityTest extends UnitTestCase
29 {
30 /**
31 * Instance of configurationManagerInterface, injected to subject
32 *
33 * @var ConfigurationManagerInterface
34 */
35 protected $configurationManagerInterfaceProphecy;
36
37 /**
38 * LOCAL_LANG array fixture
39 *
40 * @var array
41 */
42 protected $LOCAL_LANG = [];
43
44 /**
45 * File path of locallang for extension "core"
46 * @var string
47 */
48 protected $languageFilePath = '';
49
50 /**
51 * Prepare class mocking some dependencies
52 */
53 protected function setUp(): void
54 {
55 parent::setUp();
56 $this->languageFilePath = $this->getLanguageFilePath('core');
57 $this->LOCAL_LANG = [
58 $this->languageFilePath => [
59 'default' => [
60 'key1' => [
61 [
62 'source' => 'English label for key1',
63 'target' => 'English label for key1',
64 ]
65 ],
66 'key2' => [
67 [
68 'source' => 'English label for key2',
69 'target' => 'English label for key2',
70 ]
71 ],
72 'key3' => [
73 [
74 'source' => 'English label for key3',
75 'target' => 'English label for key3',
76 ]
77 ],
78 'key4' => [
79 [
80 'source' => 'English label for key4',
81 'target' => 'English label for key4',
82 ]
83 ],
84 'keyWithPlaceholder' => [
85 [
86 'source' => 'English label with number %d',
87 'target' => 'English label with number %d',
88 ]
89 ],
90 ],
91 'dk' => [
92 'key1' => [
93 [
94 'source' => 'English label for key1',
95 'target' => 'Dansk label for key1',
96 ]
97 ],
98 // not translated in dk => no target (llxml)
99 'key2' => [
100 [
101 'source' => 'English label for key2',
102 ]
103 ],
104 'key3' => [
105 [
106 'source' => 'English label for key3',
107 ]
108 ],
109 // not translated in dk => empty target (xliff)
110 'key4' => [
111 [
112 'source' => 'English label for key4',
113 'target' => '',
114 ]
115 ],
116 // not translated in dk => empty target (xliff)
117 'key5' => [
118 [
119 'source' => 'English label for key5',
120 'target' => '',
121 ]
122 ],
123 'keyWithPlaceholder' => [
124 [
125 'source' => 'English label with number %d',
126 ]
127 ],
128 ],
129 // fallback language for labels which are not translated in dk
130 'dk_alt' => [
131 'key1' => [
132 [
133 'source' => 'English label for key1',
134 ]
135 ],
136 'key2' => [
137 [
138 'source' => 'English label for key2',
139 'target' => 'Dansk alternative label for key2',
140 ]
141 ],
142 'key3' => [
143 [
144 'source' => 'English label for key3',
145 ]
146 ],
147 // not translated in dk_alt => empty target (xliff)
148 'key4' => [
149 [
150 'source' => 'English label for key4',
151 'target' => '',
152 ]
153 ],
154 'key5' => [
155 [
156 'source' => 'English label for key5',
157 'target' => 'Dansk alternative label for key5',
158 ]
159 ],
160 'keyWithPlaceholder' => [
161 [
162 'source' => 'English label with number %d',
163 ]
164 ],
165 ],
166
167 ],
168 ];
169
170 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
171
172 $this->configurationManagerInterfaceProphecy = $this->prophesize(ConfigurationManagerInterface::class);
173 $property = $reflectionClass->getProperty('configurationManager');
174 $property->setAccessible(true);
175 $property->setValue($this->configurationManagerInterfaceProphecy->reveal());
176
177 $localizationFactoryProphecy = $this->prophesize(LocalizationFactory::class);
178 GeneralUtility::setSingletonInstance(LocalizationFactory::class, $localizationFactoryProphecy->reveal());
179 $localizationFactoryProphecy->getParsedData(Argument::cetera(), 'foo')->willReturn([]);
180 }
181
182 /**
183 * Reset static properties
184 */
185 protected function tearDown(): void
186 {
187 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
188
189 $property = $reflectionClass->getProperty('configurationManager');
190 $property->setAccessible(true);
191 $property->setValue(null);
192
193 $property = $reflectionClass->getProperty('LOCAL_LANG');
194 $property->setAccessible(true);
195 $property->setValue([]);
196
197 GeneralUtility::purgeInstances();
198
199 parent::tearDown();
200 }
201
202 /**
203 * @param string $extensionName
204 * @return string
205 */
206 protected function getLanguageFilePath(string $extensionName): string
207 {
208 return 'EXT:' . $extensionName . '/Resources/Private/Language/locallang.xlf';
209 }
210
211 /**
212 * @test
213 */
214 public function implodeTypoScriptLabelArrayWorks()
215 {
216 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
217 $method = $reflectionClass->getMethod('flattenTypoScriptLabelArray');
218 $method->setAccessible(true);
219
220 $expected = [
221 'key1' => 'value1',
222 'key2' => 'value2',
223 'key3' => 'value3',
224 'key3.subkey1' => 'subvalue1',
225 'key3.subkey2.subsubkey' => 'val'
226 ];
227 $input = [
228 'key1' => 'value1',
229 'key2' => 'value2',
230 'key3' => [
231 '_typoScriptNodeValue' => 'value3',
232 'subkey1' => 'subvalue1',
233 'subkey2' => [
234 'subsubkey' => 'val'
235 ]
236 ]
237 ];
238 $result = $method->invoke(null, $input);
239 $this->assertEquals($expected, $result);
240 }
241
242 /**
243 * @test
244 */
245 public function translateForEmptyStringKeyReturnsNull()
246 {
247 $this->assertNull(LocalizationUtility::translate('', 'extbase'));
248 }
249
250 /**
251 * @test
252 */
253 public function translateForEmptyStringKeyWithArgumentsReturnsNull()
254 {
255 $this->assertNull(LocalizationUtility::translate('', 'extbase', ['argument']));
256 }
257
258 /**
259 * @return array
260 */
261 public function translateDataProvider(): array
262 {
263 return [
264 'get translated key' =>
265 ['key1', 'dk', 'Dansk label for key1'],
266
267 'fallback to English when translation is missing for key' =>
268 ['key2', 'dk', 'English label for key2'],
269
270 'fallback to English for non existing language' =>
271 ['key2', 'xx', 'English label for key2'],
272
273 'replace placeholder with argument' =>
274 ['keyWithPlaceholder', 'default', 'English label with number 100', [], [100]],
275
276 'get translated key from primary language' =>
277 ['key1', 'dk', 'Dansk label for key1', ['dk_alt']],
278
279 'fallback to alternative language if translation is missing(llxml)' =>
280 ['key2', 'dk', 'Dansk alternative label for key2', ['dk_alt']],
281
282 'fallback to alternative language if translation is missing(xlif)' =>
283 ['key5', 'dk', 'Dansk alternative label for key5', ['dk_alt']],
284
285 'fallback to English for label not translated in dk and dk_alt(llxml)' =>
286 ['key3', 'dk', 'English label for key3', ['dk_alt']],
287
288 'fallback to English for label not translated in dk and dk_alt(xlif)' =>
289 ['key4', 'dk', 'English label for key4', ['dk_alt']],
290 ];
291 }
292
293 /**
294 * @param string $key
295 * @param string $languageKey
296 * @param string $expected
297 * @param array $altLanguageKeys
298 * @param array $arguments
299 * @dataProvider translateDataProvider
300 * @test
301 */
302 public function translateTestWithBackendUserLanguage($key, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
303 {
304 $this->configurationManagerInterfaceProphecy
305 ->getConfiguration('Framework', 'core', null)
306 ->willReturn([]);
307
308 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
309
310 $property = $reflectionClass->getProperty('LOCAL_LANG');
311 $property->setAccessible(true);
312 $property->setValue($this->LOCAL_LANG);
313
314 $backendUserAuthenticationProphecy = $this->prophesize(BackendUserAuthentication::class);
315 $GLOBALS['BE_USER'] = $backendUserAuthenticationProphecy->reveal();
316 $backendUserAuthenticationProphecy->uc = [
317 'lang' => $languageKey,
318 ];
319 $GLOBALS['LANG'] = $this->LOCAL_LANG;
320
321 $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, null, $altLanguageKeys));
322 }
323
324 /**
325 * @param string $key
326 * @param string $languageKey
327 * @param string $expected
328 * @param array $altLanguageKeys
329 * @param array $arguments
330 * @dataProvider translateDataProvider
331 * @test
332 */
333 public function translateTestWithExplicitLanguageParameters($key, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
334 {
335 $this->configurationManagerInterfaceProphecy
336 ->getConfiguration('Framework', 'core', null)
337 ->willReturn([]);
338
339 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
340
341 $property = $reflectionClass->getProperty('LOCAL_LANG');
342 $property->setAccessible(true);
343 $property->setValue($this->LOCAL_LANG);
344 $GLOBALS['LANG'] = new \TYPO3\CMS\Core\Localization\LanguageService();
345 $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, $languageKey, $altLanguageKeys));
346 }
347
348 /**
349 * @return array
350 */
351 public function loadTypoScriptLabelsProvider(): array
352 {
353 return [
354 'override labels with typoscript' => [
355 'LOCAL_LANG' => [
356 $this->getLanguageFilePath('core') => [
357 'dk' => [
358 'key1' => [
359 [
360 'source' => 'English label for key1',
361 'target' => 'Dansk label for key1 core',
362 ]
363 ],
364 'key2' => [
365 [
366 'source' => 'English label for key2',
367 ]
368 ],
369 'key3.subkey1' => [
370 [
371 'source' => 'English label for key3',
372 ]
373 ],
374 ],
375 ],
376 $this->getLanguageFilePath('backend') => [
377 'dk' => [
378 'key1' => [
379 [
380 'source' => 'English label for key1',
381 'target' => 'Dansk label for key1 backend',
382 ]
383 ],
384 'key2' => [
385 [
386 'source' => 'English label for key2',
387 ]
388 ],
389 'key3.subkey1' => [
390 [
391 'source' => 'English label for key3',
392 ]
393 ],
394 ],
395 ],
396 ],
397 'typoscript LOCAL_LANG' => [
398 '_LOCAL_LANG' => [
399 'dk' => [
400 'key1' => 'key1 value from TS core',
401 'key3' => [
402 'subkey1' => 'key3.subkey1 value from TS core',
403 // this key doesn't exist in xml files
404 'subkey2' => [
405 'subsubkey' => 'key3.subkey2.subsubkey value from TS core'
406 ]
407 ]
408 ]
409 ]
410 ],
411 'language key' => 'dk',
412 'expected' => [
413 'key1' => [
414 [
415 'source' => 'English label for key1',
416 'target' => 'key1 value from TS core',
417 ]
418 ],
419 'key2' => [
420 [
421 'source' => 'English label for key2',
422 ]
423 ],
424 'key3.subkey1' => [
425 [
426 'source' => 'English label for key3',
427 'target' => 'key3.subkey1 value from TS core',
428 ]
429 ],
430 'key3.subkey2.subsubkey' => [
431 [
432 'target' => 'key3.subkey2.subsubkey value from TS core',
433 ]
434 ],
435 ],
436 ]
437 ];
438 }
439
440 /**
441 * Tests whether labels from xml are overwritten by TypoScript labels
442 *
443 * @param array $LOCAL_LANG
444 * @param array $typoScriptLocalLang
445 * @param string $languageKey
446 * @param array $expected
447 * @dataProvider loadTypoScriptLabelsProvider
448 * @test
449 */
450 public function loadTypoScriptLabels(array $LOCAL_LANG, array $typoScriptLocalLang, $languageKey, array $expected)
451 {
452 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
453
454 $property = $reflectionClass->getProperty('LOCAL_LANG');
455 $property->setAccessible(true);
456 $property->setValue($LOCAL_LANG);
457
458 $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
459 $this->configurationManagerInterfaceProphecy
460 ->getConfiguration($configurationType, 'core', null)
461 ->shouldBeCalled()
462 ->willReturn($typoScriptLocalLang);
463
464 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
465 $method->setAccessible(true);
466 $method->invoke(null, 'core', $this->languageFilePath);
467
468 $property = $reflectionClass->getProperty('LOCAL_LANG');
469 $property->setAccessible(true);
470 $result = $property->getValue();
471
472 $this->assertEquals($expected, $result[$this->languageFilePath][$languageKey]);
473 }
474
475 /**
476 * @test
477 */
478 public function clearLabelWithTypoScript()
479 {
480 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
481
482 $property = $reflectionClass->getProperty('LOCAL_LANG');
483 $property->setAccessible(true);
484 $property->setValue($this->LOCAL_LANG);
485
486 $typoScriptLocalLang = [
487 '_LOCAL_LANG' => [
488 'dk' => [
489 'key1' => '',
490 ]
491 ]
492 ];
493
494 $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
495 $this->configurationManagerInterfaceProphecy
496 ->getConfiguration($configurationType, 'core', null)
497 ->shouldBeCalled()
498 ->willReturn($typoScriptLocalLang);
499
500 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
501 $method->setAccessible(true);
502 $method->invoke(null, 'core', $this->languageFilePath);
503
504 $GLOBALS['LANG'] = new \TYPO3\CMS\Core\Localization\LanguageService();
505
506 $result = LocalizationUtility::translate('key1', 'core', null, 'dk');
507 $this->assertNotNull($result);
508 $this->assertEquals('', $result);
509 }
510
511 /**
512 * @test
513 */
514 public function translateThrowsExceptionWithEmptyExtensionNameIfKeyIsNotPrefixedWithLLL()
515 {
516 $this->expectException(\InvalidArgumentException::class);
517 $this->expectExceptionCode(1498144052);
518 LocalizationUtility::translate('foo/bar', '');
519 }
520
521 /**
522 * @test
523 */
524 public function translateWillReturnLabelsFromTsEvenIfNoXlfFileExists()
525 {
526 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
527
528 $typoScriptLocalLang = [
529 '_LOCAL_LANG' => [
530 'dk' => [
531 'key1' => 'I am a new key and there is no xlf file',
532 ]
533 ]
534 ];
535
536 $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
537 $this->configurationManagerInterfaceProphecy
538 ->getConfiguration($configurationType, 'core', null)
539 ->shouldBeCalled()
540 ->willReturn($typoScriptLocalLang);
541
542 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
543 $method->setAccessible(true);
544 $method->invoke(null, 'core', ''); // setting the language file path to an empty string here
545
546 $GLOBALS['LANG'] = new \TYPO3\CMS\Core\Localization\LanguageService();
547
548 $result = LocalizationUtility::translate('key1', 'core', null, 'dk');
549 $this->assertNotNull($result);
550 $this->assertEquals('I am a new key and there is no xlf file', $result);
551 }
552 }