LocalizationTest.php 30 KB

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