From 9c105af4f6b664a8e2f1293eb73139a3b63b1875 Mon Sep 17 00:00:00 2001 From: Impeck Date: Fri, 18 Aug 2023 13:28:30 +0500 Subject: [PATCH] Refactoring and add Russian and Ukrainian (#42) * add lang am and pm * add lang am and pm * add case for lang * add ru lang * add Russian * add declension of words depending on numerals * refactoring code add phpDoc * multi-bytes ucfirst() * fix * refactoring and fix #38 * add lang ua --- README.md | 2 + src/CronExpression.php | 132 +++++++++++++++-------- src/CronParsingException.php | 9 ++ src/CronTranslator.php | 91 +++++++++++----- src/CronType.php | 106 ++++++++++++++----- src/DaysOfMonthField.php | 41 +++++++- src/DaysOfWeekField.php | 39 ++++++- src/Field.php | 83 ++++++++++++++- src/HoursField.php | 60 ++++++++--- src/LanguageLoader.php | 73 +++++++++++++ src/MinutesField.php | 35 ++++++- src/MonthsField.php | 39 ++++++- src/TranslationFileMissingException.php | 10 ++ src/lang/ar/fields.php | 4 + src/lang/de/fields.php | 4 + src/lang/en/fields.php | 7 ++ src/lang/es/fields.php | 4 + src/lang/fr/fields.php | 4 + src/lang/hi/fields.php | 4 + src/lang/lv/fields.php | 4 + src/lang/nl/fields.php | 4 + src/lang/pt/fields.php | 4 + src/lang/ro/fields.php | 4 + src/lang/ru/days.php | 22 ++++ src/lang/ru/fields.php | 55 ++++++++++ src/lang/ru/months.php | 32 ++++++ src/lang/ru/ordinals.php | 11 ++ src/lang/ru/times.php | 9 ++ src/lang/sk/fields.php | 4 + src/lang/ua/days.php | 22 ++++ src/lang/ua/fields.php | 55 ++++++++++ src/lang/ua/months.php | 32 ++++++ src/lang/ua/ordinals.php | 12 +++ src/lang/ua/times.php | 9 ++ src/lang/vi/fields.php | 4 + src/lang/zh-TW/fields.php | 4 + src/lang/zh/fields.php | 4 + tests/CronTranslatorROTest.php | 102 +++++++++--------- tests/CronTranslatorRUTest.php | 133 +++++++++++++++++++++++ tests/CronTranslatorTest.php | 1 + tests/CronTranslatorUATest.php | 134 ++++++++++++++++++++++++ tests/PluralizeTest.php | 16 +++ 42 files changed, 1243 insertions(+), 181 deletions(-) create mode 100644 src/LanguageLoader.php create mode 100644 src/lang/ru/days.php create mode 100644 src/lang/ru/fields.php create mode 100644 src/lang/ru/months.php create mode 100644 src/lang/ru/ordinals.php create mode 100644 src/lang/ru/times.php create mode 100644 src/lang/ua/days.php create mode 100644 src/lang/ua/fields.php create mode 100644 src/lang/ua/months.php create mode 100644 src/lang/ua/ordinals.php create mode 100644 src/lang/ua/times.php create mode 100644 tests/CronTranslatorRUTest.php create mode 100644 tests/CronTranslatorUATest.php create mode 100644 tests/PluralizeTest.php diff --git a/README.md b/README.md index 8634508..928211f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,9 @@ The following locales are currently supported. Feel free to PR more locales if y - `nl` — Dutch - `pt` — Portuguese - `ro` — Romanian +- `ru` — Russian - `sk` — Slovak +- `ua` — Ukrainian - `vi` — Vietnamese - `zh` — Simplified Chinese - `zh-TW` — Traditional Chinese diff --git a/src/CronExpression.php b/src/CronExpression.php index 9feab4e..d4218c3 100644 --- a/src/CronExpression.php +++ b/src/CronExpression.php @@ -2,6 +2,9 @@ namespace Lorisleiva\CronTranslator; +/** + * Class for parsing and translating cron expressions + */ class CronExpression { public string $raw; @@ -10,29 +13,38 @@ class CronExpression public DaysOfMonthField $day; public MonthsField $month; public DaysOfWeekField $weekday; - public string $locale; public bool $timeFormat24hours; public array $translations; + /** - * @throws CronParsingException - * @throws TranslationFileMissingException + * Constructs a new instance of the class. + * + * @param string $cron The cron expression. + * @param array $translations Optional translations for field values. + * @param bool $timeFormat24hours Whether to use 24-hour time format. */ - public function __construct(string $cron, string $locale = 'en', bool $timeFormat24hours = false) + public function __construct(string $cron, array $translations = [], bool $timeFormat24hours = false) { $this->raw = $cron; $fields = explode(' ', $cron); + $this->minute = new MinutesField($this, $fields[0]); $this->hour = new HoursField($this, $fields[1]); $this->day = new DaysOfMonthField($this, $fields[2]); $this->month = new MonthsField($this, $fields[3]); $this->weekday = new DaysOfWeekField($this, $fields[4]); - $this->locale = $locale; + $this->timeFormat24hours = $timeFormat24hours; - $this->ensureLocaleExists(); - $this->loadTranslations(); + + $this->translations = $translations; } + /** + * Get the cron fields + * + * @return array + */ public function getFields(): array { return [ @@ -44,16 +56,33 @@ public function getFields(): array ]; } - public function langCountable(string $type, int $number): array|string + /** + * Get localized countable translation + * + * @param string $type The translation type + * @param int $number The number + * @param string $case The grammatical case + * + * @return array|string The translated string + */ + public function langCountable(string $type, int $number, string $case = 'nominative'): array|string { $array = $this->translations[$type]; - $value = $array[$number] ?? ($array['default'] ?: ''); + $value = $array[$case][$number] ?? $array[$number] ?? $array[$case]['default'] ?? $array['default'] ?? ''; return str_replace(':number', $number, $value); } - public function lang(string $key, array $replacements = []) + /** + * Get a localized translation + * + * @param string $key The translation key + * @param array $replacements The replacements + * + * @return string The translated string + */ + public function lang(string $key, array $replacements = []): string { $translation = $this->getArrayDot($this->translations['fields'], $key); @@ -61,57 +90,76 @@ public function lang(string $key, array $replacements = []) $translation = str_replace(':' . $transKey, $value, $translation); } - return $translation; + return $this->pluralize($translation); } - protected function ensureLocaleExists(string $fallbackLocale = 'en'): void + /** + * Get a nested array value using dot notation + * + * @param array $array The array + * @param string $key The key + * + * @return mixed The array value + */ + protected function getArrayDot(array $array, string $key): mixed { - if (! is_dir($this->getTranslationDirectory())) { - $this->locale = $fallbackLocale; + $keys = explode('.', $key); + + foreach ($keys as $item) { + $array = $array[$item]; } + + return $array; } /** - * @throws TranslationFileMissingException + * Pluralize a string based on counts and forms + * + * @param string $inputString The input string + * + * @return string The pluralized string */ - protected function loadTranslations(): void + public function pluralize(string $inputString): string { - $this->translations = [ - 'days' => $this->loadTranslationFile('days'), - 'fields' => $this->loadTranslationFile('fields'), - 'months' => $this->loadTranslationFile('months'), - 'ordinals' => $this->loadTranslationFile('ordinals'), - 'times' => $this->loadTranslationFile('times'), - ]; + if (!preg_match_all('/(\d+)\s+{(.+?)\}/', $inputString, $matches)) { + return $inputString; + } + + [$fullMatches, $counts, $forms] = $matches; + + $conversionTable = []; + + foreach ($counts as $key => $count) { + $conversionTable['{' . $forms[$key] . '}'] = $this->declineCount((int)$count, $forms[$key]); + } + + return strtr($inputString, $conversionTable); } /** - * @throws TranslationFileMissingException + * Decline a count value based on forms + * + * @param int $count The count + * @param string $forms The forms + * + * @return string The declined form */ - protected function loadTranslationFile(string $file) + protected function declineCount(int $count, string $forms): string { - $filename = sprintf('%s/%s.php', $this->getTranslationDirectory(), $file); + $formsArray = explode('|', $forms); - if (! is_file($filename)) { - throw new TranslationFileMissingException($this->locale, $file); + if (count($formsArray) < 3) { + $formsArray[2] = $formsArray[1]; } - return include $filename; - } + $cases = [2, 0, 1, 1, 1, 2]; - protected function getTranslationDirectory(): string - { - return __DIR__ . '/lang/' . $this->locale; - } - - protected function getArrayDot(array $array, string $key) - { - $keys = explode('.', $key); + $count = abs((int)strip_tags($count)); - foreach ($keys as $item) { - $array = $array[$item]; - } + $formIndex = ($count % 100 > 4 && $count % 100 < 20) + ? 2 + : $cases[min($count % 10, 5)]; - return $array; + return $formsArray[$formIndex]; } } diff --git a/src/CronParsingException.php b/src/CronParsingException.php index bedbf33..56f501a 100644 --- a/src/CronParsingException.php +++ b/src/CronParsingException.php @@ -4,8 +4,17 @@ use Exception; +/** + * Exception for cron parsing errors + */ class CronParsingException extends Exception { + + /** + * Constructor + * + * @param string $cron The invalid cron expression + */ public function __construct(string $cron) { parent::__construct("Failed to parse the following CRON expression: $cron"); diff --git a/src/CronTranslator.php b/src/CronTranslator.php index 307849b..f659aa7 100644 --- a/src/CronTranslator.php +++ b/src/CronTranslator.php @@ -6,74 +6,111 @@ class CronTranslator { + + /** + * Extended cron map + * + * @var array + */ private static array $extendedMap = [ + '@reboot' => '@reboot', '@yearly' => '0 0 1 1 *', '@annually' => '0 0 1 1 *', '@monthly' => '0 0 1 * *', '@weekly' => '0 0 * * 0', '@daily' => '0 0 * * *', + '@midnight' => '0 0 * * *', '@hourly' => '0 * * * *' ]; /** + * Translate a cron expression + * + * @param string $cron The cron expression + * @param string $locale The locale + * @param bool $timeFormat24hours Use 24 hour time + * + * @return string The translated expression + * * @throws CronParsingException */ public static function translate(string $cron, string $locale = 'en', bool $timeFormat24hours = false): string { + // Use extended map if available if (isset(self::$extendedMap[$cron])) { $cron = self::$extendedMap[$cron]; } try { - $expression = new CronExpression($cron, $locale, $timeFormat24hours); - $orderedFields = static::orderFields($expression->getFields()); + $translations = (new LanguageLoader($locale))->translations; + + if (str_starts_with($cron, '@')) { + return self::mbUcfirst($translations["fields"]["extended"][$cron]); + } + + $expression = new CronExpression($cron, $translations, $timeFormat24hours); + $fields = $expression->getFields(); + $orderedFields = self::orderFields($fields); - $translations = array_map(static function (Field $field) { - return $field->translate(); - }, $orderedFields); + $answer = array_map(fn (Field $field) => $field->translate(), $orderedFields); - return ucfirst(implode(' ', array_filter($translations))); + return self::mbUcfirst(implode(' ', array_filter($answer))); } catch (Throwable $th) { throw new CronParsingException($cron); } } + /** + * Order fields + * + * @param array $fields The fields + * + * @return array Ordered fields + */ protected static function orderFields(array $fields): array { - // Group fields by CRON types. - $onces = static::filterType($fields, 'Once'); - $everys = static::filterType($fields, 'Every'); - $incrementsAndMultiples = static::filterType($fields, 'Increment', 'Multiple'); + // Filter by field type + $onces = self::filterByType($fields, 'Once'); + $everys = self::filterByType($fields, 'Every'); + $incrementsAndMultiples = self::filterByType($fields, 'Increment', 'Multiple'); - // Decide whether to keep one or zero CRON type "Every". + // Only keep first every if incrementals exist $firstEvery = reset($everys)->position ?? PHP_INT_MIN; $firstIncrementOrMultiple = reset($incrementsAndMultiples)->position ?? PHP_INT_MAX; $numberOfEverysKept = $firstIncrementOrMultiple < $firstEvery ? 0 : 1; - // Mark fields that will not be displayed as dropped. - // This allows other fields to check whether some - // information is missing and adapt their translation. - /** @var Field $field */ - foreach (array_slice($everys, $numberOfEverysKept) as $field) { - $field->dropped = true; - } + // Mark dropped fields + array_map(fn (Field $field) => $field->dropped = true, array_slice($everys, $numberOfEverysKept)); return array_merge( - // Place one or zero "Every" field at the beginning. array_slice($everys, 0, $numberOfEverysKept), - - // Place all "Increment" and "Multiple" fields in the middle. $incrementsAndMultiples, - - // Finish with the "Once" fields reversed (i.e. from months to minutes). array_reverse($onces) ); } - protected static function filterType(array $fields, ...$types): array + /** + * Filter fields by type + * + * @param array $fields The fields + * @param string ...$types The types + * + * @return array The filtered fields + */ + protected static function filterByType(array $fields, string ...$types): array + { + return array_filter($fields, fn (Field $field) => $field->hasType(...$types)); + } + + /** + * Capitalize the first letter + * + * @param string $string + * @return string + */ + protected static function mbUcfirst(string $string): string { - return array_filter($fields, static function (Field $field) use ($types) { - return $field->hasType(...$types); - }); + $fc = mb_strtoupper(mb_substr($string, 0, 1)); + return $fc . mb_substr($string, 1); } } diff --git a/src/CronType.php b/src/CronType.php index 6b02b44..cced0bb 100644 --- a/src/CronType.php +++ b/src/CronType.php @@ -2,10 +2,20 @@ namespace Lorisleiva\CronTranslator; +/** + * Class representing a cron schedule type + */ class CronType { + + /** + * The cron type constants + */ public const TYPES = [ - 'Every', 'Increment', 'Multiple', 'Once', + 'Every', + 'Increment', + 'Multiple', + 'Once', ]; public string $type; @@ -13,6 +23,14 @@ class CronType public ?int $count; public ?int $increment; + /** + * Constructor + * + * @param string $type The cron type + * @param int|null $value The cron value + * @param int|null $count The cron count + * @param int|null $increment The cron increment + */ private function __construct(string $type, ?int $value = null, ?int $count = null, ?int $increment = null) { $this->type = $type; @@ -21,30 +39,63 @@ private function __construct(string $type, ?int $value = null, ?int $count = nul $this->increment = $increment; } - public static function every(): CronType + /** + * Create an Every cron type + * + * @return static + */ + public static function every(): static { - return new static('Every'); + return new static(self::TYPES[0]); } - public static function increment(int $increment, int $count = 1): CronType + /** + * Create an Increment cron type + * + * @param int $increment The increment + * @param int $count The count + * + * @return static + */ + public static function increment(int $increment, int $count = 1): static { - return new static('Increment', null, $count, $increment); + return new static(self::TYPES[1], null, $count, $increment); } - public static function multiple(int $count): CronType + /** + * Create a Multiple cron type + * + * @param int $count The multiple count + * + * @return static + */ + public static function multiple(int $count): static { - return new static('Multiple', null, $count); + return new static(self::TYPES[2], null, $count); } - public static function once(int $value): CronType + /** + * Create an Once cron type + * + * @param int $value The once value + * + * @return static + */ + public static function once(int $value): static { - return new static('Once', $value); + return new static(self::TYPES[3], $value); } /** + * Parse a cron expression + * + * @param string $expression The expression + * + * @return static + * * @throws CronParsingException */ - public static function parse(string $expression): CronType + public static function parse(string $expression): static { // Parse "*". if ($expression === '*') { @@ -52,38 +103,41 @@ public static function parse(string $expression): CronType } // Parse fixed values like "1". - if (preg_match("/^[0-9]+$/", $expression)) { - return static::once((int) $expression); + if (preg_match("/^\d+$/", $expression)) { + return static::once((int)$expression); } - // Parse multiple selected values like "1,2,5". - if (preg_match("/^[0-9]+(,[0-9]+)+$/", $expression)) { + // Parse multiple values like "1,2,5". + if (preg_match("/^\d+(,\d+)+$/", $expression)) { return static::multiple(count(explode(',', $expression))); } - // Parse ranges of selected values like "1-5". - if (preg_match("/^([0-9]+)\-([0-9]+)$/", $expression, $matches)) { + // Parse ranges like "1-5". + if (preg_match("/^(\d+)-(\d+)$/", $expression, $matches)) { $count = $matches[2] - $matches[1] + 1; - return $count > 1 - ? static::multiple($count) - : static::once((int) $matches[1]); + return $count > 1 ? static::multiple($count) : static::once((int)$matches[1]); } - // Parse incremental expressions like "*/2", "1-4/10" or "1,3/4". - if (preg_match("/(.+)\/([0-9]+)$/", $expression, $matches)) { + // Parse increments like "*/2", "1-4/10". + if (preg_match("/(.+)\/(\d+)$/", $expression, $matches)) { $range = static::parse($matches[1]); - if ($range->hasType('Once', 'Every')) { - return static::Increment($matches[2]); + if ($range->hasType(self::TYPES[0], self::TYPES[3])) { + return static::increment($matches[2]); } - if ($range->hasType('Multiple')) { - return static::Increment($matches[2], $range->count); + if ($range->hasType(self::TYPES[2])) { + return static::increment($matches[2], $range->count); } } - // Unsupported expressions throw exceptions. + // Invalid expression throw new CronParsingException($expression); } + /** + * Check if the type matches + * + * @return bool + */ public function hasType(): bool { return in_array($this->type, func_get_args(), true); diff --git a/src/DaysOfMonthField.php b/src/DaysOfMonthField.php index 1bc114a..b55f8fe 100644 --- a/src/DaysOfMonthField.php +++ b/src/DaysOfMonthField.php @@ -2,11 +2,23 @@ namespace Lorisleiva\CronTranslator; +/** + * Days of month field translation class + */ class DaysOfMonthField extends Field { + + /** + * Field position + * + * @var int + */ public int $position = 2; /** + * Translate every expression + * + * @return string * @throws CronParsingException */ public function translateEvery(): string @@ -20,6 +32,11 @@ public function translateEvery(): string return $this->lang('days_of_month.every'); } + /** + * Translate increment expression + * + * @return string + */ public function translateIncrement(): string { if ($this->getCount() > 1) { @@ -34,6 +51,11 @@ public function translateIncrement(): string ]); } + /** + * Translate multiple expression + * + * @return string + */ public function translateMultiple(): string { return $this->lang('days_of_month.multiple_per_month', [ @@ -41,6 +63,11 @@ public function translateMultiple(): string ]); } + /** + * Translate once expression + * + * @return string|null + */ public function translateOnce(): ?string { $month = $this->expression->month; @@ -55,17 +82,23 @@ public function translateOnce(): ?string if ($month->hasType('Every') && $month->dropped) { return $this->lang('days_of_month.every_on_day', [ - 'day' => $this->format() + 'day' => $this->format('dative'), ]); } return $this->lang('days_of_month.once_on_day', [ - 'day' => $this->format() + 'day' => $this->format(), ]); } - public function format(): string + /** + * Format day of month + * + * @param string $case + * @return string + */ + public function format(string $case = 'nominative'): string { - return $this->langCountable('ordinals', $this->getValue()); + return $this->langCountable('ordinals', $this->getValue(), $case); } } diff --git a/src/DaysOfWeekField.php b/src/DaysOfWeekField.php index c9d167e..9414879 100644 --- a/src/DaysOfWeekField.php +++ b/src/DaysOfWeekField.php @@ -2,15 +2,34 @@ namespace Lorisleiva\CronTranslator; +/** + * Days of week field translation class + */ class DaysOfWeekField extends Field { + + /** + * Field position + * + * @var int + */ public int $position = 4; + /** + * Translate every expression + * + * @return string + */ public function translateEvery(): string { return $this->lang('years.every'); } + /** + * Translate increment expression + * + * @return string + */ public function translateIncrement(): string { if ($this->getCount() > 1) { @@ -25,6 +44,11 @@ public function translateIncrement(): string ]); } + /** + * Translate multiple expression + * + * @return string + */ public function translateMultiple(): string { return $this->lang('days_of_week.multiple_days_a_week', [ @@ -33,23 +57,30 @@ public function translateMultiple(): string } /** + * Translate once expression + * + * @return string|null * @throws CronParsingException */ public function translateOnce(): ?string { - if ($this->expression->day->hasType('Every') && ! $this->expression->day->dropped) { + if ($this->expression->day->hasType('Every') && !$this->expression->day->dropped) { return null; // DaysOfMonthField adapts to "Every Sunday". } return $this->lang('days_of_week.once_on_day', [ - 'day' => $this->format() + 'day' => $this->format('dative') ]); } /** + * Format day of week + * + * @param string $case + * @return string * @throws CronParsingException */ - public function format(): string + public function format(string $case = 'nominative'): string { $weekday = $this->getValue() === 0 ? 7 : $this->getValue(); @@ -57,6 +88,6 @@ public function format(): string throw new CronParsingException($this->expression->raw); } - return $this->langCountable('days', $weekday); + return $this->langCountable('days', $weekday, $case); } } diff --git a/src/Field.php b/src/Field.php index 72bb039..a09b0f0 100644 --- a/src/Field.php +++ b/src/Field.php @@ -2,61 +2,134 @@ namespace Lorisleiva\CronTranslator; +/** + * Abstract cron field translation class + */ abstract class Field { + + /** + * The field cron type + * + * @var CronType + */ public CronType $type; + + /** + * Whether field was dropped + * + * @var bool + */ public bool $dropped = false; + + /** + * The field position + * + * @var int + */ public int $position; /** + * Constructor + * + * @param CronExpression $expression The cron expression + * @param string $rawField The raw field value + * * @throws CronParsingException */ - public function __construct(public CronExpression $expression, public string $rawField) - { + public function __construct( + protected CronExpression $expression, + protected string $rawField + ) { $this->type = CronType::parse($rawField); } + /** + * Translate the field + * + * @return string|null + */ public function translate(): ?string { $method = 'translate' . $this->type->type; if (method_exists($this, $method)) { - return $this->{$method}(); + return $this->$method(); } return null; } + /** + * Check if field type matches + * + * @return bool + */ public function hasType(): bool { return $this->type->hasType(...func_get_args()); } + /** + * Get field value + * + * @return int|null + */ public function getValue(): ?int { return $this->type->value; } + /** + * Get field count + * + * @return int|null + */ public function getCount(): ?int { return $this->type->count; } + /** + * Get field increment + * + * @return int|null + */ public function getIncrement(): ?int { return $this->type->increment; } + /** + * Get localized times + * + * @return array|string + */ public function getTimes(): array|string { return $this->langCountable('times', $this->getCount()); } - protected function langCountable(string $key, int $value): array|string + /** + * Get localized countable translation + * + * @param string $key + * @param int $value + * @param string $case + * @return array|string + */ + protected function langCountable(string $key, int $value, string $case = 'nominative'): array|string { - return $this->expression->langCountable($key, $value); + return $this->expression->langCountable($key, $value, $case); } + /** + * Get localized translation + * + * @param string $key + * @param array $replacements + * @return string + */ protected function lang(string $key, array $replacements = []): string { return $this->expression->lang($key, $replacements); diff --git a/src/HoursField.php b/src/HoursField.php index 1b04ff8..79d77f2 100644 --- a/src/HoursField.php +++ b/src/HoursField.php @@ -2,10 +2,24 @@ namespace Lorisleiva\CronTranslator; +/** + * Hours field translation class + */ class HoursField extends Field { + + /** + * Field position + * + * @var int + */ public int $position = 1; + /** + * Translate every expression + * + * @return string + */ public function translateEvery(): string { if ($this->expression->minute->hasType('Once')) { @@ -15,71 +29,87 @@ public function translateEvery(): string return $this->lang('hours.every'); } + /** + * Translate increment expression + * + * @return string + */ public function translateIncrement(): string { if ($this->expression->minute->hasType('Once')) { return $this->lang('hours.times_per_increment', [ 'times' => $this->getTimes(), - 'increment' => $this->getIncrement(), + 'increment' => $this->getIncrement() ]); } if ($this->getCount() > 1) { return $this->lang('hours.multiple_per_increment', [ 'count' => $this->getCount(), - 'increment' => $this->getIncrement(), + 'increment' => $this->getIncrement() ]); } if ($this->expression->minute->hasType('Every')) { return $this->lang('hours.increment_chained', [ - 'increment' => $this->getIncrement(), + 'increment' => $this->getIncrement() ]); } return $this->lang('hours.increment', [ - 'increment' => $this->getIncrement(), + 'increment' => $this->getIncrement() ]); } + /** + * Translate multiple expression + * + * @return string + */ public function translateMultiple(): string { if ($this->expression->minute->hasType('Once')) { return $this->lang('hours.times_per_day', [ - 'times' => $this->getTimes(), + 'times' => $this->getTimes() ]); } return $this->lang('hours.multiple_per_day', [ - 'count' => $this->getCount(), + 'count' => $this->getCount() ]); } + /** + * Translate once expression + * + * @return string + */ public function translateOnce(): string { - $minute = $this->expression->minute->hasType('Once') - ? $this->expression->minute - : null; + $minute = $this->expression->minute->hasType('Once') ? $this->expression->minute : null; return $this->lang('hours.once_at_time', [ 'time' => $this->format($minute) ]); } + /** + * Format the hour + * + * @param MinutesField|null $minute + * @return string + */ public function format(?MinutesField $minute = null): string { if ($this->expression->timeFormat24hours) { $hour = $this->getValue(); - - return $minute ? "{$hour}:{$minute->format()}" : "{$hour}:00"; + return $minute ? "$hour:{$minute->format()}" : "$hour:00"; } - $amOrPm = $this->getValue() < 12 ? 'am' : 'pm'; + $amOrPm = $this->getValue() < 12 ? $this->lang('times.am') : $this->lang('times.pm'); $hour = $this->getValue() % 12; $hour = $hour === 0 ? 12 : $hour; - return $minute - ? "{$hour}:{$minute->format()}{$amOrPm}" - : "{$hour}{$amOrPm}"; + return $minute ? "$hour:{$minute->format()}$amOrPm" : "$hour$amOrPm"; } } diff --git a/src/LanguageLoader.php b/src/LanguageLoader.php new file mode 100644 index 0000000..e4af17d --- /dev/null +++ b/src/LanguageLoader.php @@ -0,0 +1,73 @@ +locale = $locale; + $this->loadTranslations(); + } + + /** + * Ensure the locale exists or use a fallback + * + * @param string $fallbackLocale The fallback locale + */ + protected function ensureLocaleExists(string $fallbackLocale = 'en'): void + { + if (!is_dir($this->getTranslationDirectory())) { + $this->locale = $fallbackLocale; + } + } + + /** + * Load the translation files + * + * @throws TranslationFileMissingException + */ + protected function loadTranslations(): void + { + $this->translations = [ + 'days' => $this->loadTranslationFile('days'), + 'fields' => $this->loadTranslationFile('fields'), + 'months' => $this->loadTranslationFile('months'), + 'ordinals' => $this->loadTranslationFile('ordinals'), + 'times' => $this->loadTranslationFile('times'), + ]; + } + + /** + * Load a single translation file + * + * @param string $file The file name + * + * @return array + * + * @throws TranslationFileMissingException + */ + protected function loadTranslationFile(string $file): array + { + $filename = sprintf('%s/%s.php', $this->getTranslationDirectory(), $file); + + if (!is_file($filename)) { + throw new TranslationFileMissingException($this->locale, $file); + } + + return include $filename; + } + + /** + * Get the translation directory + * + * @return string + */ + protected function getTranslationDirectory(): string + { + return __DIR__ . '/lang/' . $this->locale; + } +} diff --git a/src/MinutesField.php b/src/MinutesField.php index bd99b65..82aabd8 100644 --- a/src/MinutesField.php +++ b/src/MinutesField.php @@ -2,36 +2,65 @@ namespace Lorisleiva\CronTranslator; +/** + * Minutes field translation class + */ class MinutesField extends Field { + + /** + * Field position + * + * @var int + */ public int $position = 0; + /** + * Translate every expression + * + * @return string + */ public function translateEvery(): string { return $this->lang('minutes.every'); } + /** + * Translate increment expression + * + * @return string + */ public function translateIncrement(): string { if ($this->getCount() > 1) { return $this->lang('minutes.times_per_increment', [ 'times' => $this->getTimes(), - 'increment' => $this->getIncrement(), + 'increment' => $this->getIncrement() ]); } return $this->lang('minutes.increment', [ - 'increment' => $this->getIncrement(), + 'increment' => $this->getIncrement() ]); } + /** + * Translate multiple expression + * + * @return string + */ public function translateMultiple(): string { return $this->lang('minutes.multiple', [ - 'times' => $this->getTimes(), + 'times' => $this->getTimes() ]); } + /** + * Format minute + * + * @return string + */ public function format(): string { return ($this->getValue() < 10 ? '0' : '') . $this->getValue(); diff --git a/src/MonthsField.php b/src/MonthsField.php index 7b6d557..f2c4995 100644 --- a/src/MonthsField.php +++ b/src/MonthsField.php @@ -2,10 +2,24 @@ namespace Lorisleiva\CronTranslator; +/** + * Months field translation class + */ class MonthsField extends Field { + + /** + * Field position + * + * @var int + */ public int $position = 3; + /** + * Translate every expression + * + * @return string + */ public function translateEvery(): string { if ($this->expression->day->hasType('Once')) { @@ -17,6 +31,11 @@ public function translateEvery(): string return $this->lang('months.every'); } + /** + * Translate increment expression + * + * @return string + */ public function translateIncrement(): string { if ($this->getCount() > 1) { @@ -31,6 +50,11 @@ public function translateIncrement(): string ]); } + /** + * Translate multiple expression + * + * @return string + */ public function translateMultiple(): string { return $this->lang('months.multiple_per_year', [ @@ -39,6 +63,9 @@ public function translateMultiple(): string } /** + * Translate once expression + * + * @return string * @throws CronParsingException */ public function translateOnce(): string @@ -46,24 +73,28 @@ public function translateOnce(): string if ($this->expression->day->hasType('Once')) { return $this->lang('months.once_on_day', [ 'month' => $this->format(), - 'day' => $this->expression->day->format(), + 'day' => $this->expression->day->format('dative'), ]); } return $this->lang('months.once_on_month', [ - 'month' => $this->format() + 'month' => $this->format(), ]); } /** + * Format the month + * + * @param string $case + * @return string * @throws CronParsingException */ - public function format(): string + public function format(string $case = 'nominative'): string { if ($this->getValue() < 1 || $this->getValue() > 12) { throw new CronParsingException($this->expression->raw); } - return $this->langCountable('months', $this->getValue()); + return $this->langCountable('months', $this->getValue(), $case); } } diff --git a/src/TranslationFileMissingException.php b/src/TranslationFileMissingException.php index 060d892..14c20bb 100644 --- a/src/TranslationFileMissingException.php +++ b/src/TranslationFileMissingException.php @@ -4,8 +4,18 @@ use Exception; +/** + * Exception when translation file is missing + */ class TranslationFileMissingException extends Exception { + + /** + * Constructor + * + * @param string $locale The locale + * @param string $file The missing file + */ public function __construct(string $locale, string $file) { parent::__construct("Failed to load the translation file [{$file}] from the locale [{$locale}]."); diff --git a/src/lang/ar/fields.php b/src/lang/ar/fields.php index 9bf2551..134dc87 100644 --- a/src/lang/ar/fields.php +++ b/src/lang/ar/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'كل عام', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/de/fields.php b/src/lang/de/fields.php index f82786f..bda9739 100644 --- a/src/lang/de/fields.php +++ b/src/lang/de/fields.php @@ -47,4 +47,8 @@ 'years' => [ 'every' => 'jedes Jahr', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/en/fields.php b/src/lang/en/fields.php index 28da502..cb09a4a 100644 --- a/src/lang/en/fields.php +++ b/src/lang/en/fields.php @@ -1,6 +1,9 @@ [ + '@reboot' => 'run once at startup', + ], 'minutes' => [ 'every' => 'every minute', 'increment' => 'every :increment minutes', @@ -45,4 +48,8 @@ 'years' => [ 'every' => 'every year', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/es/fields.php b/src/lang/es/fields.php index d4bbca4..5827ac7 100644 --- a/src/lang/es/fields.php +++ b/src/lang/es/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'cada año', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/fr/fields.php b/src/lang/fr/fields.php index a339c97..6b18ae7 100644 --- a/src/lang/fr/fields.php +++ b/src/lang/fr/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'chaque année', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/hi/fields.php b/src/lang/hi/fields.php index 19cde43..cc9cc21 100644 --- a/src/lang/hi/fields.php +++ b/src/lang/hi/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'प्रत्येक वर्ष', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/lv/fields.php b/src/lang/lv/fields.php index 2af59e3..ad6d357 100644 --- a/src/lang/lv/fields.php +++ b/src/lang/lv/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'katru gadu', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/nl/fields.php b/src/lang/nl/fields.php index 5a17ce0..dc86259 100644 --- a/src/lang/nl/fields.php +++ b/src/lang/nl/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'elk jaar', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ] ]; diff --git a/src/lang/pt/fields.php b/src/lang/pt/fields.php index d126f4e..1c3920a 100644 --- a/src/lang/pt/fields.php +++ b/src/lang/pt/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'todos os anos', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ] ]; diff --git a/src/lang/ro/fields.php b/src/lang/ro/fields.php index fc99cc7..5d04020 100644 --- a/src/lang/ro/fields.php +++ b/src/lang/ro/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'în fiecare an', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ] ]; diff --git a/src/lang/ru/days.php b/src/lang/ru/days.php new file mode 100644 index 0000000..69c2fac --- /dev/null +++ b/src/lang/ru/days.php @@ -0,0 +1,22 @@ + [ + 1 => 'в понедельник', + 2 => 'во вторник', + 3 => 'в среду', + 4 => 'в четверг', + 5 => 'в пятницу', + 6 => 'в субботу', + 7 => 'в воскресенье', + ], + 'dative' => [ + 1 => 'понедельникам', + 2 => 'вторникам', + 3 => 'средам', + 4 => 'четвергам', + 5 => 'пятницам', + 6 => 'субботам', + 7 => 'воскресеньям', + ], +]; diff --git a/src/lang/ru/fields.php b/src/lang/ru/fields.php new file mode 100644 index 0000000..e3d4857 --- /dev/null +++ b/src/lang/ru/fields.php @@ -0,0 +1,55 @@ + [ + '@reboot' => 'один раз при старте', + ], + 'minutes' => [ + 'every' => 'каждую минуту', + 'increment' => 'каждые :increment {минута|минуты|минут}', + 'times_per_increment' => ':times каждые :increment {минута|минуты|минут}', + 'multiple' => ':times в час', + ], + 'hours' => [ + 'every' => 'каждый час', + 'once_an_hour' => 'раз в час', + 'increment' => 'каждые :increment {час|часа|часов}', + 'multiple_per_increment' => ':count {час|часа|часов} из :increment', + 'times_per_increment' => ':times каждые :increment {час|часа|часов}', + 'increment_chained' => 'каждые :increment {час|часа|часов}', + 'multiple_per_day' => ':count {час|часа|часов} в день', + 'times_per_day' => ':times в день', + 'once_at_time' => 'в :time', + ], + 'days_of_month' => [ + 'every' => 'каждый день', + 'increment' => 'каждые :increment {день|дня|дней}', + 'multiple_per_increment' => ':count {день|дня|дней} из :increment', + 'multiple_per_month' => ':count {день|дня|дней} в месяц', + 'once_on_day' => 'на :day число', + 'every_on_day' => ':day числа каждого месяца', + ], + 'months' => [ + 'every' => 'каждый месяц', + 'every_on_day' => ':day число каждого месяца', + 'increment' => 'каждые :increment {месяц|месяца|месяцев}', + 'multiple_per_increment' => ':count {месяц|месяца|месяцев} из :increment', + 'multiple_per_year' => ':count {месяц|месяца|месяцев} в год', + 'once_on_month' => 'в :month', + 'once_on_day' => 'в :month :day числа', + ], + 'days_of_week' => [ + 'every' => 'каждую неделю :weekday', + 'increment' => 'Каждые :increment дни недели', + 'multiple_per_increment' => ':count {день|дня|дней} недели из :increment', + 'multiple_days_a_week' => ':count {день|дня|дней} в неделю', + 'once_on_day' => 'по :day', + ], + 'years' => [ + 'every' => 'каждый год', + ], + 'times' => [ + 'am' => ' утра', + 'pm' => ' вечера', + ], +]; diff --git a/src/lang/ru/months.php b/src/lang/ru/months.php new file mode 100644 index 0000000..113ff8d --- /dev/null +++ b/src/lang/ru/months.php @@ -0,0 +1,32 @@ + [ + 1 => 'январе', + 2 => 'феврале', + 3 => 'марте', + 4 => 'апреле', + 5 => 'мае', + 6 => 'июне', + 7 => 'июле', + 8 => 'августе', + 9 => 'сентябре', + 10 => 'октябре', + 11 => 'ноябре', + 12 => 'декабре', + ], + 'dative' => [ + 1 => 'январям', + 2 => 'февралям', + 3 => 'мартам', + 4 => 'апрелям', + 5 => 'маям', + 6 => 'июням', + 7 => 'июлям', + 8 => 'августам', + 9 => 'сентябрям', + 10 => 'октябрям', + 11 => 'ноябрям', + 12 => 'декабрям', + ], +]; diff --git a/src/lang/ru/ordinals.php b/src/lang/ru/ordinals.php new file mode 100644 index 0000000..8a83ec6 --- /dev/null +++ b/src/lang/ru/ordinals.php @@ -0,0 +1,11 @@ + [ + 'default' => ':number-ое', + 3 => ':number-е', + ], + 'dative' => [ + 'default' => ':number-го', + ], +]; diff --git a/src/lang/ru/times.php b/src/lang/ru/times.php new file mode 100644 index 0000000..a7e29b9 --- /dev/null +++ b/src/lang/ru/times.php @@ -0,0 +1,9 @@ + ':number раз', + 1 => 'один раз', + 2 => 'два раза', + 3 => '3 раза', + 4 => '4 раза', +]; diff --git a/src/lang/sk/fields.php b/src/lang/sk/fields.php index 6aa9096..097558b 100644 --- a/src/lang/sk/fields.php +++ b/src/lang/sk/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'každý rok', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/ua/days.php b/src/lang/ua/days.php new file mode 100644 index 0000000..dd3bde9 --- /dev/null +++ b/src/lang/ua/days.php @@ -0,0 +1,22 @@ + [ + 1 => 'у понеділок', + 2 => 'у вівторок', + 3 => 'у середу', + 4 => 'у четвер', + 5 => 'у п\'ятницю', + 6 => 'у суботу', + 7 => 'в неділю', + ], + 'dative' => [ + 1 => 'понеділок', + 2 => 'вівторок', + 3 => 'середу', + 4 => 'четвер', + 5 => 'п\'ятницю', + 6 => 'суботу', + 7 => 'неділю', + ], +]; diff --git a/src/lang/ua/fields.php b/src/lang/ua/fields.php new file mode 100644 index 0000000..746a35c --- /dev/null +++ b/src/lang/ua/fields.php @@ -0,0 +1,55 @@ + [ + '@reboot' => 'один раз при запуску', + ], + 'minutes' => [ + 'every' => 'щохвилини', + 'increment' => 'що :increment {хвилина|хвилини|хвилин}', + 'times_per_increment' => ':times що :increment {хвилина|хвилини|хвилин}', + 'multiple' => ':times на годину', + ], + 'hours' => [ + 'every' => 'щогодини', + 'once_an_hour' => 'раз на годину', + 'increment' => 'що :increment {година|години|годин}', + 'multiple_per_increment' => ':count {година|години|годин} з :increment', + 'times_per_increment' => ':times що :increment {година|години|годин}', + 'increment_chained' => 'що :increment {година|години|годин}', + 'multiple_per_day' => ':count {година|години|годин} на день', + 'times_per_day' => ':times на день', + 'once_at_time' => 'о :time', + ], + 'days_of_month' => [ + 'every' => 'щодня', + 'increment' => 'що :increment {день|дні|днів}', + 'multiple_per_increment' => ':count {день|дні|днів} з :increment', + 'multiple_per_month' => ':count {день|дні|днів} в місяць', + 'once_on_day' => 'на :day число', + 'every_on_day' => 'щомісяця :day числа', + ], + 'months' => [ + 'every' => 'щомісяця', + 'every_on_day' => 'щомісяця :day числа', + 'increment' => 'що :increment {місяць|місяці|місяців}', + 'multiple_per_increment' => ':count {місяць|місяці|місяців} з :increment', + 'multiple_per_year' => ':count {місяць|місяці|місяців} на рік', + 'once_on_month' => 'в :month', + 'once_on_day' => 'в :month :day числа', + ], + 'days_of_week' => [ + 'every' => 'щотижня :weekday', + 'increment' => 'Що :increment дні тижня', + 'multiple_per_increment' => ':count {день|дні|днів} тижня з :increment', + 'multiple_days_a_week' => ':count {день|дні|днів} на тиждень', + 'once_on_day' => 'в :day', + ], + 'years' => [ + 'every' => 'щороку', + ], + 'times' => [ + 'am' => ' ранку', + 'pm' => ' вечора', + ], +]; diff --git a/src/lang/ua/months.php b/src/lang/ua/months.php new file mode 100644 index 0000000..107b033 --- /dev/null +++ b/src/lang/ua/months.php @@ -0,0 +1,32 @@ + [ + 1 => 'січні', + 2 => 'лютому', + 3 => 'березні', + 4 => 'квітні', + 5 => 'травні', + 6 => 'червні', + 7 => 'липні', + 8 => 'серпні', + 9 => 'вересні', + 10 => 'жовтні', + 11 => 'листопаді', + 12 => 'грудні', + ], + 'dative' => [ + 1 => 'січням', + 2 => 'лютому', + 3 => 'березням', + 4 => 'квітням', + 5 => 'травням', + 6 => 'червням', + 7 => 'липням', + 8 => 'серпням', + 9 => 'вересням', + 10 => 'жовтням', + 11 => 'листопадам', + 12 => 'грудням', + ], +]; diff --git a/src/lang/ua/ordinals.php b/src/lang/ua/ordinals.php new file mode 100644 index 0000000..0e9f359 --- /dev/null +++ b/src/lang/ua/ordinals.php @@ -0,0 +1,12 @@ + [ + 'default' => ':number-е', + 3 => ':number-є', + 23 => ':number-є', + ], + 'dative' => [ + 'default' => ':number', + ], +]; diff --git a/src/lang/ua/times.php b/src/lang/ua/times.php new file mode 100644 index 0000000..9a55a7d --- /dev/null +++ b/src/lang/ua/times.php @@ -0,0 +1,9 @@ + ':number разів', + 1 => 'один раз', + 2 => 'два рази', + 3 => '3 рази', + 4 => '4 рази', +]; diff --git a/src/lang/vi/fields.php b/src/lang/vi/fields.php index b1b5692..2b8b175 100644 --- a/src/lang/vi/fields.php +++ b/src/lang/vi/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => 'hằng năm', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/zh-TW/fields.php b/src/lang/zh-TW/fields.php index 48df2c3..786724a 100644 --- a/src/lang/zh-TW/fields.php +++ b/src/lang/zh-TW/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => '每年', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/src/lang/zh/fields.php b/src/lang/zh/fields.php index 630ed47..09444ef 100644 --- a/src/lang/zh/fields.php +++ b/src/lang/zh/fields.php @@ -45,4 +45,8 @@ 'years' => [ 'every' => '每年', ], + 'times' => [ + 'am' => 'am', + 'pm' => 'pm', + ], ]; diff --git a/tests/CronTranslatorROTest.php b/tests/CronTranslatorROTest.php index 95d3382..e14de87 100644 --- a/tests/CronTranslatorROTest.php +++ b/tests/CronTranslatorROTest.php @@ -8,22 +8,22 @@ class CronTranslatorROTest extends TestCase public function it_translates_expressions_to_romanian_with_alle_and_once(): void { // All 32 (2^5) combinations of Every/Once. - $this->assertCronTranslateToRo('în fiecare minut', '* * * * *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică', '* * * * 0'); - $this->assertCronTranslateToRo('în fiecare minut în luna Ianuarie', '* * * 1 *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică în luna Ianuarie', '* * * 1 0'); - $this->assertCronTranslateToRo('în fiecare minut în data de 1 a fiecărei luni', '* * 1 * *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică în data de 1 a fiecărei luni', '* * 1 * 0'); - $this->assertCronTranslateToRo('în fiecare minut în luna Ianuarie pe data de 1', '* * 1 1 *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică în luna Ianuarie pe data de 1', '* * 1 1 0'); - $this->assertCronTranslateToRo('în fiecare minut la 0:00', '* 0 * * *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică la 0:00', '* 0 * * 0'); - $this->assertCronTranslateToRo('în fiecare minut în luna Ianuarie la 0:00', '* 0 * 1 *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică în luna Ianuarie la 0:00', '* 0 * 1 0'); - $this->assertCronTranslateToRo('în fiecare minut în data de 1 a fiecărei luni la 0:00', '* 0 1 * *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică în data de 1 a fiecărei luni la 0:00', '* 0 1 * 0'); - $this->assertCronTranslateToRo('în fiecare minut în luna Ianuarie pe data de 1 la 0:00', '* 0 1 1 *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Duminică în luna Ianuarie pe data de 1 la 0:00', '* 0 1 1 0'); + $this->assertCronTranslateToRo('În fiecare minut', '* * * * *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică', '* * * * 0'); + $this->assertCronTranslateToRo('În fiecare minut în luna Ianuarie', '* * * 1 *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică în luna Ianuarie', '* * * 1 0'); + $this->assertCronTranslateToRo('În fiecare minut în data de 1 a fiecărei luni', '* * 1 * *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică în data de 1 a fiecărei luni', '* * 1 * 0'); + $this->assertCronTranslateToRo('În fiecare minut în luna Ianuarie pe data de 1', '* * 1 1 *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică în luna Ianuarie pe data de 1', '* * 1 1 0'); + $this->assertCronTranslateToRo('În fiecare minut la 0:00', '* 0 * * *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică la 0:00', '* 0 * * 0'); + $this->assertCronTranslateToRo('În fiecare minut în luna Ianuarie la 0:00', '* 0 * 1 *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică în luna Ianuarie la 0:00', '* 0 * 1 0'); + $this->assertCronTranslateToRo('În fiecare minut în data de 1 a fiecărei luni la 0:00', '* 0 1 * *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică în data de 1 a fiecărei luni la 0:00', '* 0 1 * 0'); + $this->assertCronTranslateToRo('În fiecare minut în luna Ianuarie pe data de 1 la 0:00', '* 0 1 1 *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Duminică în luna Ianuarie pe data de 1 la 0:00', '* 0 1 1 0'); $this->assertCronTranslateToRo('O singură dată pe oră', '0 * * * *'); $this->assertCronTranslateToRo('O singură dată pe oră în zilele de Duminică', '0 * * * 0'); $this->assertCronTranslateToRo('O singură dată pe oră în luna Ianuarie', '0 * * 1 *'); @@ -32,40 +32,40 @@ public function it_translates_expressions_to_romanian_with_alle_and_once(): void $this->assertCronTranslateToRo('O singură dată pe oră în zilele de Duminică în data de 1 a fiecărei luni', '0 * 1 * 0'); $this->assertCronTranslateToRo('O singură dată pe oră în luna Ianuarie pe data de 1', '0 * 1 1 *'); $this->assertCronTranslateToRo('O singură dată pe oră în zilele de Duminică în luna Ianuarie pe data de 1', '0 * 1 1 0'); - $this->assertCronTranslateToRo('în fiecare zi la 0:00', '0 0 * * *'); - $this->assertCronTranslateToRo('în fiecare Duminică la 0:00', '0 0 * * 0'); - $this->assertCronTranslateToRo('în fiecare zi în luna Ianuarie la 0:00', '0 0 * 1 *'); - $this->assertCronTranslateToRo('în fiecare Duminică în luna Ianuarie la 0:00', '0 0 * 1 0'); + $this->assertCronTranslateToRo('În fiecare zi la 0:00', '0 0 * * *'); + $this->assertCronTranslateToRo('În fiecare Duminică la 0:00', '0 0 * * 0'); + $this->assertCronTranslateToRo('În fiecare zi în luna Ianuarie la 0:00', '0 0 * 1 *'); + $this->assertCronTranslateToRo('În fiecare Duminică în luna Ianuarie la 0:00', '0 0 * 1 0'); $this->assertCronTranslateToRo('Pe data de 1 a fiecărei luni la 0:00', '0 0 1 * *'); $this->assertCronTranslateToRo('Pe data de 1 a fiecărei luni în zilele de Duminică la 0:00', '0 0 1 * 0'); - $this->assertCronTranslateToRo('în fiecare an în luna Ianuarie pe data de 1 la 0:00', '0 0 1 1 *'); - $this->assertCronTranslateToRo('în zilele de Duminică în luna Ianuarie pe data de 1 la 0:00', '0 0 1 1 0'); + $this->assertCronTranslateToRo('În fiecare an în luna Ianuarie pe data de 1 la 0:00', '0 0 1 1 *'); + $this->assertCronTranslateToRo('În zilele de Duminică în luna Ianuarie pe data de 1 la 0:00', '0 0 1 1 0'); // More realistic examples. - $this->assertCronTranslateToRo('în fiecare an în luna Ianuarie pe data de 1 la 12:00', '0 12 1 1 *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Luni la 15:00', '* 15 * * 1'); - $this->assertCronTranslateToRo('în fiecare minut în luna Ianuarie pe data de 3', '* * 3 1 *'); - $this->assertCronTranslateToRo('în fiecare minut în zilele de Luni în luna Aprilie', '* * * 4 1'); - $this->assertCronTranslateToRo('în zilele de Luni în luna Aprilie pe data de 22 la 15:10', '10 15 22 4 1'); + $this->assertCronTranslateToRo('În fiecare an în luna Ianuarie pe data de 1 la 12:00', '0 12 1 1 *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Luni la 15:00', '* 15 * * 1'); + $this->assertCronTranslateToRo('În fiecare minut în luna Ianuarie pe data de 3', '* * 3 1 *'); + $this->assertCronTranslateToRo('În fiecare minut în zilele de Luni în luna Aprilie', '* * * 4 1'); + $this->assertCronTranslateToRo('În zilele de Luni în luna Aprilie pe data de 22 la 15:10', '10 15 22 4 1'); // Paparazzi examples. - $this->assertCronTranslateToRo('în fiecare zi la 22:00', '0 22 * * *'); - $this->assertCronTranslateToRo('în fiecare zi la 9:00', '0 9 * * *'); - $this->assertCronTranslateToRo('în fiecare Luni la 16:00', '0 16 * * 1'); - $this->assertCronTranslateToRo('în fiecare an în luna Ianuarie pe data de 1 la 0:00', '0 0 1 1 *'); + $this->assertCronTranslateToRo('În fiecare zi la 22:00', '0 22 * * *'); + $this->assertCronTranslateToRo('În fiecare zi la 9:00', '0 9 * * *'); + $this->assertCronTranslateToRo('În fiecare Luni la 16:00', '0 16 * * 1'); + $this->assertCronTranslateToRo('În fiecare an în luna Ianuarie pe data de 1 la 0:00', '0 0 1 1 *'); $this->assertCronTranslateToRo('Pe data de 1 a fiecărei luni la 0:00', '0 0 1 * *'); } /** @test */ public function it_translate_expressions_with_multiple(): void { - $this->assertCronTranslateToRo('în fiecare minut de 2 ori pe zi', '* 8,18 * * *'); - $this->assertCronTranslateToRo('în fiecare minut de 3 ori pe zi', '* 8,18,20 * * *'); - $this->assertCronTranslateToRo('în fiecare minut de 20 ori pe zi', '* 1-20 * * *'); + $this->assertCronTranslateToRo('În fiecare minut de 2 ori pe zi', '* 8,18 * * *'); + $this->assertCronTranslateToRo('În fiecare minut de 3 ori pe zi', '* 8,18,20 * * *'); + $this->assertCronTranslateToRo('În fiecare minut de 20 ori pe zi', '* 1-20 * * *'); $this->assertCronTranslateToRo('De 2 ori pe oră', '0,30 * * * *'); $this->assertCronTranslateToRo('De 2 ori pe oră de 5 ori pe zi', '0,30 1-5 * * *'); $this->assertCronTranslateToRo('De 5 ori pe zi', '0 1-5 * * *'); - $this->assertCronTranslateToRo('în fiecare minut de 5 ori pe zi', '* 1-5 * * *'); + $this->assertCronTranslateToRo('În fiecare minut de 5 ori pe zi', '* 1-5 * * *'); $this->assertCronTranslateToRo('5 zile pe lună la 1:00', '0 1 1-5 * *'); $this->assertCronTranslateToRo('5 zile pe lună 2 luni pe an la 1:00', '0 1 1-5 5,6 *'); $this->assertCronTranslateToRo('2 luni pe an în data de 5 la 1:00', '0 1 5 5,6 *'); @@ -75,49 +75,49 @@ public function it_translate_expressions_with_multiple(): void /** @test */ public function it_translate_expressions_with_increment(): void { - $this->assertCronTranslateToRo('în fiecare 2 minute', '*/2 * * * *'); - $this->assertCronTranslateToRo('în fiecare 2 minute', '1/2 * * * *'); + $this->assertCronTranslateToRo('În fiecare 2 minute', '*/2 * * * *'); + $this->assertCronTranslateToRo('În fiecare 2 minute', '1/2 * * * *'); $this->assertCronTranslateToRo('De 2 ori fiecare 4 minute', '1,3/4 * * * *'); $this->assertCronTranslateToRo('De 3 ori fiecare 5 minute', '1-3/5 * * * *'); - $this->assertCronTranslateToRo('în fiecare 2 minute la 14:00', '*/2 14 * * *'); + $this->assertCronTranslateToRo('În fiecare 2 minute la 14:00', '*/2 14 * * *'); $this->assertCronTranslateToRo('O singură dată pe oră în fiecare 2 zile', '0 * */2 * *'); - $this->assertCronTranslateToRo('în fiecare minut în fiecare 2 zile', '* * */2 * *'); + $this->assertCronTranslateToRo('În fiecare minut în fiecare 2 zile', '* * */2 * *'); $this->assertCronTranslateToRo('O singură dată fiecare 2 ore', '0 */2 * * *'); $this->assertCronTranslateToRo('De 2 ori fiecare 5 ore', '0 1,2/5 * * *'); - $this->assertCronTranslateToRo('în fiecare minut 2 ore din 5', '* 1,2/5 * * *'); - $this->assertCronTranslateToRo('în fiecare zi în fiecare 4 luni la 0:00', '0 0 * */4 *'); + $this->assertCronTranslateToRo('În fiecare minut 2 ore din 5', '* 1,2/5 * * *'); + $this->assertCronTranslateToRo('În fiecare zi în fiecare 4 luni la 0:00', '0 0 * */4 *'); } /** @test */ public function it_adds_junctions_to_certain_combinations_of_cron_types(): void { - $this->assertCronTranslateToRo('în fiecare minut în fiecare 2 ore', '* */2 * * *'); - $this->assertCronTranslateToRo('în fiecare minut în fiecare 3 ore în data de 2 a fiecărei luni', '* 1/3 2 * *'); + $this->assertCronTranslateToRo('În fiecare minut în fiecare 2 ore', '* */2 * * *'); + $this->assertCronTranslateToRo('În fiecare minut în fiecare 3 ore în data de 2 a fiecărei luni', '* 1/3 2 * *'); } /** @test */ public function it_converts_ranges_of_one_into_once_cron_types(): void { - $this->assertCronTranslateToRo('în fiecare minut la 8:00', '* 8-8 * * *'); - $this->assertCronTranslateToRo('în fiecare minut în luna Ianuarie', '* * * 1-1 *'); + $this->assertCronTranslateToRo('În fiecare minut la 8:00', '* 8-8 * * *'); + $this->assertCronTranslateToRo('În fiecare minut în luna Ianuarie', '* * * 1-1 *'); } /** @test */ public function it_handles_extended_cron_syntax(): void { $this->assertCronTranslateToRo('O singură dată pe oră', '@hourly'); - $this->assertCronTranslateToRo('în fiecare zi la 0:00', '@daily'); - $this->assertCronTranslateToRo('în fiecare Duminică la 0:00', '@weekly'); + $this->assertCronTranslateToRo('În fiecare zi la 0:00', '@daily'); + $this->assertCronTranslateToRo('În fiecare Duminică la 0:00', '@weekly'); $this->assertCronTranslateToRo('Pe data de 1 a fiecărei luni la 0:00', '@monthly'); - $this->assertCronTranslateToRo('în fiecare an în luna Ianuarie pe data de 1 la 0:00', '@yearly'); - $this->assertCronTranslateToRo('în fiecare an în luna Ianuarie pe data de 1 la 0:00', '@annually'); + $this->assertCronTranslateToRo('În fiecare an în luna Ianuarie pe data de 1 la 0:00', '@yearly'); + $this->assertCronTranslateToRo('În fiecare an în luna Ianuarie pe data de 1 la 0:00', '@annually'); } /** @test */ public function it_can_format_the_time_in_12_and_24_hours(): void { - $this->assertCronTranslateToRo('în fiecare zi la 22:30', '30 22 * * *', 'en', true); - $this->assertCronTranslateToRo('în fiecare minut la 6:00', '* 6 * * *', 'en', true); + $this->assertCronTranslateToRo('În fiecare zi la 22:30', '30 22 * * *', 'en', true); + $this->assertCronTranslateToRo('În fiecare minut la 6:00', '* 6 * * *', 'en', true); } public function assertCronTranslateToRo(string $expected, string $actual, bool $timeFormat24hours = true): void diff --git a/tests/CronTranslatorRUTest.php b/tests/CronTranslatorRUTest.php new file mode 100644 index 0000000..9c0b70d --- /dev/null +++ b/tests/CronTranslatorRUTest.php @@ -0,0 +1,133 @@ +assertCronTranslateToRu('Каждую минуту', '* * * * *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям', '* * * * 0'); + $this->assertCronTranslateToRu('Каждую минуту в январе', '* * * 1 *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям в январе', '* * * 1 0'); + $this->assertCronTranslateToRu('Каждую минуту 1-го числа каждого месяца', '* * 1 * *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям 1-го числа каждого месяца', '* * 1 * 0'); + $this->assertCronTranslateToRu('Каждую минуту в январе 1-го числа', '* * 1 1 *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям в январе 1-го числа', '* * 1 1 0'); + $this->assertCronTranslateToRu('Каждую минуту в 0:00', '* 0 * * *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям в 0:00', '* 0 * * 0'); + $this->assertCronTranslateToRu('Каждую минуту в январе в 0:00', '* 0 * 1 *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям в январе в 0:00', '* 0 * 1 0'); + $this->assertCronTranslateToRu('Каждую минуту 1-го числа каждого месяца в 0:00', '* 0 1 * *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям 1-го числа каждого месяца в 0:00', '* 0 1 * 0'); + $this->assertCronTranslateToRu('Каждую минуту в январе 1-го числа в 0:00', '* 0 1 1 *'); + $this->assertCronTranslateToRu('Каждую минуту по воскресеньям в январе 1-го числа в 0:00', '* 0 1 1 0'); + $this->assertCronTranslateToRu('Раз в час', '0 * * * *'); + $this->assertCronTranslateToRu('Раз в час по воскресеньям', '0 * * * 0'); + $this->assertCronTranslateToRu('Раз в час в январе', '0 * * 1 *'); + $this->assertCronTranslateToRu('Раз в час по воскресеньям в январе', '0 * * 1 0'); + $this->assertCronTranslateToRu('Раз в час 1-го числа каждого месяца', '0 * 1 * *'); + $this->assertCronTranslateToRu('Раз в час по воскресеньям 1-го числа каждого месяца', '0 * 1 * 0'); + $this->assertCronTranslateToRu('Раз в час в январе 1-го числа', '0 * 1 1 *'); + $this->assertCronTranslateToRu('Раз в час по воскресеньям в январе 1-го числа', '0 * 1 1 0'); + $this->assertCronTranslateToRu('Каждый день в 0:00', '0 0 * * *'); + $this->assertCronTranslateToRu('Каждую неделю в воскресенье в 0:00', '0 0 * * 0'); + $this->assertCronTranslateToRu('Каждый день в январе в 0:00', '0 0 * 1 *'); + $this->assertCronTranslateToRu('Каждую неделю в воскресенье в январе в 0:00', '0 0 * 1 0'); + $this->assertCronTranslateToRu('1-ое число каждого месяца в 0:00', '0 0 1 * *'); + $this->assertCronTranslateToRu('1-ое число каждого месяца по воскресеньям в 0:00', '0 0 1 * 0'); + $this->assertCronTranslateToRu('3-е число каждого месяца по воскресеньям в 0:00', '0 0 3 * 0'); + $this->assertCronTranslateToRu('Каждый год в январе 1-го числа в 0:00', '0 0 1 1 *'); + $this->assertCronTranslateToRu('По воскресеньям в январе 1-го числа в 0:00', '0 0 1 1 0'); + + // More realistic examples. + $this->assertCronTranslateToRu('Каждый год в январе 1-го числа в 12:00', '0 12 1 1 *'); + $this->assertCronTranslateToRu('Каждую минуту по понедельникам в 15:00', '* 15 * * 1'); + $this->assertCronTranslateToRu('Каждую минуту в январе 3-го числа', '* * 3 1 *'); + $this->assertCronTranslateToRu('Каждую минуту по понедельникам в апреле', '* * * 4 1'); + $this->assertCronTranslateToRu('По понедельникам в апреле 22-го числа в 15:10', '10 15 22 4 1'); + + // Paparazzi examples. + $this->assertCronTranslateToRu('Каждый день в 22:00', '0 22 * * *'); + $this->assertCronTranslateToRu('Каждый день в 9:00', '0 9 * * *'); + $this->assertCronTranslateToRu('Каждую неделю в понедельник в 16:00', '0 16 * * 1'); + $this->assertCronTranslateToRu('Каждую неделю во вторник в 16:00', '0 16 * * 2'); + $this->assertCronTranslateToRu('Каждый год в январе 1-го числа в 0:00', '0 0 1 1 *'); + $this->assertCronTranslateToRu('1-ое число каждого месяца в 0:00', '0 0 1 * *'); + } + + /** @test */ + public function it_translate_expressions_with_multiple(): void + { + $this->assertCronTranslateToRu('Каждую минуту 2 часа в день', '* 8,18 * * *'); + $this->assertCronTranslateToRu('Каждую минуту 3 часа в день', '* 8,18,20 * * *'); + $this->assertCronTranslateToRu('Каждую минуту 20 часов в день', '* 1-20 * * *'); + $this->assertCronTranslateToRu('Два раза в час', '0,30 * * * *'); + $this->assertCronTranslateToRu('Два раза в час 5 часов в день', '0,30 1-5 * * *'); + $this->assertCronTranslateToRu('5 раз в день', '0 1-5 * * *'); + $this->assertCronTranslateToRu('Каждую минуту 5 часов в день', '* 1-5 * * *'); + $this->assertCronTranslateToRu('5 дней в месяц в 1:00', '0 1 1-5 * *'); + $this->assertCronTranslateToRu('5 дней в месяц 2 месяца в год в 1:00', '0 1 1-5 5,6 *'); + $this->assertCronTranslateToRu('2 месяца в год на 5-ое число в 1:00', '0 1 5 5,6 *'); + $this->assertCronTranslateToRu('5-ое число каждого месяца 4 дня в неделю в 1:00', '0 1 5 * 1-4'); + } + + /** @test */ + public function it_translate_expressions_with_increment(): void + { + $this->assertCronTranslateToRu('Каждые 2 минуты', '*/2 * * * *'); + $this->assertCronTranslateToRu('Каждые 2 минуты', '1/2 * * * *'); + $this->assertCronTranslateToRu('Два раза каждые 4 минуты', '1,3/4 * * * *'); + $this->assertCronTranslateToRu('3 раза каждые 5 минут', '1-3/5 * * * *'); + $this->assertCronTranslateToRu('Каждые 2 минуты в 14:00', '*/2 14 * * *'); + $this->assertCronTranslateToRu('Раз в час каждые 2 дня', '0 * */2 * *'); + $this->assertCronTranslateToRu('Каждую минуту каждые 2 дня', '* * */2 * *'); + $this->assertCronTranslateToRu('Один раз каждые 2 часа', '0 */2 * * *'); + $this->assertCronTranslateToRu('Два раза каждые 5 часов', '0 1,2/5 * * *'); + $this->assertCronTranslateToRu('5 раз каждые 8 часов', '0 1,2,3,4,5/8 * * *'); + $this->assertCronTranslateToRu('Каждую минуту 2 часа из 5', '* 1,2/5 * * *'); + $this->assertCronTranslateToRu('Каждый день каждые 4 месяца в 0:00', '0 0 * */4 *'); + } + + /** @test */ + public function it_adds_junctions_to_certain_combinations_of_cron_types(): void + { + $this->assertCronTranslateToRu('Каждую минуту каждые 2 часа', '* */2 * * *'); + $this->assertCronTranslateToRu('Каждую минуту каждые 3 часа 2-го числа каждого месяца', '* 1/3 2 * *'); + } + + /** @test */ + public function it_converts_ranges_of_one_into_once_cron_types(): void + { + $this->assertCronTranslateToRu('Каждую минуту в 8:00', '* 8-8 * * *'); + $this->assertCronTranslateToRu('Каждую минуту в январе', '* * * 1-1 *'); + } + + /** @test */ + public function it_handles_extended_cron_syntax(): void + { + $this->assertCronTranslateToRu('Один раз при старте', '@reboot'); + $this->assertCronTranslateToRu('Раз в час', '@hourly'); + $this->assertCronTranslateToRu('Каждый день в 0:00', '@daily'); + $this->assertCronTranslateToRu('Каждую неделю в воскресенье в 0:00', '@weekly'); + $this->assertCronTranslateToRu('1-ое число каждого месяца в 0:00', '@monthly'); + $this->assertCronTranslateToRu('Каждый год в январе 1-го числа в 0:00', '@yearly'); + $this->assertCronTranslateToRu('Каждый год в январе 1-го числа в 0:00', '@annually'); + } + + /** @test */ + public function it_can_format_the_time_in_12_and_24_hours(): void + { + $this->assertCronTranslateToRu('Каждый день в 10:30 вечера', '30 22 * * *', false); + $this->assertCronTranslateToRu('Каждый день в 22:30', '30 22 * * *', true); + $this->assertCronTranslateToRu('Каждую минуту в 6 утра', '* 6 * * *', false); + $this->assertCronTranslateToRu('Каждую минуту в 6:00', '* 6 * * *', true); + } + + public function assertCronTranslateToRu(string $expected, string $actual, bool $timeFormat24hours = true): void + { + $this->assertCronTranslateTo($expected, $actual, 'ru', $timeFormat24hours); + } +} diff --git a/tests/CronTranslatorTest.php b/tests/CronTranslatorTest.php index 41f8b51..eab9172 100644 --- a/tests/CronTranslatorTest.php +++ b/tests/CronTranslatorTest.php @@ -105,6 +105,7 @@ public function it_converts_ranges_of_one_into_once_cron_types(): void /** @test */ public function it_handles_extended_cron_syntax(): void { + $this->assertCronTranslateTo('Run once at startup', '@reboot'); $this->assertCronTranslateTo('Once an hour', '@hourly'); $this->assertCronTranslateTo('Every day at 12:00am', '@daily'); $this->assertCronTranslateTo('Every Sunday at 12:00am', '@weekly'); diff --git a/tests/CronTranslatorUATest.php b/tests/CronTranslatorUATest.php new file mode 100644 index 0000000..6f369df --- /dev/null +++ b/tests/CronTranslatorUATest.php @@ -0,0 +1,134 @@ +assertCronTranslateToUa('Щохвилини', '* * * * *'); + $this->assertCronTranslateToUa('Щохвилини в неділю', '* * * * 0'); + $this->assertCronTranslateToUa('Щохвилини в січні', '* * * 1 *'); + $this->assertCronTranslateToUa('Щохвилини в неділю в січні', '* * * 1 0'); + $this->assertCronTranslateToUa('Щохвилини щомісяця 1 числа', '* * 1 * *'); + $this->assertCronTranslateToUa('Щохвилини в неділю щомісяця 1 числа', '* * 1 * 0'); + $this->assertCronTranslateToUa('Щохвилини в січні 1 числа', '* * 1 1 *'); + $this->assertCronTranslateToUa('Щохвилини в неділю в січні 1 числа', '* * 1 1 0'); + $this->assertCronTranslateToUa('Щохвилини о 0:00', '* 0 * * *'); + $this->assertCronTranslateToUa('Щохвилини в неділю о 0:00', '* 0 * * 0'); + $this->assertCronTranslateToUa('Щохвилини в січні о 0:00', '* 0 * 1 *'); + $this->assertCronTranslateToUa('Щохвилини в неділю в січні о 0:00', '* 0 * 1 0'); + $this->assertCronTranslateToUa('Щохвилини щомісяця 1 числа о 0:00', '* 0 1 * *'); + $this->assertCronTranslateToUa('Щохвилини в неділю щомісяця 1 числа о 0:00', '* 0 1 * 0'); + $this->assertCronTranslateToUa('Щохвилини в січні 1 числа о 0:00', '* 0 1 1 *'); + $this->assertCronTranslateToUa('Щохвилини в неділю в січні 1 числа о 0:00', '* 0 1 1 0'); + $this->assertCronTranslateToUa('Раз на годину', '0 * * * *'); + $this->assertCronTranslateToUa('Раз на годину в неділю', '0 * * * 0'); + $this->assertCronTranslateToUa('Раз на годину в січні', '0 * * 1 *'); + $this->assertCronTranslateToUa('Раз на годину в неділю в січні', '0 * * 1 0'); + $this->assertCronTranslateToUa('Раз на годину щомісяця 1 числа', '0 * 1 * *'); + $this->assertCronTranslateToUa('Раз на годину в неділю щомісяця 1 числа', '0 * 1 * 0'); + $this->assertCronTranslateToUa('Раз на годину в січні 1 числа', '0 * 1 1 *'); + $this->assertCronTranslateToUa('Раз на годину в неділю в січні 1 числа', '0 * 1 1 0'); + $this->assertCronTranslateToUa('Щодня о 0:00', '0 0 * * *'); + $this->assertCronTranslateToUa('Щотижня в неділю о 0:00', '0 0 * * 0'); + $this->assertCronTranslateToUa('Щодня в січні о 0:00', '0 0 * 1 *'); + $this->assertCronTranslateToUa('Щотижня в неділю в січні о 0:00', '0 0 * 1 0'); + $this->assertCronTranslateToUa('Щомісяця 1-е числа о 0:00', '0 0 1 * *'); + $this->assertCronTranslateToUa('Щомісяця 1-е числа в неділю о 0:00', '0 0 1 * 0'); + $this->assertCronTranslateToUa('Щомісяця 3-є числа в неділю о 0:00', '0 0 3 * 0'); + $this->assertCronTranslateToUa('Щороку в січні 1 числа о 0:00', '0 0 1 1 *'); + $this->assertCronTranslateToUa('В неділю в січні 1 числа о 0:00', '0 0 1 1 0'); + + // More realistic examples. + $this->assertCronTranslateToUa('Щороку в січні 1 числа о 12:00', '0 12 1 1 *'); + $this->assertCronTranslateToUa('Щохвилини в понеділок о 15:00', '* 15 * * 1'); + $this->assertCronTranslateToUa('Щохвилини в січні 3 числа', '* * 3 1 *'); + $this->assertCronTranslateToUa('Щохвилини в понеділок в квітні', '* * * 4 1'); + $this->assertCronTranslateToUa('В понеділок в квітні 22 числа о 15:10', '10 15 22 4 1'); + + // Paparazzi examples. + $this->assertCronTranslateToUa('Щодня о 22:00', '0 22 * * *'); + $this->assertCronTranslateToUa('Щодня о 9:00', '0 9 * * *'); + $this->assertCronTranslateToUa('Щотижня у понеділок о 16:00', '0 16 * * 1'); + $this->assertCronTranslateToUa('Щотижня у вівторок о 16:00', '0 16 * * 2'); + $this->assertCronTranslateToUa('Щороку в січні 1 числа о 0:00', '0 0 1 1 *'); + $this->assertCronTranslateToUa('Щомісяця 1-е числа о 0:00', '0 0 1 * *'); + } + + /** @test */ + public function it_translate_expressions_with_multiple(): void + { + $this->assertCronTranslateToUa('Щохвилини 2 години на день', '* 8,18 * * *'); + $this->assertCronTranslateToUa('Щохвилини 3 години на день', '* 8,18,20 * * *'); + $this->assertCronTranslateToUa('Щохвилини 20 годин на день', '* 1-20 * * *'); + $this->assertCronTranslateToUa('Два рази на годину', '0,30 * * * *'); + $this->assertCronTranslateToUa('Два рази на годину 5 годин на день', '0,30 1-5 * * *'); + $this->assertCronTranslateToUa('5 разів на день', '0 1-5 * * *'); + $this->assertCronTranslateToUa('Щохвилини 5 годин на день', '* 1-5 * * *'); + $this->assertCronTranslateToUa('5 днів в місяць о 1:00', '0 1 1-5 * *'); + $this->assertCronTranslateToUa('5 днів в місяць 2 місяці на рік о 1:00', '0 1 1-5 5,6 *'); + $this->assertCronTranslateToUa('2 місяці на рік на 5-е число о 1:00', '0 1 5 5,6 *'); + $this->assertCronTranslateToUa('Щомісяця 5-е числа 4 дні на тиждень о 1:00', '0 1 5 * 1-4'); + } + + /** @test */ + public function it_translate_expressions_with_increment(): void + { + $this->assertCronTranslateToUa('Що 2 хвилини', '*/2 * * * *'); + $this->assertCronTranslateToUa('Що 2 хвилини', '1/2 * * * *'); + $this->assertCronTranslateToUa('Два рази що 4 хвилини', '1,3/4 * * * *'); + $this->assertCronTranslateToUa('3 рази що 5 хвилин', '1-3/5 * * * *'); + $this->assertCronTranslateToUa('Що 2 хвилини о 14:00', '*/2 14 * * *'); + $this->assertCronTranslateToUa('Раз на годину що 2 дні', '0 * */2 * *'); + $this->assertCronTranslateToUa('Щохвилини що 2 дні', '* * */2 * *'); + $this->assertCronTranslateToUa('Один раз що 2 години', '0 */2 * * *'); + $this->assertCronTranslateToUa('Два рази що 5 годин', '0 1,2/5 * * *'); + $this->assertCronTranslateToUa('5 разів що 8 годин', '0 1,2,3,4,5/8 * * *'); + $this->assertCronTranslateToUa('Щохвилини 2 години з 5', '* 1,2/5 * * *'); + $this->assertCronTranslateToUa('Щодня що 4 місяці о 0:00', '0 0 * */4 *'); + } + + /** @test */ + public function it_adds_junctions_to_certain_combinations_of_cron_types(): void + { + $this->assertCronTranslateToUa('Щохвилини що 2 години', '* */2 * * *'); + $this->assertCronTranslateToUa('Щохвилини що 3 години щомісяця 2 числа', '* 1/3 2 * *'); + } + + /** @test */ + public function it_converts_ranges_of_one_into_once_cron_types(): void + { + $this->assertCronTranslateToUa('Щохвилини о 8:00', '* 8-8 * * *'); + $this->assertCronTranslateToUa('Щохвилини в січні', '* * * 1-1 *'); + } + + /** @test */ + public function it_handles_extended_cron_syntax(): void + { + $this->assertCronTranslateToUa('Один раз при запуску', '@reboot'); + $this->assertCronTranslateToUa('Раз на годину', '@hourly'); + $this->assertCronTranslateToUa('Щодня о 0:00', '@daily'); + $this->assertCronTranslateToUa('Щотижня в неділю о 0:00', '@weekly'); + $this->assertCronTranslateToUa('Щомісяця 1-е числа о 0:00', '@monthly'); + $this->assertCronTranslateToUa('Щороку в січні 1 числа о 0:00', '@yearly'); + $this->assertCronTranslateToUa('Щороку в січні 1 числа о 0:00', '@annually'); + } + + /** @test */ + public function it_can_format_the_time_in_12_and_24_hours(): void + { + $this->assertCronTranslateToUa('Щодня о 10:30 вечора', '30 22 * * *', false); + $this->assertCronTranslateToUa('Щодня о 22:30', '30 22 * * *', true); + $this->assertCronTranslateToUa('Щохвилини о 6 ранку', '* 6 * * *', false); + $this->assertCronTranslateToUa('Щохвилини о 6:00', '* 6 * * *', true); + } + + public function assertCronTranslateToUa(string $expected, string $actual, bool $timeFormat24hours = true): void + { + $this->assertCronTranslateTo($expected, $actual, 'ua', $timeFormat24hours); + } +} + diff --git a/tests/PluralizeTest.php b/tests/PluralizeTest.php new file mode 100644 index 0000000..2623728 --- /dev/null +++ b/tests/PluralizeTest.php @@ -0,0 +1,16 @@ +assertEquals('1 день 2 часа 5 минут', $CronExpression->pluralize('1 {день|дня|дней} 2 {час|часа|часов} 5 {минута|минуты|минут}')); + + $this->assertEquals('1 day 2 hours 5 minutes', $CronExpression->pluralize('1 {day|days} 2 {hour|hours} 5 {minute|minutes}')); + } +}