diff --git a/src/I18Next/Exception/TranslationSyntaxError.php b/src/I18Next/Exception/TranslationSyntaxError.php new file mode 100644 index 0000000..010a7b4 --- /dev/null +++ b/src/I18Next/Exception/TranslationSyntaxError.php @@ -0,0 +1,11 @@ +processorValue->processValue($found_key, $parameters); - if ($found_key !== $key) - { + if ($found_key !== $key) { return $found_key; } diff --git a/src/I18Next/Locale/Translations.php b/src/I18Next/Locale/Translations.php index a9c9d89..768cfea 100644 --- a/src/I18Next/Locale/Translations.php +++ b/src/I18Next/Locale/Translations.php @@ -7,6 +7,7 @@ use atk4\core\ConfigTrait; use atk4\core\Exception; use DirectoryIterator; +use I18Next\Exception\TranslationSyntaxError; /** * @internal @@ -54,6 +55,7 @@ public function load(string $path, bool $use_filename_as_namespace, ?string ...$ // normalizing $this->afterReadProcessForKeyCounters(); $this->afterReadProcessForKeyDeepInline(); + $this->afterReadAddNamespaceIfNeeded($configs, $fileInfo->getBasename('.'.$this->loader_format_ext)); // always reset config after every load @@ -105,6 +107,10 @@ private function afterReadProcessForKeyCounters(): void if ('plural' === $key_plural_definition || is_numeric($key_plural_definition)) { $this->processForCounterKey($key_plural_definition, $key, $value); } + + if ('interval' === $key_plural_definition) { + $this->processForIntervalKey($key_plural_definition, $key, $value); + } } } @@ -132,6 +138,71 @@ private function processForCounterKey(string $key_plural_definition, string $key $this->setConfig($cleared_key.'/'.(string) $counter, $value); } + private function processForIntervalKey(string $key_plural_definition, string $key, string $value): void + { + $cleared_key = substr( + $key, + 0, + (strlen($key_plural_definition) + 1 /* the extra undescore before the plural_definition */) * -1 + ); + + $check_last = substr($value, -1) === ';'; + + if (! $check_last) { + throw new TranslationSyntaxError([ + 'Interval declaration must end with ";" ('.$key.' => '.$value.')', + 'key' => $key, + 'value' => $value, + ]); + } + + $count_intervals = count(explode(';', trim($value, ';'))); + + $re = '/\((\S*)\)\{(.[^\}]*)\}/m'; + //$str = '(1){one item};(2-7){a few items};(7-inf){a lot of items};'; + + preg_match_all($re, $value, $matches, PREG_SET_ORDER, 0); + + if (count($matches) !== $count_intervals) { + throw new TranslationSyntaxError([ + 'Interval declaration syntax error ('.$key.' => '.$value.')', + 'key' => $key, + 'value' => $value, + ]); + } + + foreach ($matches as $match) { + if (count($match) < 3) { + throw new TranslationSyntaxError([ + 'Interval value syntax incorrect : '.$value, + 'key' => $key, + 'value' => $value, + 'matches' => $matches, + 'error_match' => $match, + ]); + } + + $interval = explode('-', $match[1]); + + $interval_start = $interval[0]; + $interval_end = $interval[1] ?? $interval[0]; + if ($interval_end === 'inf') { + $interval_end = $interval_start; + } + + $translation = $match[2]; + + if ($interval_start === $interval_end) { + $this->setConfig($cleared_key.'/'.(string) $interval_start, $translation); + continue; + } + + for ($i = $interval_start; $i < $interval_end; $i++) { + $this->setConfig($cleared_key.'/'.(string) $i, $translation); + } + } + } + private function afterReadProcessForKeyDeepInline(): void { $filtered = array_filter($this->config, function ($key) { diff --git a/tests/Translator_Atk4Trait_Test.php b/tests/Translator_Atk4Trait_Test.php index 5bfa778..33389c6 100755 --- a/tests/Translator_Atk4Trait_Test.php +++ b/tests/Translator_Atk4Trait_Test.php @@ -22,12 +22,12 @@ public function testBase() $app->add($child = new ATK4ChildMock()); - $result = $child->_('friend', NULL, NULL, 'en'); + $result = $child->_('friend', null, null, 'en'); $this->assertEquals('A friend', $result); $this->translator->addLanguage('ro'); - $result = $child->_('friend', NULL, NULL, 'ro'); + $result = $child->_('friend', null, null, 'ro'); $this->assertEquals('Un prieten', $result); } @@ -47,7 +47,6 @@ public function testDirect() $result = $app->_('Ho trovato, $t(friend)'); $this->assertEquals('Ho trovato, Un conoscente', $result); - } } diff --git a/tests/Translator_CasePluralInterval_Test.php b/tests/Translator_CasePluralInterval_Test.php new file mode 100755 index 0000000..c82fc2b --- /dev/null +++ b/tests/Translator_CasePluralInterval_Test.php @@ -0,0 +1,89 @@ +setupTranslatorLanguages('en'); + + $result = $this->translator->_('key4'); + $this->assertEquals('one item', $result); + } + + public function testCount0() + { + $this->setupTranslatorLanguages('en'); + + $result = $this->translator->_('key4', ['count' => 0]); + $this->assertEquals('key4', $result); + } + + public function testCount1() + { + $this->setupTranslatorLanguages('en'); + + $result = $this->translator->_('key4', ['count' => 1]); + $this->assertEquals('one item', $result); + } + + public function testCount3() + { + $this->setupTranslatorLanguages('en'); + + $result = $this->translator->_('key4', ['count' => 3]); + $this->assertEquals('a few items', $result); + } + + public function testCount8() + { + $this->setupTranslatorLanguages('en'); + + $result = $this->translator->_('key4', ['count' => 8]); + $this->assertEquals('a lot of items', $result); + } + + public function testException1() + { + $this->expectException(TranslationSyntaxError::class); + + $path = '/tmp/locale_exception'; + @mkdir($path.'/en', 0777, true); + + file_put_contents( + $path.'/en/exception.json', + json_encode(['key4_interval' => '(1){one item};2-7){a few items};(7-inf){a lot of items};']) + ); + + $this->translator = new Translator(); + $this->translator->setTranslationsPath($path); + $this->translator->setLanguagePrimary('en'); + } + + public function testException2() + { + $this->expectException(TranslationSyntaxError::class); + + $path = '/tmp/locale_exception'; + @mkdir($path.'/en', 0777, true); + + file_put_contents( + $path.'/en/exception.json', + json_encode(['key4_interval' => '(1){one item};(2-7){a few items};(7-inf){a lot of items}']) + ); + + $this->translator = new Translator(); + $this->translator->setTranslationsPath($path); + $this->translator->setLanguagePrimary('en'); + } +} diff --git a/tests/data/locales/en/key.json b/tests/data/locales/en/key.json index 8a8ab8d..ca835c2 100644 --- a/tests/data/locales/en/key.json +++ b/tests/data/locales/en/key.json @@ -23,5 +23,6 @@ "key_4": "many", "key_5": "other", "key_nesting_interpolate1": "hello world", - "key_nesting_interpolate2": "say {{val}}" + "key_nesting_interpolate2": "say {{val}}", + "key4_interval": "(1){one item};(2-7){a few items};(7-inf){a lot of items};" } \ No newline at end of file diff --git a/tests/data/utils/cases.php b/tests/data/utils/cases.php index 69559b1..d6ec05f 100644 --- a/tests/data/utils/cases.php +++ b/tests/data/utils/cases.php @@ -131,6 +131,9 @@ 'en' => 'user : {{user.first_name}} {{user.last_name}} with email : {{address.email}}', 'it' => 'utente : {{user.first_name}} {{user.last_name}} con email : {{address.email}}', ], + 'key4_interval' => [ + 'en' => '(1){one item};(2-7){a few items};(7-inf){a lot of items};', + ], ]; $path_def = [];