60f161f6253a6798a49c3fc7fe02b1c56299fa22
[yaffs-website] / symfony / translation / Tests / TranslatorTest.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Translation\Tests;
13
14 use PHPUnit\Framework\TestCase;
15 use Symfony\Component\Translation\Translator;
16 use Symfony\Component\Translation\MessageSelector;
17 use Symfony\Component\Translation\Loader\ArrayLoader;
18 use Symfony\Component\Translation\MessageCatalogue;
19
20 class TranslatorTest extends TestCase
21 {
22     /**
23      * @dataProvider      getInvalidLocalesTests
24      * @expectedException \InvalidArgumentException
25      */
26     public function testConstructorInvalidLocale($locale)
27     {
28         $translator = new Translator($locale, new MessageSelector());
29     }
30
31     /**
32      * @dataProvider getValidLocalesTests
33      */
34     public function testConstructorValidLocale($locale)
35     {
36         $translator = new Translator($locale, new MessageSelector());
37
38         $this->assertEquals($locale, $translator->getLocale());
39     }
40
41     public function testConstructorWithoutLocale()
42     {
43         $translator = new Translator(null, new MessageSelector());
44
45         $this->assertNull($translator->getLocale());
46     }
47
48     public function testSetGetLocale()
49     {
50         $translator = new Translator('en');
51
52         $this->assertEquals('en', $translator->getLocale());
53
54         $translator->setLocale('fr');
55         $this->assertEquals('fr', $translator->getLocale());
56     }
57
58     /**
59      * @dataProvider      getInvalidLocalesTests
60      * @expectedException \InvalidArgumentException
61      */
62     public function testSetInvalidLocale($locale)
63     {
64         $translator = new Translator('fr', new MessageSelector());
65         $translator->setLocale($locale);
66     }
67
68     /**
69      * @dataProvider getValidLocalesTests
70      */
71     public function testSetValidLocale($locale)
72     {
73         $translator = new Translator($locale, new MessageSelector());
74         $translator->setLocale($locale);
75
76         $this->assertEquals($locale, $translator->getLocale());
77     }
78
79     public function testGetCatalogue()
80     {
81         $translator = new Translator('en');
82
83         $this->assertEquals(new MessageCatalogue('en'), $translator->getCatalogue());
84
85         $translator->setLocale('fr');
86         $this->assertEquals(new MessageCatalogue('fr'), $translator->getCatalogue('fr'));
87     }
88
89     public function testGetCatalogueReturnsConsolidatedCatalogue()
90     {
91         /*
92          * This will be useful once we refactor so that different domains will be loaded lazily (on-demand).
93          * In that case, getCatalogue() will probably have to load all missing domains in order to return
94          * one complete catalogue.
95          */
96
97         $locale = 'whatever';
98         $translator = new Translator($locale);
99         $translator->addLoader('loader-a', new ArrayLoader());
100         $translator->addLoader('loader-b', new ArrayLoader());
101         $translator->addResource('loader-a', array('foo' => 'foofoo'), $locale, 'domain-a');
102         $translator->addResource('loader-b', array('bar' => 'foobar'), $locale, 'domain-b');
103
104         /*
105          * Test that we get a single catalogue comprising messages
106          * from different loaders and different domains
107          */
108         $catalogue = $translator->getCatalogue($locale);
109         $this->assertTrue($catalogue->defines('foo', 'domain-a'));
110         $this->assertTrue($catalogue->defines('bar', 'domain-b'));
111     }
112
113     public function testSetFallbackLocales()
114     {
115         $translator = new Translator('en');
116         $translator->addLoader('array', new ArrayLoader());
117         $translator->addResource('array', array('foo' => 'foofoo'), 'en');
118         $translator->addResource('array', array('bar' => 'foobar'), 'fr');
119
120         // force catalogue loading
121         $translator->trans('bar');
122
123         $translator->setFallbackLocales(array('fr'));
124         $this->assertEquals('foobar', $translator->trans('bar'));
125     }
126
127     public function testSetFallbackLocalesMultiple()
128     {
129         $translator = new Translator('en');
130         $translator->addLoader('array', new ArrayLoader());
131         $translator->addResource('array', array('foo' => 'foo (en)'), 'en');
132         $translator->addResource('array', array('bar' => 'bar (fr)'), 'fr');
133
134         // force catalogue loading
135         $translator->trans('bar');
136
137         $translator->setFallbackLocales(array('fr_FR', 'fr'));
138         $this->assertEquals('bar (fr)', $translator->trans('bar'));
139     }
140
141     /**
142      * @dataProvider      getInvalidLocalesTests
143      * @expectedException \InvalidArgumentException
144      */
145     public function testSetFallbackInvalidLocales($locale)
146     {
147         $translator = new Translator('fr', new MessageSelector());
148         $translator->setFallbackLocales(array('fr', $locale));
149     }
150
151     /**
152      * @dataProvider getValidLocalesTests
153      */
154     public function testSetFallbackValidLocales($locale)
155     {
156         $translator = new Translator($locale, new MessageSelector());
157         $translator->setFallbackLocales(array('fr', $locale));
158         // no assertion. this method just asserts that no exception is thrown
159         $this->addToAssertionCount(1);
160     }
161
162     public function testTransWithFallbackLocale()
163     {
164         $translator = new Translator('fr_FR');
165         $translator->setFallbackLocales(array('en'));
166
167         $translator->addLoader('array', new ArrayLoader());
168         $translator->addResource('array', array('bar' => 'foobar'), 'en');
169
170         $this->assertEquals('foobar', $translator->trans('bar'));
171     }
172
173     /**
174      * @dataProvider      getInvalidLocalesTests
175      * @expectedException \InvalidArgumentException
176      */
177     public function testAddResourceInvalidLocales($locale)
178     {
179         $translator = new Translator('fr', new MessageSelector());
180         $translator->addResource('array', array('foo' => 'foofoo'), $locale);
181     }
182
183     /**
184      * @dataProvider getValidLocalesTests
185      */
186     public function testAddResourceValidLocales($locale)
187     {
188         $translator = new Translator('fr', new MessageSelector());
189         $translator->addResource('array', array('foo' => 'foofoo'), $locale);
190         // no assertion. this method just asserts that no exception is thrown
191         $this->addToAssertionCount(1);
192     }
193
194     public function testAddResourceAfterTrans()
195     {
196         $translator = new Translator('fr');
197         $translator->addLoader('array', new ArrayLoader());
198
199         $translator->setFallbackLocales(array('en'));
200
201         $translator->addResource('array', array('foo' => 'foofoo'), 'en');
202         $this->assertEquals('foofoo', $translator->trans('foo'));
203
204         $translator->addResource('array', array('bar' => 'foobar'), 'en');
205         $this->assertEquals('foobar', $translator->trans('bar'));
206     }
207
208     /**
209      * @dataProvider      getTransFileTests
210      * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
211      */
212     public function testTransWithoutFallbackLocaleFile($format, $loader)
213     {
214         $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
215         $translator = new Translator('en');
216         $translator->addLoader($format, new $loaderClass());
217         $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en');
218         $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en');
219
220         // force catalogue loading
221         $translator->trans('foo');
222     }
223
224     /**
225      * @dataProvider getTransFileTests
226      */
227     public function testTransWithFallbackLocaleFile($format, $loader)
228     {
229         $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
230         $translator = new Translator('en_GB');
231         $translator->addLoader($format, new $loaderClass());
232         $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en_GB');
233         $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources');
234
235         $this->assertEquals('bar', $translator->trans('foo', array(), 'resources'));
236     }
237
238     public function testTransWithFallbackLocaleBis()
239     {
240         $translator = new Translator('en_US');
241         $translator->addLoader('array', new ArrayLoader());
242         $translator->addResource('array', array('foo' => 'foofoo'), 'en_US');
243         $translator->addResource('array', array('bar' => 'foobar'), 'en');
244         $this->assertEquals('foobar', $translator->trans('bar'));
245     }
246
247     public function testTransWithFallbackLocaleTer()
248     {
249         $translator = new Translator('fr_FR');
250         $translator->addLoader('array', new ArrayLoader());
251         $translator->addResource('array', array('foo' => 'foo (en_US)'), 'en_US');
252         $translator->addResource('array', array('bar' => 'bar (en)'), 'en');
253
254         $translator->setFallbackLocales(array('en_US', 'en'));
255
256         $this->assertEquals('foo (en_US)', $translator->trans('foo'));
257         $this->assertEquals('bar (en)', $translator->trans('bar'));
258     }
259
260     public function testTransNonExistentWithFallback()
261     {
262         $translator = new Translator('fr');
263         $translator->setFallbackLocales(array('en'));
264         $translator->addLoader('array', new ArrayLoader());
265         $this->assertEquals('non-existent', $translator->trans('non-existent'));
266     }
267
268     /**
269      * @expectedException \RuntimeException
270      */
271     public function testWhenAResourceHasNoRegisteredLoader()
272     {
273         $translator = new Translator('en');
274         $translator->addResource('array', array('foo' => 'foofoo'), 'en');
275
276         $translator->trans('foo');
277     }
278
279     public function testNestedFallbackCatalogueWhenUsingMultipleLocales()
280     {
281         $translator = new Translator('fr');
282         $translator->setFallbackLocales(array('ru', 'en'));
283
284         $translator->getCatalogue('fr');
285
286         $this->assertNotNull($translator->getCatalogue('ru')->getFallbackCatalogue());
287     }
288
289     public function testFallbackCatalogueResources()
290     {
291         $translator = new Translator('en_GB', new MessageSelector());
292         $translator->addLoader('yml', new \Symfony\Component\Translation\Loader\YamlFileLoader());
293         $translator->addResource('yml', __DIR__.'/fixtures/empty.yml', 'en_GB');
294         $translator->addResource('yml', __DIR__.'/fixtures/resources.yml', 'en');
295
296         // force catalogue loading
297         $this->assertEquals('bar', $translator->trans('foo', array()));
298
299         $resources = $translator->getCatalogue('en')->getResources();
300         $this->assertCount(1, $resources);
301         $this->assertContains(__DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'resources.yml', $resources);
302
303         $resources = $translator->getCatalogue('en_GB')->getResources();
304         $this->assertCount(2, $resources);
305         $this->assertContains(__DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'empty.yml', $resources);
306         $this->assertContains(__DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'resources.yml', $resources);
307     }
308
309     /**
310      * @dataProvider getTransTests
311      */
312     public function testTrans($expected, $id, $translation, $parameters, $locale, $domain)
313     {
314         $translator = new Translator('en');
315         $translator->addLoader('array', new ArrayLoader());
316         $translator->addResource('array', array((string) $id => $translation), $locale, $domain);
317
318         $this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale));
319     }
320
321     /**
322      * @dataProvider      getInvalidLocalesTests
323      * @expectedException \InvalidArgumentException
324      */
325     public function testTransInvalidLocale($locale)
326     {
327         $translator = new Translator('en', new MessageSelector());
328         $translator->addLoader('array', new ArrayLoader());
329         $translator->addResource('array', array('foo' => 'foofoo'), 'en');
330
331         $translator->trans('foo', array(), '', $locale);
332     }
333
334     /**
335      * @dataProvider      getValidLocalesTests
336      */
337     public function testTransValidLocale($locale)
338     {
339         $translator = new Translator($locale, new MessageSelector());
340         $translator->addLoader('array', new ArrayLoader());
341         $translator->addResource('array', array('test' => 'OK'), $locale);
342
343         $this->assertEquals('OK', $translator->trans('test'));
344         $this->assertEquals('OK', $translator->trans('test', array(), null, $locale));
345     }
346
347     /**
348      * @dataProvider getFlattenedTransTests
349      */
350     public function testFlattenedTrans($expected, $messages, $id)
351     {
352         $translator = new Translator('en');
353         $translator->addLoader('array', new ArrayLoader());
354         $translator->addResource('array', $messages, 'fr', '');
355
356         $this->assertEquals($expected, $translator->trans($id, array(), '', 'fr'));
357     }
358
359     /**
360      * @dataProvider getTransChoiceTests
361      */
362     public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain)
363     {
364         $translator = new Translator('en');
365         $translator->addLoader('array', new ArrayLoader());
366         $translator->addResource('array', array((string) $id => $translation), $locale, $domain);
367
368         $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale));
369     }
370
371     /**
372      * @dataProvider      getInvalidLocalesTests
373      * @expectedException \InvalidArgumentException
374      */
375     public function testTransChoiceInvalidLocale($locale)
376     {
377         $translator = new Translator('en', new MessageSelector());
378         $translator->addLoader('array', new ArrayLoader());
379         $translator->addResource('array', array('foo' => 'foofoo'), 'en');
380
381         $translator->transChoice('foo', 1, array(), '', $locale);
382     }
383
384     /**
385      * @dataProvider      getValidLocalesTests
386      */
387     public function testTransChoiceValidLocale($locale)
388     {
389         $translator = new Translator('en', new MessageSelector());
390         $translator->addLoader('array', new ArrayLoader());
391         $translator->addResource('array', array('foo' => 'foofoo'), 'en');
392
393         $translator->transChoice('foo', 1, array(), '', $locale);
394         // no assertion. this method just asserts that no exception is thrown
395         $this->addToAssertionCount(1);
396     }
397
398     public function getTransFileTests()
399     {
400         return array(
401             array('csv', 'CsvFileLoader'),
402             array('ini', 'IniFileLoader'),
403             array('mo', 'MoFileLoader'),
404             array('po', 'PoFileLoader'),
405             array('php', 'PhpFileLoader'),
406             array('ts', 'QtFileLoader'),
407             array('xlf', 'XliffFileLoader'),
408             array('yml', 'YamlFileLoader'),
409             array('json', 'JsonFileLoader'),
410         );
411     }
412
413     public function getTransTests()
414     {
415         return array(
416             array('Symfony est super !', 'Symfony is great!', 'Symfony est super !', array(), 'fr', ''),
417             array('Symfony est awesome !', 'Symfony is %what%!', 'Symfony est %what% !', array('%what%' => 'awesome'), 'fr', ''),
418             array('Symfony est super !', new StringClass('Symfony is great!'), 'Symfony est super !', array(), 'fr', ''),
419         );
420     }
421
422     public function getFlattenedTransTests()
423     {
424         $messages = array(
425             'symfony' => array(
426                 'is' => array(
427                     'great' => 'Symfony est super!',
428                 ),
429             ),
430             'foo' => array(
431                 'bar' => array(
432                     'baz' => 'Foo Bar Baz',
433                 ),
434                 'baz' => 'Foo Baz',
435             ),
436         );
437
438         return array(
439             array('Symfony est super!', $messages, 'symfony.is.great'),
440             array('Foo Bar Baz', $messages, 'foo.bar.baz'),
441             array('Foo Baz', $messages, 'foo.baz'),
442         );
443     }
444
445     public function getTransChoiceTests()
446     {
447         return array(
448             array('Il y a 0 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
449             array('Il y a 1 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
450             array('Il y a 10 pommes', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
451
452             array('Il y a 0 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
453             array('Il y a 1 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
454             array('Il y a 10 pommes', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
455
456             array('Il y a 0 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
457             array('Il y a 1 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
458             array('Il y a 10 pommes', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
459
460             array('Il n\'y a aucune pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
461             array('Il y a 1 pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
462             array('Il y a 10 pommes', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
463
464             array('Il y a 0 pomme', new StringClass('{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
465         );
466     }
467
468     public function getInvalidLocalesTests()
469     {
470         return array(
471             array('fr FR'),
472             array('français'),
473             array('fr+en'),
474             array('utf#8'),
475             array('fr&en'),
476             array('fr~FR'),
477             array(' fr'),
478             array('fr '),
479             array('fr*'),
480             array('fr/FR'),
481             array('fr\\FR'),
482         );
483     }
484
485     public function getValidLocalesTests()
486     {
487         return array(
488             array(''),
489             array(null),
490             array('fr'),
491             array('francais'),
492             array('FR'),
493             array('frFR'),
494             array('fr-FR'),
495             array('fr_FR'),
496             array('fr.FR'),
497             array('fr-FR.UTF8'),
498             array('sr@latin'),
499         );
500     }
501
502     public function testTransChoiceFallback()
503     {
504         $translator = new Translator('ru');
505         $translator->setFallbackLocales(array('en'));
506         $translator->addLoader('array', new ArrayLoader());
507         $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en');
508
509         $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
510     }
511
512     public function testTransChoiceFallbackBis()
513     {
514         $translator = new Translator('ru');
515         $translator->setFallbackLocales(array('en_US', 'en'));
516         $translator->addLoader('array', new ArrayLoader());
517         $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en_US');
518
519         $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
520     }
521
522     public function testTransChoiceFallbackWithNoTranslation()
523     {
524         $translator = new Translator('ru');
525         $translator->setFallbackLocales(array('en'));
526         $translator->addLoader('array', new ArrayLoader());
527
528         // consistent behavior with Translator::trans(), which returns the string
529         // unchanged if it can't be found
530         $this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
531     }
532
533     /**
534      * @group legacy
535      * @dataProvider dataProviderGetMessages
536      */
537     public function testLegacyGetMessages($resources, $locale, $expected)
538     {
539         $locales = array_keys($resources);
540         $_locale = null !== $locale ? $locale : reset($locales);
541         $locales = array_slice($locales, 0, array_search($_locale, $locales));
542
543         $translator = new Translator($_locale, new MessageSelector());
544         $translator->setFallbackLocales(array_reverse($locales));
545         $translator->addLoader('array', new ArrayLoader());
546         foreach ($resources as $_locale => $domainMessages) {
547             foreach ($domainMessages as $domain => $messages) {
548                 $translator->addResource('array', $messages, $_locale, $domain);
549             }
550         }
551         $result = $translator->getMessages($locale);
552
553         $this->assertEquals($expected, $result);
554     }
555
556     public function dataProviderGetMessages()
557     {
558         $resources = array(
559             'en' => array(
560                 'jsmessages' => array(
561                     'foo' => 'foo (EN)',
562                     'bar' => 'bar (EN)',
563                 ),
564                 'messages' => array(
565                     'foo' => 'foo messages (EN)',
566                 ),
567                 'validators' => array(
568                     'int' => 'integer (EN)',
569                 ),
570             ),
571             'pt-PT' => array(
572                 'messages' => array(
573                     'foo' => 'foo messages (PT)',
574                 ),
575                 'validators' => array(
576                     'str' => 'integer (PT)',
577                 ),
578             ),
579             'pt_BR' => array(
580                 'validators' => array(
581                     'int' => 'integer (BR)',
582                 ),
583             ),
584         );
585
586         return array(
587             array($resources, null,
588                 array(
589                     'jsmessages' => array(
590                         'foo' => 'foo (EN)',
591                         'bar' => 'bar (EN)',
592                     ),
593                     'messages' => array(
594                         'foo' => 'foo messages (EN)',
595                     ),
596                     'validators' => array(
597                         'int' => 'integer (EN)',
598                     ),
599                 ),
600             ),
601             array($resources, 'en',
602                 array(
603                     'jsmessages' => array(
604                         'foo' => 'foo (EN)',
605                         'bar' => 'bar (EN)',
606                     ),
607                     'messages' => array(
608                         'foo' => 'foo messages (EN)',
609                     ),
610                     'validators' => array(
611                         'int' => 'integer (EN)',
612                     ),
613                 ),
614             ),
615             array($resources, 'pt-PT',
616                 array(
617                     'jsmessages' => array(
618                         'foo' => 'foo (EN)',
619                         'bar' => 'bar (EN)',
620                     ),
621                     'messages' => array(
622                         'foo' => 'foo messages (PT)',
623                     ),
624                     'validators' => array(
625                         'int' => 'integer (EN)',
626                         'str' => 'integer (PT)',
627                     ),
628                 ),
629             ),
630             array($resources, 'pt_BR',
631                 array(
632                     'jsmessages' => array(
633                         'foo' => 'foo (EN)',
634                         'bar' => 'bar (EN)',
635                     ),
636                     'messages' => array(
637                         'foo' => 'foo messages (PT)',
638                     ),
639                     'validators' => array(
640                         'int' => 'integer (BR)',
641                         'str' => 'integer (PT)',
642                     ),
643                 ),
644             ),
645         );
646     }
647 }
648
649 class StringClass
650 {
651     protected $str;
652
653     public function __construct($str)
654     {
655         $this->str = $str;
656     }
657
658     public function __toString()
659     {
660         return $this->str;
661     }
662 }