I18nTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\I18n;
  16. use Aura\Intl\Package;
  17. use Cake\Cache\Cache;
  18. use Cake\Core\Plugin;
  19. use Cake\I18n\I18n;
  20. use Cake\TestSuite\TestCase;
  21. /**
  22. * I18nTest class
  23. */
  24. class I18nTest extends TestCase
  25. {
  26. /**
  27. * Used to restore the internal locale after tests
  28. *
  29. * @var string
  30. */
  31. public $locale;
  32. /**
  33. * Set Up
  34. *
  35. * @return void
  36. */
  37. public function setUp()
  38. {
  39. parent::setUp();
  40. $this->locale = I18n::locale();
  41. }
  42. /**
  43. * Tear down method
  44. *
  45. * @return void
  46. */
  47. public function tearDown()
  48. {
  49. parent::tearDown();
  50. I18n::clear();
  51. I18n::defaultFormatter('default');
  52. I18n::locale($this->locale);
  53. Plugin::unload();
  54. Cache::clear(false, '_cake_core_');
  55. }
  56. /**
  57. * Tests that a default translator is created and messages are parsed
  58. * correctly
  59. *
  60. * @return void
  61. */
  62. public function testDefaultTranslator()
  63. {
  64. $translator = I18n::translator();
  65. $this->assertInstanceOf('Aura\Intl\Translator', $translator);
  66. $this->assertEquals('%d is 1 (po translated)', $translator->translate('%d = 1'));
  67. }
  68. /**
  69. * Tests that the translator can automatically load messages from a .mo file
  70. *
  71. * @return void
  72. */
  73. public function testTranslatorLoadMoFile()
  74. {
  75. $translator = I18n::translator('default', 'es_ES');
  76. $this->assertEquals('Plural Rule 6 (translated)', $translator->translate('Plural Rule 1'));
  77. }
  78. /**
  79. * Tests that plural rules are correctly used for the English language
  80. * using the sprintf formatter
  81. *
  82. * @return void
  83. */
  84. public function testPluralSelection()
  85. {
  86. I18n::defaultFormatter('sprintf');
  87. $translator = I18n::translator(); // en_US
  88. $result = $translator->translate('%d = 0 or > 1', ['_count' => 1]);
  89. $this->assertEquals('1 is 1 (po translated)', $result);
  90. $result = $translator->translate('%d = 0 or > 1', ['_count' => 2]);
  91. $this->assertEquals('2 is 2-4 (po translated)', $result);
  92. }
  93. /**
  94. * Tests that plural rules are correctly used for the English language
  95. * using the basic formatter
  96. *
  97. * @return void
  98. */
  99. public function testPluralSelectionBasicFormatter()
  100. {
  101. $translator = I18n::translator('special');
  102. $result = $translator->translate('There are {0} things', ['_count' => 2, 'plenty']);
  103. $this->assertEquals('There are plenty things', $result);
  104. $result = $translator->translate('There are {0} things', ['_count' => 1]);
  105. $this->assertEquals('There is only one', $result);
  106. }
  107. /**
  108. * Test plural rules are used for non-english languages
  109. *
  110. * @return void
  111. */
  112. public function testPluralSelectionRussian()
  113. {
  114. $translator = I18n::translator('default', 'ru');
  115. $result = $translator->translate('{0} months', ['_count' => 1, 1]);
  116. $this->assertEquals('1 months ends in 1, not 11', $result);
  117. $result = $translator->translate('{0} months', ['_count' => 2, 2]);
  118. $this->assertEquals('2 months ends in 2-4, not 12-14', $result);
  119. $result = $translator->translate('{0} months', ['_count' => 7, 7]);
  120. $this->assertEquals('7 months everything else', $result);
  121. }
  122. /**
  123. * Tests that custom translation packages can be created on the fly and used later on
  124. *
  125. * @return void
  126. */
  127. public function testCreateCustomTranslationPackage()
  128. {
  129. I18n::translator('custom', 'fr_FR', function () {
  130. $package = new Package('default');
  131. $package->setMessages([
  132. 'Cow' => 'Le moo'
  133. ]);
  134. return $package;
  135. });
  136. $translator = I18n::translator('custom', 'fr_FR');
  137. $this->assertEquals('Le moo', $translator->translate('Cow'));
  138. }
  139. /**
  140. * Tests that messages can also be loaded from plugins by using the
  141. * domain = plugin_name convention
  142. *
  143. * @return void
  144. */
  145. public function testPluginMesagesLoad()
  146. {
  147. Plugin::load([
  148. 'TestPlugin',
  149. 'Company/TestPluginThree'
  150. ]);
  151. $translator = I18n::translator('test_plugin');
  152. $this->assertEquals(
  153. 'Plural Rule 1 (from plugin)',
  154. $translator->translate('Plural Rule 1')
  155. );
  156. $translator = I18n::translator('company/test_plugin_three');
  157. $this->assertEquals(
  158. 'String 1 (from plugin three)',
  159. $translator->translate('String 1')
  160. );
  161. }
  162. /**
  163. * Tests that messages messages from a plugin can be automatically
  164. * overridden by messages in app
  165. *
  166. * @return void
  167. */
  168. public function testPluginOverride()
  169. {
  170. Plugin::load('TestTheme');
  171. $translator = I18n::translator('test_theme');
  172. $this->assertEquals(
  173. 'translated',
  174. $translator->translate('A Message')
  175. );
  176. }
  177. /**
  178. * Tests the locale method
  179. *
  180. * @return void
  181. */
  182. public function testDefaultLocale()
  183. {
  184. $this->assertEquals('en_US', I18n::locale());
  185. $this->assertEquals('en_US', ini_get('intl.default_locale'));
  186. I18n::locale('fr_FR');
  187. $this->assertEquals('fr_FR', I18n::locale());
  188. $this->assertEquals('fr_FR', ini_get('intl.default_locale'));
  189. }
  190. /**
  191. * Tests that changing the default locale also changes the way translators
  192. * are fetched
  193. *
  194. * @return void
  195. */
  196. public function testGetTranslatorByDefaultLocale()
  197. {
  198. I18n::translator('custom', 'fr_FR', function () {
  199. $package = new Package('default');
  200. $package->setMessages([
  201. 'Cow' => 'Le moo'
  202. ]);
  203. return $package;
  204. });
  205. I18n::locale('fr_FR');
  206. $translator = I18n::translator('custom');
  207. $this->assertEquals('Le moo', $translator->translate('Cow'));
  208. }
  209. /**
  210. * Tests the __() function
  211. *
  212. * @return void
  213. */
  214. public function testBasicTranslateFunction()
  215. {
  216. I18n::defaultFormatter('sprintf');
  217. $this->assertEquals('%d is 1 (po translated)', __('%d = 1'));
  218. $this->assertEquals('1 is 1 (po translated)', __('%d = 1', 1));
  219. }
  220. /**
  221. * Tests the __n() function
  222. *
  223. * @return void
  224. */
  225. public function testBasicTranslatePluralFunction()
  226. {
  227. I18n::defaultFormatter('sprintf');
  228. $result = __n('singular msg', '%d = 0 or > 1', 1);
  229. $this->assertEquals('1 is 1 (po translated)', $result);
  230. $result = __n('singular msg', '%d = 0 or > 1', 2);
  231. $this->assertEquals('2 is 2-4 (po translated)', $result);
  232. }
  233. /**
  234. * Tests the __d() function
  235. *
  236. * @return void
  237. */
  238. public function testBasicDomainFunction()
  239. {
  240. I18n::translator('custom', 'en_US', function () {
  241. $package = new Package('default');
  242. $package->setMessages([
  243. 'Cow' => 'Le moo',
  244. 'The {0} is tasty' => 'The {0} is delicious',
  245. 'Average price {0}' => 'Price Average {0}',
  246. ]);
  247. return $package;
  248. });
  249. $this->assertEquals('Le moo', __d('custom', 'Cow'));
  250. $result = __d('custom', 'The {0} is tasty', ['fruit']);
  251. $this->assertEquals('The fruit is delicious', $result);
  252. $result = __d('custom', 'Average price {0}', ['9.99']);
  253. $this->assertEquals('Price Average 9.99', $result);
  254. }
  255. /**
  256. * Tests the __dn() function
  257. *
  258. * @return void
  259. */
  260. public function testBasicDomainPluralFunction()
  261. {
  262. I18n::translator('custom', 'en_US', function () {
  263. $package = new Package('default');
  264. $package->setMessages([
  265. 'Cow' => 'Le Moo',
  266. 'Cows' => [
  267. 'Le Moo',
  268. 'Les Moos'
  269. ]
  270. ]);
  271. return $package;
  272. });
  273. $this->assertEquals('Le Moo', __dn('custom', 'Cow', 'Cows', 1));
  274. $this->assertEquals('Les Moos', __dn('custom', 'Cow', 'Cows', 2));
  275. }
  276. /**
  277. * Tests the __x() function
  278. *
  279. * @return void
  280. */
  281. public function testBasicContextFunction()
  282. {
  283. I18n::translator('default', 'en_US', function () {
  284. $package = new Package('default');
  285. $package->setMessages([
  286. 'letter' => [
  287. '_context' => [
  288. 'character' => 'The letter {0}',
  289. 'communication' => 'She wrote a letter to {0}'
  290. ]
  291. ],
  292. 'letters' => [
  293. '_context' => [
  294. 'character' => [
  295. 'The letter {0}',
  296. 'The letters {0} and {1}'
  297. ],
  298. 'communication' => [
  299. 'She wrote a letter to {0}',
  300. 'She wrote a letter to {0} and {1}'
  301. ]
  302. ]
  303. ]
  304. ]);
  305. return $package;
  306. });
  307. $this->assertEquals('The letters A and B', __x('character', 'letters', ['A', 'B']));
  308. $this->assertEquals('The letter A', __x('character', 'letter', ['A']));
  309. $this->assertEquals(
  310. 'She wrote a letter to Thomas and Sara',
  311. __x('communication', 'letters', ['Thomas', 'Sara'])
  312. );
  313. $this->assertEquals(
  314. 'She wrote a letter to Thomas',
  315. __x('communication', 'letter', ['Thomas'])
  316. );
  317. $this->assertEquals(
  318. 'She wrote a letter to Thomas and Sara',
  319. __x('communication', 'letters', 'Thomas', 'Sara')
  320. );
  321. $this->assertEquals(
  322. 'She wrote a letter to Thomas',
  323. __x('communication', 'letter', 'Thomas')
  324. );
  325. }
  326. /**
  327. * Tests the __x() function with no msgstr
  328. *
  329. * @return void
  330. */
  331. public function testBasicContextFunctionNoString()
  332. {
  333. I18n::translator('default', 'en_US', function () {
  334. $package = new Package('default');
  335. $package->setMessages([
  336. 'letter' => [
  337. '_context' => [
  338. 'character' => '',
  339. ]
  340. ]
  341. ]);
  342. return $package;
  343. });
  344. $this->assertEquals('', __x('character', 'letter'));
  345. }
  346. /**
  347. * Tests the __xn() function
  348. *
  349. * @return void
  350. */
  351. public function testPluralContextFunction()
  352. {
  353. I18n::translator('default', 'en_US', function () {
  354. $package = new Package('default');
  355. $package->setMessages([
  356. 'letter' => [
  357. '_context' => [
  358. 'character' => 'The letter {0}',
  359. 'communication' => 'She wrote a letter to {0}',
  360. ]
  361. ],
  362. 'letters' => [
  363. '_context' => [
  364. 'character' => [
  365. 'The letter {0}',
  366. 'The letters {0} and {1}'
  367. ],
  368. 'communication' => [
  369. 'She wrote a letter to {0}',
  370. 'She wrote a letter to {0} and {1}'
  371. ]
  372. ]
  373. ]
  374. ]);
  375. return $package;
  376. });
  377. $this->assertEquals('The letters A and B', __xn('character', 'letter', 'letters', 2, ['A', 'B']));
  378. $this->assertEquals('The letter A', __xn('character', 'letter', 'letters', 1, ['A']));
  379. $this->assertEquals(
  380. 'She wrote a letter to Thomas and Sara',
  381. __xn('communication', 'letter', 'letters', 2, ['Thomas', 'Sara'])
  382. );
  383. $this->assertEquals(
  384. 'She wrote a letter to Thomas',
  385. __xn('communication', 'letter', 'letters', 1, ['Thomas'])
  386. );
  387. $this->assertEquals(
  388. 'She wrote a letter to Thomas and Sara',
  389. __xn('communication', 'letter', 'letters', 2, 'Thomas', 'Sara')
  390. );
  391. $this->assertEquals(
  392. 'She wrote a letter to Thomas',
  393. __xn('communication', 'letter', 'letters', 1, 'Thomas')
  394. );
  395. }
  396. /**
  397. * Tests the __dx() function
  398. *
  399. * @return void
  400. */
  401. public function testDomainContextFunction()
  402. {
  403. I18n::translator('custom', 'en_US', function () {
  404. $package = new Package('default');
  405. $package->setMessages([
  406. 'letter' => [
  407. '_context' => [
  408. 'character' => 'The letter {0}',
  409. 'communication' => 'She wrote a letter to {0}'
  410. ]
  411. ],
  412. 'letters' => [
  413. '_context' => [
  414. 'character' => [
  415. 'The letter {0}',
  416. 'The letters {0} and {1}'
  417. ],
  418. 'communication' => [
  419. 'She wrote a letter to {0}',
  420. 'She wrote a letter to {0} and {1}'
  421. ]
  422. ]
  423. ]
  424. ]);
  425. return $package;
  426. });
  427. $this->assertEquals('The letters A and B', __dx('custom', 'character', 'letters', ['A', 'B']));
  428. $this->assertEquals('The letter A', __dx('custom', 'character', 'letter', ['A']));
  429. $this->assertEquals(
  430. 'She wrote a letter to Thomas and Sara',
  431. __dx('custom', 'communication', 'letters', ['Thomas', 'Sara'])
  432. );
  433. $this->assertEquals(
  434. 'She wrote a letter to Thomas',
  435. __dx('custom', 'communication', 'letter', ['Thomas'])
  436. );
  437. $this->assertEquals(
  438. 'She wrote a letter to Thomas and Sara',
  439. __dx('custom', 'communication', 'letters', 'Thomas', 'Sara')
  440. );
  441. $this->assertEquals(
  442. 'She wrote a letter to Thomas',
  443. __dx('custom', 'communication', 'letter', 'Thomas')
  444. );
  445. }
  446. /**
  447. * Tests the __dxn() function
  448. *
  449. * @return void
  450. */
  451. public function testDomainPluralContextFunction()
  452. {
  453. I18n::translator('custom', 'en_US', function () {
  454. $package = new Package('default');
  455. $package->setMessages([
  456. 'letter' => [
  457. '_context' => [
  458. 'character' => 'The letter {0}',
  459. 'communication' => 'She wrote a letter to {0}',
  460. ]
  461. ],
  462. 'letters' => [
  463. '_context' => [
  464. 'character' => [
  465. 'The letter {0}',
  466. 'The letters {0} and {1}'
  467. ],
  468. 'communication' => [
  469. 'She wrote a letter to {0}',
  470. 'She wrote a letter to {0} and {1}'
  471. ]
  472. ]
  473. ]
  474. ]);
  475. return $package;
  476. });
  477. $this->assertEquals(
  478. 'The letters A and B',
  479. __dxn('custom', 'character', 'letter', 'letters', 2, ['A', 'B'])
  480. );
  481. $this->assertEquals(
  482. 'The letter A',
  483. __dxn('custom', 'character', 'letter', 'letters', 1, ['A'])
  484. );
  485. $this->assertEquals(
  486. 'She wrote a letter to Thomas and Sara',
  487. __dxn('custom', 'communication', 'letter', 'letters', 2, ['Thomas', 'Sara'])
  488. );
  489. $this->assertEquals(
  490. 'She wrote a letter to Thomas',
  491. __dxn('custom', 'communication', 'letter', 'letters', 1, ['Thomas'])
  492. );
  493. $this->assertEquals(
  494. 'She wrote a letter to Thomas and Sara',
  495. __dxn('custom', 'communication', 'letter', 'letters', 2, 'Thomas', 'Sara')
  496. );
  497. $this->assertEquals(
  498. 'She wrote a letter to Thomas',
  499. __dxn('custom', 'communication', 'letter', 'letters', 1, 'Thomas')
  500. );
  501. }
  502. /**
  503. * Tests that translators are cached for performance
  504. *
  505. * @return void
  506. */
  507. public function testTranslatorCache()
  508. {
  509. $english = I18n::translator();
  510. $spanish = I18n::translator('default', 'es_ES');
  511. $cached = Cache::read('translations.default.en_US', '_cake_core_');
  512. $this->assertEquals($english, $cached);
  513. $cached = Cache::read('translations.default.es_ES', '_cake_core_');
  514. $this->assertEquals($spanish, $cached);
  515. $this->assertSame($english, I18n::translator());
  516. $this->assertSame($spanish, I18n::translator('default', 'es_ES'));
  517. $this->assertSame($english, I18n::translator());
  518. }
  519. /**
  520. * Tests that it is possible to register a generic translators factory for a domain
  521. * instead of having to create them manually
  522. *
  523. * @return void
  524. */
  525. public function testloaderFactory()
  526. {
  527. I18n::config('custom', function ($name, $locale) {
  528. $this->assertEquals('custom', $name);
  529. $package = new Package('default');
  530. if ($locale == 'fr_FR') {
  531. $package->setMessages([
  532. 'Cow' => 'Le Moo',
  533. 'Cows' => [
  534. 'Le Moo',
  535. 'Les Moos'
  536. ]
  537. ]);
  538. }
  539. if ($locale === 'es_ES') {
  540. $package->setMessages([
  541. 'Cow' => 'El Moo',
  542. 'Cows' => [
  543. 'El Moo',
  544. 'Los Moos'
  545. ]
  546. ]);
  547. }
  548. return $package;
  549. });
  550. $translator = I18n::translator('custom', 'fr_FR');
  551. $this->assertEquals('Le Moo', $translator->translate('Cow'));
  552. $this->assertEquals('Les Moos', $translator->translate('Cows', ['_count' => 2]));
  553. $translator = I18n::translator('custom', 'es_ES');
  554. $this->assertEquals('El Moo', $translator->translate('Cow'));
  555. $this->assertEquals('Los Moos', $translator->translate('Cows', ['_count' => 2]));
  556. $translator = I18n::translator();
  557. $this->assertEquals('%d is 1 (po translated)', $translator->translate('%d = 1'));
  558. }
  559. /**
  560. * Tests that missing translations will get fallbacked to the default translator
  561. *
  562. * @return void
  563. */
  564. public function testFallbackTranslator()
  565. {
  566. I18n::translator('default', 'fr_FR', function () {
  567. $package = new Package('default');
  568. $package->setMessages([
  569. 'Dog' => 'Le bark'
  570. ]);
  571. return $package;
  572. });
  573. I18n::translator('custom', 'fr_FR', function () {
  574. $package = new Package('default');
  575. $package->setMessages([
  576. 'Cow' => 'Le moo'
  577. ]);
  578. return $package;
  579. });
  580. $translator = I18n::translator('custom', 'fr_FR');
  581. $this->assertEquals('Le moo', $translator->translate('Cow'));
  582. $this->assertEquals('Le bark', $translator->translate('Dog'));
  583. }
  584. /**
  585. * Test that the translation fallback can be disabled
  586. *
  587. * @return void
  588. */
  589. public function testFallbackTranslatorDisabled()
  590. {
  591. I18n::useFallback(false);
  592. I18n::translator('default', 'fr_FR', function () {
  593. $package = new Package('default');
  594. $package->setMessages(['Dog' => 'Le bark']);
  595. return $package;
  596. });
  597. I18n::translator('custom', 'fr_FR', function () {
  598. $package = new Package('default');
  599. $package->setMessages(['Cow' => 'Le moo']);
  600. return $package;
  601. });
  602. $translator = I18n::translator('custom', 'fr_FR');
  603. $this->assertEquals('Le moo', $translator->translate('Cow'));
  604. $this->assertEquals('Dog', $translator->translate('Dog'));
  605. }
  606. /**
  607. * Tests that it is possible to register a generic translators factory for a domain
  608. * instead of having to create them manually
  609. *
  610. * @return void
  611. */
  612. public function testFallbackTranslatorWithFactory()
  613. {
  614. I18n::translator('default', 'fr_FR', function () {
  615. $package = new Package('default');
  616. $package->setMessages([
  617. 'Dog' => 'Le bark'
  618. ]);
  619. return $package;
  620. });
  621. I18n::config('custom', function ($name, $locale) {
  622. $this->assertEquals('custom', $name);
  623. $package = new Package('default');
  624. $package->setMessages([
  625. 'Cow' => 'Le moo',
  626. ]);
  627. return $package;
  628. });
  629. $translator = I18n::translator('custom', 'fr_FR');
  630. $this->assertEquals('Le moo', $translator->translate('Cow'));
  631. $this->assertEquals('Le bark', $translator->translate('Dog'));
  632. }
  633. }