<?php

declare(strict_types=1);

/**
 * This file is part of the Carbon package.
 *
 * (c) Brian Nesbitt <brian@nesbot.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tests\Carbon;

use Carbon\Carbon;
use Carbon\CarbonInterface;
use Carbon\CarbonInterval;
use Carbon\Language;
use Carbon\Translator;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\TestWith;
use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Translator as SymfonyTranslator;
use Tests\AbstractTestCase;
use Tests\Carbon\Fixtures\MyCarbon;
use Tests\Carbon\Fixtures\NoLocaleTranslator;

#[Group('localization')]
class LocalizationTest extends AbstractTestCase
{
    public function testGetTranslator()
    {
        /** @var Translator $t */
        $t = Carbon::getTranslator();
        $this->assertNotNull($t);
        $this->assertSame('en', $t->getLocale());
    }

    public function testResetTranslator()
    {
        /** @var Translator $t */
        $t = MyCarbon::getTranslator();
        $this->assertNotNull($t);
        $this->assertSame('en', $t->getLocale());
    }

    public function testSetLocaleToAuto()
    {
        $currentLocale = setlocale(LC_ALL, '0');
        if (setlocale(LC_ALL, 'fr_FR.UTF-8', 'fr_FR.utf8', 'fr_FR', 'fr') === false) {
            $this->markTestSkipped('testSetLocaleToAuto test need fr_FR.UTF-8.');
        }
        Carbon::setLocale('auto');
        $locale = Carbon::getLocale();
        $diff = Carbon::now()->subSeconds(2)->diffForHumans();
        setlocale(LC_ALL, $currentLocale);

        $this->assertSame('fr', $locale === 'fr_FR' ? 'fr' : $locale);
        $this->assertSame('il y a 2 secondes', $diff);

        if (setlocale(LC_ALL, 'ar_AE.UTF-8', 'ar_AE.utf8', 'ar_AE', 'ar') === false) {
            $this->markTestSkipped('testSetLocaleToAuto test need ar_AE.UTF-8.');
        }
        rename(__DIR__.'/../../src/Carbon/Lang/ar_AE.php', __DIR__.'/../../src/Carbon/Lang/disabled_ar_AE.php');
        Carbon::setLocale('auto');
        $locale = Carbon::getLocale();
        $diff = Carbon::now()->subSeconds(2)->diffForHumans();
        setlocale(LC_ALL, $currentLocale);
        rename(__DIR__.'/../../src/Carbon/Lang/disabled_ar_AE.php', __DIR__.'/../../src/Carbon/Lang/ar_AE.php');

        $this->assertStringStartsWith('ar', $locale);
        $this->assertSame('منذ ثانيتين', $diff);

        if (setlocale(LC_ALL, 'sr_ME.UTF-8', 'sr_ME.utf8', 'sr_ME', 'sr') === false) {
            $this->markTestSkipped('testSetLocaleToAuto test need sr_ME.UTF-8.');
        }
        Carbon::setLocale('auto');
        $locale = Carbon::getLocale();
        $diff = Carbon::now()->subSeconds(2)->diffForHumans();
        setlocale(LC_ALL, $currentLocale);

        $this->assertStringStartsWith('sr', $locale);
        $this->assertSame('pre 2 sekunde', str_replace('prije', 'pre', $diff));

        if (setlocale(LC_ALL, 'zh_TW.UTF-8', 'zh_TW.utf8', 'zh_TW', 'zh') === false) {
            $this->markTestSkipped('testSetLocaleToAuto test need zh_TW.UTF-8.');
        }
        Carbon::setLocale('auto');
        $locale = Carbon::getLocale();
        $diff = Carbon::now()->subSeconds(2)->diffForHumans();
        setlocale(LC_ALL, $currentLocale);

        $this->assertStringStartsWith('zh', $locale);
        $this->assertSame('2秒前', $diff);

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->resetMessages();
        $translator->setLocale('en');
        $directories = $translator->getDirectories();
        $directory = sys_get_temp_dir().'/carbon'.mt_rand(0, 9999999);
        mkdir($directory);
        $translator->setDirectories([$directory]);

        $files = [
            'zh_Hans',
            'zh',
            'fr',
            'fr_CA',
        ];

        foreach ($files as $file) {
            copy(__DIR__."/../../src/Carbon/Lang/$file.php", "$directory/$file.php");
        }

        $currentLocale = setlocale(LC_ALL, '0');
        if (setlocale(LC_ALL, 'fr_FR.UTF-8', 'fr_FR.utf8', 'fr_FR', 'fr') === false) {
            $this->markTestSkipped('testSetLocaleToAuto test need fr_FR.UTF-8.');
        }
        Carbon::setLocale('auto');
        $locale = Carbon::getLocale();
        $diff = Carbon::now()->subSeconds(2)->diffForHumans();
        setlocale(LC_ALL, $currentLocale);

        $this->assertSame('fr', $locale);
        $this->assertSame('il y a 2 secondes', $diff);

        if (setlocale(LC_ALL, 'zh_CN.UTF-8', 'zh_CN.utf8', 'zh_CN', 'zh') === false) {
            $this->markTestSkipped('testSetLocaleToAuto test need zh_CN.UTF-8.');
        }
        Carbon::setLocale('auto');
        $locale = Carbon::getLocale();
        $diff = Carbon::now()->subSeconds(2)->diffForHumans();
        setlocale(LC_ALL, $currentLocale);

        $this->assertSame('zh', $locale);
        $this->assertSame('2秒前', $diff);

        if (setlocale(LC_ALL, 'yo_NG.UTF-8', 'yo_NG.utf8', 'yo_NG', 'yo') === false) {
            $this->markTestSkipped('testSetLocaleToAuto test need yo_NG.UTF-8.');
        }
        Carbon::setLocale('en');
        Carbon::setLocale('auto');
        $locale = Carbon::getLocale();
        $diff = Carbon::now()->subSeconds(2)->diffForHumans();
        setlocale(LC_ALL, $currentLocale);

        $this->assertSame('en', $locale);
        $this->assertSame('2 seconds ago', $diff);

        $translator->setDirectories($directories);

        foreach ($files as $file) {
            unlink("$directory/$file.php");
        }

        rmdir($directory);
    }

    /**
     * @see \Tests\Carbon\LocalizationTest::testSetLocale
     * @see \Tests\Carbon\LocalizationTest::testSetTranslator
     */
    public static function dataForLocales(): array
    {
        return [
            'af' => ['af'],
            'ar' => ['ar'],
            'ar_DZ' => ['ar_DZ'],
            'ar_KW' => ['ar_KW'],
            'ar_LY' => ['ar_LY'],
            'ar_MA' => ['ar_MA'],
            'ar_SA' => ['ar_SA'],
            'ar_Shakl' => ['ar_Shakl'],
            'ar_TN' => ['ar_TN'],
            'az' => ['az'],
            'be' => ['be'],
            'bg' => ['bg'],
            'bm' => ['bm'],
            'bn' => ['bn'],
            'bo' => ['bo'],
            'br' => ['br'],
            'bs' => ['bs'],
            'bs_BA' => ['bs_BA'],
            'ca' => ['ca'],
            'cs' => ['cs'],
            'cv' => ['cv'],
            'cy' => ['cy'],
            'da' => ['da'],
            'de' => ['de'],
            'de_AT' => ['de_AT'],
            'de_CH' => ['de_CH'],
            'dv' => ['dv'],
            'dv_MV' => ['dv_MV'],
            'el' => ['el'],
            'en' => ['en'],
            'en_AU' => ['en_AU'],
            'en_CA' => ['en_CA'],
            'en_GB' => ['en_GB'],
            'en_IE' => ['en_IE'],
            'en_IL' => ['en_IL'],
            'en_NZ' => ['en_NZ'],
            'eo' => ['eo'],
            'es' => ['es'],
            'es_DO' => ['es_DO'],
            'es_US' => ['es_US'],
            'et' => ['et'],
            'eu' => ['eu'],
            'fa' => ['fa'],
            'fi' => ['fi'],
            'fo' => ['fo'],
            'fr' => ['fr'],
            'fr_CA' => ['fr_CA'],
            'fr_CH' => ['fr_CH'],
            'fy' => ['fy'],
            'gd' => ['gd'],
            'gl' => ['gl'],
            'gom_Latn' => ['gom_Latn'],
            'gu' => ['gu'],
            'he' => ['he'],
            'hi' => ['hi'],
            'hr' => ['hr'],
            'hu' => ['hu'],
            'hy' => ['hy'],
            'hy_AM' => ['hy_AM'],
            'id' => ['id'],
            'is' => ['is'],
            'it' => ['it'],
            'ja' => ['ja'],
            'jv' => ['jv'],
            'ka' => ['ka'],
            'kk' => ['kk'],
            'km' => ['km'],
            'kn' => ['kn'],
            'ko' => ['ko'],
            'ku' => ['ku'],
            'ky' => ['ky'],
            'lb' => ['lb'],
            'lo' => ['lo'],
            'lt' => ['lt'],
            'lv' => ['lv'],
            'me' => ['me'],
            'mi' => ['mi'],
            'mk' => ['mk'],
            'ml' => ['ml'],
            'mn' => ['mn'],
            'mr' => ['mr'],
            'ms' => ['ms'],
            'ms_MY' => ['ms_MY'],
            'mt' => ['mt'],
            'my' => ['my'],
            'nb' => ['nb'],
            'ne' => ['ne'],
            'nl' => ['nl'],
            'nl_BE' => ['nl_BE'],
            'nn' => ['nn'],
            'no' => ['no'],
            'oc' => ['oc'],
            'pa_IN' => ['pa_IN'],
            'pl' => ['pl'],
            'ps' => ['ps'],
            'pt' => ['pt'],
            'pt_BR' => ['pt_BR'],
            'ro' => ['ro'],
            'ru' => ['ru'],
            'sd' => ['sd'],
            'se' => ['se'],
            'sh' => ['sh'],
            'si' => ['si'],
            'sk' => ['sk'],
            'sl' => ['sl'],
            'sq' => ['sq'],
            'sr' => ['sr'],
            'sr_Cyrl' => ['sr_Cyrl'],
            'sr_Cyrl_ME' => ['sr_Cyrl_ME'],
            'sr_Latn_ME' => ['sr_Latn_ME'],
            'sr_ME' => ['sr_ME'],
            'ss' => ['ss'],
            'sv' => ['sv'],
            'sw' => ['sw'],
            'ta' => ['ta'],
            'te' => ['te'],
            'tet' => ['tet'],
            'tg' => ['tg'],
            'th' => ['th'],
            'tl_PH' => ['tl_PH'],
            'tlh' => ['tlh'],
            'tr' => ['tr'],
            'tzl' => ['tzl'],
            'tzm' => ['tzm'],
            'tzm_Latn' => ['tzm_Latn'],
            'ug_CN' => ['ug_CN'],
            'uk' => ['uk'],
            'ur' => ['ur'],
            'uz' => ['uz'],
            'uz_Latn' => ['uz_Latn'],
            'vi' => ['vi'],
            'yo' => ['yo'],
            'zh' => ['zh'],
            'zh_CN' => ['zh_CN'],
            'zh_HK' => ['zh_HK'],
            'zh_TW' => ['zh_TW'],
        ];
    }

    #[DataProvider('dataForLocales')]
    public function testSetLocale(string $locale)
    {
        Carbon::setLocale($locale);
        $this->assertTrue($this->areSameLocales($locale, Carbon::getLocale()));
    }

    #[DataProvider('dataForLocales')]
    public function testSetTranslator(string $locale)
    {
        $ori = Carbon::getTranslator();
        $t = new Translator($locale);
        $t->addLoader('array', new ArrayLoader());
        Carbon::setTranslator($t);

        /** @var Translator $t */
        $t = Carbon::getTranslator();
        $this->assertNotNull($t);
        $this->assertTrue($this->areSameLocales($locale, $t->getLocale()));
        $this->assertTrue($this->areSameLocales($locale, Carbon::now()->locale()));
        Carbon::setTranslator($ori);
    }

    public function testSetLocaleWithKnownLocale()
    {
        Carbon::setLocale('fr');

        $this->assertSame('fr', Carbon::getLocale());
    }

    #[TestWith(['DE'])]
    #[TestWith(['pt-BR'])]
    #[TestWith(['pt-br'])]
    #[TestWith(['PT-br'])]
    #[TestWith(['PT-BR'])]
    #[TestWith(['pt_br'])]
    #[TestWith(['PT_br'])]
    #[TestWith(['PT_BR'])]
    public function testSetLocaleWithMalformedLocale(string $malformedLocale)
    {
        Carbon::setLocale($malformedLocale);
        $split = preg_split('/[-_]/', $malformedLocale);

        $this->assertSame(
            strtolower($split[0]).(\count($split) === 1 ? '' : '_'.strtoupper($split[1])),
            Carbon::getLocale(),
        );
    }

    public function testSetLocaleWithNonExistingLocale()
    {
        Carbon::setLocale('pt-XX');

        $this->assertSame('pt', Carbon::getLocale());
    }

    public function testSetLocaleWithUnknownLocale()
    {
        Carbon::setLocale('zz');

        $this->assertSame('en', Carbon::getLocale());
    }

    public function testCustomTranslation()
    {
        Carbon::setLocale('en');
        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        /** @var MessageCatalogue $messages */
        $messages = $translator->getCatalogue('en');
        $resources = $messages->all('messages');
        $resources['day'] = '1 boring day|%count% boring days';
        $translator->addResource('array', $resources, 'en');

        $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
            ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);

        $this->assertSame('3 boring days 4 hours', $diff);

        Carbon::setLocale('en');
    }

    public function testCustomLocalTranslation()
    {
        $boringLanguage = 'en_Overboring';
        $translator = Translator::get($boringLanguage);
        $translator->setTranslations([
            'day' => ':count boring day|:count boring days',
        ]);

        $date1 = Carbon::create(2018, 1, 1, 0, 0, 0);
        $date2 = Carbon::create(2018, 1, 4, 4, 0, 0);

        $this->assertSame('3 boring days before', $date1->locale($boringLanguage)->diffForHumans($date2));

        $translator->setTranslations([
            'before' => function ($time) {
                return '['.strtoupper($time).']';
            },
        ]);

        $this->assertSame('[3 BORING DAYS]', $date1->locale($boringLanguage)->diffForHumans($date2));

        $meridiem = Translator::get('ru')->trans('meridiem', [
            'hours' => 9,
            'minutes' => 30,
            'seconds' => 0,
        ]);

        $this->assertSame('утра', $meridiem);
    }

    public function testAddCustomTranslation()
    {
        $enBoring = [
            'day' => '1 boring day|%count% boring days',
        ];

        Carbon::setLocale('en');
        $this->assertSame('en', Carbon::getLocale());
        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->setMessages('en', $enBoring);

        $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
            ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);

        $this->assertSame('3 boring days 4 hours', $diff);

        $translator->resetMessages('en');

        $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
            ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);

        $this->assertSame('3 days 4 hours', $diff);

        $translator->setMessages('en_Boring', $enBoring);

        $this->assertSame($enBoring, $translator->getMessages('en_Boring'));

        $messages = $translator->getMessages();

        $this->assertArrayHasKey('en', $messages);
        $this->assertArrayHasKey('en_Boring', $messages);
        $this->assertSame($enBoring, $messages['en_Boring']);

        Carbon::setLocale('en_Boring');
        $this->assertSame('en_Boring', Carbon::getLocale());

        $diff = Carbon::create(2018, 1, 1, 0, 0, 0)
            ->diffForHumans(Carbon::create(2018, 1, 4, 4, 0, 0), true, false, 2);

        // en_Boring inherit en because it starts with "en", see symfony-translation behavior
        $this->assertSame('3 boring days 4 hours', $diff);

        Carbon::setLocale('en');

        $diff = Carbon::parse('2018-01-01')
            ->diffForHumans('2018-01-04 04:00', [
                'syntax' => CarbonInterface::DIFF_ABSOLUTE,
                'parts' => 2,
                'locale' => 'de',
            ]);

        $this->assertSame('3 Tage 4 Stunden', $diff);

        $translator->resetMessages();

        $diff = Carbon::parse('2018-01-01')
            ->diffForHumans('2018-01-04 04:00', [
                'syntax' => CarbonInterface::DIFF_ABSOLUTE,
                'parts' => 2,
                'locale' => 'de',
            ]);

        $this->assertSame('3 Tage 4 Stunden', $diff);

        $this->assertSame([], $translator->getMessages());

        $this->assertSame('en', Carbon::getLocale());
    }

    public function testLocaleOption()
    {
        $translator = Translator::get('en_Boring');
        $translator->setTranslations([
            'day' => ':count boring day|:count boring days',
        ]);

        $diff = Carbon::parse('2018-01-01')
            ->diffForHumans('2018-01-04 04:00', [
                'syntax' => CarbonInterface::DIFF_ABSOLUTE,
                'parts' => 2,
                'locale' => 'en_Boring',
            ]);

        $translator->setLocale('en');
        $translator->resetMessages();

        $this->assertSame('3 boring days 4 hours', $diff);
    }

    public function testCustomWeekStart()
    {
        Carbon::setLocale('ru');

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();

        $translator->setMessages('ru', [
            'first_day_of_week' => 1,
        ]);

        $calendar = Carbon::parse('2018-07-07 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-07 00:00:00'));
        $this->assertSame('В следующий вторник, в 0:00', $calendar);
        $calendar = Carbon::parse('2018-07-12 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-12 00:00:00'));
        $this->assertSame('В воскресенье, в 0:00', $calendar);

        $translator->setMessages('ru', [
            'first_day_of_week' => 5,
        ]);

        $calendar = Carbon::parse('2018-07-07 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-07 00:00:00'));
        $this->assertSame('Во вторник, в 0:00', $calendar);
        $calendar = Carbon::parse('2018-07-12 00:00:00')->addDays(3)->calendar(Carbon::parse('2018-07-12 00:00:00'));
        $this->assertSame('В следующее воскресенье, в 0:00', $calendar);

        $translator->resetMessages('ru');

        Carbon::setLocale('en');
    }

    public function testAddAndRemoveDirectory()
    {
        $directory = sys_get_temp_dir().'/carbon'.mt_rand(0, 9999999);
        mkdir($directory);
        copy(__DIR__.'/../../src/Carbon/Lang/fr.php', "$directory/foo.php");
        copy(__DIR__.'/../../src/Carbon/Lang/fr.php', "$directory/bar.php");

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        Carbon::setLocale('en');

        Carbon::setLocale('foo');
        $this->assertSame('Saturday', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));

        $translator->addDirectory($directory);

        Carbon::setLocale('foo');
        $this->assertSame('samedi', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));

        Carbon::setLocale('en');
        $translator->removeDirectory($directory);

        Carbon::setLocale('bar');
        $this->assertSame('Saturday', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));

        Carbon::setLocale('foo');
        $this->assertSame('samedi', Carbon::parse('2018-07-07 00:00:00')->isoFormat('dddd'));

        Carbon::setLocale('en');
    }

    public function testLocaleHasShortUnits()
    {
        $withShortUnit = [
            'year' => 'foo',
            'y' => 'bar',
        ];
        $withShortHourOnly = [
            'year' => 'foo',
            'y' => 'foo',
            'day' => 'foo',
            'd' => 'foo',
            'hour' => 'foo',
            'h' => 'bar',
        ];
        $withoutShortUnit = [
            'year' => 'foo',
        ];
        $withSameShortUnit = [
            'year' => 'foo',
            'y' => 'foo',
        ];
        $withShortHourOnlyLocale = 'zz_'.ucfirst(strtolower('withShortHourOnly'));
        $withShortUnitLocale = 'zz_'.ucfirst(strtolower('withShortUnit'));
        $withoutShortUnitLocale = 'zz_'.ucfirst(strtolower('withoutShortUnit'));
        $withSameShortUnitLocale = 'zz_'.ucfirst(strtolower('withSameShortUnit'));

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->setMessages($withShortUnitLocale, $withShortUnit);
        $translator->setMessages($withShortHourOnlyLocale, $withShortHourOnly);
        $translator->setMessages($withoutShortUnitLocale, $withoutShortUnit);
        $translator->setMessages($withSameShortUnitLocale, $withSameShortUnit);

        $this->assertTrue(Carbon::localeHasShortUnits($withShortUnitLocale));
        $this->assertTrue(Carbon::localeHasShortUnits($withShortHourOnlyLocale));
        $this->assertFalse(Carbon::localeHasShortUnits($withoutShortUnitLocale));
        $this->assertFalse(Carbon::localeHasShortUnits($withSameShortUnitLocale));
    }

    public function testLocaleHasDiffSyntax()
    {
        $withDiffSyntax = [
            'year' => 'foo',
            'ago' => ':time ago',
            'from_now' => ':time from now',
            'after' => ':time after',
            'before' => ':time before',
        ];
        $withoutDiffSyntax = [
            'year' => 'foo',
        ];
        $withDiffSyntaxLocale = 'zz_'.ucfirst(strtolower('withDiffSyntax'));
        $withoutDiffSyntaxLocale = 'zz_'.ucfirst(strtolower('withoutDiffSyntax'));

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->setMessages($withDiffSyntaxLocale, $withDiffSyntax);
        $translator->setMessages($withoutDiffSyntaxLocale, $withoutDiffSyntax);

        $this->assertTrue(Carbon::localeHasDiffSyntax($withDiffSyntaxLocale));
        $this->assertFalse(Carbon::localeHasDiffSyntax($withoutDiffSyntaxLocale));

        $this->assertTrue(Carbon::localeHasDiffSyntax('ka'));
        $this->assertFalse(Carbon::localeHasDiffSyntax('foobar'));
    }

    public function testLocaleHasDiffOneDayWords()
    {
        $withOneDayWords = [
            'year' => 'foo',
            'diff_now' => 'just now',
            'diff_yesterday' => 'yesterday',
            'diff_tomorrow' => 'tomorrow',
        ];
        $withoutOneDayWords = [
            'year' => 'foo',
        ];
        $withOneDayWordsLocale = 'zz_'.ucfirst(strtolower('withOneDayWords'));
        $withoutOneDayWordsLocale = 'zz_'.ucfirst(strtolower('withoutOneDayWords'));

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->setMessages($withOneDayWordsLocale, $withOneDayWords);
        $translator->setMessages($withoutOneDayWordsLocale, $withoutOneDayWords);

        $this->assertTrue(Carbon::localeHasDiffOneDayWords($withOneDayWordsLocale));
        $this->assertFalse(Carbon::localeHasDiffOneDayWords($withoutOneDayWordsLocale));
    }

    public function testLocaleHasDiffTwoDayWords()
    {
        $withTwoDayWords = [
            'year' => 'foo',
            'diff_before_yesterday' => 'before yesterday',
            'diff_after_tomorrow' => 'after tomorrow',
        ];
        $withoutTwoDayWords = [
            'year' => 'foo',
        ];
        $withTwoDayWordsLocale = 'zz_'.ucfirst(strtolower('withTwoDayWords'));
        $withoutTwoDayWordsLocale = 'zz_'.ucfirst(strtolower('withoutTwoDayWords'));

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->setMessages($withTwoDayWordsLocale, $withTwoDayWords);
        $translator->setMessages($withoutTwoDayWordsLocale, $withoutTwoDayWords);

        $this->assertTrue(Carbon::localeHasDiffTwoDayWords($withTwoDayWordsLocale));
        $this->assertFalse(Carbon::localeHasDiffTwoDayWords($withoutTwoDayWordsLocale));
    }

    public function testLocaleHasPeriodSyntax()
    {
        $withPeriodSyntax = [
            'year' => 'foo',
            'period_recurrences' => 'once|%count% times',
            'period_interval' => 'every :interval',
            'period_start_date' => 'from :date',
            'period_end_date' => 'to :date',
        ];
        $withoutPeriodSyntax = [
            'year' => 'foo',
        ];
        $withPeriodSyntaxLocale = 'zz_'.ucfirst(strtolower('withPeriodSyntax'));
        $withoutPeriodSyntaxLocale = 'zz_'.ucfirst(strtolower('withoutPeriodSyntax'));

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->setMessages($withPeriodSyntaxLocale, $withPeriodSyntax);
        $translator->setMessages($withoutPeriodSyntaxLocale, $withoutPeriodSyntax);

        $this->assertTrue(Carbon::localeHasPeriodSyntax($withPeriodSyntaxLocale));
        $this->assertFalse(Carbon::localeHasPeriodSyntax($withoutPeriodSyntaxLocale));

        $this->assertTrue(Carbon::localeHasPeriodSyntax('nl'));
    }

    public function testGetAvailableLocales()
    {
        $this->assertCount(\count(glob(__DIR__.'/../../src/Carbon/Lang/*.php')), Carbon::getAvailableLocales());

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();
        $translator->setMessages('zz_ZZ', []);
        $this->assertContains('zz_ZZ', Carbon::getAvailableLocales());

        Carbon::setTranslator(new SymfonyTranslator('en'));
        $this->assertSame(['en'], Carbon::getAvailableLocales());
    }

    public function testGetAvailableLocalesInfo()
    {
        $infos = Carbon::getAvailableLocalesInfo();
        $this->assertCount(\count(Carbon::getAvailableLocales()), Carbon::getAvailableLocalesInfo());
        $this->assertArrayHasKey('en', $infos);
        $this->assertInstanceOf(Language::class, $infos['en']);
        $this->assertSame('English', $infos['en']->getIsoName());
    }

    public function testGeorgianSpecialFromNowTranslation()
    {
        $diff = Carbon::now()->locale('ka')->addWeeks(3)->diffForHumans();

        $this->assertSame('3 კვირაში', $diff);
    }

    public function testSinhaleseSpecialAfterTranslation()
    {
        $diff = Carbon::now()->locale('si')->addDays(3)->diffForHumans(Carbon::now());

        $this->assertSame('දින 3 න්', $diff);
    }

    public function testWeekDayMultipleForms()
    {
        $date = Carbon::parse('2018-10-10')->locale('ru');

        $this->assertSame('в среду', $date->isoFormat('[в] dddd'));
        $this->assertSame('среда, 10 октября 2018', $date->isoFormat('dddd, D MMMM YYYY'));
        $this->assertSame('среда', $date->dayName);
        $this->assertSame('ср', $date->isoFormat('dd'));

        $date = Carbon::parse('2018-10-10')->locale('uk');

        $this->assertSame('середа, 10', $date->isoFormat('dddd, D'));
        $this->assertSame('в середу', $date->isoFormat('[в] dddd'));
        $this->assertSame('минулої середи', $date->isoFormat('[минулої] dddd'));
    }

    public function testTranslationCustomWithCustomTranslator()
    {
        $this->expectExceptionObject(new InvalidArgumentException(
            'Translator does not implement Symfony\Contracts\Translation\TranslatorInterface '.
            'and Symfony\Component\Translation\TranslatorBagInterface. '.
            'Symfony\Component\Translation\IdentityTranslator has been given.',
        ));

        $date = Carbon::create(2018, 1, 1, 0, 0, 0);
        $date->setLocalTranslator(new IdentityTranslator());

        $date->getTranslationMessage('foo');
    }

    public function testNoLocaleTranslator()
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage(
            'Tests\Carbon\Fixtures\NoLocaleTranslator does neither implements '.
            'Symfony\Contracts\Translation\LocaleAwareInterface nor getLocale() method.',
        );

        $date = Carbon::create(2018, 1, 1, 0, 0, 0);
        $date->setLocalTranslator(new NoLocaleTranslator());

        $date->locale;
    }

    public function testTranslateTimeStringTo()
    {
        $date = Carbon::parse('2019-07-05')->locale('de');
        $baseString = $date->isoFormat('LLLL');

        $this->assertSame('Freitag, 5. Juli 2019 00:00', $baseString);
        $this->assertSame('Friday, 5. July 2019 00:00', $date->translateTimeStringTo($baseString));
        $this->assertSame('vendredi, 5. juillet 2019 00:00', $date->translateTimeStringTo($baseString, 'fr'));
    }

    public function testFallbackLocales()
    {
        // /!\ Used for backward compatibility, please avoid this method
        // @see testMultiLocales() as preferred method

        $myDialect = 'xx_MY_Dialect';
        $secondChoice = 'xy_MY_Dialect';
        $thirdChoice = 'it_CH';

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();

        $translator->setMessages($myDialect, [
            'day' => ':count yub yub',
        ]);

        $translator->setMessages($secondChoice, [
            'day' => ':count buza',
            'hour' => ':count ohto',
        ]);

        Carbon::setLocale($myDialect);

        $this->assertNull(Carbon::getFallbackLocale());

        Carbon::setFallbackLocale($thirdChoice);

        $this->assertSame($thirdChoice, Carbon::getFallbackLocale());
        $this->assertSame('3 yub yub e 5 ora fa', Carbon::now()->subDays(3)->subHours(5)->ago([
            'parts' => 2,
            'join' => true,
        ]));

        Carbon::setTranslator(new Translator('en'));

        /** @var Translator $translator */
        $translator = Carbon::getTranslator();

        $translator->setMessages($myDialect, [
            'day' => ':count yub yub',
        ]);

        $translator->setMessages($secondChoice, [
            'day' => ':count buza',
            'hour' => ':count ohto',
        ]);

        Carbon::setLocale($myDialect);
        Carbon::setFallbackLocale($secondChoice);
        Carbon::setFallbackLocale($thirdChoice);

        $this->assertSame($thirdChoice, Carbon::getFallbackLocale());
        $this->assertSame('3 yub yub e 5 ohto fa', Carbon::now()->subDays(3)->subHours(5)->ago([
            'parts' => 2,
            'join' => true,
        ]));

        Carbon::setTranslator(new IdentityTranslator());

        $this->assertNull(Carbon::getFallbackLocale());

        Carbon::setTranslator(new Translator('en'));
    }

    public function testMultiLocales()
    {
        $myDialect = 'xx_MY_Dialect';
        $secondChoice = 'xy_MY_Dialect';
        $thirdChoice = 'it_CH';

        Translator::get($myDialect)->setTranslations([
            'day' => ':count yub yub',
        ]);

        Translator::get($secondChoice)->setTranslations([
            'day' => ':count buza',
            'hour' => ':count ohto',
        ]);

        $date = Carbon::now()->subDays(3)->subHours(5)->locale($myDialect, $secondChoice, $thirdChoice);

        $this->assertSame('3 yub yub e 5 ohto fa', $date->ago([
            'parts' => 2,
            'join' => true,
        ]));
    }

    public function testStandAloneMonthsInLLLFormat()
    {
        $this->assertSame(
            '29 февраля 2020 г., 12:24',
            Carbon::parse('2020-02-29 12:24:00')->locale('ru_RU')->isoFormat('LLL'),
            'Use "months" for date formatting',
        );
    }

    public function testStandAloneMonthName()
    {
        $this->assertSame(
            'февраль',
            Carbon::parse('2020-02-29 12:24:00')->locale('ru_RU')->monthName,
            'Use "months_standalone" the month alone',
        );
    }

    public function testShortMonthNameInFormat()
    {
        $this->assertSame(
            '29. мая',
            Carbon::parse('2020-05-29 12:24:00')->locale('ru_RU')->isoFormat('D. MMM'),
            'Use "months_short" for date formatting',
        );
        $this->assertSame(
            'май',
            Carbon::parse('2020-05-29 12:24:00')->locale('ru_RU')->isoFormat('MMM'),
            'Use "months_short" for date formatting',
        );
    }

    public function testStandAloneShortMonthName()
    {
        $this->assertSame(
            'май',
            Carbon::parse('2020-05-29 12:24:00')->locale('ru_RU')->shortMonthName,
            'Use "months_short_standalone" the month alone',
        );
    }

    public function testAgoDeclension()
    {
        $this->assertSame(
            'година',
            CarbonInterval::hour()->locale('uk')->forHumans(['aUnit' => true]),
        );

        $this->assertSame(
            'годину тому',
            Carbon::now()->subHour()->locale('uk')->diffForHumans(['aUnit' => true]),
        );
    }

    public function testAustriaGermanJanuary()
    {
        $this->assertSame(
            'Jänner',
            Carbon::parse('2020-01-15')->locale('de_AT')->monthName,
        );

        $this->assertSame(
            'Januar',
            Carbon::parse('2020-01-15')->locale('de')->monthName,
        );

        $this->assertSame(
            'Februar',
            Carbon::parse('2020-02-15')->locale('de_AT')->monthName,
        );

        $this->assertSame(
            'Februar',
            Carbon::parse('2020-02-15')->locale('de')->monthName,
        );
    }

    public function testDeclensionModes()
    {
        Carbon::setTestNow('2022-12-30');
        $this->assertSame(
            '2 жил 3 сар 1 өдөр 1с өмнө',
            Carbon::now()
                ->subYears(2)
                ->subMonths(3)
                ->subDay()
                ->subSecond()
                ->locale('mn')
                ->diffForHumans(null, null, true, 4)
        );
        $this->assertSame(
            '2 жил 3 сар 1 өдөр 1 секундын өмнө',
            Carbon::now()
                ->subYears(2)
                ->subMonths(3)
                ->subDay()
                ->subSecond()
                ->locale('mn')
                ->diffForHumans(null, null, false, 4)
        );
    }

    /** @see https://github.com/CarbonPHP/carbon/issues/57 */
    public function testResetMessagesMemoryConsumption()
    {
        Carbon::getTranslator()->resetMessages('en');

        $start = memory_get_usage();

        for ($i = 0; $i < 100; $i++) {
            Carbon::getTranslator()->resetMessages('en');
        }

        $consumedMemory = memory_get_usage() - $start;

        $this->assertLessThan(100_000, $consumedMemory);
    }

    #[TestWith(['мая', 'May'])]
    #[TestWith(['май', 'May'])]
    #[TestWith(['февраль', 'February'])]
    #[TestWith(['февраля', 'February'])]
    public function testTranslateMonthsEitherStandaloneOrNot(string $ru, string $en)
    {
        $this->assertSame($en, Carbon::translateTimeString($ru, 'ru', 'en'));
    }
}
