LocalizationTest.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of the Carbon package.
  5. *
  6. * (c) Brian Nesbitt <brian@nesbot.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. namespace Tests\CarbonImmutable;
  12. use Carbon\CarbonImmutable as Carbon;
  13. use Carbon\CarbonInterval;
  14. use Carbon\Exceptions\NotLocaleAwareException;
  15. use Carbon\Language;
  16. use Carbon\Translator;
  17. use InvalidArgumentException;
  18. use Symfony\Component\Translation\IdentityTranslator;
  19. use Symfony\Component\Translation\Loader\ArrayLoader;
  20. use Symfony\Component\Translation\MessageCatalogue;
  21. use Symfony\Component\Translation\MessageSelector;
  22. use Symfony\Component\Translation\Translator as SymfonyTranslator;
  23. use Symfony\Component\Translation\TranslatorInterface;
  24. use Tests\AbstractTestCase;
  25. use Tests\CarbonImmutable\Fixtures\MyCarbon;
  26. /**
  27. * @group localization
  28. */
  29. class LocalizationTest extends AbstractTestCase
  30. {
  31. protected function tearDown(): void
  32. {
  33. parent::tearDown();
  34. Carbon::setLocale('en');
  35. }
  36. public function testGetTranslator()
  37. {
  38. /** @var Translator $t */
  39. $t = Carbon::getTranslator();
  40. $this->assertNotNull($t);
  41. $this->assertSame('en', $t->getLocale());
  42. }
  43. public function testResetTranslator()
  44. {
  45. /** @var Translator $t */
  46. $t = MyCarbon::getTranslator();
  47. $this->assertNotNull($t);
  48. $this->assertSame('en', $t->getLocale());
  49. }
  50. public function testSetLocaleToAuto()
  51. {
  52. $currentLocale = setlocale(LC_ALL, '0');
  53. if (setlocale(LC_ALL, 'fr_FR.UTF-8', 'fr_FR.utf8', 'fr_FR', 'fr') === false) {
  54. $this->markTestSkipped('testSetLocaleToAuto test need fr_FR.UTF-8.');
  55. }
  56. Carbon::setLocale('auto');
  57. $locale = Carbon::getLocale();
  58. $diff = Carbon::now()->subSeconds(2)->diffForHumans();
  59. setlocale(LC_ALL, $currentLocale);
  60. $this->assertSame('fr', $locale === 'fr_FR' ? 'fr' : $locale);
  61. $this->assertSame('il y a 2 secondes', $diff);
  62. if (setlocale(LC_ALL, 'ar_AE.UTF-8', 'ar_AE.utf8', 'ar_AE', 'ar') === false) {
  63. $this->markTestSkipped('testSetLocaleToAuto test need ar_AE.UTF-8.');
  64. }
  65. rename(__DIR__.'/../../src/Carbon/Lang/ar_AE.php', __DIR__.'/../../src/Carbon/Lang/disabled_ar_AE.php');
  66. Carbon::setLocale('auto');
  67. $locale = Carbon::getLocale();
  68. $diff = Carbon::now()->subSeconds(2)->diffForHumans();
  69. setlocale(LC_ALL, $currentLocale);
  70. rename(__DIR__.'/../../src/Carbon/Lang/disabled_ar_AE.php', __DIR__.'/../../src/Carbon/Lang/ar_AE.php');
  71. $this->assertStringStartsWith('ar', $locale);
  72. $this->assertSame('منذ ثانيتين', $diff);
  73. if (setlocale(LC_ALL, 'sr_ME.UTF-8', 'sr_ME.utf8', 'sr_ME', 'sr') === false) {
  74. $this->markTestSkipped('testSetLocaleToAuto test need sr_ME.UTF-8.');
  75. }
  76. Carbon::setLocale('auto');
  77. $locale = Carbon::getLocale();
  78. $diff = Carbon::now()->subSeconds(2)->diffForHumans();
  79. setlocale(LC_ALL, $currentLocale);
  80. $this->assertStringStartsWith('sr', $locale);
  81. $this->assertSame('pre 2 sekunde', str_replace('prije', 'pre', $diff));
  82. if (setlocale(LC_ALL, 'zh_TW.UTF-8', 'zh_TW.utf8', 'zh_TW', 'zh') === false) {
  83. $this->markTestSkipped('testSetLocaleToAuto test need zh_TW.UTF-8.');
  84. }
  85. Carbon::setLocale('auto');
  86. $locale = Carbon::getLocale();
  87. $diff = Carbon::now()->subSeconds(2)->diffForHumans();
  88. setlocale(LC_ALL, $currentLocale);
  89. $this->assertStringStartsWith('zh', $locale);
  90. $this->assertSame('2秒前', $diff);
  91. /** @var Translator $translator */
  92. $translator = Carbon::getTranslator();
  93. $translator->resetMessages();
  94. $translator->setLocale('en');
  95. $directories = $translator->getDirectories();
  96. $directory = sys_get_temp_dir().'/carbon'.mt_rand(0, 9999999);
  97. mkdir($directory);
  98. $translator->setDirectories([$directory]);
  99. $files = [
  100. 'zh_Hans',
  101. 'zh',
  102. 'fr',
  103. 'fr_CA',
  104. ];
  105. foreach ($files as $file) {
  106. copy(__DIR__."/../../src/Carbon/Lang/$file.php", "$directory/$file.php");
  107. }
  108. $currentLocale = setlocale(LC_ALL, '0');
  109. if (setlocale(LC_ALL, 'fr_FR.UTF-8', 'fr_FR.utf8', 'fr_FR', 'fr') === false) {
  110. $this->markTestSkipped('testSetLocaleToAuto test need fr_FR.UTF-8.');
  111. }
  112. Carbon::setLocale('auto');
  113. $locale = Carbon::getLocale();
  114. $diff = Carbon::now()->subSeconds(2)->diffForHumans();
  115. setlocale(LC_ALL, $currentLocale);
  116. $this->assertSame('fr', $locale);
  117. $this->assertSame('il y a 2 secondes', $diff);
  118. if (setlocale(LC_ALL, 'zh_CN.UTF-8', 'zh_CN.utf8', 'zh_CN', 'zh') === false) {
  119. $this->markTestSkipped('testSetLocaleToAuto test need zh_CN.UTF-8.');
  120. }
  121. Carbon::setLocale('auto');
  122. $locale = Carbon::getLocale();
  123. $diff = Carbon::now()->subSeconds(2)->diffForHumans();
  124. setlocale(LC_ALL, $currentLocale);
  125. $this->assertSame('zh', $locale);
  126. $this->assertSame('2秒前', $diff);
  127. if (setlocale(LC_ALL, 'yo_NG.UTF-8', 'yo_NG.utf8', 'yo_NG', 'yo') === false) {
  128. $this->markTestSkipped('testSetLocaleToAuto test need yo_NG.UTF-8.');
  129. }
  130. Carbon::setLocale('en');
  131. Carbon::setLocale('auto');
  132. $locale = Carbon::getLocale();
  133. $diff = Carbon::now()->subSeconds(2)->diffForHumans();
  134. setlocale(LC_ALL, $currentLocale);
  135. $this->assertSame('en', $locale);
  136. $this->assertSame('2 seconds ago', $diff);
  137. $translator->setDirectories($directories);
  138. foreach ($files as $file) {
  139. unlink("$directory/$file.php");
  140. }
  141. rmdir($directory);
  142. }
  143. /**
  144. * @see \Tests\CarbonImmutable\LocalizationTest::testSetLocale
  145. * @see \Tests\CarbonImmutable\LocalizationTest::testSetTranslator
  146. *
  147. * @return array
  148. */
  149. public static function dataForLocales()
  150. {
  151. return [
  152. ['af'],
  153. ['ar'],
  154. ['ar_DZ'],
  155. ['ar_KW'],
  156. ['ar_LY'],
  157. ['ar_MA'],
  158. ['ar_SA'],
  159. ['ar_Shakl'],
  160. ['ar_TN'],
  161. ['az'],
  162. ['be'],
  163. ['bg'],
  164. ['bm'],
  165. ['bn'],
  166. ['bo'],
  167. ['br'],
  168. ['bs'],
  169. ['bs_BA'],
  170. ['ca'],
  171. ['cs'],
  172. ['cv'],
  173. ['cy'],
  174. ['da'],
  175. ['de'],
  176. ['de_AT'],
  177. ['de_CH'],
  178. ['dv'],
  179. ['dv_MV'],
  180. ['el'],
  181. ['en'],
  182. ['en_AU'],
  183. ['en_CA'],
  184. ['en_GB'],
  185. ['en_IE'],
  186. ['en_IL'],
  187. ['en_NZ'],
  188. ['eo'],
  189. ['es'],
  190. ['es_DO'],
  191. ['es_US'],
  192. ['et'],
  193. ['eu'],
  194. ['fa'],
  195. ['fi'],
  196. ['fo'],
  197. ['fr'],
  198. ['fr_CA'],
  199. ['fr_CH'],
  200. ['fy'],
  201. ['gd'],
  202. ['gl'],
  203. ['gom_Latn'],
  204. ['gu'],
  205. ['he'],
  206. ['hi'],
  207. ['hr'],
  208. ['hu'],
  209. ['hy'],
  210. ['hy_AM'],
  211. ['id'],
  212. ['is'],
  213. ['it'],
  214. ['ja'],
  215. ['jv'],
  216. ['ka'],
  217. ['kk'],
  218. ['km'],
  219. ['kn'],
  220. ['ko'],
  221. ['ku'],
  222. ['ky'],
  223. ['lb'],
  224. ['lo'],
  225. ['lt'],
  226. ['lv'],
  227. ['me'],
  228. ['mi'],
  229. ['mk'],
  230. ['ml'],
  231. ['mn'],
  232. ['mr'],
  233. ['ms'],
  234. ['ms_MY'],
  235. ['mt'],
  236. ['my'],
  237. ['nb'],
  238. ['ne'],
  239. ['nl'],
  240. ['nl_BE'],
  241. ['nn'],
  242. ['no'],
  243. ['oc'],
  244. ['pa_IN'],
  245. ['pl'],
  246. ['ps'],
  247. ['pt'],
  248. ['pt_BR'],
  249. ['ro'],
  250. ['ru'],
  251. ['sd'],
  252. ['se'],
  253. ['sh'],
  254. ['si'],
  255. ['sk'],
  256. ['sl'],
  257. ['sq'],
  258. ['sr'],
  259. ['sr_Cyrl'],
  260. ['sr_Cyrl_ME'],
  261. ['sr_Latn_ME'],
  262. ['sr_ME'],
  263. ['ss'],
  264. ['sv'],
  265. ['sw'],
  266. ['ta'],
  267. ['te'],
  268. ['tet'],
  269. ['tg'],
  270. ['th'],
  271. ['tl_PH'],
  272. ['tlh'],
  273. ['tr'],
  274. ['tzl'],
  275. ['tzm'],
  276. ['tzm_Latn'],
  277. ['ug_CN'],
  278. ['uk'],
  279. ['ur'],
  280. ['uz'],
  281. ['uz_Latn'],
  282. ['vi'],
  283. ['yo'],
  284. ['zh'],
  285. ['zh_CN'],
  286. ['zh_HK'],
  287. ['zh_TW'],
  288. ];
  289. }
  290. /**
  291. * @dataProvider \Tests\CarbonImmutable\LocalizationTest::dataForLocales
  292. *
  293. * @param string $locale
  294. */
  295. public function testSetLocale($locale)
  296. {
  297. $this->assertTrue(Carbon::setLocale($locale));
  298. $this->assertTrue($this->areSameLocales($locale, Carbon::getLocale()));
  299. }
  300. /**
  301. * @dataProvider \Tests\CarbonImmutable\LocalizationTest::dataForLocales
  302. *
  303. * @param string $locale
  304. */
  305. public function testSetTranslator($locale)
  306. {
  307. $ori = Carbon::getTranslator();
  308. $t = new Translator($locale);
  309. $t->addLoader('array', new ArrayLoader());
  310. Carbon::setTranslator($t);
  311. /** @var Translator $t */
  312. $t = Carbon::getTranslator();
  313. $this->assertNotNull($t);
  314. $this->assertTrue($this->areSameLocales($locale, $t->getLocale()));
  315. Carbon::setTranslator($ori);
  316. }
  317. public function testSetLocaleWithKnownLocale()
  318. {
  319. $this->assertTrue(Carbon::setLocale('fr'));
  320. }
  321. /**
  322. * @see \Tests\CarbonImmutable\LocalizationTest::testSetLocaleWithMalformedLocale
  323. *
  324. * @return array
  325. */
  326. public static function dataForTestSetLocaleWithMalformedLocale()
  327. {
  328. return [
  329. ['DE'],
  330. ['pt-BR'],
  331. ['pt-br'],
  332. ['PT-br'],
  333. ['PT-BR'],
  334. ['pt_br'],
  335. ['PT_br'],
  336. ['PT_BR'],
  337. ];
  338. }
  339. /**
  340. * @dataProvider \Tests\CarbonImmutable\LocalizationTest::dataForTestSetLocaleWithMalformedLocale
  341. *
  342. * @param string $malformedLocale
  343. */
  344. public function testSetLocaleWithMalformedLocale($malformedLocale)
  345. {
  346. $this->assertTrue(Carbon::setLocale($malformedLocale));
  347. }
  348. public function testSetLocaleWithNonExistingLocale()
  349. {
  350. $this->assertFalse(Carbon::setLocale('pt-XX'));
  351. }
  352. public function testSetLocaleWithUnknownLocale()
  353. {
  354. $this->assertFalse(Carbon::setLocale('zz'));
  355. }
  356. public function testCustomTranslation()
  357. {
  358. Carbon::setLocale('en');
  359. /** @var Translator $translator */
  360. $translator = Carbon::getTranslator();
  361. /** @var MessageCatalogue $messages */
  362. $messages = $translator->getCatalogue('en');
  363. $resources = $messages->all('messages');
  364. $resources['day'] = '1 boring day|%count% boring days';
  365. $translator->addResource('array', $resources, 'en');
  366. $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
  367. ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);
  368. $this->assertSame('3 boring days 4 hours', $diff);
  369. Carbon::setLocale('en');
  370. }
  371. public function testAddCustomTranslation()
  372. {
  373. $enBoring = [
  374. 'day' => '1 boring day|%count% boring days',
  375. ];
  376. $this->assertTrue(Carbon::setLocale('en'));
  377. /** @var Translator $translator */
  378. $translator = Carbon::getTranslator();
  379. $translator->setMessages('en', $enBoring);
  380. $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
  381. ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);
  382. $this->assertSame('3 boring days 4 hours', $diff);
  383. $translator->resetMessages('en');
  384. $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
  385. ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);
  386. $this->assertSame('3 days 4 hours', $diff);
  387. $translator->setMessages('en_Boring', $enBoring);
  388. $this->assertSame($enBoring, $translator->getMessages('en_Boring'));
  389. $messages = $translator->getMessages();
  390. $this->assertArrayHasKey('en', $messages);
  391. $this->assertArrayHasKey('en_Boring', $messages);
  392. $this->assertSame($enBoring, $messages['en_Boring']);
  393. $this->assertTrue(Carbon::setLocale('en_Boring'));
  394. $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
  395. ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);
  396. // en_Boring inherit en because it starts with "en", see symfony-translation behavior
  397. $this->assertSame('3 boring days 4 hours', $diff);
  398. $translator->resetMessages();
  399. $this->assertSame([], $translator->getMessages());
  400. $this->assertTrue(Carbon::setLocale('en'));
  401. }
  402. public function testCustomWeekStart()
  403. {
  404. $this->assertTrue(Carbon::setLocale('ru'));
  405. /** @var Translator $translator */
  406. $translator = Carbon::getTranslator();
  407. $translator->setMessages('ru', [
  408. 'first_day_of_week' => 1,
  409. ]);
  410. $calendar = Carbon::parse('2018-07-07 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-07 00:00:00'));
  411. $this->assertSame('В следующий вторник, в 0:00', $calendar);
  412. $calendar = Carbon::parse('2018-07-12 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-12 00:00:00'));
  413. $this->assertSame('В воскресенье, в 0:00', $calendar);
  414. $translator->setMessages('ru', [
  415. 'first_day_of_week' => 5,
  416. ]);
  417. $calendar = Carbon::parse('2018-07-07 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-07 00:00:00'));
  418. $this->assertSame('Во вторник, в 0:00', $calendar);
  419. $calendar = Carbon::parse('2018-07-12 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-12 00:00:00'));
  420. $this->assertSame('В следующее воскресенье, в 0:00', $calendar);
  421. $translator->resetMessages('ru');
  422. $this->assertTrue(Carbon::setLocale('en'));
  423. }
  424. public function testAddAndRemoveDirectory()
  425. {
  426. $directory = sys_get_temp_dir().'/carbon'.mt_rand(0, 9999999);
  427. mkdir($directory);
  428. copy(__DIR__.'/../../src/Carbon/Lang/fr.php', "$directory/foo.php");
  429. copy(__DIR__.'/../../src/Carbon/Lang/fr.php', "$directory/bar.php");
  430. /** @var Translator $translator */
  431. $translator = Carbon::getTranslator();
  432. Carbon::setLocale('en');
  433. $this->assertFalse(Carbon::setLocale('foo'));
  434. $this->assertSame('Saturday', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));
  435. $translator->addDirectory($directory);
  436. $this->assertTrue(Carbon::setLocale('foo'));
  437. $this->assertSame('samedi', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));
  438. Carbon::setLocale('en');
  439. $translator->removeDirectory($directory);
  440. $this->assertFalse(Carbon::setLocale('bar'));
  441. $this->assertSame('Saturday', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));
  442. $this->assertTrue(Carbon::setLocale('foo'));
  443. $this->assertSame('samedi', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));
  444. $this->assertTrue(Carbon::setLocale('en'));
  445. }
  446. public function testLocaleHasShortUnits()
  447. {
  448. $withShortUnit = [
  449. 'year' => 'foo',
  450. 'y' => 'bar',
  451. ];
  452. $withShortHourOnly = [
  453. 'year' => 'foo',
  454. 'y' => 'foo',
  455. 'day' => 'foo',
  456. 'd' => 'foo',
  457. 'hour' => 'foo',
  458. 'h' => 'bar',
  459. ];
  460. $withoutShortUnit = [
  461. 'year' => 'foo',
  462. ];
  463. $withSameShortUnit = [
  464. 'year' => 'foo',
  465. 'y' => 'foo',
  466. ];
  467. $withShortHourOnlyLocale = 'zz_'.ucfirst(strtolower('withShortHourOnly'));
  468. $withShortUnitLocale = 'zz_'.ucfirst(strtolower('withShortUnit'));
  469. $withoutShortUnitLocale = 'zz_'.ucfirst(strtolower('withoutShortUnit'));
  470. $withSameShortUnitLocale = 'zz_'.ucfirst(strtolower('withSameShortUnit'));
  471. /** @var Translator $translator */
  472. $translator = Carbon::getTranslator();
  473. $translator->setMessages($withShortUnitLocale, $withShortUnit);
  474. $translator->setMessages($withShortHourOnlyLocale, $withShortHourOnly);
  475. $translator->setMessages($withoutShortUnitLocale, $withoutShortUnit);
  476. $translator->setMessages($withSameShortUnitLocale, $withSameShortUnit);
  477. $this->assertTrue(Carbon::localeHasShortUnits($withShortUnitLocale));
  478. $this->assertTrue(Carbon::localeHasShortUnits($withShortHourOnlyLocale));
  479. $this->assertFalse(Carbon::localeHasShortUnits($withoutShortUnitLocale));
  480. $this->assertFalse(Carbon::localeHasShortUnits($withSameShortUnitLocale));
  481. }
  482. public function testLocaleHasDiffSyntax()
  483. {
  484. $withDiffSyntax = [
  485. 'year' => 'foo',
  486. 'ago' => ':time ago',
  487. 'from_now' => ':time from now',
  488. 'after' => ':time after',
  489. 'before' => ':time before',
  490. ];
  491. $withoutDiffSyntax = [
  492. 'year' => 'foo',
  493. ];
  494. $withDiffSyntaxLocale = 'zz_'.ucfirst(strtolower('withDiffSyntax'));
  495. $withoutDiffSyntaxLocale = 'zz_'.ucfirst(strtolower('withoutDiffSyntax'));
  496. /** @var Translator $translator */
  497. $translator = Carbon::getTranslator();
  498. $translator->setMessages($withDiffSyntaxLocale, $withDiffSyntax);
  499. $translator->setMessages($withoutDiffSyntaxLocale, $withoutDiffSyntax);
  500. $this->assertTrue(Carbon::localeHasDiffSyntax($withDiffSyntaxLocale));
  501. $this->assertFalse(Carbon::localeHasDiffSyntax($withoutDiffSyntaxLocale));
  502. $this->assertTrue(Carbon::localeHasDiffSyntax('ka'));
  503. $this->assertFalse(Carbon::localeHasDiffSyntax('foobar'));
  504. }
  505. public function testLocaleHasDiffOneDayWords()
  506. {
  507. $withOneDayWords = [
  508. 'year' => 'foo',
  509. 'diff_now' => 'just now',
  510. 'diff_yesterday' => 'yesterday',
  511. 'diff_tomorrow' => 'tomorrow',
  512. ];
  513. $withoutOneDayWords = [
  514. 'year' => 'foo',
  515. ];
  516. $withOneDayWordsLocale = 'zz_'.ucfirst(strtolower('withOneDayWords'));
  517. $withoutOneDayWordsLocale = 'zz_'.ucfirst(strtolower('withoutOneDayWords'));
  518. /** @var Translator $translator */
  519. $translator = Carbon::getTranslator();
  520. $translator->setMessages($withOneDayWordsLocale, $withOneDayWords);
  521. $translator->setMessages($withoutOneDayWordsLocale, $withoutOneDayWords);
  522. $this->assertTrue(Carbon::localeHasDiffOneDayWords($withOneDayWordsLocale));
  523. $this->assertFalse(Carbon::localeHasDiffOneDayWords($withoutOneDayWordsLocale));
  524. }
  525. public function testLocaleHasDiffTwoDayWords()
  526. {
  527. $withTwoDayWords = [
  528. 'year' => 'foo',
  529. 'diff_before_yesterday' => 'before yesterday',
  530. 'diff_after_tomorrow' => 'after tomorrow',
  531. ];
  532. $withoutTwoDayWords = [
  533. 'year' => 'foo',
  534. ];
  535. $withTwoDayWordsLocale = 'zz_'.ucfirst(strtolower('withTwoDayWords'));
  536. $withoutTwoDayWordsLocale = 'zz_'.ucfirst(strtolower('withoutTwoDayWords'));
  537. /** @var Translator $translator */
  538. $translator = Carbon::getTranslator();
  539. $translator->setMessages($withTwoDayWordsLocale, $withTwoDayWords);
  540. $translator->setMessages($withoutTwoDayWordsLocale, $withoutTwoDayWords);
  541. $this->assertTrue(Carbon::localeHasDiffTwoDayWords($withTwoDayWordsLocale));
  542. $this->assertFalse(Carbon::localeHasDiffTwoDayWords($withoutTwoDayWordsLocale));
  543. }
  544. public function testLocaleHasPeriodSyntax()
  545. {
  546. $withPeriodSyntax = [
  547. 'year' => 'foo',
  548. 'period_recurrences' => 'once|%count% times',
  549. 'period_interval' => 'every :interval',
  550. 'period_start_date' => 'from :date',
  551. 'period_end_date' => 'to :date',
  552. ];
  553. $withoutPeriodSyntax = [
  554. 'year' => 'foo',
  555. ];
  556. $withPeriodSyntaxLocale = 'zz_'.ucfirst(strtolower('withPeriodSyntax'));
  557. $withoutPeriodSyntaxLocale = 'zz_'.ucfirst(strtolower('withoutPeriodSyntax'));
  558. /** @var Translator $translator */
  559. $translator = Carbon::getTranslator();
  560. $translator->setMessages($withPeriodSyntaxLocale, $withPeriodSyntax);
  561. $translator->setMessages($withoutPeriodSyntaxLocale, $withoutPeriodSyntax);
  562. $this->assertTrue(Carbon::localeHasPeriodSyntax($withPeriodSyntaxLocale));
  563. $this->assertFalse(Carbon::localeHasPeriodSyntax($withoutPeriodSyntaxLocale));
  564. $this->assertTrue(Carbon::localeHasPeriodSyntax('nl'));
  565. }
  566. public function testGetAvailableLocales()
  567. {
  568. $this->assertCount(\count(glob(__DIR__.'/../../src/Carbon/Lang/*.php')), Carbon::getAvailableLocales());
  569. /** @var Translator $translator */
  570. $translator = Carbon::getTranslator();
  571. $translator->setMessages('zz_ZZ', []);
  572. $this->assertContains('zz_ZZ', Carbon::getAvailableLocales());
  573. Carbon::setTranslator(new SymfonyTranslator('en'));
  574. $this->assertSame(['en'], Carbon::getAvailableLocales());
  575. }
  576. public function testNotLocaleAwareException()
  577. {
  578. if (method_exists(TranslatorInterface::class, 'getLocale')) {
  579. $this->markTestSkipped('In Symfony < 5, NotLocaleAwareException will never been thrown.');
  580. }
  581. $translator = new class() implements TranslatorInterface {
  582. public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null)
  583. {
  584. return 'x';
  585. }
  586. };
  587. Carbon::setTranslator($translator);
  588. $this->expectExceptionObject(new NotLocaleAwareException($translator));
  589. Carbon::now()->locale();
  590. }
  591. public function testGetAvailableLocalesInfo()
  592. {
  593. $infos = Carbon::getAvailableLocalesInfo();
  594. $this->assertCount(\count(Carbon::getAvailableLocales()), Carbon::getAvailableLocalesInfo());
  595. $this->assertArrayHasKey('en', $infos);
  596. $this->assertInstanceOf(Language::class, $infos['en']);
  597. $this->assertSame('English', $infos['en']->getIsoName());
  598. }
  599. public function testGeorgianSpecialFromNowTranslation()
  600. {
  601. $diff = Carbon::now()->locale('ka')->addWeeks(3)->diffForHumans();
  602. $this->assertSame('3 კვირაში', $diff);
  603. }
  604. public function testWeekDayMultipleForms()
  605. {
  606. $date = Carbon::parse('2018-10-10')->locale('ru');
  607. $this->assertSame('в среду', $date->isoFormat('[в] dddd'));
  608. $this->assertSame('среда, 10 октября 2018', $date->isoFormat('dddd, D MMMM YYYY'));
  609. $this->assertSame('среда', $date->dayName);
  610. $this->assertSame('ср', $date->isoFormat('dd'));
  611. $date = Carbon::parse('2018-10-10')->locale('uk');
  612. $this->assertSame('середа, 10', $date->isoFormat('dddd, D'));
  613. $this->assertSame('в середу', $date->isoFormat('[в] dddd'));
  614. $this->assertSame('минулої середи', $date->isoFormat('[минулої] dddd'));
  615. }
  616. public function testTranslationCustomWithCustomTranslator()
  617. {
  618. $this->expectExceptionObject(new InvalidArgumentException(
  619. 'Translator does not implement Symfony\Component\Translation\TranslatorInterface '.
  620. 'and Symfony\Component\Translation\TranslatorBagInterface. '.
  621. 'Symfony\Component\Translation\IdentityTranslator has been given.'
  622. ));
  623. $date = Carbon::create(2018, 1, 1, 0, 0, 0);
  624. $date->setLocalTranslator(
  625. class_exists(MessageSelector::class)
  626. ? new IdentityTranslator(new MessageSelector())
  627. : new IdentityTranslator()
  628. );
  629. $date->getTranslationMessage('foo');
  630. }
  631. public function testTranslateTimeStringTo()
  632. {
  633. $date = Carbon::parse('2019-07-05')->locale('de');
  634. $baseString = $date->isoFormat('LLLL');
  635. $this->assertSame('Freitag, 5. Juli 2019 00:00', $baseString);
  636. $this->assertSame('Friday, 5. July 2019 00:00', $date->translateTimeStringTo($baseString));
  637. $this->assertSame('vendredi, 5. juillet 2019 00:00', $date->translateTimeStringTo($baseString, 'fr'));
  638. }
  639. public function testFallbackLocales()
  640. {
  641. // /!\ Used for backward compatibility, please avoid this method
  642. // @see testMultiLocales() as preferred method
  643. $myDialect = 'xx_MY_Dialect';
  644. $secondChoice = 'xy_MY_Dialect';
  645. $thirdChoice = 'it_CH';
  646. /** @var Translator $translator */
  647. $translator = Carbon::getTranslator();
  648. $translator->setMessages($myDialect, [
  649. 'day' => ':count yub yub',
  650. ]);
  651. $translator->setMessages($secondChoice, [
  652. 'day' => ':count buza',
  653. 'hour' => ':count ohto',
  654. ]);
  655. Carbon::setLocale($myDialect);
  656. Carbon::setFallbackLocale($thirdChoice);
  657. $this->assertSame($thirdChoice, Carbon::getFallbackLocale());
  658. $this->assertSame('3 yub yub e 5 ora fa', Carbon::now()->subDays(3)->subHours(5)->ago([
  659. 'parts' => 2,
  660. 'join' => true,
  661. ]));
  662. Carbon::setTranslator(new Translator('en'));
  663. /** @var Translator $translator */
  664. $translator = Carbon::getTranslator();
  665. $translator->setMessages($myDialect, [
  666. 'day' => ':count yub yub',
  667. ]);
  668. $translator->setMessages($secondChoice, [
  669. 'day' => ':count buza',
  670. 'hour' => ':count ohto',
  671. ]);
  672. Carbon::setLocale($myDialect);
  673. Carbon::setFallbackLocale($secondChoice);
  674. Carbon::setFallbackLocale($thirdChoice);
  675. $this->assertSame($thirdChoice, Carbon::getFallbackLocale());
  676. $this->assertSame('3 yub yub e 5 ohto fa', Carbon::now()->subDays(3)->subHours(5)->ago([
  677. 'parts' => 2,
  678. 'join' => true,
  679. ]));
  680. Carbon::setTranslator(new IdentityTranslator());
  681. $this->assertNull(Carbon::getFallbackLocale());
  682. Carbon::setTranslator(new Translator('en'));
  683. }
  684. public function testMultiLocales()
  685. {
  686. $myDialect = 'xx_MY_Dialect';
  687. $secondChoice = 'xy_MY_Dialect';
  688. $thirdChoice = 'it_CH';
  689. Translator::get($myDialect)->setTranslations([
  690. 'day' => ':count yub yub',
  691. ]);
  692. Translator::get($secondChoice)->setTranslations([
  693. 'day' => ':count buza',
  694. 'hour' => ':count ohto',
  695. ]);
  696. $date = Carbon::now()->subDays(3)->subHours(5)->locale($myDialect, $secondChoice, $thirdChoice);
  697. $this->assertSame('3 yub yub e 5 ohto fa', $date->ago([
  698. 'parts' => 2,
  699. 'join' => true,
  700. ]));
  701. }
  702. public function testStandAloneMonthsInLLLFormat()
  703. {
  704. $this->assertSame(
  705. '29 февраля 2020 г., 12:24',
  706. Carbon::parse('2020-02-29 12:24:00')->locale('ru_RU')->isoFormat('LLL')
  707. );
  708. }
  709. public function testAgoDeclension()
  710. {
  711. $this->assertSame(
  712. 'година',
  713. CarbonInterval::hour()->locale('uk')->forHumans(['aUnit' => true])
  714. );
  715. $this->assertSame(
  716. 'годину тому',
  717. Carbon::now()->subHour()->locale('uk')->diffForHumans(['aUnit' => true])
  718. );
  719. }
  720. public function testPolishDeclensions()
  721. {
  722. $hour = Carbon::now()->addHour()->locale('pl');
  723. $minute = Carbon::now()->addMinute()->locale('pl');
  724. $second = Carbon::now()->addSecond()->locale('pl');
  725. $this->assertSame('za 1 godzinę', $hour->diffForHumans());
  726. $this->assertSame('za 1 minutę', $minute->diffForHumans());
  727. $this->assertSame('za 1 sekundę', $second->diffForHumans());
  728. $this->assertSame('za godzinę', $hour->diffForHumans(['aUnit' => true]));
  729. $this->assertSame('za minutę', $minute->diffForHumans(['aUnit' => true]));
  730. $this->assertSame('za sekundę', $second->translate('from_now', ['time' => 'sekunda']));
  731. }
  732. public function testDeclensionModes()
  733. {
  734. Carbon::setTestNow('2022-12-30');
  735. $this->assertSame(
  736. '2 жил 3 сар 1 өдөр 1с өмнө',
  737. Carbon::now()
  738. ->subYears(2)
  739. ->subMonths(3)
  740. ->subDay()
  741. ->subSecond()
  742. ->locale('mn')
  743. ->diffForHumans(null, null, true, 4)
  744. );
  745. $this->assertSame(
  746. '2 жил 3 сар 1 өдөр 1 секундын өмнө',
  747. Carbon::now()
  748. ->subYears(2)
  749. ->subMonths(3)
  750. ->subDay()
  751. ->subSecond()
  752. ->locale('mn')
  753. ->diffForHumans(null, null, false, 4)
  754. );
  755. }
  756. }