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