From d88a8a8154ce361c38d491bd5dec9f8dee24c976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Barto=C5=A1?= Date: Mon, 15 Apr 2024 22:28:51 +0200 Subject: [PATCH] Translations --- .github/workflows/ci.yaml | 10 + Makefile | 3 + composer.json | 2 + docs/README.md | 40 +- src/CronExpressionExplainer.php | 21 +- src/DefaultCronExpressionExplainer.php | 296 +++++-- src/Exception/UnsupportedLanguage.php | 23 - src/Exception/UnsupportedLocale.php | 23 + src/Interpreter/BasePartInterpreter.php | 113 +-- src/Interpreter/DayOfMonthInterpreter.php | 51 +- src/Interpreter/DayOfWeekInterpreter.php | 62 +- src/Interpreter/HourInterpreter.php | 30 +- src/Interpreter/MinuteInterpreter.php | 32 +- src/Interpreter/MonthInterpreter.php | 37 +- src/Part/ListPart.php | 5 + src/Part/Part.php | 2 + src/Part/RangePart.php | 5 + src/Part/StepPart.php | 5 + src/Part/ValuePart.php | 5 + src/Translator/PartTranslator.php | 53 ++ src/Translator/translations/cs.php | 166 ++++ src/Translator/translations/en.php | 156 ++++ tests/Snapshots/TranslationsDataGenerator.php | 303 ++++++++ tests/Snapshots/TranslationsSnapshotTest.php | 42 + tests/Snapshots/translations/cs.json | 730 ++++++++++++++++++ tests/Snapshots/translations/en.json | 730 ++++++++++++++++++ tests/Snapshots/update-snapshots.php | 7 + .../DefaultCronExpressionExplainerTest.php | 52 +- .../Exception/UnsupportedLanguageTest.php | 19 - .../Unit/Exception/UnsupportedLocaleTest.php | 19 + tests/Unit/Part/ListPartTest.php | 1 + tests/Unit/Part/RangePartTest.php | 1 + tests/Unit/Part/StepPartTest.php | 1 + tests/Unit/Part/ValuePartTest.php | 1 + 34 files changed, 2753 insertions(+), 293 deletions(-) delete mode 100644 src/Exception/UnsupportedLanguage.php create mode 100644 src/Exception/UnsupportedLocale.php create mode 100644 src/Translator/PartTranslator.php create mode 100644 src/Translator/translations/cs.php create mode 100644 src/Translator/translations/en.php create mode 100644 tests/Snapshots/TranslationsDataGenerator.php create mode 100644 tests/Snapshots/TranslationsSnapshotTest.php create mode 100644 tests/Snapshots/translations/cs.json create mode 100644 tests/Snapshots/translations/en.json create mode 100644 tests/Snapshots/update-snapshots.php delete mode 100644 tests/Unit/Exception/UnsupportedLanguageTest.php create mode 100644 tests/Unit/Exception/UnsupportedLocaleTest.php diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6620380..ade7162 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -102,15 +102,24 @@ jobs: matrix: operating-system: [ "ubuntu-latest", "macos-latest", "windows-latest" ] php-version: [ "7.4", "8.0", "8.1" ] + php-extensions: [ "fileinfo, intl, json, mbstring, sodium, ssh2" ] composer-flags: [ "" ] experimental: [ false ] include: - operating-system: "ubuntu-latest" php-version: "7.4" + php-extensions: "fileinfo, intl, json, mbstring, sodium, ssh2" composer-flags: "--prefer-lowest --prefer-stable" experimental: false - operating-system: "ubuntu-latest" php-version: "8.2" + php-extensions: "fileinfo, intl, json, mbstring, sodium, ssh2" + composer-flags: "--ignore-platform-req=php+" + experimental: false + # No intl + - operating-system: "ubuntu-latest" + php-version: "8.2" + php-extensions: "fileinfo, json, mbstring, sodium, ssh2" composer-flags: "--ignore-platform-req=php+" experimental: false @@ -124,6 +133,7 @@ jobs: version: "${{ matrix.php-version }}" coverage: "pcov" token: "${{ secrets.GITHUB_TOKEN }}" + extensions: "${{ matrix.php-extensions }}" - name: "Composer" uses: "orisai/github-workflows/.github/actions/setup-composer@v1" diff --git a/Makefile b/Makefile index 5792bae..0a2f9d4 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,9 @@ phpstan-baseline: ## Add PHPStan errors to baseline # Tests +update-snapshots: ## Update snapshots used for testing + $(PRE_PHP) php tests/Snapshots/update-snapshots.php + .PHONY: tests tests: ## Run all tests $(PRE_PHP) $(PHPUNIT_COMMAND) $(ARGS) diff --git a/composer.json b/composer.json index f65485e..b0c08d1 100644 --- a/composer.json +++ b/composer.json @@ -30,11 +30,13 @@ "require": { "php": ">=7.4.0 <8.3.0", "dragonmantank/cron-expression": "^3.3.0", + "symfony/intl": "^5.4.35|^6.4.3|^7.0.3", "symfony/polyfill-php80": "^1.29" }, "require-dev": { "brianium/paratest": "^6.3.0", "infection/infection": "^0.26.0", + "nette/utils": "^3.1.0|^4.0.0", "orisai/coding-standard": "^3.0.0", "phpstan/extension-installer": "^1.0.0", "phpstan/phpstan": "^1.0.0", diff --git a/docs/README.md b/docs/README.md index bc5a583..9ef31fd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ Human-readable cron expressions - [Usage](#usage) - [Seconds](#seconds) - [Time zones](#time-zones) -- [Translations](#translations) +- [Localization](#localization) - [Handling unsupported expressions](#handling-unsupported-expressions) - [Compatibility](#compatibility) @@ -22,6 +22,8 @@ composer require orisai/cron-expression-explainer ## Usage +Explain any cron expression + ```php use Orisai\CronExpressionExplainer\DefaultCronExpressionExplainer; @@ -45,6 +47,10 @@ $explainer->explain('* * * * 7L'); // At every minute on the last Sunday. ## Seconds +Add amount of seconds after which expression should match again + +> This is a feature of [orisai/scheduler](https://github.com/orisai/scheduler) + ```php $explainer->explain('* * * * *', 1); // At every second. $explainer->explain('* * * * *', 30); // At every 30 seconds. @@ -54,21 +60,34 @@ $explainer->explain('1 * * * *', 2); // At every 2 seconds at minute 1. ## Time zones +Add timezone in which the cron expression should be interpreted + +> This is a feature of [orisai/scheduler](https://github.com/orisai/scheduler) + ```php use DateTimeZone; $explainer->explain('30 10 * * *', null, new DateTimeZone('America/New_York')); // At 10:30 in America/New_York time zone. ``` -## Translations +## Localization -Yeah, localization is actually not supported (yet). But the interface is ready for it! +Translate expression into any supported locale ```php -$explainer->getSupportedLanguages(); // array e.g. ['en' => 'english'] -$explainer->explain('* * * * *', null, null, 'en'); +$explainer->explain('* * * * *', null, null, 'en'); // At every minute. +$explainer->explain('* * * * *', null, null, 'cs'); // Každou minutu. +$explainer->getSupportedLocales(); // array e.g. ['en' => 'english', 'cs' => 'czech', /* ... */] +$explainer->setDefaultLocale('cs'); ``` +Currently supported locales are: + +- `cs` - czech / čeština +- `en` - english + +In case given locale is not supported, the `UnsupportedLocale` exception is thrown. + ## Handling unsupported expressions Syntax may not be recognized as valid or may just be some complex variant that we don't support (yet). @@ -89,3 +108,14 @@ try { This library is built on top of [dragonmantank/cron-expression](https://github.com/dragonmantank/cron-expression). For best compatibility, use it to interpret your expressions. For example with [orisai/scheduler](https://github.com/orisai/scheduler)! + +## Contributing + +To add support for a new locale: + +- create file in `src/Translator/translations` and add translations for all the keys used in other translation files +- add it to supported locales in `DefaultCronExpressionExplainer` +- generate translations via `make update-snapshots` +- verify that the generated test translations in `tests/Snapshots/translations` make sense and match their configuration +- run `make tests`, it should pass now :) +- mention the locale in the documentation diff --git a/src/CronExpressionExplainer.php b/src/CronExpressionExplainer.php index 37ddf9e..e7108b2 100644 --- a/src/CronExpressionExplainer.php +++ b/src/CronExpressionExplainer.php @@ -4,7 +4,7 @@ use DateTimeZone; use Orisai\CronExpressionExplainer\Exception\UnsupportedExpression; -use Orisai\CronExpressionExplainer\Exception\UnsupportedLanguage; +use Orisai\CronExpressionExplainer\Exception\UnsupportedLocale; interface CronExpressionExplainer { @@ -12,18 +12,31 @@ interface CronExpressionExplainer /** * @param int<0,59>|null $repeatSeconds * @throws UnsupportedExpression - * @throws UnsupportedLanguage + * @throws UnsupportedLocale */ public function explain( string $expression, ?int $repeatSeconds = null, ?DateTimeZone $timeZone = null, - ?string $language = null + ?string $locale = null ): string; + /** + * @template T of string + * @param list $locales + * @param int<0, 59>|null $repeatSeconds + * @return array + */ + public function explainInLocales( + array $locales, + string $expression, + ?int $repeatSeconds = null, + ?DateTimeZone $timeZone = null + ): array; + /** * @return array */ - public function getSupportedLanguages(): array; + public function getSupportedLocales(): array; } diff --git a/src/DefaultCronExpressionExplainer.php b/src/DefaultCronExpressionExplainer.php index b65e62a..483bc16 100644 --- a/src/DefaultCronExpressionExplainer.php +++ b/src/DefaultCronExpressionExplainer.php @@ -6,7 +6,7 @@ use DateTimeZone; use InvalidArgumentException; use Orisai\CronExpressionExplainer\Exception\UnsupportedExpression; -use Orisai\CronExpressionExplainer\Exception\UnsupportedLanguage; +use Orisai\CronExpressionExplainer\Exception\UnsupportedLocale; use Orisai\CronExpressionExplainer\Interpreter\BasePartInterpreter; use Orisai\CronExpressionExplainer\Interpreter\DayOfMonthInterpreter; use Orisai\CronExpressionExplainer\Interpreter\DayOfWeekInterpreter; @@ -19,10 +19,13 @@ use Orisai\CronExpressionExplainer\Part\RangePart; use Orisai\CronExpressionExplainer\Part\StepPart; use Orisai\CronExpressionExplainer\Part\ValuePart; +use Orisai\CronExpressionExplainer\Translator\PartTranslator; use function array_key_exists; use function assert; use function is_numeric; +use function str_ends_with; use function str_pad; +use function ucfirst; use const STR_PAD_LEFT; final class DefaultCronExpressionExplainer implements CronExpressionExplainer @@ -30,6 +33,8 @@ final class DefaultCronExpressionExplainer implements CronExpressionExplainer private PartParser $parser; + private PartTranslator $translator; + private MinuteInterpreter $minuteInterpreter; private HourInterpreter $hourInterpreter; @@ -40,34 +45,117 @@ final class DefaultCronExpressionExplainer implements CronExpressionExplainer private DayOfWeekInterpreter $dayOfWeekInterpreter; + private string $defaultLocale = 'en'; + public function __construct() { $this->parser = new PartParser(); - $this->minuteInterpreter = new MinuteInterpreter(); - $this->hourInterpreter = new HourInterpreter(); - $this->dayOfMonthInterpreter = new DayOfMonthInterpreter(); - $this->monthInterpreter = new MonthInterpreter(); - $this->dayOfWeekInterpreter = new DayOfWeekInterpreter(); + $this->translator = new PartTranslator(); + $this->minuteInterpreter = new MinuteInterpreter($this->translator); + $this->hourInterpreter = new HourInterpreter($this->translator); + $this->dayOfMonthInterpreter = new DayOfMonthInterpreter($this->translator); + $this->monthInterpreter = new MonthInterpreter($this->translator); + $this->dayOfWeekInterpreter = new DayOfWeekInterpreter($this->translator); } - public function getSupportedLanguages(): array + public function getSupportedLocales(): array { return [ + 'cs' => 'czech', 'en' => 'english', ]; } + public function setDefaultLocale(string $locale): void + { + $this->checkLocaleIsSupported($locale); + $this->defaultLocale = $locale; + } + + /** + * @throws UnsupportedLocale + */ + private function checkLocaleIsSupported(?string $locale): void + { + if ($locale !== null && !array_key_exists($locale, $this->getSupportedLocales())) { + throw new UnsupportedLocale($locale); + } + } + public function explain( string $expression, ?int $repeatSeconds = null, ?DateTimeZone $timeZone = null, - ?string $language = null + ?string $locale = null ): string { - $this->checkLanguageIsSupported($language); - $expr = $this->createExpression($expression); + $this->checkLocaleIsSupported($locale); + $locale ??= $this->defaultLocale; + + $repeatSeconds ??= 0; + + [$minutePart, $hourPart, $dayOfMonthPart, $monthPart, $dayOfWeekPart] = $this->expressionToParts($expression); + + return $this->build( + $locale, + $repeatSeconds, + $minutePart, + $hourPart, + $dayOfWeekPart, + $dayOfMonthPart, + $monthPart, + $timeZone, + ); + } + + public function explainInLocales( + array $locales, + string $expression, + ?int $repeatSeconds = null, + ?DateTimeZone $timeZone = null + ): array + { + if ($locales === []) { + return []; + } + + foreach ($locales as $locale) { + $this->checkLocaleIsSupported($locale); + } $repeatSeconds ??= 0; + + [$minutePart, $hourPart, $dayOfMonthPart, $monthPart, $dayOfWeekPart] = $this->expressionToParts($expression); + + $translations = []; + foreach ($locales as $locale) { + $translations[$locale] = $this->build( + $locale, + $repeatSeconds, + $minutePart, + $hourPart, + $dayOfWeekPart, + $dayOfMonthPart, + $monthPart, + $timeZone, + ); + } + + return $translations; + } + + /** + * @return array{ + * 0: ListPart|StepPart|RangePart|ValuePart, + * 1: ListPart|StepPart|RangePart|ValuePart, + * 2: ListPart|StepPart|RangePart|ValuePart, + * 3: ListPart|StepPart|RangePart|ValuePart, + * 4: ListPart|StepPart|RangePart|ValuePart, + * } + */ + private function expressionToParts(string $expression): array + { + $expr = $this->createExpression($expression); $minutePart = $this->processExpressionPart( $expr, CronExpression::MINUTE, @@ -94,8 +182,59 @@ public function explain( $this->dayOfWeekInterpreter, ); - $explanation = 'At '; - $secondsExplanation = $this->explainSeconds($repeatSeconds); + return [$minutePart, $hourPart, $dayOfMonthPart, $monthPart, $dayOfWeekPart]; + } + + /** + * @throws UnsupportedExpression + */ + private function createExpression(string $expression): CronExpression + { + try { + return new CronExpression($expression); + } catch (InvalidArgumentException $exception) { + throw new UnsupportedExpression($exception->getMessage(), $exception); + } + } + + /** + * @return ListPart|StepPart|RangePart|ValuePart + */ + private function processExpressionPart( + CronExpression $expression, + int $partName, + BasePartInterpreter $interpreter + ): Part + { + $part = $expression->getExpression($partName); + assert($part !== null); + + return $interpreter->reducePart( + $this->parser->parsePart($part), + ); + } + + /** + * @param int<0, 59> $repeatSeconds + * @param ListPart|StepPart|RangePart|ValuePart $minutePart + * @param ListPart|StepPart|RangePart|ValuePart $hourPart + * @param ListPart|StepPart|RangePart|ValuePart $dayOfWeekPart + * @param ListPart|StepPart|RangePart|ValuePart $dayOfMonthPart + * @param ListPart|StepPart|RangePart|ValuePart $monthPart + */ + private function build( + string $locale, + int $repeatSeconds, + Part $minutePart, + Part $hourPart, + Part $dayOfWeekPart, + Part $dayOfMonthPart, + Part $monthPart, + ?DateTimeZone $timeZone + ): string + { + $explanation = ''; + $secondsExplanation = $this->explainSeconds($repeatSeconds, $locale); $explanation .= $secondsExplanation; if ( @@ -105,15 +244,26 @@ public function explain( && is_numeric($hourPartValue = $hourPart->getValue()) ) { if ($secondsExplanation !== '') { - $explanation .= ' at '; + $explanation .= ' '; } - $hourPartValue = $this->hourInterpreter->convertNumericValue($hourPartValue); - $minutePartValue = $this->minuteInterpreter->convertNumericValue($minutePartValue); - - $explanation .= str_pad((string) $hourPartValue, 2, '0', STR_PAD_LEFT) - . ':' - . str_pad((string) $minutePartValue, 2, '0', STR_PAD_LEFT); + $hourPartValue = str_pad( + (string) $this->hourInterpreter->convertNumericValue($hourPartValue), + 2, + '0', + STR_PAD_LEFT, + ); + $minutePartValue = str_pad( + (string) $this->minuteInterpreter->convertNumericValue($minutePartValue), + 2, + '0', + STR_PAD_LEFT, + ); + + $explanation .= $this->translator->translate('hour+minute', [ + 'hour' => $hourPartValue, + 'minute' => $minutePartValue, + ], $locale); } else { if ( !( @@ -123,17 +273,21 @@ public function explain( ) ) { if ($secondsExplanation !== '') { - $explanation .= ' at '; + $explanation .= ' '; } - $explanation .= $this->minuteInterpreter->explainPart($minutePart); + $explanation .= $this->translator->translate('before-minute', [], $locale); + $explanation .= $this->minuteInterpreter->explainPart($minutePart, $locale); } - $hourExplanation = $this->hourInterpreter->explainPart($hourPart); - $explanation .= $hourExplanation !== '' ? ' past ' . $hourExplanation : ''; + $hourExplanation = $this->hourInterpreter->explainPart($hourPart, $locale); + if ($hourExplanation !== '') { + $explanation .= $this->translator->translate('before-hour', [], $locale); + $explanation .= $hourExplanation; + } } - $dayOfWeekExplanation = $this->dayOfWeekInterpreter->explainPart($dayOfWeekPart); + $dayOfWeekExplanation = $this->dayOfWeekInterpreter->explainPart($dayOfWeekPart, $locale); if ( $dayOfWeekExplanation === '' && $dayOfMonthPart instanceof ValuePart @@ -141,96 +295,70 @@ public function explain( && is_numeric($dayOfMonthPart->getValue()) && is_numeric($monthPart->getValue()) ) { - $dayOfMonthValue = $this->dayOfMonthInterpreter->convertNumericValue($dayOfMonthPart->getValue()); - $explanation .= ' on ' - . $dayOfMonthValue - . $this->dayOfMonthInterpreter->getNumberExtension($dayOfMonthValue) - . ' of ' - . $this->monthInterpreter->explainPart($monthPart); + $explanation .= ' ' . $this->translator->translate('day-of-month+month', [ + 'day' => $this->dayOfMonthInterpreter->convertNumericValue($dayOfMonthPart->getValue()), + 'month' => $monthPart->getValue(), + ], $locale); } else { - $dayOfMonthExplanation = $this->dayOfMonthInterpreter->explainPart($dayOfMonthPart); + $dayOfMonthExplanation = $this->dayOfMonthInterpreter->explainPart($dayOfMonthPart, $locale); - $explanation .= $dayOfMonthExplanation !== '' ? ' on ' . $dayOfMonthExplanation : ''; + if ($dayOfMonthExplanation !== '') { + $explanation .= $this->translator->translate('before-day-of-month', [], $locale); + $explanation .= $dayOfMonthExplanation; + } if ($dayOfMonthExplanation !== '' && $dayOfWeekExplanation !== '') { - $explanation .= ' and'; + $explanation .= $this->translator->translate('between-day-of-month-and-week', [], $locale); + } + + if ($dayOfWeekExplanation !== '') { + $explanation .= $this->translator->translate('before-day-of-week', [], $locale); } - $explanation .= $dayOfWeekExplanation !== '' ? ' on ' : ''; if ( $dayOfMonthExplanation !== '' - && $dayOfWeekPart instanceof ValuePart - && !$this->dayOfWeekInterpreter->isAll($dayOfWeekPart) + && ( + ($dayOfWeekPart instanceof ValuePart && !$this->dayOfWeekInterpreter->isAll($dayOfWeekPart)) + || $dayOfWeekPart instanceof ListPart + ) ) { - $explanation .= 'every '; + $explanation .= $this->translator->translate('before-day-of-week-every', [], $locale); } $explanation .= $dayOfWeekExplanation; - $monthExplanation = $this->monthInterpreter->explainPart($monthPart); - $explanation .= $monthExplanation !== '' ? ' in ' . $monthExplanation : ''; + $monthExplanation = $this->monthInterpreter->explainPart($monthPart, $locale); + if ($monthExplanation !== '') { + $explanation .= $this->translator->translate('before-month', [], $locale); + $explanation .= $monthExplanation; + } } if ($timeZone !== null) { - $explanation .= " in {$timeZone->getName()} time zone"; + $explanation .= ' ' . $this->translator->translate('timezone', [ + 'tz' => $timeZone->getName(), + ], $locale); } - return $explanation . '.'; - } - - /** - * @throws UnsupportedLanguage - */ - private function checkLanguageIsSupported(?string $language): void - { - if ($language !== null && !array_key_exists($language, $this->getSupportedLanguages())) { - throw new UnsupportedLanguage($language); + if (!str_ends_with($explanation, '.')) { + $explanation .= '.'; } - } - /** - * @throws UnsupportedExpression - */ - private function createExpression(string $expression): CronExpression - { - try { - return new CronExpression($expression); - } catch (InvalidArgumentException $exception) { - throw new UnsupportedExpression($exception->getMessage(), $exception); - } - } - - /** - * @return ListPart|StepPart|RangePart|ValuePart - */ - private function processExpressionPart( - CronExpression $expression, - int $partName, - BasePartInterpreter $interpreter - ): Part - { - $part = $expression->getExpression($partName); - assert($part !== null); - - return $interpreter->reducePart( - $this->parser->parsePart($part), - ); + return ucfirst($explanation); } /** * @param int<0, 59> $repeatSeconds */ - private function explainSeconds(int $repeatSeconds): string + private function explainSeconds(int $repeatSeconds, string $locale): string { if ($repeatSeconds <= 0) { return ''; } - if ($repeatSeconds === 1) { - return 'every second'; - } - - return "every $repeatSeconds seconds"; + return $this->translator->translate('second', [ + 'second' => $repeatSeconds, + ], $locale); } } diff --git a/src/Exception/UnsupportedLanguage.php b/src/Exception/UnsupportedLanguage.php deleted file mode 100644 index 066d83b..0000000 --- a/src/Exception/UnsupportedLanguage.php +++ /dev/null @@ -1,23 +0,0 @@ -language = $language; - } - - public function getLanguage(): string - { - return $this->language; - } - -} diff --git a/src/Exception/UnsupportedLocale.php b/src/Exception/UnsupportedLocale.php new file mode 100644 index 0000000..2f04c20 --- /dev/null +++ b/src/Exception/UnsupportedLocale.php @@ -0,0 +1,23 @@ +locale = $locale; + } + + public function getLocale(): string + { + return $this->locale; + } + +} diff --git a/src/Interpreter/BasePartInterpreter.php b/src/Interpreter/BasePartInterpreter.php index 2150dd2..e43091c 100644 --- a/src/Interpreter/BasePartInterpreter.php +++ b/src/Interpreter/BasePartInterpreter.php @@ -7,6 +7,7 @@ use Orisai\CronExpressionExplainer\Part\RangePart; use Orisai\CronExpressionExplainer\Part\StepPart; use Orisai\CronExpressionExplainer\Part\ValuePart; +use Orisai\CronExpressionExplainer\Translator\PartTranslator; use function array_key_first; use function array_key_last; @@ -16,28 +17,51 @@ abstract class BasePartInterpreter { + protected PartTranslator $translator; + + public function __construct(PartTranslator $translator) + { + $this->translator = $translator; + } + + /** + * @param ListPart|StepPart|RangePart|ValuePart $part + */ + public function explainPart(Part $part, string $locale, bool $renderName = true): string + { + return $this->explainPartInternal($part, $part, $locale, $renderName); + } + /** * @param ListPart|StepPart|RangePart|ValuePart $part */ - public function explainPart(Part $part, bool $renderName = true): string + private function explainPartInternal(Part $part, Part $contextPart, string $locale, bool $renderName = true): string { if ($part instanceof ListPart) { $list = $part->getParts(); + $listSeparator = $this->translator->translate('listSeparator', [], $locale); $firstKey = array_key_first($list); $lastKey = array_key_last($list); + $string = ''; + $lastValue = ''; foreach ($list as $key => $item) { - $string .= $this->explainPart($item, $key === $firstKey); + $explainedPart = $this->explainPartInternal($item, $part, $locale, $key === $firstKey); if ($key !== $lastKey) { - if (++$key === $lastKey) { - $string .= ' and '; - } else { - $string .= ', '; + $string .= $explainedPart; + + if (++$key !== $lastKey) { + $string .= $listSeparator; } + } else { + $lastValue = $explainedPart; } } - return $string; + return $this->translator->translate('list', [ + 'values' => $string, + 'lastValue' => $lastValue, + ], $locale); } if ($part instanceof StepPart) { @@ -45,35 +69,48 @@ public function explainPart(Part $part, bool $renderName = true): string $step = $part->getStep(); if ($range instanceof ValuePart && $this->isAll($range)) { - return 'every ' - . $step - . $this->getNumberExtension($step) - . " {$this->getInStepName()}"; + return $this->translator->translate( + "step-all-{$this->getKey()}", + [ + 'step' => $step, + ], + $locale, + ); } - return 'every ' - . $step - . $this->getNumberExtension($step) - . " {$this->getInStepName()} " - . $this->explainPart($range, false); + return $this->translator->translate( + "step-{$this->getKey()}", + [ + 'step' => $step, + 'part' => $this->explainPartInternal($range, $part, $locale, false), + ], + $locale, + ); } if ($part instanceof RangePart) { $left = $part->getLeft(); $right = $part->getRight(); + $key = "range-{$this->getKey()}"; + if ($renderName) { + $key .= '-named'; + } - return ($renderName ? "every {$this->getInRangeName()} " : '') - . 'from ' - . $this->explainPart($left, false) - . ' through ' - . $this->explainPart($right, false); + return $this->translator->translate( + $key, + [ + 'left' => $this->explainPartInternal($left, $part, $locale, false), + 'right' => $this->explainPartInternal($right, $part, $locale, false), + ], + $locale, + ); } if ($this->isAll($part)) { - return $this->getAsteriskDescription(); + return $this->getAsteriskDescription($locale); } - return $this->translateValue($part->getValue(), $renderName); + return $this->translateValue($part->getValue(), $contextPart->getName(), $locale, $renderName); } /** @@ -165,29 +202,15 @@ abstract public function isAll(ValuePart $part): bool; abstract public function reduceValuePart(ValuePart $part): ValuePart; - abstract protected function getInRangeName(): string; - - abstract protected function getInStepName(): string; - - abstract protected function getAsteriskDescription(): string; + abstract protected function getKey(): string; - abstract protected function translateValue(string $value, bool $renderName): string; - - public function getNumberExtension(int $number): string - { - if ($number === 1) { - return 'st'; - } + abstract protected function getAsteriskDescription(string $locale): string; - if ($number === 2) { - return 'nd'; - } - - if ($number === 3) { - return 'rd'; - } - - return 'th'; - } + abstract protected function translateValue( + string $value, + string $context, + string $locale, + bool $renderName + ): string; } diff --git a/src/Interpreter/DayOfMonthInterpreter.php b/src/Interpreter/DayOfMonthInterpreter.php index e787611..57d1aa0 100644 --- a/src/Interpreter/DayOfMonthInterpreter.php +++ b/src/Interpreter/DayOfMonthInterpreter.php @@ -22,34 +22,32 @@ public function reduceValuePart(ValuePart $part): ValuePart return $part; } - protected function getInStepName(): string - { - return $this->getInValueName(); - } - - protected function getInRangeName(): string - { - return $this->getInValueName(); - } - - protected function getInValueName(): string + protected function getKey(): string { return 'day-of-month'; } - protected function getAsteriskDescription(): string + protected function getAsteriskDescription(string $locale): string { return ''; } - protected function translateValue(string $value, bool $renderName): string + protected function translateValue(string $value, string $context, string $locale, bool $renderName): string { if ($value === 'L') { - return 'a last ' . $this->getInValueName(); + return $this->translator->translate( + 'day-of-month-last-day', + [], + $locale, + ); } if ($value === 'LW') { - return 'a last weekday'; + return $this->translator->translate( + 'day-of-month-last-weekday', + [], + $locale, + ); } $nearest = str_ends_with($value, 'W'); @@ -61,11 +59,28 @@ protected function translateValue(string $value, bool $renderName): string $intValue = $this->convertNumericValue($value); if ($nearest) { - return 'a weekday closest to the ' . $value . $this->getNumberExtension($intValue); + return $this->translator->translate( + 'day-of-month-nearest-weekday', + [ + 'day' => $intValue, + 'context' => $context, + ], + $locale, + ); + } + + $key = $this->getKey(); + if ($renderName) { + $key .= '-named'; } - return ($renderName ? ("{$this->getInValueName()} ") : '') - . $intValue; + return $this->translator->translate( + $key, + [ + 'day' => $intValue, + ], + $locale, + ); } public function convertNumericValue(string $value): int diff --git a/src/Interpreter/DayOfWeekInterpreter.php b/src/Interpreter/DayOfWeekInterpreter.php index 53bbd04..db3034a 100644 --- a/src/Interpreter/DayOfWeekInterpreter.php +++ b/src/Interpreter/DayOfWeekInterpreter.php @@ -16,14 +16,14 @@ final class DayOfWeekInterpreter extends BasePartInterpreter { private const Duplicates = [ - 7 => '0', - 'SUN' => '0', 'MON' => '1', 'TUE' => '2', 'WED' => '3', 'THU' => '4', 'FRI' => '5', 'SAT' => '6', + 'SUN' => '7', + 0 => '7', ]; public function isAll(ValuePart $part): bool @@ -33,7 +33,10 @@ public function isAll(ValuePart $part): bool public function reduceValuePart(ValuePart $part): ValuePart { - $value = strtoupper($part->getValue()); + $value = $part->getValue(); + $value = is_numeric($value) + ? (int) $value + : strtoupper($value); $deduplicated = self::Duplicates[$value] ?? null; if ($deduplicated !== null) { @@ -43,22 +46,17 @@ public function reduceValuePart(ValuePart $part): ValuePart return $part; } - protected function getInStepName(): string - { - return $this->getInRangeName(); - } - - protected function getInRangeName(): string + protected function getKey(): string { return 'day-of-week'; } - protected function getAsteriskDescription(): string + protected function getAsteriskDescription(string $locale): string { return ''; } - protected function translateValue(string $value, bool $renderName): string + protected function translateValue(string $value, string $context, string $locale, bool $renderName): string { if (str_contains($value, '#')) { [$value, $nth] = explode('#', $value); @@ -74,29 +72,45 @@ protected function translateValue(string $value, bool $renderName): string $value = $this->deduplicateValue($value); $intValue = $this->convertNumericValue($value); - $map = [ - 0 => 'Sunday', - 1 => 'Monday', - 2 => 'Tuesday', - 3 => 'Wednesday', - 4 => 'Thursday', - 5 => 'Friday', - 6 => 'Saturday', - ]; + $translated = $this->translator->translate( + $this->getKey(), + [ + 'dayNumber' => $intValue, + 'context' => $context, + ], + $locale, + ); if (isset($nth)) { $intNth = (int) $nth; assert($nth === (string) $intNth); assert($intNth >= 0 && $intNth <= 5); - return $nth . $this->getNumberExtension($intNth) . ' ' . $map[$intValue]; + return $this->translator->translate( + 'day-of-week-nth', + [ + 'dayNumber' => $intValue, + 'day' => $translated, + 'nth' => $nth, + 'context' => $context, + ], + $locale, + ); } if ($last) { - return 'the last ' . $map[$intValue]; + return $this->translator->translate( + 'day-of-week-last', + [ + 'dayNumber' => $intValue, + 'day' => $translated, + 'context' => $context, + ], + $locale, + ); } - return $map[$intValue]; + return $translated; } private function deduplicateValue(string $value): string @@ -109,7 +123,7 @@ private function convertNumericValue(string $value): int assert(is_numeric($value)); $intValue = (int) $value; assert((float) $value === (float) $intValue); - assert($intValue >= 0 && $intValue <= 6); + assert($intValue >= 1 && $intValue <= 7); return $intValue; } diff --git a/src/Interpreter/HourInterpreter.php b/src/Interpreter/HourInterpreter.php index 18a72d8..5bd0c7b 100644 --- a/src/Interpreter/HourInterpreter.php +++ b/src/Interpreter/HourInterpreter.php @@ -19,22 +19,12 @@ public function reduceValuePart(ValuePart $part): ValuePart return $part; } - protected function getInStepName(): string - { - return $this->getInValueName(); - } - - protected function getInRangeName(): string - { - return $this->getInValueName(); - } - - private function getInValueName(): string + protected function getKey(): string { return 'hour'; } - protected function getAsteriskDescription(): string + protected function getAsteriskDescription(string $locale): string { return ''; } @@ -49,10 +39,20 @@ public function convertNumericValue(string $value): int return $intValue; } - protected function translateValue(string $value, bool $renderName): string + protected function translateValue(string $value, string $context, string $locale, bool $renderName): string { - return ($renderName ? "{$this->getInValueName()} " : '') - . $this->convertNumericValue($value); + $key = $this->getKey(); + if ($renderName) { + $key .= '-named'; + } + + return $this->translator->translate( + $key, + [ + 'hour' => $this->convertNumericValue($value), + ], + $locale, + ); } } diff --git a/src/Interpreter/MinuteInterpreter.php b/src/Interpreter/MinuteInterpreter.php index 82128bb..dedf99a 100644 --- a/src/Interpreter/MinuteInterpreter.php +++ b/src/Interpreter/MinuteInterpreter.php @@ -19,24 +19,14 @@ public function reduceValuePart(ValuePart $part): ValuePart return $part; } - protected function getInStepName(): string - { - return $this->getInValueName(); - } - - protected function getInRangeName(): string - { - return $this->getInValueName(); - } - - private function getInValueName(): string + protected function getKey(): string { return 'minute'; } - protected function getAsteriskDescription(): string + protected function getAsteriskDescription(string $locale): string { - return 'every minute'; + return $this->translator->translate('every-minute', [], $locale); } public function convertNumericValue(string $value): int @@ -49,10 +39,20 @@ public function convertNumericValue(string $value): int return $intValue; } - protected function translateValue(string $value, bool $renderName): string + protected function translateValue(string $value, string $context, string $locale, bool $renderName): string { - return ($renderName ? "{$this->getInValueName()} " : '') - . $this->convertNumericValue($value); + $key = $this->getKey(); + if ($renderName) { + $key .= '-named'; + } + + return $this->translator->translate( + $key, + [ + 'minute' => $this->convertNumericValue($value), + ], + $locale, + ); } } diff --git a/src/Interpreter/MonthInterpreter.php b/src/Interpreter/MonthInterpreter.php index bfe468c..fa5ef10 100644 --- a/src/Interpreter/MonthInterpreter.php +++ b/src/Interpreter/MonthInterpreter.php @@ -42,41 +42,26 @@ public function reduceValuePart(ValuePart $part): ValuePart return $part; } - protected function getInStepName(): string - { - return $this->getInRangeName(); - } - - protected function getInRangeName(): string + protected function getKey(): string { return 'month'; } - protected function getAsteriskDescription(): string + protected function getAsteriskDescription(string $locale): string { return ''; } - protected function translateValue(string $value, bool $renderName): string + protected function translateValue(string $value, string $context, string $locale, bool $renderName): string { - $intValue = $this->convertNumericValue($value); - - $map = [ - 1 => 'January', - 2 => 'February', - 3 => 'March', - 4 => 'April', - 5 => 'May', - 6 => 'June', - 7 => 'July', - 8 => 'August', - 9 => 'September', - 10 => 'October', - 11 => 'November', - 12 => 'December', - ]; - - return $map[$intValue]; + return $this->translator->translate( + $this->getKey(), + [ + 'month' => $this->convertNumericValue($value), + 'context' => $context, + ], + $locale, + ); } private function convertNumericValue(string $value): int diff --git a/src/Part/ListPart.php b/src/Part/ListPart.php index 0ea5da6..095dc99 100644 --- a/src/Part/ListPart.php +++ b/src/Part/ListPart.php @@ -16,6 +16,11 @@ public function __construct(array $parts) $this->parts = $parts; } + public function getName(): string + { + return 'list'; + } + /** * @return list */ diff --git a/src/Part/Part.php b/src/Part/Part.php index 342a734..7300c2f 100644 --- a/src/Part/Part.php +++ b/src/Part/Part.php @@ -8,4 +8,6 @@ interface Part { + public function getName(): string; + } diff --git a/src/Part/RangePart.php b/src/Part/RangePart.php index af8d6f8..35fc527 100644 --- a/src/Part/RangePart.php +++ b/src/Part/RangePart.php @@ -15,6 +15,11 @@ public function __construct(ValuePart $left, ValuePart $right) $this->right = $right; } + public function getName(): string + { + return 'range'; + } + public function getLeft(): ValuePart { return $this->left; diff --git a/src/Part/StepPart.php b/src/Part/StepPart.php index 7abfea4..102a649 100644 --- a/src/Part/StepPart.php +++ b/src/Part/StepPart.php @@ -19,6 +19,11 @@ public function __construct(Part $range, int $step) $this->step = $step; } + public function getName(): string + { + return 'step'; + } + /** * @return RangePart|ValuePart */ diff --git a/src/Part/ValuePart.php b/src/Part/ValuePart.php index f8a0013..87a58a2 100644 --- a/src/Part/ValuePart.php +++ b/src/Part/ValuePart.php @@ -12,6 +12,11 @@ public function __construct(string $value) $this->value = $value; } + public function getName(): string + { + return 'value'; + } + public function getValue(): string { return $this->value; diff --git a/src/Translator/PartTranslator.php b/src/Translator/PartTranslator.php new file mode 100644 index 0000000..aad06f9 --- /dev/null +++ b/src/Translator/PartTranslator.php @@ -0,0 +1,53 @@ +> */ + private array $translations = []; + + /** + * @param array $parameters + */ + public function translate(string $key, array $parameters, string $locale): string + { + $message = $this->loadTranslations($locale)[$key]; + if ($message === '') { + return ''; + } + + $formatter = new MessageFormatter($locale, $message); + $translatedMessage = $formatter->format($parameters); + assert($translatedMessage !== false); + + return $translatedMessage; + } + + /** + * @return array + */ + private function loadTranslations(string $locale): array + { + $translations = $this->translations[$locale] ?? null; + + if ($translations !== null) { + return $translations; + } + + return $this->translations[$locale] = require $this->getTranslationFile($locale); + } + + private function getTranslationFile(string $locale): string + { + return __DIR__ . '/translations/' . $locale . '.php'; + } + +} diff --git a/src/Translator/translations/cs.php b/src/Translator/translations/cs.php new file mode 100644 index 0000000..533b8da --- /dev/null +++ b/src/Translator/translations/cs.php @@ -0,0 +1,166 @@ + ', ', + 'list' => '{values} a {lastValue}', + 'step-all-minute' => 'každou {step}. minutu', + 'step-all-hour' => 'každou {step}. hodinu', + 'step-all-day-of-week' => 'každý {step}. den v týdnu', + 'step-all-day-of-month' => 'každý {step}. den v měsíci', + 'step-all-month' => 'každý {step}. měsíc', + 'step-minute' => 'každou {step}. minutu {part}', + 'step-hour' => 'každou {step}. hodinu {part}', + 'step-day-of-week' => 'každý {step}. den v týdnu {part}', + 'step-day-of-month' => 'každý {step}. den v měsíci {part}', + 'step-month' => 'každý {step}. měsíc {part}', + 'range-minute' => 'od {left} do {right}', + 'range-minute-named' => 'každou minutu od {left} do {right}', + 'range-hour' => 'od {left} do {right}', + 'range-hour-named' => 'každou hodinu od {left} do {right}', + 'range-day-of-week' => 'od {left} do {right}', + 'range-day-of-week-named' => 'každý den v týdnu od {left} do {right}', + 'range-day-of-month' => 'od {left} do {right}', + 'range-day-of-month-named' => 'každý den v měsíci od {left} do {right}', + 'range-month' => 'od {left} do {right}', + 'range-month-named' => 'každý měsíc od {left} do {right}', + 'second' => '{second, plural, + one {každou sekundu} + few {každé # sekundy} + other {každých # sekund} + }', + 'every-minute' => 'každou minutu', + 'before-minute' => '', + 'minute' => '{minute}', + 'minute-named' => 'v minutě {minute}', + 'before-hour' => ' ', + 'hour' => '{hour}', + 'hour-named' => 'v hodině {hour}', + 'between-day-of-month-and-week' => ' a', + //TODO - v úterý, ve středu + list (v pondělí, středu | ve středu, sobotu) - přidat test + 'before-day-of-week' => ' v ', + 'before-day-of-week-every' => 'každé ', + //TODO - otestovat ve všech kontextech - dny i měsíce + 'day-of-week' => '{context, select, + step {{dayNumber, select, + 1 {pondělí} + 2 {úterý} + 3 {středy} + 4 {čtvrtka} + 5 {pátku} + 6 {soboty} + 7 {neděle} + other {{dayNumber} - unknown} + }} + range {{dayNumber, select, + 1 {pondělí} + 2 {úterý} + 3 {středy} + 4 {čtvrtka} + 5 {pátku} + 6 {soboty} + 7 {neděle} + other {{dayNumber} - unknown} + }} + other {{dayNumber, select, + 1 {pondělí} + 2 {úterý} + 3 {středu} + 4 {čtvrtek} + 5 {pátek} + 6 {sobotu} + 7 {neděli} + other {{dayNumber} - unknown} + }} + }', + 'day-of-week-nth' => '{nth}. {day}', + //TODO - day-of-week-21, 4, 3 + 'day-of-week-last' => '{context, select, + range {{dayNumber, select, + 1 {poslední pondělí} + 2 {poslední úterý} + 3 {poslední středy} + 4 {posledního čtvrtka} + 5 {posledního pátku} + 6 {poslední soboty} + 7 {poslední neděle} + other {{day} - unknown} + }} + other {poslední {day}} + }', + 'before-day-of-month' => ' ', + 'day-of-month' => '{day}', + 'day-of-month-named' => 've dni v měsíci {day}', + // TODO - kombinace + 'day-of-month-last-day' => 'v poslední den v měsíci', + // TODO - kombinace + 'day-of-month-last-weekday' => 'v poslední pracovní den', + 'day-of-month-nearest-weekday' => '{context, select, + range {pracovního dne nejbližšího k {day}.} + other {v pracovním dni nejbližším k {day}.} + }', + 'before-month' => ' v ', + 'month' => '{context, select, + step {{month, select, + 1 {ledna} + 2 {února} + 3 {března} + 4 {dubna} + 5 {května} + 6 {června} + 7 {července} + 8 {srpna} + 9 {září} + 10 {října} + 11 {listopadu} + 12 {prosince} + other {{month} - unknown} + }} + range {{month, select, + 1 {ledna} + 2 {února} + 3 {března} + 4 {dubna} + 5 {května} + 6 {června} + 7 {července} + 8 {srpna} + 9 {září} + 10 {října} + 11 {listopadu} + 12 {prosince} + other {{month} - unknown} + }} + other {{month, select, + 1 {lednu} + 2 {únoru} + 3 {březnu} + 4 {dubnu} + 5 {květnu} + 6 {červnu} + 7 {červenci} + 8 {srpnu} + 9 {září} + 10 {říjnu} + 11 {listopadu} + 12 {prosinci} + other {{month} - unknown} + }} + }', + 'hour+minute' => 'v {hour}:{minute}', + 'day-of-month+month' => '{day}. {month, select, + 1 {ledna} + 2 {února} + 3 {března} + 4 {dubna} + 5 {května} + 6 {června} + 7 {července} + 8 {srpna} + 9 {září} + 10 {října} + 11 {listopadu} + 12 {prosince} + other {{month} - unknown} + }', + 'timezone' => 'v časové zóně {tz}', +]; diff --git a/src/Translator/translations/en.php b/src/Translator/translations/en.php new file mode 100644 index 0000000..a1be54a --- /dev/null +++ b/src/Translator/translations/en.php @@ -0,0 +1,156 @@ + ', ', + 'list' => '{values} and {lastValue}', + 'step-all-minute' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } minute', + 'step-all-hour' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } hour', + 'step-all-day-of-week' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } day-of-week', + 'step-all-day-of-month' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } day-of-month', + 'step-all-month' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } month', + 'step-minute' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } minute {part}', + 'step-hour' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } hour {part}', + 'step-day-of-week' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } day-of-week {part}', + 'step-day-of-month' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } day-of-month {part}', + 'step-month' => 'every {step, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } month {part}', + 'range-minute' => 'from {left} through {right}', + 'range-minute-named' => 'every minute from {left} through {right}', + 'range-hour' => 'from {left} through {right}', + 'range-hour-named' => 'every hour from {left} through {right}', + 'range-day-of-week' => 'from {left} through {right}', + 'range-day-of-week-named' => 'every day-of-week from {left} through {right}', + 'range-day-of-month' => 'from {left} through {right}', + 'range-day-of-month-named' => 'every day-of-month from {left} through {right}', + 'range-month' => 'from {left} through {right}', + 'range-month-named' => 'every month from {left} through {right}', + 'second' => 'at every {second, plural, + one {second} + other {# seconds} + }', + 'before-minute' => 'at ', + 'every-minute' => 'every minute', + 'minute' => '{minute}', + 'minute-named' => 'minute {minute}', + 'before-hour' => ' past ', + 'hour' => '{hour}', + 'hour-named' => 'hour {hour}', + 'between-day-of-month-and-week' => ' and', + 'before-day-of-week' => ' on ', + 'before-day-of-week-every' => 'every ', + 'day-of-week' => '{dayNumber, select, + 1 {Monday} + 2 {Tuesday} + 3 {Wednesday} + 4 {Thursday} + 5 {Friday} + 6 {Saturday} + 7 {Sunday} + other {{dayNumber} - unknown} + }', + 'day-of-week-nth' => '{nth, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } {day}', + 'day-of-week-last' => 'the last {day}', + 'before-day-of-month' => ' on ', + 'day-of-month' => '{day}', + 'day-of-month-named' => 'day-of-month {day}', + 'day-of-month-last-day' => 'a last day-of-month', + 'day-of-month-last-weekday' => 'a last weekday', + 'day-of-month-nearest-weekday' => 'a weekday nearest to the {day, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + }', + 'before-month' => ' in ', + 'month' => '{month, select, + 1 {January} + 2 {February} + 3 {March} + 4 {April} + 5 {May} + 6 {June} + 7 {July} + 8 {August} + 9 {September} + 10 {October} + 11 {November} + 12 {December} + other {{month} - unknown} + }', + 'hour+minute' => 'at {hour}:{minute}', + 'day-of-month+month' => 'on {day, selectordinal, + one {#st} + two {#nd} + few {#rd} + other {#th} + } of {month, select, + 1 {January} + 2 {February} + 3 {March} + 4 {April} + 5 {May} + 6 {June} + 7 {July} + 8 {August} + 9 {September} + 10 {October} + 11 {November} + 12 {December} + other {{month} - unknown} + }', + 'timezone' => 'in {tz} time zone', +]; diff --git a/tests/Snapshots/TranslationsDataGenerator.php b/tests/Snapshots/TranslationsDataGenerator.php new file mode 100644 index 0000000..d52c46d --- /dev/null +++ b/tests/Snapshots/TranslationsDataGenerator.php @@ -0,0 +1,303 @@ +|null, + * 2?: string|null, + * } + * @phpstan-type T_MAIN_SOURCE array{ + * 0: string, + * 1: int<0,59>|null, + * 2: string|null, + * } + * @phpstan-type T_GENERATOR Generator + */ +final class TranslationsDataGenerator +{ + + /** + * @return array|null, + * timezone: string|null, + * english: string, + * translation: string, + * }>> + */ + public static function provideResultsGroupedByLocale(): array + { + $explainer = new DefaultCronExpressionExplainer(); + + $resultsByLocale = []; + foreach (self::provideData() as $key => [$expression, $repeatSeconds, $timezone]) { + $explained = $explainer->explainInLocales( + array_keys($explainer->getSupportedLocales()), + $expression, + $repeatSeconds, + $timezone !== null ? new DateTimeZone($timezone) : null, + ); + + foreach ($explained as $locale => $translation) { + $resultsByLocale[$locale][$key] = [ + 'expression' => $expression, + 'seconds' => $repeatSeconds, + 'timezone' => $timezone, + 'english' => $explained['en'], + 'translation' => $translation, + ]; + } + } + + return $resultsByLocale; + } + + public static function update(): void + { + FileSystem::delete(__DIR__ . '/translations'); + FileSystem::createDir(__DIR__ . '/translations'); + + foreach (self::provideResultsGroupedByLocale() as $locale => $localizedResults) { + file_put_contents( + __DIR__ . '/translations/' . $locale . '.json', + json_encode($localizedResults, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n", + ); + } + } + + /** + * @return Generator + */ + private static function provideData(): Generator + { + foreach (self::provideMinuteData() as $key => $data) { + yield "minute-$key" => self::fillData($data); + } + + foreach (self::provideHourData() as $key => $data) { + yield "hour-$key" => self::fillData($data); + } + + foreach (self::provideHourMinuteData() as $key => $data) { + yield "hour+minute-$key" => self::fillData($data); + } + + foreach (self::provideDayOfMonthData() as $key => $data) { + yield "day-of-month-$key" => self::fillData($data); + } + + foreach (self::provideDayOfWeekData() as $key => $data) { + yield "day-of-week-$key" => self::fillData($data); + } + + foreach (self::provideMonthData() as $key => $data) { + yield "month-$key" => self::fillData($data); + } + + foreach (self::provideMixedData() as $key => $data) { + yield "mixed-$key" => self::fillData($data); + } + } + + /** + * @param T_PARTIAL_SOURCE $data + * @return T_MAIN_SOURCE + */ + private static function fillData(array $data): array + { + if (!array_key_exists(1, $data)) { + $data[1] = null; + } + + if (!array_key_exists(2, $data)) { + $data[2] = null; + } + + return $data; + } + + /** + * @return T_GENERATOR + */ + private static function provideMinuteData(): Generator + { + yield ['* * * * *']; + yield ['1 * * * *']; + yield ['1,2 * * * *']; + yield ['1,2,3,4,5 * * * *']; + yield ['1-10 * * * *']; + yield ['*/2 * * * *']; + yield ['*/3 * * * *']; + yield ['*/4 * * * *']; + yield ['*/5 * * * *']; + yield ['1-10/2 * * * *']; + yield ['1-15,31-45 * * * *']; + yield ['1-30/2,40,41,42,45-50,51,52 * * * *']; + } + + /** + * @return T_GENERATOR + */ + private static function provideHourData(): Generator + { + yield ['* 1 * * *']; + yield ['* 1,2 * * *']; + yield ['* 1,2,3,4,5 * * *']; + yield ['* 1-10 * * *']; + yield ['* */2 * * *']; + yield ['* */3 * * *']; + yield ['* */4 * * *']; + yield ['* */5 * * *']; + yield ['* 1-10/2 * * *']; + yield ['* 1-5,11-15 * * *']; + yield ['* 1 * * *']; + yield ['* 1-5/2,6,7,8,10-12,13,14 * * *']; + } + + /** + * @return T_GENERATOR + */ + private static function provideHourMinuteData(): Generator + { + yield ['@midnight']; + yield ['0 0 * * *']; + yield ['10 0 * * *']; + yield ['0 10 * * *']; + } + + /** + * @return T_GENERATOR + */ + private static function provideDayOfMonthData(): Generator + { + yield ['* * 1 * *']; + yield ['* * 1,2 * *']; + yield ['* * 1,2,3,4,5 * *']; + yield ['* * 1-10 * *']; + yield ['* * */2 * *']; + yield ['* * */3 * *']; + yield ['* * */4 * *']; + yield ['* * */5 * *']; + yield ['* * 1-10/2 * *']; + yield ['* * 1-5,11-15 * *']; + yield ['* * 1-10/2,11,12,13,15-20,21,22 * *']; + yield ['* * 1W * *']; + yield ['* * 15W * *']; + yield ['* * 1W-5W * *']; + yield ['* * L * *']; + yield ['* * LW * *']; + } + + /** + * @return T_GENERATOR + */ + private static function provideDayOfWeekData(): Generator + { + yield ['* * * * MON']; + yield ['* * * * 1']; + yield ['* * * * 2']; + yield ['* * * * 3']; + yield ['* * * * 4']; + yield ['* * * * 5']; + yield ['* * * * 6']; + yield ['* * * * 7']; + yield ['* * * * 1,2']; + yield ['* * * * 1,2,3,4,5']; + yield ['* * * * 0-5']; + yield ['* * * * */2']; + yield ['* * * * */3']; + yield ['* * * * */4']; + yield ['* * * * */5']; + yield ['* * * * 0-5']; + yield ['* * * * 0-5/2']; + yield ['* * * * 0-2,3,4,5-6']; + yield ['* * * * 7#5']; + yield ['* * * * 7L']; + yield ['* * * * 7L,4']; + yield ['* * * * 4,7L']; + yield ['* * * * 7L,4L']; + yield ['* * * * 7L-4L']; + yield ['* * * * 7L-4']; + yield ['* * * * 7#5,3#3']; + yield ['* * * * 3#3-7#5']; + yield ['* * * * 7#5,3']; + yield ['* * * * 7#5-3']; + yield ['* * * * 3-7#5']; + } + + /** + * @return T_GENERATOR + */ + private static function provideMonthData(): Generator + { + yield ['* * * JAN *']; + yield ['* * * 1 *']; + yield ['* * * 2 *']; + yield ['* * * 3 *']; + yield ['* * * 4 *']; + yield ['* * * 5 *']; + yield ['* * * 6 *']; + yield ['* * * 7 *']; + yield ['* * * 8 *']; + yield ['* * * 9 *']; + yield ['* * * 10 *']; + yield ['* * * 11 *']; + yield ['* * * 12 *']; + yield ['* * * 1,2 *']; + yield ['* * * 1,2,3,4,5 *']; + yield ['* * * 1-11 *']; + yield ['* * * */2 *']; + yield ['* * * */3 *']; + yield ['* * * */4 *']; + yield ['* * * */5 *']; + yield ['* * * 1-11/2 *']; + yield ['* * * 1-2,3,4,5-6 *']; + } + + /** + * @return T_GENERATOR + */ + private static function provideMixedData(): Generator + { + yield [ + '* * * * *', + 1, + ]; + + yield [ + '* * * * *', + 30, + ]; + + yield [ + '* * * * *', + null, + 'Europe/Prague', + ]; + + yield [ + '10 * * * *', + 10, + ]; + + yield ['30 10 1 2 *']; + yield ['* * 1 * 1']; + yield ['* * 1 * 1,2']; + yield ['0-59 0-23 1-31 1-12 0-6']; + } + +} diff --git a/tests/Snapshots/TranslationsSnapshotTest.php b/tests/Snapshots/TranslationsSnapshotTest.php new file mode 100644 index 0000000..688e94e --- /dev/null +++ b/tests/Snapshots/TranslationsSnapshotTest.php @@ -0,0 +1,42 @@ + $expectedData + * + * @dataProvider provide + */ + public function test(string $locale, array $expectedData): void + { + $givenData = file_get_contents(__DIR__ . '/translations/' . $locale . '.json'); + self::assertNotFalse($givenData); + + self::assertSame( + $expectedData, + json_decode( + $givenData, + true, + 512, + JSON_THROW_ON_ERROR, + ), + ); + } + + public function provide(): Generator + { + foreach (TranslationsDataGenerator::provideResultsGroupedByLocale() as $locale => $localizedResults) { + yield [$locale, $localizedResults]; + } + } + +} diff --git a/tests/Snapshots/translations/cs.json b/tests/Snapshots/translations/cs.json new file mode 100644 index 0000000..4ead1f0 --- /dev/null +++ b/tests/Snapshots/translations/cs.json @@ -0,0 +1,730 @@ +{ + "minute-0": { + "expression": "* * * * *", + "seconds": null, + "timezone": null, + "english": "At every minute.", + "translation": "Každou minutu." + }, + "minute-1": { + "expression": "1 * * * *", + "seconds": null, + "timezone": null, + "english": "At minute 1.", + "translation": "V minutě 1." + }, + "minute-2": { + "expression": "1,2 * * * *", + "seconds": null, + "timezone": null, + "english": "At minute 1 and 2.", + "translation": "V minutě 1 a 2." + }, + "minute-3": { + "expression": "1,2,3,4,5 * * * *", + "seconds": null, + "timezone": null, + "english": "At minute 1, 2, 3, 4 and 5.", + "translation": "V minutě 1, 2, 3, 4 a 5." + }, + "minute-4": { + "expression": "1-10 * * * *", + "seconds": null, + "timezone": null, + "english": "At every minute from 1 through 10.", + "translation": "Každou minutu od 1 do 10." + }, + "minute-5": { + "expression": "*\/2 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 2nd minute.", + "translation": "Každou 2. minutu." + }, + "minute-6": { + "expression": "*\/3 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 3rd minute.", + "translation": "Každou 3. minutu." + }, + "minute-7": { + "expression": "*\/4 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 4th minute.", + "translation": "Každou 4. minutu." + }, + "minute-8": { + "expression": "*\/5 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 5th minute.", + "translation": "Každou 5. minutu." + }, + "minute-9": { + "expression": "1-10\/2 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 2nd minute from 1 through 10.", + "translation": "Každou 2. minutu od 1 do 10." + }, + "minute-10": { + "expression": "1-15,31-45 * * * *", + "seconds": null, + "timezone": null, + "english": "At every minute from 1 through 15 and from 31 through 45.", + "translation": "Každou minutu od 1 do 15 a od 31 do 45." + }, + "minute-11": { + "expression": "1-30\/2,40,41,42,45-50,51,52 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 2nd minute from 1 through 30, 40, 41, 42, from 45 through 50, 51 and 52.", + "translation": "Každou 2. minutu od 1 do 30, 40, 41, 42, od 45 do 50, 51 a 52." + }, + "hour-0": { + "expression": "* 1 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1.", + "translation": "Každou minutu v hodině 1." + }, + "hour-1": { + "expression": "* 1,2 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1 and 2.", + "translation": "Každou minutu v hodině 1 a 2." + }, + "hour-2": { + "expression": "* 1,2,3,4,5 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1, 2, 3, 4 and 5.", + "translation": "Každou minutu v hodině 1, 2, 3, 4 a 5." + }, + "hour-3": { + "expression": "* 1-10 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every hour from 1 through 10.", + "translation": "Každou minutu každou hodinu od 1 do 10." + }, + "hour-4": { + "expression": "* *\/2 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 2nd hour.", + "translation": "Každou minutu každou 2. hodinu." + }, + "hour-5": { + "expression": "* *\/3 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 3rd hour.", + "translation": "Každou minutu každou 3. hodinu." + }, + "hour-6": { + "expression": "* *\/4 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 4th hour.", + "translation": "Každou minutu každou 4. hodinu." + }, + "hour-7": { + "expression": "* *\/5 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 5th hour.", + "translation": "Každou minutu každou 5. hodinu." + }, + "hour-8": { + "expression": "* 1-10\/2 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 2nd hour from 1 through 10.", + "translation": "Každou minutu každou 2. hodinu od 1 do 10." + }, + "hour-9": { + "expression": "* 1-5,11-15 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every hour from 1 through 5 and from 11 through 15.", + "translation": "Každou minutu každou hodinu od 1 do 5 a od 11 do 15." + }, + "hour-10": { + "expression": "* 1 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1.", + "translation": "Každou minutu v hodině 1." + }, + "hour-11": { + "expression": "* 1-5\/2,6,7,8,10-12,13,14 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 2nd hour from 1 through 5, 6, 7, 8, from 10 through 12, 13 and 14.", + "translation": "Každou minutu každou 2. hodinu od 1 do 5, 6, 7, 8, od 10 do 12, 13 a 14." + }, + "hour+minute-0": { + "expression": "@midnight", + "seconds": null, + "timezone": null, + "english": "At 00:00.", + "translation": "V 00:00." + }, + "hour+minute-1": { + "expression": "0 0 * * *", + "seconds": null, + "timezone": null, + "english": "At 00:00.", + "translation": "V 00:00." + }, + "hour+minute-2": { + "expression": "10 0 * * *", + "seconds": null, + "timezone": null, + "english": "At 00:10.", + "translation": "V 00:10." + }, + "hour+minute-3": { + "expression": "0 10 * * *", + "seconds": null, + "timezone": null, + "english": "At 10:00.", + "translation": "V 10:00." + }, + "day-of-month-0": { + "expression": "* * 1 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1.", + "translation": "Každou minutu ve dni v měsíci 1." + }, + "day-of-month-1": { + "expression": "* * 1,2 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1 and 2.", + "translation": "Každou minutu ve dni v měsíci 1 a 2." + }, + "day-of-month-2": { + "expression": "* * 1,2,3,4,5 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1, 2, 3, 4 and 5.", + "translation": "Každou minutu ve dni v měsíci 1, 2, 3, 4 a 5." + }, + "day-of-month-3": { + "expression": "* * 1-10 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-month from 1 through 10.", + "translation": "Každou minutu každý den v měsíci od 1 do 10." + }, + "day-of-month-4": { + "expression": "* * *\/2 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-month.", + "translation": "Každou minutu každý 2. den v měsíci." + }, + "day-of-month-5": { + "expression": "* * *\/3 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 3rd day-of-month.", + "translation": "Každou minutu každý 3. den v měsíci." + }, + "day-of-month-6": { + "expression": "* * *\/4 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 4th day-of-month.", + "translation": "Každou minutu každý 4. den v měsíci." + }, + "day-of-month-7": { + "expression": "* * *\/5 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 5th day-of-month.", + "translation": "Každou minutu každý 5. den v měsíci." + }, + "day-of-month-8": { + "expression": "* * 1-10\/2 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-month from 1 through 10.", + "translation": "Každou minutu každý 2. den v měsíci od 1 do 10." + }, + "day-of-month-9": { + "expression": "* * 1-5,11-15 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-month from 1 through 5 and from 11 through 15.", + "translation": "Každou minutu každý den v měsíci od 1 do 5 a od 11 do 15." + }, + "day-of-month-10": { + "expression": "* * 1-10\/2,11,12,13,15-20,21,22 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-month from 1 through 10, 11, 12, 13, from 15 through 20, 21 and 22.", + "translation": "Každou minutu každý 2. den v měsíci od 1 do 10, 11, 12, 13, od 15 do 20, 21 a 22." + }, + "day-of-month-11": { + "expression": "* * 1W * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a weekday nearest to the 1st.", + "translation": "Každou minutu v pracovním dni nejbližším k 1." + }, + "day-of-month-12": { + "expression": "* * 15W * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a weekday nearest to the 15th.", + "translation": "Každou minutu v pracovním dni nejbližším k 15." + }, + "day-of-month-13": { + "expression": "* * 1W-5W * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-month from a weekday nearest to the 1st through a weekday nearest to the 5th.", + "translation": "Každou minutu každý den v měsíci od pracovního dne nejbližšího k 1. do pracovního dne nejbližšího k 5." + }, + "day-of-month-14": { + "expression": "* * L * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a last day-of-month.", + "translation": "Každou minutu v poslední den v měsíci." + }, + "day-of-month-15": { + "expression": "* * LW * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a last weekday.", + "translation": "Každou minutu v poslední pracovní den." + }, + "day-of-week-0": { + "expression": "* * * * MON", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday.", + "translation": "Každou minutu v pondělí." + }, + "day-of-week-1": { + "expression": "* * * * 1", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday.", + "translation": "Každou minutu v pondělí." + }, + "day-of-week-2": { + "expression": "* * * * 2", + "seconds": null, + "timezone": null, + "english": "At every minute on Tuesday.", + "translation": "Každou minutu v úterý." + }, + "day-of-week-3": { + "expression": "* * * * 3", + "seconds": null, + "timezone": null, + "english": "At every minute on Wednesday.", + "translation": "Každou minutu v středu." + }, + "day-of-week-4": { + "expression": "* * * * 4", + "seconds": null, + "timezone": null, + "english": "At every minute on Thursday.", + "translation": "Každou minutu v čtvrtek." + }, + "day-of-week-5": { + "expression": "* * * * 5", + "seconds": null, + "timezone": null, + "english": "At every minute on Friday.", + "translation": "Každou minutu v pátek." + }, + "day-of-week-6": { + "expression": "* * * * 6", + "seconds": null, + "timezone": null, + "english": "At every minute on Saturday.", + "translation": "Každou minutu v sobotu." + }, + "day-of-week-7": { + "expression": "* * * * 7", + "seconds": null, + "timezone": null, + "english": "At every minute on Sunday.", + "translation": "Každou minutu v neděli." + }, + "day-of-week-8": { + "expression": "* * * * 1,2", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday and Tuesday.", + "translation": "Každou minutu v pondělí a úterý." + }, + "day-of-week-9": { + "expression": "* * * * 1,2,3,4,5", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday, Tuesday, Wednesday, Thursday and Friday.", + "translation": "Každou minutu v pondělí, úterý, středu, čtvrtek a pátek." + }, + "day-of-week-10": { + "expression": "* * * * 0-5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Sunday through Friday.", + "translation": "Každou minutu v každý den v týdnu od neděle do pátku." + }, + "day-of-week-11": { + "expression": "* * * * *\/2", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-week.", + "translation": "Každou minutu v každý 2. den v týdnu." + }, + "day-of-week-12": { + "expression": "* * * * *\/3", + "seconds": null, + "timezone": null, + "english": "At every minute on every 3rd day-of-week.", + "translation": "Každou minutu v každý 3. den v týdnu." + }, + "day-of-week-13": { + "expression": "* * * * *\/4", + "seconds": null, + "timezone": null, + "english": "At every minute on every 4th day-of-week.", + "translation": "Každou minutu v každý 4. den v týdnu." + }, + "day-of-week-14": { + "expression": "* * * * *\/5", + "seconds": null, + "timezone": null, + "english": "At every minute on every 5th day-of-week.", + "translation": "Každou minutu v každý 5. den v týdnu." + }, + "day-of-week-15": { + "expression": "* * * * 0-5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Sunday through Friday.", + "translation": "Každou minutu v každý den v týdnu od neděle do pátku." + }, + "day-of-week-16": { + "expression": "* * * * 0-5\/2", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-week from Sunday through Friday.", + "translation": "Každou minutu v každý 2. den v týdnu od neděle do pátku." + }, + "day-of-week-17": { + "expression": "* * * * 0-2,3,4,5-6", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Sunday through Tuesday, Wednesday, Thursday and from Friday through Saturday.", + "translation": "Každou minutu v každý den v týdnu od neděle do úterý, středu, čtvrtek a od pátku do soboty." + }, + "day-of-week-18": { + "expression": "* * * * 7#5", + "seconds": null, + "timezone": null, + "english": "At every minute on 5th Sunday.", + "translation": "Každou minutu v 5. neděli." + }, + "day-of-week-19": { + "expression": "* * * * 7L", + "seconds": null, + "timezone": null, + "english": "At every minute on the last Sunday.", + "translation": "Každou minutu v poslední neděli." + }, + "day-of-week-20": { + "expression": "* * * * 7L,4", + "seconds": null, + "timezone": null, + "english": "At every minute on the last Sunday and Thursday.", + "translation": "Každou minutu v poslední neděli a čtvrtek." + }, + "day-of-week-21": { + "expression": "* * * * 4,7L", + "seconds": null, + "timezone": null, + "english": "At every minute on Thursday and the last Sunday.", + "translation": "Každou minutu v čtvrtek a poslední neděli." + }, + "day-of-week-22": { + "expression": "* * * * 7L,4L", + "seconds": null, + "timezone": null, + "english": "At every minute on the last Sunday and the last Thursday.", + "translation": "Každou minutu v poslední neděli a poslední čtvrtek." + }, + "day-of-week-23": { + "expression": "* * * * 7L-4L", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from the last Sunday through the last Thursday.", + "translation": "Každou minutu v každý den v týdnu od poslední neděle do posledního čtvrtka." + }, + "day-of-week-24": { + "expression": "* * * * 7L-4", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from the last Sunday through Thursday.", + "translation": "Každou minutu v každý den v týdnu od poslední neděle do čtvrtka." + }, + "day-of-week-25": { + "expression": "* * * * 7#5,3#3", + "seconds": null, + "timezone": null, + "english": "At every minute on 5th Sunday and 3rd Wednesday.", + "translation": "Každou minutu v 5. neděli a 3. středu." + }, + "day-of-week-26": { + "expression": "* * * * 3#3-7#5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from 3rd Wednesday through 5th Sunday.", + "translation": "Každou minutu v každý den v týdnu od 3. středy do 5. neděle." + }, + "day-of-week-27": { + "expression": "* * * * 7#5,3", + "seconds": null, + "timezone": null, + "english": "At every minute on 5th Sunday and Wednesday.", + "translation": "Každou minutu v 5. neděli a středu." + }, + "day-of-week-28": { + "expression": "* * * * 7#5-3", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from 5th Sunday through Wednesday.", + "translation": "Každou minutu v každý den v týdnu od 5. neděle do středy." + }, + "day-of-week-29": { + "expression": "* * * * 3-7#5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Wednesday through 5th Sunday.", + "translation": "Každou minutu v každý den v týdnu od středy do 5. neděle." + }, + "month-0": { + "expression": "* * * JAN *", + "seconds": null, + "timezone": null, + "english": "At every minute in January.", + "translation": "Každou minutu v lednu." + }, + "month-1": { + "expression": "* * * 1 *", + "seconds": null, + "timezone": null, + "english": "At every minute in January.", + "translation": "Každou minutu v lednu." + }, + "month-2": { + "expression": "* * * 2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in February.", + "translation": "Každou minutu v únoru." + }, + "month-3": { + "expression": "* * * 3 *", + "seconds": null, + "timezone": null, + "english": "At every minute in March.", + "translation": "Každou minutu v březnu." + }, + "month-4": { + "expression": "* * * 4 *", + "seconds": null, + "timezone": null, + "english": "At every minute in April.", + "translation": "Každou minutu v dubnu." + }, + "month-5": { + "expression": "* * * 5 *", + "seconds": null, + "timezone": null, + "english": "At every minute in May.", + "translation": "Každou minutu v květnu." + }, + "month-6": { + "expression": "* * * 6 *", + "seconds": null, + "timezone": null, + "english": "At every minute in June.", + "translation": "Každou minutu v červnu." + }, + "month-7": { + "expression": "* * * 7 *", + "seconds": null, + "timezone": null, + "english": "At every minute in July.", + "translation": "Každou minutu v červenci." + }, + "month-8": { + "expression": "* * * 8 *", + "seconds": null, + "timezone": null, + "english": "At every minute in August.", + "translation": "Každou minutu v srpnu." + }, + "month-9": { + "expression": "* * * 9 *", + "seconds": null, + "timezone": null, + "english": "At every minute in September.", + "translation": "Každou minutu v září." + }, + "month-10": { + "expression": "* * * 10 *", + "seconds": null, + "timezone": null, + "english": "At every minute in October.", + "translation": "Každou minutu v říjnu." + }, + "month-11": { + "expression": "* * * 11 *", + "seconds": null, + "timezone": null, + "english": "At every minute in November.", + "translation": "Každou minutu v listopadu." + }, + "month-12": { + "expression": "* * * 12 *", + "seconds": null, + "timezone": null, + "english": "At every minute in December.", + "translation": "Každou minutu v prosinci." + }, + "month-13": { + "expression": "* * * 1,2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in January and February.", + "translation": "Každou minutu v lednu a únoru." + }, + "month-14": { + "expression": "* * * 1,2,3,4,5 *", + "seconds": null, + "timezone": null, + "english": "At every minute in January, February, March, April and May.", + "translation": "Každou minutu v lednu, únoru, březnu, dubnu a květnu." + }, + "month-15": { + "expression": "* * * 1-11 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every month from January through November.", + "translation": "Každou minutu v každý měsíc od ledna do listopadu." + }, + "month-16": { + "expression": "* * * *\/2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 2nd month.", + "translation": "Každou minutu v každý 2. měsíc." + }, + "month-17": { + "expression": "* * * *\/3 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 3rd month.", + "translation": "Každou minutu v každý 3. měsíc." + }, + "month-18": { + "expression": "* * * *\/4 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 4th month.", + "translation": "Každou minutu v každý 4. měsíc." + }, + "month-19": { + "expression": "* * * *\/5 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 5th month.", + "translation": "Každou minutu v každý 5. měsíc." + }, + "month-20": { + "expression": "* * * 1-11\/2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 2nd month from January through November.", + "translation": "Každou minutu v každý 2. měsíc od ledna do listopadu." + }, + "month-21": { + "expression": "* * * 1-2,3,4,5-6 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every month from January through February, March, April and from May through June.", + "translation": "Každou minutu v každý měsíc od ledna do února, březnu, dubnu a od května do června." + }, + "mixed-0": { + "expression": "* * * * *", + "seconds": 1, + "timezone": null, + "english": "At every second.", + "translation": "Každou sekundu." + }, + "mixed-1": { + "expression": "* * * * *", + "seconds": 30, + "timezone": null, + "english": "At every 30 seconds.", + "translation": "Každých 30 sekund." + }, + "mixed-2": { + "expression": "* * * * *", + "seconds": null, + "timezone": "Europe\/Prague", + "english": "At every minute in Europe\/Prague time zone.", + "translation": "Každou minutu v časové zóně Europe\/Prague." + }, + "mixed-3": { + "expression": "10 * * * *", + "seconds": 10, + "timezone": null, + "english": "At every 10 seconds at minute 10.", + "translation": "Každých 10 sekund v minutě 10." + }, + "mixed-4": { + "expression": "30 10 1 2 *", + "seconds": null, + "timezone": null, + "english": "At 10:30 on 1st of February.", + "translation": "V 10:30 1. února." + }, + "mixed-5": { + "expression": "* * 1 * 1", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1 and on every Monday.", + "translation": "Každou minutu ve dni v měsíci 1 a v každé pondělí." + }, + "mixed-6": { + "expression": "* * 1 * 1,2", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1 and on every Monday and Tuesday.", + "translation": "Každou minutu ve dni v měsíci 1 a v každé pondělí a úterý." + }, + "mixed-7": { + "expression": "0-59 0-23 1-31 1-12 0-6", + "seconds": null, + "timezone": null, + "english": "At every minute from 0 through 59 past every hour from 0 through 23 on every day-of-month from 1 through 31 and on every day-of-week from Sunday through Saturday in every month from January through December.", + "translation": "Každou minutu od 0 do 59 každou hodinu od 0 do 23 každý den v měsíci od 1 do 31 a v každý den v týdnu od neděle do soboty v každý měsíc od ledna do prosince." + } +} diff --git a/tests/Snapshots/translations/en.json b/tests/Snapshots/translations/en.json new file mode 100644 index 0000000..1db4402 --- /dev/null +++ b/tests/Snapshots/translations/en.json @@ -0,0 +1,730 @@ +{ + "minute-0": { + "expression": "* * * * *", + "seconds": null, + "timezone": null, + "english": "At every minute.", + "translation": "At every minute." + }, + "minute-1": { + "expression": "1 * * * *", + "seconds": null, + "timezone": null, + "english": "At minute 1.", + "translation": "At minute 1." + }, + "minute-2": { + "expression": "1,2 * * * *", + "seconds": null, + "timezone": null, + "english": "At minute 1 and 2.", + "translation": "At minute 1 and 2." + }, + "minute-3": { + "expression": "1,2,3,4,5 * * * *", + "seconds": null, + "timezone": null, + "english": "At minute 1, 2, 3, 4 and 5.", + "translation": "At minute 1, 2, 3, 4 and 5." + }, + "minute-4": { + "expression": "1-10 * * * *", + "seconds": null, + "timezone": null, + "english": "At every minute from 1 through 10.", + "translation": "At every minute from 1 through 10." + }, + "minute-5": { + "expression": "*\/2 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 2nd minute.", + "translation": "At every 2nd minute." + }, + "minute-6": { + "expression": "*\/3 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 3rd minute.", + "translation": "At every 3rd minute." + }, + "minute-7": { + "expression": "*\/4 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 4th minute.", + "translation": "At every 4th minute." + }, + "minute-8": { + "expression": "*\/5 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 5th minute.", + "translation": "At every 5th minute." + }, + "minute-9": { + "expression": "1-10\/2 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 2nd minute from 1 through 10.", + "translation": "At every 2nd minute from 1 through 10." + }, + "minute-10": { + "expression": "1-15,31-45 * * * *", + "seconds": null, + "timezone": null, + "english": "At every minute from 1 through 15 and from 31 through 45.", + "translation": "At every minute from 1 through 15 and from 31 through 45." + }, + "minute-11": { + "expression": "1-30\/2,40,41,42,45-50,51,52 * * * *", + "seconds": null, + "timezone": null, + "english": "At every 2nd minute from 1 through 30, 40, 41, 42, from 45 through 50, 51 and 52.", + "translation": "At every 2nd minute from 1 through 30, 40, 41, 42, from 45 through 50, 51 and 52." + }, + "hour-0": { + "expression": "* 1 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1.", + "translation": "At every minute past hour 1." + }, + "hour-1": { + "expression": "* 1,2 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1 and 2.", + "translation": "At every minute past hour 1 and 2." + }, + "hour-2": { + "expression": "* 1,2,3,4,5 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1, 2, 3, 4 and 5.", + "translation": "At every minute past hour 1, 2, 3, 4 and 5." + }, + "hour-3": { + "expression": "* 1-10 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every hour from 1 through 10.", + "translation": "At every minute past every hour from 1 through 10." + }, + "hour-4": { + "expression": "* *\/2 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 2nd hour.", + "translation": "At every minute past every 2nd hour." + }, + "hour-5": { + "expression": "* *\/3 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 3rd hour.", + "translation": "At every minute past every 3rd hour." + }, + "hour-6": { + "expression": "* *\/4 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 4th hour.", + "translation": "At every minute past every 4th hour." + }, + "hour-7": { + "expression": "* *\/5 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 5th hour.", + "translation": "At every minute past every 5th hour." + }, + "hour-8": { + "expression": "* 1-10\/2 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 2nd hour from 1 through 10.", + "translation": "At every minute past every 2nd hour from 1 through 10." + }, + "hour-9": { + "expression": "* 1-5,11-15 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every hour from 1 through 5 and from 11 through 15.", + "translation": "At every minute past every hour from 1 through 5 and from 11 through 15." + }, + "hour-10": { + "expression": "* 1 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past hour 1.", + "translation": "At every minute past hour 1." + }, + "hour-11": { + "expression": "* 1-5\/2,6,7,8,10-12,13,14 * * *", + "seconds": null, + "timezone": null, + "english": "At every minute past every 2nd hour from 1 through 5, 6, 7, 8, from 10 through 12, 13 and 14.", + "translation": "At every minute past every 2nd hour from 1 through 5, 6, 7, 8, from 10 through 12, 13 and 14." + }, + "hour+minute-0": { + "expression": "@midnight", + "seconds": null, + "timezone": null, + "english": "At 00:00.", + "translation": "At 00:00." + }, + "hour+minute-1": { + "expression": "0 0 * * *", + "seconds": null, + "timezone": null, + "english": "At 00:00.", + "translation": "At 00:00." + }, + "hour+minute-2": { + "expression": "10 0 * * *", + "seconds": null, + "timezone": null, + "english": "At 00:10.", + "translation": "At 00:10." + }, + "hour+minute-3": { + "expression": "0 10 * * *", + "seconds": null, + "timezone": null, + "english": "At 10:00.", + "translation": "At 10:00." + }, + "day-of-month-0": { + "expression": "* * 1 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1.", + "translation": "At every minute on day-of-month 1." + }, + "day-of-month-1": { + "expression": "* * 1,2 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1 and 2.", + "translation": "At every minute on day-of-month 1 and 2." + }, + "day-of-month-2": { + "expression": "* * 1,2,3,4,5 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1, 2, 3, 4 and 5.", + "translation": "At every minute on day-of-month 1, 2, 3, 4 and 5." + }, + "day-of-month-3": { + "expression": "* * 1-10 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-month from 1 through 10.", + "translation": "At every minute on every day-of-month from 1 through 10." + }, + "day-of-month-4": { + "expression": "* * *\/2 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-month.", + "translation": "At every minute on every 2nd day-of-month." + }, + "day-of-month-5": { + "expression": "* * *\/3 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 3rd day-of-month.", + "translation": "At every minute on every 3rd day-of-month." + }, + "day-of-month-6": { + "expression": "* * *\/4 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 4th day-of-month.", + "translation": "At every minute on every 4th day-of-month." + }, + "day-of-month-7": { + "expression": "* * *\/5 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 5th day-of-month.", + "translation": "At every minute on every 5th day-of-month." + }, + "day-of-month-8": { + "expression": "* * 1-10\/2 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-month from 1 through 10.", + "translation": "At every minute on every 2nd day-of-month from 1 through 10." + }, + "day-of-month-9": { + "expression": "* * 1-5,11-15 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-month from 1 through 5 and from 11 through 15.", + "translation": "At every minute on every day-of-month from 1 through 5 and from 11 through 15." + }, + "day-of-month-10": { + "expression": "* * 1-10\/2,11,12,13,15-20,21,22 * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-month from 1 through 10, 11, 12, 13, from 15 through 20, 21 and 22.", + "translation": "At every minute on every 2nd day-of-month from 1 through 10, 11, 12, 13, from 15 through 20, 21 and 22." + }, + "day-of-month-11": { + "expression": "* * 1W * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a weekday nearest to the 1st.", + "translation": "At every minute on a weekday nearest to the 1st." + }, + "day-of-month-12": { + "expression": "* * 15W * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a weekday nearest to the 15th.", + "translation": "At every minute on a weekday nearest to the 15th." + }, + "day-of-month-13": { + "expression": "* * 1W-5W * *", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-month from a weekday nearest to the 1st through a weekday nearest to the 5th.", + "translation": "At every minute on every day-of-month from a weekday nearest to the 1st through a weekday nearest to the 5th." + }, + "day-of-month-14": { + "expression": "* * L * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a last day-of-month.", + "translation": "At every minute on a last day-of-month." + }, + "day-of-month-15": { + "expression": "* * LW * *", + "seconds": null, + "timezone": null, + "english": "At every minute on a last weekday.", + "translation": "At every minute on a last weekday." + }, + "day-of-week-0": { + "expression": "* * * * MON", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday.", + "translation": "At every minute on Monday." + }, + "day-of-week-1": { + "expression": "* * * * 1", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday.", + "translation": "At every minute on Monday." + }, + "day-of-week-2": { + "expression": "* * * * 2", + "seconds": null, + "timezone": null, + "english": "At every minute on Tuesday.", + "translation": "At every minute on Tuesday." + }, + "day-of-week-3": { + "expression": "* * * * 3", + "seconds": null, + "timezone": null, + "english": "At every minute on Wednesday.", + "translation": "At every minute on Wednesday." + }, + "day-of-week-4": { + "expression": "* * * * 4", + "seconds": null, + "timezone": null, + "english": "At every minute on Thursday.", + "translation": "At every minute on Thursday." + }, + "day-of-week-5": { + "expression": "* * * * 5", + "seconds": null, + "timezone": null, + "english": "At every minute on Friday.", + "translation": "At every minute on Friday." + }, + "day-of-week-6": { + "expression": "* * * * 6", + "seconds": null, + "timezone": null, + "english": "At every minute on Saturday.", + "translation": "At every minute on Saturday." + }, + "day-of-week-7": { + "expression": "* * * * 7", + "seconds": null, + "timezone": null, + "english": "At every minute on Sunday.", + "translation": "At every minute on Sunday." + }, + "day-of-week-8": { + "expression": "* * * * 1,2", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday and Tuesday.", + "translation": "At every minute on Monday and Tuesday." + }, + "day-of-week-9": { + "expression": "* * * * 1,2,3,4,5", + "seconds": null, + "timezone": null, + "english": "At every minute on Monday, Tuesday, Wednesday, Thursday and Friday.", + "translation": "At every minute on Monday, Tuesday, Wednesday, Thursday and Friday." + }, + "day-of-week-10": { + "expression": "* * * * 0-5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Sunday through Friday.", + "translation": "At every minute on every day-of-week from Sunday through Friday." + }, + "day-of-week-11": { + "expression": "* * * * *\/2", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-week.", + "translation": "At every minute on every 2nd day-of-week." + }, + "day-of-week-12": { + "expression": "* * * * *\/3", + "seconds": null, + "timezone": null, + "english": "At every minute on every 3rd day-of-week.", + "translation": "At every minute on every 3rd day-of-week." + }, + "day-of-week-13": { + "expression": "* * * * *\/4", + "seconds": null, + "timezone": null, + "english": "At every minute on every 4th day-of-week.", + "translation": "At every minute on every 4th day-of-week." + }, + "day-of-week-14": { + "expression": "* * * * *\/5", + "seconds": null, + "timezone": null, + "english": "At every minute on every 5th day-of-week.", + "translation": "At every minute on every 5th day-of-week." + }, + "day-of-week-15": { + "expression": "* * * * 0-5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Sunday through Friday.", + "translation": "At every minute on every day-of-week from Sunday through Friday." + }, + "day-of-week-16": { + "expression": "* * * * 0-5\/2", + "seconds": null, + "timezone": null, + "english": "At every minute on every 2nd day-of-week from Sunday through Friday.", + "translation": "At every minute on every 2nd day-of-week from Sunday through Friday." + }, + "day-of-week-17": { + "expression": "* * * * 0-2,3,4,5-6", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Sunday through Tuesday, Wednesday, Thursday and from Friday through Saturday.", + "translation": "At every minute on every day-of-week from Sunday through Tuesday, Wednesday, Thursday and from Friday through Saturday." + }, + "day-of-week-18": { + "expression": "* * * * 7#5", + "seconds": null, + "timezone": null, + "english": "At every minute on 5th Sunday.", + "translation": "At every minute on 5th Sunday." + }, + "day-of-week-19": { + "expression": "* * * * 7L", + "seconds": null, + "timezone": null, + "english": "At every minute on the last Sunday.", + "translation": "At every minute on the last Sunday." + }, + "day-of-week-20": { + "expression": "* * * * 7L,4", + "seconds": null, + "timezone": null, + "english": "At every minute on the last Sunday and Thursday.", + "translation": "At every minute on the last Sunday and Thursday." + }, + "day-of-week-21": { + "expression": "* * * * 4,7L", + "seconds": null, + "timezone": null, + "english": "At every minute on Thursday and the last Sunday.", + "translation": "At every minute on Thursday and the last Sunday." + }, + "day-of-week-22": { + "expression": "* * * * 7L,4L", + "seconds": null, + "timezone": null, + "english": "At every minute on the last Sunday and the last Thursday.", + "translation": "At every minute on the last Sunday and the last Thursday." + }, + "day-of-week-23": { + "expression": "* * * * 7L-4L", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from the last Sunday through the last Thursday.", + "translation": "At every minute on every day-of-week from the last Sunday through the last Thursday." + }, + "day-of-week-24": { + "expression": "* * * * 7L-4", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from the last Sunday through Thursday.", + "translation": "At every minute on every day-of-week from the last Sunday through Thursday." + }, + "day-of-week-25": { + "expression": "* * * * 7#5,3#3", + "seconds": null, + "timezone": null, + "english": "At every minute on 5th Sunday and 3rd Wednesday.", + "translation": "At every minute on 5th Sunday and 3rd Wednesday." + }, + "day-of-week-26": { + "expression": "* * * * 3#3-7#5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from 3rd Wednesday through 5th Sunday.", + "translation": "At every minute on every day-of-week from 3rd Wednesday through 5th Sunday." + }, + "day-of-week-27": { + "expression": "* * * * 7#5,3", + "seconds": null, + "timezone": null, + "english": "At every minute on 5th Sunday and Wednesday.", + "translation": "At every minute on 5th Sunday and Wednesday." + }, + "day-of-week-28": { + "expression": "* * * * 7#5-3", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from 5th Sunday through Wednesday.", + "translation": "At every minute on every day-of-week from 5th Sunday through Wednesday." + }, + "day-of-week-29": { + "expression": "* * * * 3-7#5", + "seconds": null, + "timezone": null, + "english": "At every minute on every day-of-week from Wednesday through 5th Sunday.", + "translation": "At every minute on every day-of-week from Wednesday through 5th Sunday." + }, + "month-0": { + "expression": "* * * JAN *", + "seconds": null, + "timezone": null, + "english": "At every minute in January.", + "translation": "At every minute in January." + }, + "month-1": { + "expression": "* * * 1 *", + "seconds": null, + "timezone": null, + "english": "At every minute in January.", + "translation": "At every minute in January." + }, + "month-2": { + "expression": "* * * 2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in February.", + "translation": "At every minute in February." + }, + "month-3": { + "expression": "* * * 3 *", + "seconds": null, + "timezone": null, + "english": "At every minute in March.", + "translation": "At every minute in March." + }, + "month-4": { + "expression": "* * * 4 *", + "seconds": null, + "timezone": null, + "english": "At every minute in April.", + "translation": "At every minute in April." + }, + "month-5": { + "expression": "* * * 5 *", + "seconds": null, + "timezone": null, + "english": "At every minute in May.", + "translation": "At every minute in May." + }, + "month-6": { + "expression": "* * * 6 *", + "seconds": null, + "timezone": null, + "english": "At every minute in June.", + "translation": "At every minute in June." + }, + "month-7": { + "expression": "* * * 7 *", + "seconds": null, + "timezone": null, + "english": "At every minute in July.", + "translation": "At every minute in July." + }, + "month-8": { + "expression": "* * * 8 *", + "seconds": null, + "timezone": null, + "english": "At every minute in August.", + "translation": "At every minute in August." + }, + "month-9": { + "expression": "* * * 9 *", + "seconds": null, + "timezone": null, + "english": "At every minute in September.", + "translation": "At every minute in September." + }, + "month-10": { + "expression": "* * * 10 *", + "seconds": null, + "timezone": null, + "english": "At every minute in October.", + "translation": "At every minute in October." + }, + "month-11": { + "expression": "* * * 11 *", + "seconds": null, + "timezone": null, + "english": "At every minute in November.", + "translation": "At every minute in November." + }, + "month-12": { + "expression": "* * * 12 *", + "seconds": null, + "timezone": null, + "english": "At every minute in December.", + "translation": "At every minute in December." + }, + "month-13": { + "expression": "* * * 1,2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in January and February.", + "translation": "At every minute in January and February." + }, + "month-14": { + "expression": "* * * 1,2,3,4,5 *", + "seconds": null, + "timezone": null, + "english": "At every minute in January, February, March, April and May.", + "translation": "At every minute in January, February, March, April and May." + }, + "month-15": { + "expression": "* * * 1-11 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every month from January through November.", + "translation": "At every minute in every month from January through November." + }, + "month-16": { + "expression": "* * * *\/2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 2nd month.", + "translation": "At every minute in every 2nd month." + }, + "month-17": { + "expression": "* * * *\/3 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 3rd month.", + "translation": "At every minute in every 3rd month." + }, + "month-18": { + "expression": "* * * *\/4 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 4th month.", + "translation": "At every minute in every 4th month." + }, + "month-19": { + "expression": "* * * *\/5 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 5th month.", + "translation": "At every minute in every 5th month." + }, + "month-20": { + "expression": "* * * 1-11\/2 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every 2nd month from January through November.", + "translation": "At every minute in every 2nd month from January through November." + }, + "month-21": { + "expression": "* * * 1-2,3,4,5-6 *", + "seconds": null, + "timezone": null, + "english": "At every minute in every month from January through February, March, April and from May through June.", + "translation": "At every minute in every month from January through February, March, April and from May through June." + }, + "mixed-0": { + "expression": "* * * * *", + "seconds": 1, + "timezone": null, + "english": "At every second.", + "translation": "At every second." + }, + "mixed-1": { + "expression": "* * * * *", + "seconds": 30, + "timezone": null, + "english": "At every 30 seconds.", + "translation": "At every 30 seconds." + }, + "mixed-2": { + "expression": "* * * * *", + "seconds": null, + "timezone": "Europe\/Prague", + "english": "At every minute in Europe\/Prague time zone.", + "translation": "At every minute in Europe\/Prague time zone." + }, + "mixed-3": { + "expression": "10 * * * *", + "seconds": 10, + "timezone": null, + "english": "At every 10 seconds at minute 10.", + "translation": "At every 10 seconds at minute 10." + }, + "mixed-4": { + "expression": "30 10 1 2 *", + "seconds": null, + "timezone": null, + "english": "At 10:30 on 1st of February.", + "translation": "At 10:30 on 1st of February." + }, + "mixed-5": { + "expression": "* * 1 * 1", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1 and on every Monday.", + "translation": "At every minute on day-of-month 1 and on every Monday." + }, + "mixed-6": { + "expression": "* * 1 * 1,2", + "seconds": null, + "timezone": null, + "english": "At every minute on day-of-month 1 and on every Monday and Tuesday.", + "translation": "At every minute on day-of-month 1 and on every Monday and Tuesday." + }, + "mixed-7": { + "expression": "0-59 0-23 1-31 1-12 0-6", + "seconds": null, + "timezone": null, + "english": "At every minute from 0 through 59 past every hour from 0 through 23 on every day-of-month from 1 through 31 and on every day-of-week from Sunday through Saturday in every month from January through December.", + "translation": "At every minute from 0 through 59 past every hour from 0 through 23 on every day-of-month from 1 through 31 and on every day-of-week from Sunday through Saturday in every month from January through December." + } +} diff --git a/tests/Snapshots/update-snapshots.php b/tests/Snapshots/update-snapshots.php new file mode 100644 index 0000000..7d34fe1 --- /dev/null +++ b/tests/Snapshots/update-snapshots.php @@ -0,0 +1,7 @@ + 'english'], - $explainer->getSupportedLanguages(), + [ + 'cs' => 'czech', + 'en' => 'english', + ], + $explainer->getSupportedLocales(), ); } - public function testNotSupportedLanguage(): void + public function testNotSupportedLocale(): void { $explainer = new DefaultCronExpressionExplainer(); $exception = null; try { $explainer->explain('* * * * *', null, null, 'nope'); - } catch (UnsupportedLanguage $exception) { + } catch (UnsupportedLocale $exception) { // Bellow } self::assertNotNull($exception); - self::assertSame('nope', $exception->getLanguage()); + self::assertSame('nope', $exception->getLocale()); + } + + public function testDefaultLocale(): void + { + $explainer = new DefaultCronExpressionExplainer(); + self::assertSame('At every minute.', $explainer->explain('* * * * *')); + + $explainer->setDefaultLocale('cs'); + self::assertSame('Každou minutu.', $explainer->explain('* * * * *')); } } diff --git a/tests/Unit/Exception/UnsupportedLanguageTest.php b/tests/Unit/Exception/UnsupportedLanguageTest.php deleted file mode 100644 index c5efdad..0000000 --- a/tests/Unit/Exception/UnsupportedLanguageTest.php +++ /dev/null @@ -1,19 +0,0 @@ -getMessage()); - self::assertSame('en', $exception->getLanguage()); - } - -} diff --git a/tests/Unit/Exception/UnsupportedLocaleTest.php b/tests/Unit/Exception/UnsupportedLocaleTest.php new file mode 100644 index 0000000..f7859bb --- /dev/null +++ b/tests/Unit/Exception/UnsupportedLocaleTest.php @@ -0,0 +1,19 @@ +getMessage()); + self::assertSame('en', $exception->getLocale()); + } + +} diff --git a/tests/Unit/Part/ListPartTest.php b/tests/Unit/Part/ListPartTest.php index 3b1cbdd..31a3456 100644 --- a/tests/Unit/Part/ListPartTest.php +++ b/tests/Unit/Part/ListPartTest.php @@ -20,6 +20,7 @@ public function test(): void ]; $part = new ListPart($parts); + self::assertSame('list', $part->getName()); self::assertSame($parts, $part->getParts()); } diff --git a/tests/Unit/Part/RangePartTest.php b/tests/Unit/Part/RangePartTest.php index a4166e8..07421cf 100644 --- a/tests/Unit/Part/RangePartTest.php +++ b/tests/Unit/Part/RangePartTest.php @@ -15,6 +15,7 @@ public function test(): void $right = new ValuePart('20'); $part = new RangePart($left, $right); + self::assertSame('range', $part->getName()); self::assertSame($left, $part->getLeft()); self::assertSame($right, $part->getRight()); } diff --git a/tests/Unit/Part/StepPartTest.php b/tests/Unit/Part/StepPartTest.php index 5a38d9c..e56da46 100644 --- a/tests/Unit/Part/StepPartTest.php +++ b/tests/Unit/Part/StepPartTest.php @@ -16,6 +16,7 @@ public function test(): void $step = 2; $part = new StepPart($range, $step); + self::assertSame('step', $part->getName()); self::assertSame($range, $part->getRange()); self::assertSame($step, $part->getStep()); } diff --git a/tests/Unit/Part/ValuePartTest.php b/tests/Unit/Part/ValuePartTest.php index e188668..3736402 100644 --- a/tests/Unit/Part/ValuePartTest.php +++ b/tests/Unit/Part/ValuePartTest.php @@ -13,6 +13,7 @@ public function test(): void $value = '*'; $part = new ValuePart($value); + self::assertSame('value', $part->getName()); self::assertSame($value, $part->getValue()); }