TranslatorTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Translation\Tests;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\Translation\Translator;
  13. use Symfony\Component\Translation\MessageSelector;
  14. use Symfony\Component\Translation\Loader\ArrayLoader;
  15. use Symfony\Component\Translation\MessageCatalogue;
  16. class TranslatorTest extends TestCase
  17. {
  18. /**
  19. * @dataProvider getInvalidLocalesTests
  20. * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
  21. */
  22. public function testConstructorInvalidLocale($locale)
  23. {
  24. $translator = new Translator($locale, new MessageSelector());
  25. }
  26. /**
  27. * @dataProvider getValidLocalesTests
  28. */
  29. public function testConstructorValidLocale($locale)
  30. {
  31. $translator = new Translator($locale, new MessageSelector());
  32. $this->assertEquals($locale, $translator->getLocale());
  33. }
  34. public function testConstructorWithoutLocale()
  35. {
  36. $translator = new Translator(null, new MessageSelector());
  37. $this->assertNull($translator->getLocale());
  38. }
  39. public function testSetGetLocale()
  40. {
  41. $translator = new Translator('en');
  42. $this->assertEquals('en', $translator->getLocale());
  43. $translator->setLocale('fr');
  44. $this->assertEquals('fr', $translator->getLocale());
  45. }
  46. /**
  47. * @dataProvider getInvalidLocalesTests
  48. * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
  49. */
  50. public function testSetInvalidLocale($locale)
  51. {
  52. $translator = new Translator('fr', new MessageSelector());
  53. $translator->setLocale($locale);
  54. }
  55. /**
  56. * @dataProvider getValidLocalesTests
  57. */
  58. public function testSetValidLocale($locale)
  59. {
  60. $translator = new Translator($locale, new MessageSelector());
  61. $translator->setLocale($locale);
  62. $this->assertEquals($locale, $translator->getLocale());
  63. }
  64. public function testGetCatalogue()
  65. {
  66. $translator = new Translator('en');
  67. $this->assertEquals(new MessageCatalogue('en'), $translator->getCatalogue());
  68. $translator->setLocale('fr');
  69. $this->assertEquals(new MessageCatalogue('fr'), $translator->getCatalogue('fr'));
  70. }
  71. public function testGetCatalogueReturnsConsolidatedCatalogue()
  72. {
  73. /*
  74. * This will be useful once we refactor so that different domains will be loaded lazily (on-demand).
  75. * In that case, getCatalogue() will probably have to load all missing domains in order to return
  76. * one complete catalogue.
  77. */
  78. $locale = 'whatever';
  79. $translator = new Translator($locale);
  80. $translator->addLoader('loader-a', new ArrayLoader());
  81. $translator->addLoader('loader-b', new ArrayLoader());
  82. $translator->addResource('loader-a', array('foo' => 'foofoo'), $locale, 'domain-a');
  83. $translator->addResource('loader-b', array('bar' => 'foobar'), $locale, 'domain-b');
  84. /*
  85. * Test that we get a single catalogue comprising messages
  86. * from different loaders and different domains
  87. */
  88. $catalogue = $translator->getCatalogue($locale);
  89. $this->assertTrue($catalogue->defines('foo', 'domain-a'));
  90. $this->assertTrue($catalogue->defines('bar', 'domain-b'));
  91. }
  92. public function testSetFallbackLocales()
  93. {
  94. $translator = new Translator('en');
  95. $translator->addLoader('array', new ArrayLoader());
  96. $translator->addResource('array', array('foo' => 'foofoo'), 'en');
  97. $translator->addResource('array', array('bar' => 'foobar'), 'fr');
  98. // force catalogue loading
  99. $translator->trans('bar');
  100. $translator->setFallbackLocales(array('fr'));
  101. $this->assertEquals('foobar', $translator->trans('bar'));
  102. }
  103. public function testSetFallbackLocalesMultiple()
  104. {
  105. $translator = new Translator('en');
  106. $translator->addLoader('array', new ArrayLoader());
  107. $translator->addResource('array', array('foo' => 'foo (en)'), 'en');
  108. $translator->addResource('array', array('bar' => 'bar (fr)'), 'fr');
  109. // force catalogue loading
  110. $translator->trans('bar');
  111. $translator->setFallbackLocales(array('fr_FR', 'fr'));
  112. $this->assertEquals('bar (fr)', $translator->trans('bar'));
  113. }
  114. /**
  115. * @dataProvider getInvalidLocalesTests
  116. * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
  117. */
  118. public function testSetFallbackInvalidLocales($locale)
  119. {
  120. $translator = new Translator('fr', new MessageSelector());
  121. $translator->setFallbackLocales(array('fr', $locale));
  122. }
  123. /**
  124. * @dataProvider getValidLocalesTests
  125. */
  126. public function testSetFallbackValidLocales($locale)
  127. {
  128. $translator = new Translator($locale, new MessageSelector());
  129. $translator->setFallbackLocales(array('fr', $locale));
  130. // no assertion. this method just asserts that no exception is thrown
  131. }
  132. public function testTransWithFallbackLocale()
  133. {
  134. $translator = new Translator('fr_FR');
  135. $translator->setFallbackLocales(array('en'));
  136. $translator->addLoader('array', new ArrayLoader());
  137. $translator->addResource('array', array('bar' => 'foobar'), 'en');
  138. $this->assertEquals('foobar', $translator->trans('bar'));
  139. }
  140. /**
  141. * @dataProvider getInvalidLocalesTests
  142. * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
  143. */
  144. public function testAddResourceInvalidLocales($locale)
  145. {
  146. $translator = new Translator('fr', new MessageSelector());
  147. $translator->addResource('array', array('foo' => 'foofoo'), $locale);
  148. }
  149. /**
  150. * @dataProvider getValidLocalesTests
  151. */
  152. public function testAddResourceValidLocales($locale)
  153. {
  154. $translator = new Translator('fr', new MessageSelector());
  155. $translator->addResource('array', array('foo' => 'foofoo'), $locale);
  156. // no assertion. this method just asserts that no exception is thrown
  157. }
  158. public function testAddResourceAfterTrans()
  159. {
  160. $translator = new Translator('fr');
  161. $translator->addLoader('array', new ArrayLoader());
  162. $translator->setFallbackLocales(array('en'));
  163. $translator->addResource('array', array('foo' => 'foofoo'), 'en');
  164. $this->assertEquals('foofoo', $translator->trans('foo'));
  165. $translator->addResource('array', array('bar' => 'foobar'), 'en');
  166. $this->assertEquals('foobar', $translator->trans('bar'));
  167. }
  168. /**
  169. * @dataProvider getTransFileTests
  170. * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
  171. */
  172. public function testTransWithoutFallbackLocaleFile($format, $loader)
  173. {
  174. $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
  175. $translator = new Translator('en');
  176. $translator->addLoader($format, new $loaderClass());
  177. $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en');
  178. $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en');
  179. // force catalogue loading
  180. $translator->trans('foo');
  181. }
  182. /**
  183. * @dataProvider getTransFileTests
  184. */
  185. public function testTransWithFallbackLocaleFile($format, $loader)
  186. {
  187. $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
  188. $translator = new Translator('en_GB');
  189. $translator->addLoader($format, new $loaderClass());
  190. $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en_GB');
  191. $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources');
  192. $this->assertEquals('bar', $translator->trans('foo', array(), 'resources'));
  193. }
  194. public function testTransWithFallbackLocaleBis()
  195. {
  196. $translator = new Translator('en_US');
  197. $translator->addLoader('array', new ArrayLoader());
  198. $translator->addResource('array', array('foo' => 'foofoo'), 'en_US');
  199. $translator->addResource('array', array('bar' => 'foobar'), 'en');
  200. $this->assertEquals('foobar', $translator->trans('bar'));
  201. }
  202. public function testTransWithFallbackLocaleTer()
  203. {
  204. $translator = new Translator('fr_FR');
  205. $translator->addLoader('array', new ArrayLoader());
  206. $translator->addResource('array', array('foo' => 'foo (en_US)'), 'en_US');
  207. $translator->addResource('array', array('bar' => 'bar (en)'), 'en');
  208. $translator->setFallbackLocales(array('en_US', 'en'));
  209. $this->assertEquals('foo (en_US)', $translator->trans('foo'));
  210. $this->assertEquals('bar (en)', $translator->trans('bar'));
  211. }
  212. public function testTransNonExistentWithFallback()
  213. {
  214. $translator = new Translator('fr');
  215. $translator->setFallbackLocales(array('en'));
  216. $translator->addLoader('array', new ArrayLoader());
  217. $this->assertEquals('non-existent', $translator->trans('non-existent'));
  218. }
  219. /**
  220. * @expectedException \Symfony\Component\Translation\Exception\RuntimeException
  221. */
  222. public function testWhenAResourceHasNoRegisteredLoader()
  223. {
  224. $translator = new Translator('en');
  225. $translator->addResource('array', array('foo' => 'foofoo'), 'en');
  226. $translator->trans('foo');
  227. }
  228. public function testNestedFallbackCatalogueWhenUsingMultipleLocales()
  229. {
  230. $translator = new Translator('fr');
  231. $translator->setFallbackLocales(array('ru', 'en'));
  232. $translator->getCatalogue('fr');
  233. $this->assertNotNull($translator->getCatalogue('ru')->getFallbackCatalogue());
  234. }
  235. public function testFallbackCatalogueResources()
  236. {
  237. $translator = new Translator('en_GB', new MessageSelector());
  238. $translator->addLoader('yml', new \Symfony\Component\Translation\Loader\YamlFileLoader());
  239. $translator->addResource('yml', __DIR__.'/fixtures/empty.yml', 'en_GB');
  240. $translator->addResource('yml', __DIR__.'/fixtures/resources.yml', 'en');
  241. // force catalogue loading
  242. $this->assertEquals('bar', $translator->trans('foo', array()));
  243. $resources = $translator->getCatalogue('en')->getResources();
  244. $this->assertCount(1, $resources);
  245. $this->assertContains(__DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'resources.yml', $resources);
  246. $resources = $translator->getCatalogue('en_GB')->getResources();
  247. $this->assertCount(2, $resources);
  248. $this->assertContains(__DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'empty.yml', $resources);
  249. $this->assertContains(__DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'resources.yml', $resources);
  250. }
  251. /**
  252. * @dataProvider getTransTests
  253. */
  254. public function testTrans($expected, $id, $translation, $parameters, $locale, $domain)
  255. {
  256. $translator = new Translator('en');
  257. $translator->addLoader('array', new ArrayLoader());
  258. $translator->addResource('array', array((string) $id => $translation), $locale, $domain);
  259. $this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale));
  260. }
  261. /**
  262. * @dataProvider getInvalidLocalesTests
  263. * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
  264. */
  265. public function testTransInvalidLocale($locale)
  266. {
  267. $translator = new Translator('en', new MessageSelector());
  268. $translator->addLoader('array', new ArrayLoader());
  269. $translator->addResource('array', array('foo' => 'foofoo'), 'en');
  270. $translator->trans('foo', array(), '', $locale);
  271. }
  272. /**
  273. * @dataProvider getValidLocalesTests
  274. */
  275. public function testTransValidLocale($locale)
  276. {
  277. $translator = new Translator($locale, new MessageSelector());
  278. $translator->addLoader('array', new ArrayLoader());
  279. $translator->addResource('array', array('test' => 'OK'), $locale);
  280. $this->assertEquals('OK', $translator->trans('test'));
  281. $this->assertEquals('OK', $translator->trans('test', array(), null, $locale));
  282. }
  283. /**
  284. * @dataProvider getFlattenedTransTests
  285. */
  286. public function testFlattenedTrans($expected, $messages, $id)
  287. {
  288. $translator = new Translator('en');
  289. $translator->addLoader('array', new ArrayLoader());
  290. $translator->addResource('array', $messages, 'fr', '');
  291. $this->assertEquals($expected, $translator->trans($id, array(), '', 'fr'));
  292. }
  293. /**
  294. * @dataProvider getTransChoiceTests
  295. */
  296. public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain)
  297. {
  298. $translator = new Translator('en');
  299. $translator->addLoader('array', new ArrayLoader());
  300. $translator->addResource('array', array((string) $id => $translation), $locale, $domain);
  301. $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale));
  302. }
  303. /**
  304. * @dataProvider getInvalidLocalesTests
  305. * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
  306. */
  307. public function testTransChoiceInvalidLocale($locale)
  308. {
  309. $translator = new Translator('en', new MessageSelector());
  310. $translator->addLoader('array', new ArrayLoader());
  311. $translator->addResource('array', array('foo' => 'foofoo'), 'en');
  312. $translator->transChoice('foo', 1, array(), '', $locale);
  313. }
  314. /**
  315. * @dataProvider getValidLocalesTests
  316. */
  317. public function testTransChoiceValidLocale($locale)
  318. {
  319. $translator = new Translator('en', new MessageSelector());
  320. $translator->addLoader('array', new ArrayLoader());
  321. $translator->addResource('array', array('foo' => 'foofoo'), 'en');
  322. $translator->transChoice('foo', 1, array(), '', $locale);
  323. // no assertion. this method just asserts that no exception is thrown
  324. }
  325. public function getTransFileTests()
  326. {
  327. return array(
  328. array('csv', 'CsvFileLoader'),
  329. array('ini', 'IniFileLoader'),
  330. array('mo', 'MoFileLoader'),
  331. array('po', 'PoFileLoader'),
  332. array('php', 'PhpFileLoader'),
  333. array('ts', 'QtFileLoader'),
  334. array('xlf', 'XliffFileLoader'),
  335. array('yml', 'YamlFileLoader'),
  336. array('json', 'JsonFileLoader'),
  337. );
  338. }
  339. public function getTransTests()
  340. {
  341. return array(
  342. array('Symfony est super !', 'Symfony is great!', 'Symfony est super !', array(), 'fr', ''),
  343. array('Symfony est awesome !', 'Symfony is %what%!', 'Symfony est %what% !', array('%what%' => 'awesome'), 'fr', ''),
  344. array('Symfony est super !', new StringClass('Symfony is great!'), 'Symfony est super !', array(), 'fr', ''),
  345. );
  346. }
  347. public function getFlattenedTransTests()
  348. {
  349. $messages = array(
  350. 'symfony' => array(
  351. 'is' => array(
  352. 'great' => 'Symfony est super!',
  353. ),
  354. ),
  355. 'foo' => array(
  356. 'bar' => array(
  357. 'baz' => 'Foo Bar Baz',
  358. ),
  359. 'baz' => 'Foo Baz',
  360. ),
  361. );
  362. return array(
  363. array('Symfony est super!', $messages, 'symfony.is.great'),
  364. array('Foo Bar Baz', $messages, 'foo.bar.baz'),
  365. array('Foo Baz', $messages, 'foo.baz'),
  366. );
  367. }
  368. public function getTransChoiceTests()
  369. {
  370. return array(
  371. 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(), 'fr', ''),
  372. 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(), 'fr', ''),
  373. 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(), 'fr', ''),
  374. 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(), 'fr', ''),
  375. 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(), 'fr', ''),
  376. 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(), 'fr', ''),
  377. 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(), 'fr', ''),
  378. 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(), 'fr', ''),
  379. 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(), 'fr', ''),
  380. 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(), 'fr', ''),
  381. 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(), 'fr', ''),
  382. 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(), 'fr', ''),
  383. 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(), 'fr', ''),
  384. // Override %count% with a custom value
  385. array('Il y a quelques pommes', 'one: There is one apple|more: There are %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 2, array('%count%' => 'quelques'), 'fr', ''),
  386. );
  387. }
  388. public function getInvalidLocalesTests()
  389. {
  390. return array(
  391. array('fr FR'),
  392. array('français'),
  393. array('fr+en'),
  394. array('utf#8'),
  395. array('fr&en'),
  396. array('fr~FR'),
  397. array(' fr'),
  398. array('fr '),
  399. array('fr*'),
  400. array('fr/FR'),
  401. array('fr\\FR'),
  402. );
  403. }
  404. public function getValidLocalesTests()
  405. {
  406. return array(
  407. array(''),
  408. array(null),
  409. array('fr'),
  410. array('francais'),
  411. array('FR'),
  412. array('frFR'),
  413. array('fr-FR'),
  414. array('fr_FR'),
  415. array('fr.FR'),
  416. array('fr-FR.UTF8'),
  417. array('sr@latin'),
  418. );
  419. }
  420. public function testTransChoiceFallback()
  421. {
  422. $translator = new Translator('ru');
  423. $translator->setFallbackLocales(array('en'));
  424. $translator->addLoader('array', new ArrayLoader());
  425. $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en');
  426. $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
  427. }
  428. public function testTransChoiceFallbackBis()
  429. {
  430. $translator = new Translator('ru');
  431. $translator->setFallbackLocales(array('en_US', 'en'));
  432. $translator->addLoader('array', new ArrayLoader());
  433. $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en_US');
  434. $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
  435. }
  436. public function testTransChoiceFallbackWithNoTranslation()
  437. {
  438. $translator = new Translator('ru');
  439. $translator->setFallbackLocales(array('en'));
  440. $translator->addLoader('array', new ArrayLoader());
  441. // consistent behavior with Translator::trans(), which returns the string
  442. // unchanged if it can't be found
  443. $this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
  444. }
  445. }
  446. class StringClass
  447. {
  448. protected $str;
  449. public function __construct($str)
  450. {
  451. $this->str = $str;
  452. }
  453. public function __toString()
  454. {
  455. return $this->str;
  456. }
  457. }