TranslateBehaviorTest.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  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 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM\Behavior;
  16. use Cake\Collection\Collection;
  17. use Cake\I18n\I18n;
  18. use Cake\ORM\Behavior\Translate\TranslateTrait;
  19. use Cake\ORM\Entity;
  20. use Cake\ORM\TableRegistry;
  21. use Cake\TestSuite\TestCase;
  22. /**
  23. * Stub entity class
  24. */
  25. class Article extends Entity
  26. {
  27. use TranslateTrait;
  28. }
  29. /**
  30. * Translate behavior test case
  31. */
  32. class TranslateBehaviorTest extends TestCase
  33. {
  34. /**
  35. * fixtures
  36. *
  37. * @var array
  38. */
  39. public $fixtures = [
  40. 'core.articles',
  41. 'core.authors',
  42. 'core.special_tags',
  43. 'core.tags',
  44. 'core.comments',
  45. 'core.translates'
  46. ];
  47. public function tearDown()
  48. {
  49. parent::tearDown();
  50. I18n::locale(I18n::defaultLocale());
  51. TableRegistry::clear();
  52. }
  53. /**
  54. * Returns an array with all the translations found for a set of records
  55. *
  56. * @param array|\Traversable $data
  57. * @return Collection
  58. */
  59. protected function _extractTranslations($data)
  60. {
  61. return (new Collection($data))->map(function ($row) {
  62. $translations = $row->get('_translations');
  63. if (!$translations) {
  64. return [];
  65. }
  66. return array_map(function ($t) {
  67. return $t->toArray();
  68. }, $translations);
  69. });
  70. }
  71. /**
  72. * Tests that custom translation tables are respected
  73. *
  74. * @return void
  75. */
  76. public function testCustomTranslationTable()
  77. {
  78. $table = TableRegistry::get('Articles');
  79. $table->addBehavior('Translate', [
  80. 'translationTable' => '\TestApp\Model\Table\I18nTable',
  81. 'fields' => ['title', 'body']
  82. ]);
  83. $items = $table->associations();
  84. $i18n = $items->getByProperty('_i18n');
  85. $this->assertEquals('\TestApp\Model\Table\I18nTable', $i18n->name());
  86. $this->assertInstanceOf('TestApp\Model\Table\I18nTable', $i18n->target());
  87. $this->assertEquals('test_custom_i18n_datasource', $i18n->target()->connection()->configName());
  88. $this->assertEquals('custom_i18n_table', $i18n->target()->table());
  89. }
  90. /**
  91. * Tests that the strategy can be changed for i18n
  92. *
  93. * @return void
  94. */
  95. public function testStrategy()
  96. {
  97. $table = TableRegistry::get('Articles');
  98. $table->addBehavior('Translate', [
  99. 'strategy' => 'select',
  100. 'fields' => ['title', 'body']
  101. ]);
  102. $items = $table->associations();
  103. $i18n = $items->getByProperty('_i18n');
  104. $this->assertEquals('select', $i18n->strategy());
  105. }
  106. /**
  107. * Tests that fields from a translated model are overridden
  108. *
  109. * @return void
  110. */
  111. public function testFindSingleLocale()
  112. {
  113. $table = TableRegistry::get('Articles');
  114. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  115. $table->locale('eng');
  116. $results = $table->find()->combine('title', 'body', 'id')->toArray();
  117. $expected = [
  118. 1 => ['Title #1' => 'Content #1'],
  119. 2 => ['Title #2' => 'Content #2'],
  120. 3 => ['Title #3' => 'Content #3'],
  121. ];
  122. $this->assertSame($expected, $results);
  123. }
  124. /**
  125. * Test that iterating in a formatResults() does not drop data.
  126. *
  127. * @return void
  128. */
  129. public function testFindTranslationsFormatResultsIteration()
  130. {
  131. $table = TableRegistry::get('Articles');
  132. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  133. $table->locale('eng');
  134. $results = $table->find('translations')
  135. ->limit(1)
  136. ->formatResults(function ($results) {
  137. foreach ($results as $res) {
  138. $res->first = 'val';
  139. }
  140. foreach ($results as $res) {
  141. $res->second = 'loop';
  142. }
  143. return $results;
  144. })
  145. ->toArray();
  146. $this->assertCount(1, $results);
  147. $this->assertSame('Title #1', $results[0]->title);
  148. $this->assertSame('val', $results[0]->first);
  149. $this->assertSame('loop', $results[0]->second);
  150. $this->assertNotEmpty($results[0]->_translations);
  151. }
  152. /**
  153. * Tests that fields from a translated model use the I18n class locale
  154. * and that it propogates to associated models
  155. *
  156. * @return void
  157. */
  158. public function testFindSingleLocaleAssociatedEnv()
  159. {
  160. I18n::locale('eng');
  161. $table = TableRegistry::get('Articles');
  162. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  163. $table->hasMany('Comments');
  164. $table->Comments->addBehavior('Translate', ['fields' => ['comment']]);
  165. $results = $table->find()
  166. ->select(['id', 'title', 'body'])
  167. ->contain(['Comments' => ['fields' => ['article_id', 'comment']]])
  168. ->hydrate(false)
  169. ->toArray();
  170. $expected = [
  171. [
  172. 'id' => 1,
  173. 'title' => 'Title #1',
  174. 'body' => 'Content #1',
  175. 'comments' => [
  176. ['article_id' => 1, 'comment' => 'Comment #1', '_locale' => 'eng'],
  177. ['article_id' => 1, 'comment' => 'Comment #2', '_locale' => 'eng'],
  178. ['article_id' => 1, 'comment' => 'Comment #3', '_locale' => 'eng'],
  179. ['article_id' => 1, 'comment' => 'Comment #4', '_locale' => 'eng']
  180. ],
  181. '_locale' => 'eng'
  182. ],
  183. [
  184. 'id' => 2,
  185. 'title' => 'Title #2',
  186. 'body' => 'Content #2',
  187. 'comments' => [
  188. ['article_id' => 2, 'comment' => 'First Comment for Second Article', '_locale' => 'eng'],
  189. ['article_id' => 2, 'comment' => 'Second Comment for Second Article', '_locale' => 'eng']
  190. ],
  191. '_locale' => 'eng'
  192. ],
  193. [
  194. 'id' => 3,
  195. 'title' => 'Title #3',
  196. 'body' => 'Content #3',
  197. 'comments' => [],
  198. '_locale' => 'eng'
  199. ]
  200. ];
  201. $this->assertSame($expected, $results);
  202. I18n::locale('spa');
  203. $results = $table->find()
  204. ->select(['id', 'title', 'body'])
  205. ->contain([
  206. 'Comments' => [
  207. 'fields' => ['article_id', 'comment'],
  208. 'sort' => ['Comments.id' => 'ASC']
  209. ]
  210. ])
  211. ->hydrate(false)
  212. ->toArray();
  213. $expected = [
  214. [
  215. 'id' => 1,
  216. 'title' => 'First Article',
  217. 'body' => 'Contenido #1',
  218. 'comments' => [
  219. ['article_id' => 1, 'comment' => 'First Comment for First Article', '_locale' => 'spa'],
  220. ['article_id' => 1, 'comment' => 'Second Comment for First Article', '_locale' => 'spa'],
  221. ['article_id' => 1, 'comment' => 'Third Comment for First Article', '_locale' => 'spa'],
  222. ['article_id' => 1, 'comment' => 'Comentario #4', '_locale' => 'spa']
  223. ],
  224. '_locale' => 'spa'
  225. ],
  226. [
  227. 'id' => 2,
  228. 'title' => 'Second Article',
  229. 'body' => 'Second Article Body',
  230. 'comments' => [
  231. ['article_id' => 2, 'comment' => 'First Comment for Second Article', '_locale' => 'spa'],
  232. ['article_id' => 2, 'comment' => 'Second Comment for Second Article', '_locale' => 'spa']
  233. ],
  234. '_locale' => 'spa'
  235. ],
  236. [
  237. 'id' => 3,
  238. 'title' => 'Third Article',
  239. 'body' => 'Third Article Body',
  240. 'comments' => [],
  241. '_locale' => 'spa'
  242. ]
  243. ];
  244. $this->assertSame($expected, $results);
  245. }
  246. /**
  247. * Tests that fields from a translated model are not overridden if translation
  248. * is null
  249. *
  250. * @return void
  251. */
  252. public function testFindSingleLocaleWithNullTranslation()
  253. {
  254. $table = TableRegistry::get('Comments');
  255. $table->addBehavior('Translate', ['fields' => ['comment']]);
  256. $table->locale('spa');
  257. $results = $table->find()
  258. ->where(['Comments.id' => 6])
  259. ->combine('id', 'comment')->toArray();
  260. $expected = [6 => 'Second Comment for Second Article'];
  261. $this->assertSame($expected, $results);
  262. }
  263. /**
  264. * Tests that overriding fields with the translate behavior works when
  265. * using conditions and that all other columns are preserved
  266. *
  267. * @return void
  268. */
  269. public function testFindSingleLocaleWithConditions()
  270. {
  271. $table = TableRegistry::get('Articles');
  272. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  273. $table->locale('eng');
  274. $results = $table->find()
  275. ->where(['Articles.id' => 2])
  276. ->all();
  277. $this->assertCount(1, $results);
  278. $row = $results->first();
  279. $expected = [
  280. 'id' => 2,
  281. 'title' => 'Title #2',
  282. 'body' => 'Content #2',
  283. 'author_id' => 3,
  284. 'published' => 'Y',
  285. '_locale' => 'eng'
  286. ];
  287. $this->assertEquals($expected, $row->toArray());
  288. }
  289. /**
  290. * Tests that translating fields work when other formatters are used
  291. *
  292. * @return void
  293. */
  294. public function testFindList()
  295. {
  296. $table = TableRegistry::get('Articles');
  297. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  298. $table->locale('eng');
  299. $results = $table->find('list')->toArray();
  300. $expected = [1 => 'Title #1', 2 => 'Title #2', 3 => 'Title #3'];
  301. $this->assertSame($expected, $results);
  302. }
  303. /**
  304. * Tests that the query count return the correct results
  305. *
  306. * @return void
  307. */
  308. public function testFindCount()
  309. {
  310. $table = TableRegistry::get('Articles');
  311. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  312. $table->locale('eng');
  313. $this->assertEquals(3, $table->find()->count());
  314. }
  315. /**
  316. * Tests that it is possible to get all translated fields at once
  317. *
  318. * @return void
  319. */
  320. public function testFindTranslations()
  321. {
  322. $table = TableRegistry::get('Articles');
  323. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  324. $results = $table->find('translations');
  325. $expected = [
  326. [
  327. 'eng' => ['title' => 'Title #1', 'body' => 'Content #1', 'description' => 'Description #1', 'locale' => 'eng'],
  328. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  329. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze'],
  330. 'spa' => ['body' => 'Contenido #1', 'locale' => 'spa', 'description' => '']
  331. ],
  332. [
  333. 'eng' => ['title' => 'Title #2', 'body' => 'Content #2', 'locale' => 'eng'],
  334. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  335. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  336. ],
  337. [
  338. 'eng' => ['title' => 'Title #3', 'body' => 'Content #3', 'locale' => 'eng'],
  339. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  340. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  341. ]
  342. ];
  343. $translations = $this->_extractTranslations($results);
  344. $this->assertEquals($expected, $translations->toArray());
  345. $expected = [
  346. 1 => ['First Article' => 'First Article Body'],
  347. 2 => ['Second Article' => 'Second Article Body'],
  348. 3 => ['Third Article' => 'Third Article Body']
  349. ];
  350. $grouped = $results->combine('title', 'body', 'id');
  351. $this->assertEquals($expected, $grouped->toArray());
  352. }
  353. /**
  354. * Tests that it is possible to request just a few translations
  355. *
  356. * @return void
  357. */
  358. public function testFindFilteredTranslations()
  359. {
  360. $table = TableRegistry::get('Articles');
  361. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  362. $results = $table->find('translations', ['locales' => ['deu', 'cze']]);
  363. $expected = [
  364. [
  365. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  366. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze']
  367. ],
  368. [
  369. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  370. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  371. ],
  372. [
  373. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  374. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  375. ]
  376. ];
  377. $translations = $this->_extractTranslations($results);
  378. $this->assertEquals($expected, $translations->toArray());
  379. $expected = [
  380. 1 => ['First Article' => 'First Article Body'],
  381. 2 => ['Second Article' => 'Second Article Body'],
  382. 3 => ['Third Article' => 'Third Article Body']
  383. ];
  384. $grouped = $results->combine('title', 'body', 'id');
  385. $this->assertEquals($expected, $grouped->toArray());
  386. }
  387. /**
  388. * Tests that it is possible to combine find('list') and find('translations')
  389. *
  390. * @return void
  391. */
  392. public function testFindTranslationsList()
  393. {
  394. $table = TableRegistry::get('Articles');
  395. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  396. $results = $table
  397. ->find('list', [
  398. 'keyField' => 'title',
  399. 'valueField' => '_translations.deu.title',
  400. 'groupField' => 'id'
  401. ])
  402. ->find('translations', ['locales' => ['deu']]);
  403. $expected = [
  404. 1 => ['First Article' => 'Titel #1'],
  405. 2 => ['Second Article' => 'Titel #2'],
  406. 3 => ['Third Article' => 'Titel #3']
  407. ];
  408. $this->assertEquals($expected, $results->toArray());
  409. }
  410. /**
  411. * Tests that you can both override fields and find all translations
  412. *
  413. * @return void
  414. */
  415. public function testFindTranslationsWithFieldOverriding()
  416. {
  417. $table = TableRegistry::get('Articles');
  418. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  419. $table->locale('cze');
  420. $results = $table->find('translations', ['locales' => ['deu', 'cze']]);
  421. $expected = [
  422. [
  423. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  424. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze']
  425. ],
  426. [
  427. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  428. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  429. ],
  430. [
  431. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  432. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  433. ]
  434. ];
  435. $translations = $this->_extractTranslations($results);
  436. $this->assertEquals($expected, $translations->toArray());
  437. $expected = [
  438. 1 => ['Titulek #1' => 'Obsah #1'],
  439. 2 => ['Titulek #2' => 'Obsah #2'],
  440. 3 => ['Titulek #3' => 'Obsah #3']
  441. ];
  442. $grouped = $results->combine('title', 'body', 'id');
  443. $this->assertEquals($expected, $grouped->toArray());
  444. }
  445. /**
  446. * Tests that fields can be overridden in a hasMany association
  447. *
  448. * @return void
  449. */
  450. public function testFindSingleLocaleHasMany()
  451. {
  452. $table = TableRegistry::get('Articles');
  453. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  454. $table->hasMany('Comments');
  455. $comments = $table->hasMany('Comments')->target();
  456. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  457. $table->locale('eng');
  458. $comments->locale('eng');
  459. $results = $table->find()->contain(['Comments' => function ($q) {
  460. return $q->select(['id', 'comment', 'article_id']);
  461. }]);
  462. $list = new Collection($results->first()->comments);
  463. $expected = [
  464. 1 => 'Comment #1',
  465. 2 => 'Comment #2',
  466. 3 => 'Comment #3',
  467. 4 => 'Comment #4'
  468. ];
  469. $this->assertEquals($expected, $list->combine('id', 'comment')->toArray());
  470. }
  471. /**
  472. * Test that it is possible to bring translations from hasMany relations
  473. *
  474. * @return void
  475. */
  476. public function testTranslationsHasMany()
  477. {
  478. $table = TableRegistry::get('Articles');
  479. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  480. $table->hasMany('Comments');
  481. $comments = $table->hasMany('Comments')->target();
  482. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  483. $results = $table->find('translations')->contain([
  484. 'Comments' => function ($q) {
  485. return $q->find('translations')->select(['id', 'comment', 'article_id']);
  486. }
  487. ]);
  488. $comments = $results->first()->comments;
  489. $expected = [
  490. [
  491. 'eng' => ['comment' => 'Comment #1', 'locale' => 'eng']
  492. ],
  493. [
  494. 'eng' => ['comment' => 'Comment #2', 'locale' => 'eng']
  495. ],
  496. [
  497. 'eng' => ['comment' => 'Comment #3', 'locale' => 'eng']
  498. ],
  499. [
  500. 'eng' => ['comment' => 'Comment #4', 'locale' => 'eng'],
  501. 'spa' => ['comment' => 'Comentario #4', 'locale' => 'spa']
  502. ]
  503. ];
  504. $translations = $this->_extractTranslations($comments);
  505. $this->assertEquals($expected, $translations->toArray());
  506. }
  507. /**
  508. * Tests that it is possible to both override fields with a translation and
  509. * also find separately other translations
  510. *
  511. * @return void
  512. */
  513. public function testTranslationsHasManyWithOverride()
  514. {
  515. $table = TableRegistry::get('Articles');
  516. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  517. $table->hasMany('Comments');
  518. $comments = $table->hasMany('Comments')->target();
  519. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  520. $table->locale('cze');
  521. $comments->locale('eng');
  522. $results = $table->find('translations')->contain([
  523. 'Comments' => function ($q) {
  524. return $q->find('translations')->select(['id', 'comment', 'article_id']);
  525. }
  526. ]);
  527. $comments = $results->first()->comments;
  528. $expected = [
  529. 1 => 'Comment #1',
  530. 2 => 'Comment #2',
  531. 3 => 'Comment #3',
  532. 4 => 'Comment #4'
  533. ];
  534. $list = new Collection($comments);
  535. $this->assertEquals($expected, $list->combine('id', 'comment')->toArray());
  536. $expected = [
  537. [
  538. 'eng' => ['comment' => 'Comment #1', 'locale' => 'eng']
  539. ],
  540. [
  541. 'eng' => ['comment' => 'Comment #2', 'locale' => 'eng']
  542. ],
  543. [
  544. 'eng' => ['comment' => 'Comment #3', 'locale' => 'eng']
  545. ],
  546. [
  547. 'eng' => ['comment' => 'Comment #4', 'locale' => 'eng'],
  548. 'spa' => ['comment' => 'Comentario #4', 'locale' => 'spa']
  549. ]
  550. ];
  551. $translations = $this->_extractTranslations($comments);
  552. $this->assertEquals($expected, $translations->toArray());
  553. $this->assertEquals('Titulek #1', $results->first()->title);
  554. $this->assertEquals('Obsah #1', $results->first()->body);
  555. }
  556. /**
  557. * Tests that it is possible to translate belongsTo associations
  558. *
  559. * @return void
  560. */
  561. public function testFindSingleLocaleBelongsto()
  562. {
  563. $table = TableRegistry::get('Articles');
  564. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  565. $authors = $table->belongsTo('Authors')->target();
  566. $authors->addBehavior('Translate', ['fields' => ['name']]);
  567. $table->locale('eng');
  568. $authors->locale('eng');
  569. $results = $table->find()
  570. ->select(['title', 'body'])
  571. ->order(['title' => 'asc'])
  572. ->contain(['Authors' => function ($q) {
  573. return $q->select(['id', 'name']);
  574. }]);
  575. $expected = [
  576. [
  577. 'title' => 'Title #1',
  578. 'body' => 'Content #1',
  579. 'author' => ['id' => 1, 'name' => 'May-rianoh', '_locale' => 'eng'],
  580. '_locale' => 'eng'
  581. ],
  582. [
  583. 'title' => 'Title #2',
  584. 'body' => 'Content #2',
  585. 'author' => ['id' => 3, 'name' => 'larry', '_locale' => 'eng'],
  586. '_locale' => 'eng'
  587. ],
  588. [
  589. 'title' => 'Title #3',
  590. 'body' => 'Content #3',
  591. 'author' => ['id' => 1, 'name' => 'May-rianoh', '_locale' => 'eng'],
  592. '_locale' => 'eng'
  593. ]
  594. ];
  595. $results = array_map(function ($r) {
  596. return $r->toArray();
  597. }, $results->toArray());
  598. $this->assertEquals($expected, $results);
  599. }
  600. /**
  601. * Tests that it is possible to translate belongsToMany associations
  602. *
  603. * @return void
  604. */
  605. public function testFindSingleLocaleBelongsToMany()
  606. {
  607. $table = TableRegistry::get('Articles');
  608. $specialTags = TableRegistry::get('SpecialTags');
  609. $specialTags->addBehavior('Translate', ['fields' => ['extra_info']]);
  610. $table->belongsToMany('Tags', [
  611. 'through' => $specialTags
  612. ]);
  613. $specialTags->locale('eng');
  614. $result = $table->get(2, ['contain' => 'Tags']);
  615. $this->assertNotEmpty($result);
  616. $this->assertNotEmpty($result->tags);
  617. $this->assertEquals('Translated Info', $result->tags[0]->special_tags[0]->extra_info);
  618. }
  619. /**
  620. * Tests that updating an existing record translations work
  621. *
  622. * @return void
  623. */
  624. public function testUpdateSingleLocale()
  625. {
  626. $table = TableRegistry::get('Articles');
  627. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  628. $table->locale('eng');
  629. $article = $table->find()->first();
  630. $this->assertEquals(1, $article->get('id'));
  631. $article->set('title', 'New translated article');
  632. $table->save($article);
  633. $this->assertNull($article->get('_i18n'));
  634. $article = $table->find()->first();
  635. $this->assertEquals(1, $article->get('id'));
  636. $this->assertEquals('New translated article', $article->get('title'));
  637. $this->assertEquals('Content #1', $article->get('body'));
  638. $table->locale(false);
  639. $article = $table->find()->first();
  640. $this->assertEquals(1, $article->get('id'));
  641. $this->assertEquals('First Article', $article->get('title'));
  642. $table->locale('eng');
  643. $article->set('title', 'Wow, such translated article');
  644. $article->set('body', 'A translated body');
  645. $table->save($article);
  646. $this->assertNull($article->get('_i18n'));
  647. $article = $table->find()->first();
  648. $this->assertEquals(1, $article->get('id'));
  649. $this->assertEquals('Wow, such translated article', $article->get('title'));
  650. $this->assertEquals('A translated body', $article->get('body'));
  651. }
  652. /**
  653. * Tests adding new translation to a record
  654. *
  655. * @return void
  656. */
  657. public function testInsertNewTranslations()
  658. {
  659. $table = TableRegistry::get('Articles');
  660. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  661. $table->locale('fra');
  662. $article = $table->find()->first();
  663. $this->assertEquals(1, $article->get('id'));
  664. $article->set('title', 'Le titre');
  665. $table->save($article);
  666. $this->assertEquals('fra', $article->get('_locale'));
  667. $article = $table->find()->first();
  668. $this->assertEquals(1, $article->get('id'));
  669. $this->assertEquals('Le titre', $article->get('title'));
  670. $this->assertEquals('First Article Body', $article->get('body'));
  671. $article->set('title', 'Un autre titre');
  672. $article->set('body', 'Le contenu');
  673. $table->save($article);
  674. $this->assertNull($article->get('_i18n'));
  675. $article = $table->find()->first();
  676. $this->assertEquals('Un autre titre', $article->get('title'));
  677. $this->assertEquals('Le contenu', $article->get('body'));
  678. }
  679. /**
  680. * Tests that it is possible to use the _locale property to specify the language
  681. * to use for saving an entity
  682. *
  683. * @return void
  684. */
  685. public function testUpdateTranslationWithLocaleInEntity()
  686. {
  687. $table = TableRegistry::get('Articles');
  688. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  689. $article = $table->find()->first();
  690. $this->assertEquals(1, $article->get('id'));
  691. $article->set('_locale', 'fra');
  692. $article->set('title', 'Le titre');
  693. $table->save($article);
  694. $this->assertNull($article->get('_i18n'));
  695. $article = $table->find()->first();
  696. $this->assertEquals(1, $article->get('id'));
  697. $this->assertEquals('First Article', $article->get('title'));
  698. $this->assertEquals('First Article Body', $article->get('body'));
  699. $table->locale('fra');
  700. $article = $table->find()->first();
  701. $this->assertEquals(1, $article->get('id'));
  702. $this->assertEquals('Le titre', $article->get('title'));
  703. $this->assertEquals('First Article Body', $article->get('body'));
  704. }
  705. /**
  706. * Tests that translations are added to the whitelist of associations to be
  707. * saved
  708. *
  709. * @return void
  710. */
  711. public function testSaveTranslationWithAssociationWhitelist()
  712. {
  713. $table = TableRegistry::get('Articles');
  714. $table->hasMany('Comments');
  715. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  716. $table->locale('fra');
  717. $article = $table->find()->first();
  718. $this->assertEquals(1, $article->get('id'));
  719. $article->set('title', 'Le titre');
  720. $table->save($article, ['associated' => ['Comments']]);
  721. $this->assertNull($article->get('_i18n'));
  722. $article = $table->find()->first();
  723. $this->assertEquals('Le titre', $article->get('title'));
  724. }
  725. /**
  726. * Tests that after deleting a translated entity, all translations are also removed
  727. *
  728. * @return void
  729. */
  730. public function testDelete()
  731. {
  732. $table = TableRegistry::get('Articles');
  733. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  734. $article = $table->find()->first();
  735. $this->assertTrue($table->delete($article));
  736. $translations = TableRegistry::get('I18n')->find()
  737. ->where(['model' => 'Articles', 'foreign_key' => $article->id])
  738. ->count();
  739. $this->assertEquals(0, $translations);
  740. }
  741. /**
  742. * Tests saving multiple translations at once when the translations already
  743. * exist in the database
  744. *
  745. * @return void
  746. */
  747. public function testSaveMultipleTranslations()
  748. {
  749. $table = TableRegistry::get('Articles');
  750. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  751. $article = $results = $table->find('translations')->first();
  752. $translations = $article->get('_translations');
  753. $translations['deu']->set('title', 'Another title');
  754. $translations['eng']->set('body', 'Another body');
  755. $article->set('_translations', $translations);
  756. $table->save($article);
  757. $this->assertNull($article->get('_i18n'));
  758. $article = $results = $table->find('translations')->first();
  759. $translations = $article->get('_translations');
  760. $this->assertEquals('Another title', $translations['deu']->get('title'));
  761. $this->assertEquals('Another body', $translations['eng']->get('body'));
  762. }
  763. /**
  764. * Tests saving multiple existing translations and adding new ones
  765. *
  766. * @return void
  767. */
  768. public function testSaveMultipleNewTranslations()
  769. {
  770. $table = TableRegistry::get('Articles');
  771. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  772. $article = $results = $table->find('translations')->first();
  773. $translations = $article->get('_translations');
  774. $translations['deu']->set('title', 'Another title');
  775. $translations['eng']->set('body', 'Another body');
  776. $translations['spa'] = new Entity(['title' => 'Titulo']);
  777. $translations['fre'] = new Entity(['title' => 'Titre']);
  778. $article->set('_translations', $translations);
  779. $table->save($article);
  780. $this->assertNull($article->get('_i18n'));
  781. $article = $results = $table->find('translations')->first();
  782. $translations = $article->get('_translations');
  783. $this->assertEquals('Another title', $translations['deu']->get('title'));
  784. $this->assertEquals('Another body', $translations['eng']->get('body'));
  785. $this->assertEquals('Titulo', $translations['spa']->get('title'));
  786. $this->assertEquals('Titre', $translations['fre']->get('title'));
  787. }
  788. /**
  789. * Tests that iterating a resultset twice when using the translations finder
  790. * will not cause any errors nor information loss
  791. *
  792. * @return void
  793. */
  794. public function testUseCountInFindTranslations()
  795. {
  796. $table = TableRegistry::get('Articles');
  797. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  798. $articles = $results = $table->find('translations');
  799. $all = $articles->all();
  800. $this->assertCount(3, $all);
  801. $article = $all->first();
  802. $this->assertNotEmpty($article->get('_translations'));
  803. }
  804. /**
  805. * Tests that multiple translations saved when having a default locale
  806. * are correctly saved
  807. *
  808. * @return void
  809. */
  810. public function testSavingWithNonDefaultLocale()
  811. {
  812. $table = TableRegistry::get('Articles');
  813. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  814. $table->entityClass(__NAMESPACE__ . '\Article');
  815. I18n::locale('fra');
  816. $translations = [
  817. 'fra' => ['title' => 'Un article'],
  818. 'spa' => ['title' => 'Un artículo']
  819. ];
  820. $article = $table->get(1);
  821. foreach ($translations as $lang => $data) {
  822. $article->translation($lang)->set($data, ['guard' => false]);
  823. }
  824. $table->save($article);
  825. $article = $table->find('translations')->where(['Articles.id' => 1])->first();
  826. $this->assertEquals('Un article', $article->translation('fra')->title);
  827. $this->assertEquals('Un artículo', $article->translation('spa')->title);
  828. }
  829. /**
  830. * Tests that translation queries are added to union queries as well.
  831. *
  832. * @return void
  833. */
  834. public function testTranslationWithUnionQuery()
  835. {
  836. $table = TableRegistry::get('Comments');
  837. $table->addBehavior('Translate', ['fields' => ['comment']]);
  838. $table->locale('spa');
  839. $query = $table->find()->where(['Comments.id' => 6]);
  840. $query2 = $table->find()->where(['Comments.id' => 5]);
  841. $query->union($query2);
  842. $results = $query->sortBy('id', SORT_ASC)->toList();
  843. $this->assertCount(2, $results);
  844. $this->assertEquals('First Comment for Second Article', $results[0]->comment);
  845. $this->assertEquals('Second Comment for Second Article', $results[1]->comment);
  846. }
  847. /**
  848. * Tests the use of `referenceName` config option.
  849. *
  850. * @return void
  851. */
  852. public function testAutoReferenceName()
  853. {
  854. $table = TableRegistry::get('Articles');
  855. $table->hasMany('OtherComments', ['className' => 'Comments']);
  856. $table->OtherComments->addBehavior(
  857. 'Translate',
  858. ['fields' => ['comment']]
  859. );
  860. $items = $table->OtherComments->associations();
  861. $association = $items->getByProperty('comment_translation');
  862. $this->assertNotEmpty($association, 'Translation association not found');
  863. $found = false;
  864. foreach ($association->conditions() as $key => $value) {
  865. if (strpos($key, 'comment_translation.model') !== false) {
  866. $found = true;
  867. $this->assertEquals('Comments', $value);
  868. break;
  869. }
  870. }
  871. $this->assertTrue($found, '`referenceName` field condition on a Translation association was not found');
  872. }
  873. /**
  874. * Tests the use of unconventional `referenceName` config option.
  875. *
  876. * @return void
  877. */
  878. public function testChangingReferenceName()
  879. {
  880. $table = TableRegistry::get('Articles');
  881. $table->alias('FavoritePost');
  882. $table->addBehavior(
  883. 'Translate',
  884. ['fields' => ['body'], 'referenceName' => 'Posts']
  885. );
  886. $items = $table->associations();
  887. $association = $items->getByProperty('body_translation');
  888. $this->assertNotEmpty($association, 'Translation association not found');
  889. $found = false;
  890. foreach ($association->conditions() as $key => $value) {
  891. if (strpos($key, 'body_translation.model') !== false) {
  892. $found = true;
  893. $this->assertEquals('Posts', $value);
  894. break;
  895. }
  896. }
  897. $this->assertTrue($found, '`referenceName` field condition on a Translation association was not found');
  898. }
  899. /**
  900. * Tests that onlyTranslated will remove records from the result set
  901. * if they are not fully translated
  902. *
  903. * @return void
  904. */
  905. public function testFilterUntranslated()
  906. {
  907. $table = TableRegistry::get('Articles');
  908. $table->addBehavior('Translate', [
  909. 'fields' => ['title', 'body'],
  910. 'onlyTranslated' => true
  911. ]);
  912. $table->locale('eng');
  913. $results = $table->find()->where(['Articles.id' => 1])->all();
  914. $this->assertCount(1, $results);
  915. $table->locale('fr');
  916. $results = $table->find()->where(['Articles.id' => 1])->all();
  917. $this->assertCount(0, $results);
  918. }
  919. /**
  920. * Tests that records not translated in the current locale will not be
  921. * present in the results for the translations finder, and also proves
  922. * that this can be overridden.
  923. *
  924. * @return void
  925. */
  926. public function testFilterUntranslatedWithFinder()
  927. {
  928. $table = TableRegistry::get('Comments');
  929. $table->addBehavior('Translate', [
  930. 'fields' => ['comment'],
  931. 'onlyTranslated' => true
  932. ]);
  933. $table->locale('eng');
  934. $results = $table->find('translations')->all();
  935. $this->assertCount(4, $results);
  936. $table->locale('spa');
  937. $results = $table->find('translations')->all();
  938. $this->assertCount(1, $results);
  939. $table->locale('spa');
  940. $results = $table->find('translations', ['filterByCurrentLocale' => false])->all();
  941. $this->assertCount(6, $results);
  942. $table->locale('spa');
  943. $results = $table->find('translations')->all();
  944. $this->assertCount(1, $results);
  945. }
  946. /**
  947. * Tests that allowEmptyTranslations takes effect
  948. *
  949. * @return void
  950. */
  951. public function testEmptyTranslations()
  952. {
  953. $table = TableRegistry::get('Articles');
  954. $table->addBehavior('Translate', [
  955. 'fields' => ['title', 'body', 'description'],
  956. 'allowEmptyTranslations' => false,
  957. ]);
  958. $table->locale('spa');
  959. $result = $table->find()->first();
  960. $this->assertNull($result->description);
  961. }
  962. /**
  963. * Test save with clean translate fields
  964. *
  965. * @return void
  966. */
  967. public function testSaveWithCleanFields()
  968. {
  969. $table = TableRegistry::get('Articles');
  970. $table->addBehavior('Translate', ['fields' => ['title']]);
  971. $table->entityClass(__NAMESPACE__ . '\Article');
  972. I18n::locale('fra');
  973. $article = $table->get(1);
  974. $article->set('body', 'New Body');
  975. $table->save($article);
  976. $result = $table->get(1);
  977. $this->assertEquals('New Body', $result->body);
  978. $this->assertSame($article->title, $result->title);
  979. }
  980. }