3e17481254208571c4da3de6bfc0651698d5eee4
[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\Core\Environment;
20 use TYPO3\CMS\Core\Localization\LocalizationFactory;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
23 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
24 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
25
26 /**
27 * Test case
28 */
29 class LocalizationUtilityTest extends UnitTestCase
30 {
31 /**
32 * Instance of configurationManagerInterface, injected to subject
33 *
34 * @var ConfigurationManagerInterface
35 */
36 protected $configurationManagerInterfaceProphecy;
37
38 /**
39 * LOCAL_LANG array fixture
40 *
41 * @var array
42 */
43 protected $LOCAL_LANG = [];
44
45 /**
46 * File path of locallang for extension "core"
47 * @var string
48 */
49 protected $languageFilePath = '';
50
51 /**
52 * Prepare class mocking some dependencies
53 */
54 protected function setUp()
55 {
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()
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 Environment::getFrameworkBasePath() . '/' . $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 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
305
306 $property = $reflectionClass->getProperty('LOCAL_LANG');
307 $property->setAccessible(true);
308 $property->setValue($this->LOCAL_LANG);
309
310 $backendUserAuthenticationProphecy = $this->prophesize(BackendUserAuthentication::class);
311 $GLOBALS['BE_USER'] = $backendUserAuthenticationProphecy->reveal();
312 $backendUserAuthenticationProphecy->uc = [
313 'lang' => $languageKey,
314 ];
315 $GLOBALS['LANG'] = $this->LOCAL_LANG;
316
317 $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, null, $altLanguageKeys));
318 }
319
320 /**
321 * @param string $key
322 * @param string $languageKey
323 * @param string $expected
324 * @param array $altLanguageKeys
325 * @param array $arguments
326 * @dataProvider translateDataProvider
327 * @test
328 */
329 public function translateTestWithExplicitLanguageParameters($key, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
330 {
331 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
332
333 $property = $reflectionClass->getProperty('LOCAL_LANG');
334 $property->setAccessible(true);
335 $property->setValue($this->LOCAL_LANG);
336 $GLOBALS['LANG'] = $this->LOCAL_LANG;
337 $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, $languageKey, $altLanguageKeys));
338 }
339
340 /**
341 * @return array
342 */
343 public function loadTypoScriptLabelsProvider(): array
344 {
345 return [
346 'override labels with typoscript' => [
347 'LOCAL_LANG' => [
348 $this->getLanguageFilePath('core') => [
349 'dk' => [
350 'key1' => [
351 [
352 'source' => 'English label for key1',
353 'target' => 'Dansk label for key1 core',
354 ]
355 ],
356 'key2' => [
357 [
358 'source' => 'English label for key2',
359 ]
360 ],
361 'key3.subkey1' => [
362 [
363 'source' => 'English label for key3',
364 ]
365 ],
366 ],
367 ],
368 $this->getLanguageFilePath('backend') => [
369 'dk' => [
370 'key1' => [
371 [
372 'source' => 'English label for key1',
373 'target' => 'Dansk label for key1 backend',
374 ]
375 ],
376 'key2' => [
377 [
378 'source' => 'English label for key2',
379 ]
380 ],
381 'key3.subkey1' => [
382 [
383 'source' => 'English label for key3',
384 ]
385 ],
386 ],
387 ],
388 ],
389 'typoscript LOCAL_LANG' => [
390 '_LOCAL_LANG' => [
391 'dk' => [
392 'key1' => 'key1 value from TS core',
393 'key3' => [
394 'subkey1' => 'key3.subkey1 value from TS core',
395 // this key doesn't exist in xml files
396 'subkey2' => [
397 'subsubkey' => 'key3.subkey2.subsubkey value from TS core'
398 ]
399 ]
400 ]
401 ]
402 ],
403 'language key' => 'dk',
404 'expected' => [
405 'key1' => [
406 [
407 'source' => 'English label for key1',
408 'target' => 'key1 value from TS core',
409 ]
410 ],
411 'key2' => [
412 [
413 'source' => 'English label for key2',
414 ]
415 ],
416 'key3.subkey1' => [
417 [
418 'source' => 'English label for key3',
419 'target' => 'key3.subkey1 value from TS core',
420 ]
421 ],
422 'key3.subkey2.subsubkey' => [
423 [
424 'target' => 'key3.subkey2.subsubkey value from TS core',
425 ]
426 ],
427 ],
428 ]
429 ];
430 }
431
432 /**
433 * Tests whether labels from xml are overwritten by TypoScript labels
434 *
435 * @param array $LOCAL_LANG
436 * @param array $typoScriptLocalLang
437 * @param string $languageKey
438 * @param array $expected
439 * @dataProvider loadTypoScriptLabelsProvider
440 * @test
441 */
442 public function loadTypoScriptLabels(array $LOCAL_LANG, array $typoScriptLocalLang, $languageKey, array $expected)
443 {
444 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
445
446 $property = $reflectionClass->getProperty('LOCAL_LANG');
447 $property->setAccessible(true);
448 $property->setValue($LOCAL_LANG);
449
450 $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
451 $this->configurationManagerInterfaceProphecy
452 ->getConfiguration($configurationType, 'core', null)
453 ->shouldBeCalled()
454 ->willReturn($typoScriptLocalLang);
455
456 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
457 $method->setAccessible(true);
458 $method->invoke(null, 'core', $this->languageFilePath);
459
460 $property = $reflectionClass->getProperty('LOCAL_LANG');
461 $property->setAccessible(true);
462 $result = $property->getValue();
463
464 $this->assertEquals($expected, $result[$this->languageFilePath][$languageKey]);
465 }
466
467 /**
468 * @test
469 */
470 public function clearLabelWithTypoScript()
471 {
472 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
473
474 $property = $reflectionClass->getProperty('LOCAL_LANG');
475 $property->setAccessible(true);
476 $property->setValue($this->LOCAL_LANG);
477
478 $typoScriptLocalLang = [
479 '_LOCAL_LANG' => [
480 'dk' => [
481 'key1' => '',
482 ]
483 ]
484 ];
485
486 $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
487 $this->configurationManagerInterfaceProphecy
488 ->getConfiguration($configurationType, 'core', null)
489 ->shouldBeCalled()
490 ->willReturn($typoScriptLocalLang);
491
492 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
493 $method->setAccessible(true);
494 $method->invoke(null, 'core', $this->languageFilePath);
495
496 $GLOBALS['LANG'] = $this->LOCAL_LANG;
497
498 $result = LocalizationUtility::translate('key1', 'core', null, 'dk');
499 $this->assertNotNull($result);
500 $this->assertEquals('', $result);
501 }
502
503 /**
504 * @test
505 */
506 public function translateThrowsExceptionWithEmptyExtensionNameIfKeyIsNotPrefixedWithLLL()
507 {
508 $this->expectException(\InvalidArgumentException::class);
509 $this->expectExceptionCode(1498144052);
510 LocalizationUtility::translate('foo/bar', '');
511 }
512
513 /**
514 * @test
515 */
516 public function translateWillReturnLabelsFromTsEvenIfNoXlfFileExists()
517 {
518 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
519
520 $typoScriptLocalLang = [
521 '_LOCAL_LANG' => [
522 'dk' => [
523 'key1' => 'I am a new key and there is no xlf file',
524 ]
525 ]
526 ];
527
528 $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
529 $this->configurationManagerInterfaceProphecy
530 ->getConfiguration($configurationType, 'core', null)
531 ->shouldBeCalled()
532 ->willReturn($typoScriptLocalLang);
533
534 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
535 $method->setAccessible(true);
536 $method->invoke(null, 'core', ''); // setting the language file path to an empty string here
537
538 $GLOBALS['LANG'] = $this->LOCAL_LANG;
539
540 $result = LocalizationUtility::translate('key1', 'core', null, 'dk');
541 $this->assertNotNull($result);
542 $this->assertEquals('I am a new key and there is no xlf file', $result);
543 }
544 }