diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
new file mode 100644
index 0000000..350603f
--- /dev/null
+++ b/.github/workflows/tests.yaml
@@ -0,0 +1,95 @@
+# This is a basic workflow to help you get started with Actions
+
+name: Tests
+
+# Controls when the workflow will run
+on:
+ # Triggers the workflow on push or pull request events but only for the master branch
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ phpstan:
+ name: PHPStan
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.4
+ coverage: none
+
+ - run: composer install --no-progress --prefer-dist
+ - run: composer phpstan -- --no-progress
+ continue-on-error: true
+
+ phpcs:
+ name: PHP CS
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.4
+ coverage: none
+
+ - run: composer install --no-progress --prefer-dist
+ - run: composer phpcs
+ continue-on-error: true
+
+# psalm:
+# name: Psalm
+# runs-on: ubuntu-latest
+# steps:
+# - uses: actions/checkout@v3
+# - uses: shivammathur/setup-php@v2
+# with:
+# php-version: 8.0
+# coverage: none
+#
+# - run: composer install --no-progress --prefer-dist
+# - run: composer psalm -- --no-progress
+# continue-on-error: true
+
+ tests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: [ '8.2', '8.3', '8.4' ]
+
+ fail-fast: false
+
+ name: PHP ${{ matrix.php }} tests
+ steps:
+ - uses: actions/checkout@v3
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - run: composer install --no-progress --prefer-dist
+ - run: vendor/bin/tester tests -s -C
+
+ code_coverage:
+ name: Code Coverage
+ runs-on: ubuntu-latest
+ needs: [tests]
+ steps:
+ - uses: actions/checkout@v3
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.4
+ coverage: none
+
+ - run: composer install --no-progress --prefer-dist
+ - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
+# - run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar
+# - env:
+# COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+# run: php php-coveralls.phar --verbose --config tests/.coveralls.yml
diff --git a/.gitignore b/.gitignore
index 50e0c90..6bd167c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
/vendor
/temp
/composer.lock
+.php-cs-fixer.cache
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..7392061
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,15 @@
+in(__DIR__ . '/src')
+;
+
+return (new PhpCsFixer\Config())
+ ->setRules([
+ '@PER-CS' => true,
+ '@PHP82Migration' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ])
+ ->setIndent("\t")
+ ->setFinder($finder)
+ ;
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0c8f5fb..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-language: php
-php:
- - 7.1
- - 7.2
- - 7.3
- - 7.4
- - 8.0snapshot
-
-env:
- - PHP_BIN=php
- - PHP_BIN=php-cgi
-
-before_install:
- # turn off XDebug
- - phpenv config-rm xdebug.ini || return 0
-
-install:
- - travis_retry composer install --no-progress --prefer-dist
-
-script:
- - travis_retry vendor/bin/tester -p $PHP_BIN tests -s
-
-after_failure:
- # Print *.actual content
- - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done
-
-jobs:
- include:
- - name: Lowest Dependencies
- env: PHP_BIN=php
- install:
- - travis_retry composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable
-
-
- - name: Lint
- install:
- - travis_retry composer create-project jakub-onderka/php-parallel-lint temp/php-parallel-lint --no-interaction --no-progress
- script:
- - php temp/php-parallel-lint/parallel-lint src
-
-
- - name: Code Checker
- script:
- - vendor/bin/phpcs --standard=psr12 src
-
-
- - stage: Static Analysis (informative)
- install:
- - travis_retry composer install --no-progress --prefer-dist
- script:
- - vendor/bin/phpstan.phar analyse --level 8 src
-
-
- - stage: Code Coverage
- script:
- - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src
- after_script:
- - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar
- - php coveralls.phar --verbose --config tests/.coveralls.yml
-
-
- allow_failures:
- - stage: Static Analysis (informative)
- - stage: Code Coverage
-
-
-sudo: false
-
-cache:
- directories:
- - $HOME/.composer/cache
-
-notifications:
- email: false
diff --git a/README.md b/README.md
index e3c3441..e46cc46 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,7 @@ Bckp\Translator
====================
[](https://packagist.org/packages/bckp/translator-core)
-[](https://travis-ci.org/bckp/translator-core)
-[](https://coveralls.io/github/bckp/translator-core?branch=master)
+[](https://github.com/bckp/translator-core/actions/workflows/tests.yaml/badge.svg)
[](https://scrutinizer-ci.com/g/bckp/translator-core/?branch=master)
[](https://packagist.org/packages/bckp/translator-core)
[](https://github.com/nette/application/blob/master/license.md)
@@ -25,7 +24,7 @@ $compiledCatalogue = $catalogue->compile();
$translator = new Translator($compiledCatalogue);
$translator->translate('errors.error.notFound'); // Will output "Soubor nenalezen"
-$translator->translate(['messages.plural', 4]); // Will output "4 lidé"
+$translator->translate('messages.plural', 4); // Will output "4 lidé"
$translator->translate('messages.withArgs', 'Honza', 'poledne'); // Will output "Ahoj, já jsem Honza, přeji krásné poledne"
$translator->translate('messages.withArgsRev', 'Honza', 'poledne'); // Will output "Krásné poledne, já jsem Honza"
```
diff --git a/composer.json b/composer.json
index 0014ca7..c6fb6a4 100644
--- a/composer.json
+++ b/composer.json
@@ -1,29 +1,42 @@
{
- "name": "bckp/translator-core",
- "description": "Simple and fast PHP translator",
- "keywords": ["translator"],
- "homepage": "https://github.com/bckp/translator-core",
- "license": "BSD-3-Clause",
- "authors": [
- {
- "name": "Radovan Kepák",
- "homepage": "https://kepak.eu"
- }
- ],
- "require": {
- "php": "^7.1|^8.0",
- "nette/php-generator": ">3.3 <4",
- "nette/neon": ">=2.4"
- },
- "suggest": {
- "bckp/translator-nette": "For NETTE support"
- },
- "autoload": {
- "classmap": ["src/"]
- },
- "require-dev": {
- "squizlabs/php_codesniffer": "*",
- "phpstan/phpstan": "^0.12",
- "nette/tester": "^2.3"
- }
+ "name": "bckp/translator-core",
+ "description": "Simple and fast PHP translator",
+ "keywords": [
+ "translator"
+ ],
+ "homepage": "https://github.com/bckp/translator-core",
+ "license": "BSD-3-Clause",
+ "authors": [
+ {
+ "name": "Radovan Kepák",
+ "homepage": "https://kepak.dev"
+ }
+ ],
+ "require": {
+ "php": "^8.2",
+ "nette/php-generator": ">=4 <5",
+ "nette/neon": ">3.3 <4"
+ },
+ "suggest": {
+ "bckp/translator-nette": "For NETTE support"
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "phpstan": "phpstan analyse",
+ "tests": "tester tests -s",
+ "tests-watch": "tester tests -s -w src",
+ "phpcs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer check",
+ "phpcs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix"
+ },
+ "require-dev": {
+ "roave/security-advisories": "dev-latest",
+ "phpstan/phpstan": "^2",
+ "nette/utils": "^4.0",
+ "nette/tester": "^2.5",
+ "friendsofphp/php-cs-fixer": "^3.75"
+ }
}
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..4ae2171
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,5 @@
+parameters:
+ level: 8
+
+ paths:
+ - src
diff --git a/ruleset.xml b/ruleset.xml
deleted file mode 100644
index dee3d38..0000000
--- a/ruleset.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- bckp/translator-core coding standard.
-
-
-
-
-
diff --git a/src/Builder/Catalogue.php b/src/Builder/Catalogue.php
deleted file mode 100644
index 10f099a..0000000
--- a/src/Builder/Catalogue.php
+++ /dev/null
@@ -1,396 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Bckp\Translator\Builder;
-
-use Bckp\Translator\BuilderException;
-use Bckp\Translator\FileInvalidException;
-use Bckp\Translator\ICatalogue;
-use Bckp\Translator\PathInvalidException;
-use Bckp\Translator\PluralProvider;
-use Nette\Neon\Neon;
-use Nette\PhpGenerator\Method;
-use Nette\PhpGenerator\PhpFile;
-use Throwable;
-
-use function class_exists;
-use function file_exists;
-use function file_get_contents;
-use function file_put_contents;
-use function filemtime;
-use function is_readable;
-use function is_writable;
-use function strtolower;
-use function uniqid;
-use function unlink;
-
-use const PHP_VERSION_ID;
-
-/**
- * Class Catalogue
- *
- * @package Bckp\Translator\Builder
- */
-class Catalogue
-{
- /** @var array */
- public $onCompile = [];
-
- /** @var array */
- public $onCheck = [];
-
- /** @var ICatalogue|null */
- private $catalogue;
-
- /** @var array */
- private $collection = [];
-
- /** @var array */
- private $dynamic = [];
-
- /** @var bool */
- private $debug;
-
- /** @var bool */
- private $loaded = false;
-
- /** @var string */
- private $locale;
-
- /** @var string */
- private $path;
-
- /** @var PluralProvider */
- private $plural;
-
- /**
- * Catalogue constructor.
- *
- * @param PluralProvider $plural
- * @param string $path
- * @param string $locale
- */
- public function __construct(PluralProvider $plural, string $path, string $locale)
- {
- if (!is_writable($path)) {
- throw new PathInvalidException("Path '{$path}' is not writable.");
- }
-
- $this->path = $path;
- $this->plural = $plural;
- $this->locale = strtolower($locale);
- }
-
- /**
- * @param string $file
- * @return static
- */
- public function addFile(string $file): self
- {
- $this->collection[] = $file;
- return $this;
- }
-
- /**
- * @param string $resource
- * @param callable $callback
- * @return static
- */
- public function addDynamic(string $resource, callable $callback): self
- {
- $this->dynamic[strtolower($resource)] = $callback;
- return $this;
- }
-
- /**
- * @param int $attempt
- * @return ICatalogue
- * @throws Throwable
- */
- public function rebuild(int $attempt = 0): ICatalogue
- {
- $filename = $this->path . '/' . $this->getName() . '.php';
- $this->unlink($filename);
- return $this->compile($attempt);
- }
-
- /**
- * @return string
- */
- protected function getName(): string
- {
- return $this->locale . 'Catalogue';
- }
-
- /**
- * @param string $filename
- */
- private function unlink(string $filename): void
- {
- /** @scrutinizer ignore-unhandled */
- @unlink($filename); // @ intentionally as file may not exists
- $this->loaded = false;
- }
-
- /**
- * Compile catalogue (or load from cache if exists)
- *
- * @param int $rebuild
- * @return ICatalogue
- * @throws Throwable
- */
- public function compile(int $rebuild = 0): ICatalogue
- {
- // Exception on to many rebuild try
- if ($rebuild > 3) {
- throw new BuilderException('Failed to build language file');
- }
-
- // Check for file exist, create if no
- $filename = $this->path . '/' . $this->getName() . '.php';
- if (!file_exists($filename)) {
- file_put_contents($filename, $this->compileCode());
- }
-
- // Link file and rebuild if error
- try {
- $this->checkForChanges($filename);
- $this->link($filename);
-
- if (!$this->catalogue instanceof ICatalogue) {
- throw new BuilderException('Catalogue is not implementing ICatalogue');
- }
-
- return $this->catalogue;
- } catch (Throwable $e) {
- $this->unlink($filename);
- return $this->compile(++$rebuild);
- }
- }
-
- /**
- * @param string $filename
- */
- protected function checkForChanges(string $filename): void
- {
- if (!$this->debug) {
- return;
- }
-
- $cacheTime = (int)filemtime($filename);
- foreach ($this->collection as $file) {
- $file = new \SplFileInfo($file);
- $fileTime = $file->getMTime();
- if ($fileTime > $cacheTime || ($this->catalogue && $fileTime > $this->catalogue->buildTime())) {
- throw new BuilderException('Rebuild required');
- }
- }
-
- $this->onCheck($cacheTime);
- }
-
- /**
- * Compile cache code
- *
- * @return string
- */
- protected function compileCode(): string
- {
- // Load messages, then generate code
- $messages = $this->getMessages();
- $this->onCompile($messages);
-
- do {
- $className = $this->getName() . uniqid();
- } while (class_exists($className));
-
- // File
- $file = new PhpFile();
- $file->setStrictTypes(true);
- $file->setComment('This file was auto-generated');
-
- // Create class
- $file->addUse('Bckp\Translator\PluralProvider');
- $class = $file->addClass($className);
- $class->setExtends(\Bckp\Translator\Catalogue::class);
- $class->setImplements([ICatalogue::class]);
- $class->addComment("This file was auto-generated");
-
- // Setup plural method
- $method = $class->addMethod('plural');
- $plural = Method::from((array)$this->plural->getPlural($this->locale));
- $method->setParameters($plural->getParameters());
- $parameters = $method->getParameters();
- $method->setBody('return PluralProvider::?($?);', [$plural->getName(), key($parameters)]);
- $method->setReturnNullable($plural->isReturnNullable());
- $method->setReturnType($plural->getReturnType());
-
- // Messages & build time
- $class->addProperty('locale', $this->getLocale())->setVisibility('protected');
- $class->addProperty('build', time())->setVisibility('protected');
- $class->addProperty('messages', $messages)->setStatic(true)->setVisibility('protected');
-
- // Generate code
- $code = (string)$file;
- $code .= "\nreturn new {$class->getName()};\n";
-
- // Return string
- return $code;
- }
-
- /**
- * Get all messages
- *
- * @return string[]
- * @throws FileInvalidException
- */
- protected function getMessages(): array
- {
- $messages = [];
-
- // Add files
- foreach ($this->collection as $file) {
- $info = new \SplFileInfo($file);
- $resource = strtolower($info->getBasename('.' . $this->locale . '.neon'));
- foreach ($this->loadFile($file) as $key => $item) {
- $messages[$resource . '.' . $key] = $item;
- }
- }
-
- // Add dynamic translations
- foreach ($this->dynamic as $resource => $callback) {
- $resource = $namespace = strtolower($resource);
- $locale = $this->locale;
- $array = [];
-
- $callback($array, $resource, $locale);
-
- // @phpstan-ignore-next-line
- foreach ($array as $key => $item) {
- $messages[$namespace . '.' . $key] = $item;
- }
- }
-
- return $messages;
- }
-
- /**
- * @param string $file
- * @return string[]
- * @throws PathInvalidException
- * @throws FileInvalidException
- */
- protected function loadFile(string $file): array
- {
- if (!file_exists($file) || !is_readable($file)) {
- throw new PathInvalidException("File '{$file}' not found or is not readable.");
- }
-
- $content = file_get_contents($file);
- if (!$content) {
- return [];
- }
-
- try {
- $content = Neon::decode($content);
- if (!is_array($content)) {
- throw new \Exception('No array');
- }
- } catch (Throwable $e) {
- throw new FileInvalidException(
- "File '{$file}' do not contain array of translations",
- $e->getCode(),
- $e
- );
- }
- return $content;
- }
-
- /**
- * Occurs when new catalogue is compiled, after all strings are loaded
- * @param array|string> $messages
- */
- private function onCompile(array &$messages): void
- {
- foreach ($this->onCompile as $callback) {
- $locale = $this->locale;
- $callback($messages, $locale);
- }
- }
-
- /**
- * @return string
- */
- public function getLocale(): string
- {
- return $this->locale;
- }
-
- /**
- * Occurs on debug mode in check for changes
- * @param int $fileTime
- */
- private function onCheck(int $fileTime): void
- {
- foreach ($this->onCheck as $callback) {
- $locale = $this->locale;
- $callback($fileTime, $locale);
- }
- }
-
- /**
- * Link catalogue if not already linked
- *
- * @param string $filename
- * @throws BuilderException
- */
- private function link(string $filename): void
- {
- if ($this->loaded) {
- return;
- }
-
- /** @noinspection PhpIncludeInspection */
- $this->catalogue = include $filename;
- $this->loaded = true;
- }
-
- /**
- * @param callable $callback function(array &$messages, string $locale): void
- */
- public function addCompileCallback(callable $callback): void
- {
- $this->onCompile[] = $callback;
- }
-
- /**
- * @param callable $callback function(string $locale): void
- */
- public function addCheckCallback(callable $callback): void
- {
- $this->onCheck[] = $callback;
- }
-
- /**
- * Enable debug mode
- *
- * @param bool $debug
- * @return static
- */
- public function setDebugMode(bool $debug): self
- {
- $this->debug = $debug;
- return $this;
- }
-}
diff --git a/src/Catalogue.php b/src/Catalogue.php
index eabe7b3..3f829e1 100644
--- a/src/Catalogue.php
+++ b/src/Catalogue.php
@@ -19,62 +19,25 @@
*
* @package Bckp\Translator
*/
-abstract class Catalogue implements ICatalogue
+abstract class Catalogue
{
- /** @var array */
- protected static $messages;
-
- /** @var int */
- protected $build;
-
- /** @var string */
- protected $locale;
-
- /**
- * Get build time
- *
- * @return int
- */
- public function buildTime(): int
- {
- return $this->build;
- }
-
- /**
- * Get the message translation
- *
- * @param string $message
- * @return string|array return array if plural is detected
- */
- public function get(string $message)
- {
- return static::$messages[$message] ?? '';
- }
-
- /**
- * Check if catalogue has message translation
- *
- * @param string $message
- * @return bool
- */
- public function has(string $message): bool
- {
- return isset(static::$messages[$message]);
- }
-
- /**
- * @return string
- */
- public function locale(): string
- {
- return $this->locale;
- }
-
- /**
- * Plural form getter
- *
- * @param int $n
- * @return string
- */
- abstract public function plural(int $n): string;
+ /** @var array> */
+ protected static array $messages;
+
+ /**
+ * @return string|array
+ */
+ public function get(string $message): array|string
+ {
+ return static::$messages[$message] ?? '';
+ }
+
+ public function has(string $message): bool
+ {
+ return array_key_exists($message, static::$messages);
+ }
+
+ abstract public function plural(int $n): Plural;
+ abstract public function locale(): string;
+ abstract public function build(): int;
}
diff --git a/src/CatalogueBuilder.php b/src/CatalogueBuilder.php
new file mode 100644
index 0000000..4ab25fc
--- /dev/null
+++ b/src/CatalogueBuilder.php
@@ -0,0 +1,340 @@
+
+ */
+
+declare(strict_types=1);
+
+namespace Bckp\Translator;
+
+use Bckp\Translator\Exceptions\BuilderException;
+use Bckp\Translator\Exceptions\FileInvalidException;
+use Bckp\Translator\Exceptions\PathInvalidException;
+use Nette\Neon\Neon;
+use Nette\PhpGenerator\ClassType;
+use Nette\PhpGenerator\Method;
+use Nette\PhpGenerator\PhpFile;
+use RuntimeException;
+use SplFileInfo;
+use Throwable;
+
+use function file_exists;
+use function file_get_contents;
+use function file_put_contents;
+use function filemtime;
+use function is_readable;
+use function is_writable;
+use function strtolower;
+use function unlink;
+
+final class CatalogueBuilder
+{
+ /** @var callable[] */
+ public array $onCompile = [];
+
+ /** @var callable[] */
+ public array $onCheck = [];
+
+ /** @var callable[] */
+ private array $dynamic = [];
+
+ private ?Catalogue $catalogue = null;
+
+ /** @var array */
+ private array $collection = [];
+
+ private bool $debug = false;
+ private bool $loaded = false;
+ private string $locale;
+
+
+ public function __construct(
+ private readonly PluralProvider $plural,
+ private readonly string $path,
+ string $locale
+ ) {
+ if (!is_writable($path)) {
+ throw new PathInvalidException("Path '$path' is not writable.");
+ }
+
+ $this->locale = strtolower($locale);
+ }
+
+ public function addFile(string $file): self
+ {
+ $this->collection[] = $file;
+ return $this;
+ }
+
+ public function addDynamic(string $resource, callable $callback): self
+ {
+ $this->dynamic[strtolower($resource)] = $callback;
+ return $this;
+ }
+
+ /**
+ * @throws Throwable
+ */
+ public function rebuild(int $attempt = 0): Catalogue
+ {
+ $filename = $this->path . '/' . $this->getName() . '.php';
+ $this->unlink($filename);
+ return $this->compile($attempt);
+ }
+
+ /**
+ * @return string
+ */
+ protected function getName(): string
+ {
+ return $this->locale . 'Catalogue';
+ }
+
+ /**
+ * @param string $filename
+ */
+ private function unlink(string $filename): void
+ {
+ /** @scrutinizer ignore-unhandled */
+ @unlink($filename); // @ intentionally as file may not exist
+ $this->loaded = false;
+ }
+
+ /**
+ * @throws BuilderException
+ */
+ public function compile(int $rebuild = 0): Catalogue
+ {
+ // Exception on to many rebuild try
+ if ($rebuild > 3) {
+ throw new BuilderException('Failed to build language file');
+ }
+
+ // Check for file exist, create if no
+ $filename = $this->path . '/' . $this->getName() . '.php';
+ if (!file_exists($filename)) {
+ file_put_contents($filename, $this->compileCode());
+ }
+
+ // Link file and rebuild if error
+ try {
+ $this->checkForChanges($filename);
+ $this->link($filename);
+
+ if (!$this->catalogue instanceof Catalogue) {
+ throw new BuilderException('Catalogue is not implementing Catalogue');
+ }
+
+ return $this->catalogue;
+ } catch (Throwable $e) {
+ $this->unlink($filename);
+ return $this->compile(++$rebuild);
+ }
+ }
+
+ /**
+ * @param string $filename
+ */
+ protected function checkForChanges(string $filename): void
+ {
+ if (!$this->debug) {
+ return;
+ }
+
+ $cacheTime = (int) filemtime($filename);
+ foreach ($this->collection as $file) {
+ $file = new SplFileInfo($file);
+ $fileTime = $file->getMTime();
+ if ($fileTime > $cacheTime || ($this->catalogue && $fileTime > $this->catalogue->build())) {
+ throw new BuilderException('Rebuild required');
+ }
+ }
+
+ $this->onCheck($cacheTime);
+ }
+
+ /**
+ * Compile cache code
+ *
+ * @return string
+ */
+ protected function compileCode(): string
+ {
+ // Load messages, then generate code
+ $messages = $this->getMessages();
+ $this->onCompile($messages);
+
+ /*
+ do {
+ $className = $this->getName() . substr(md5((string) mt_rand()), 4, 8);
+ } while (class_exists($className));
+ */
+
+ // File
+ $file = new PhpFile();
+ $file->setStrictTypes();
+ $file->setComment('This file was auto-generated');
+
+ $class = new ClassType();
+ $class->setExtends(Catalogue::class);
+
+ // Setup plural method
+ $method = $class->addMethod('plural');
+ $plural = Method::from((array) $this->plural->getPlural($this->locale));
+ $method->setParameters($plural->getParameters());
+ $parameters = $method->getParameters();
+ $method->setBody('return Bckp\Translator\PluralProvider::?($?);', [$plural->getName(), key($parameters)]);
+ $method->setReturnNullable($plural->isReturnNullable());
+ $method->setReturnType($plural->getReturnType());
+
+ // Messages & build time
+ $class->addMethod('locale')->setBody("return '{$this->getLocale()}';")->setReturnType('string');
+ $class->addMethod('build')->setBody('return ' . time() . ';')->setReturnType('int');
+ $class->addProperty('messages', $messages)->setType('array')->setStatic()->setVisibility('protected');
+
+ // Generate code
+ $code = (string) $file;
+ $code .= "\nreturn new class {$class};\n";
+
+ // Return string
+ return $code;
+ }
+
+ /**
+ * Get all messages
+ *
+ * @return string[]
+ * @throws FileInvalidException
+ */
+ protected function getMessages(): array
+ {
+ $messages = [];
+
+ // Add files
+ foreach ($this->collection as $file) {
+ $info = new SplFileInfo($file);
+ $resource = strtolower($info->getBasename('.' . $this->locale . '.neon'));
+ foreach ($this->loadFile($file) as $key => $item) {
+ $messages[$resource . '.' . $key] = $item;
+ }
+ }
+
+ // Add dynamic translations
+ foreach ($this->dynamic as $resource => $callback) {
+ $resource = $namespace = strtolower($resource);
+ $locale = $this->locale;
+ $array = [];
+
+ $callback($array, $resource, $locale);
+
+ // @phpstan-ignore-next-line
+ foreach ($array as $key => $item) {
+ $messages[$namespace . '.' . $key] = $item;
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * @param string $file
+ * @return string[]
+ * @throws PathInvalidException
+ * @throws FileInvalidException
+ */
+ protected function loadFile(string $file): array
+ {
+ if (!file_exists($file) || !is_readable($file)) {
+ throw new PathInvalidException("File '$file' not found or is not readable.");
+ }
+
+ $content = file_get_contents($file);
+ if (!$content) {
+ return [];
+ }
+
+ try {
+ $content = Neon::decode($content);
+ if (!is_array($content)) {
+ throw new RuntimeException('No array');
+ }
+ } catch (Throwable $e) {
+ throw new FileInvalidException(
+ "File '$file' do not contain array of translations",
+ $e->getCode(),
+ $e
+ );
+ }
+ return $content;
+ }
+
+ /**
+ * Occurs when new catalogue is compiled, after all strings are loaded
+ * @param array|string> $messages
+ */
+ private function onCompile(array &$messages): void
+ {
+ foreach ($this->onCompile as $callback) {
+ $locale = $this->locale;
+ $callback($messages, $locale);
+ }
+ }
+
+ public function getLocale(): string
+ {
+ return $this->locale;
+ }
+
+ private function onCheck(int $fileTime): void
+ {
+ foreach ($this->onCheck as $callback) {
+ $locale = $this->locale;
+ $callback($fileTime, $locale);
+ }
+ }
+
+ /**
+ * Link catalogue if not already linked
+ *
+ * @param string $filename
+ * @throws BuilderException
+ */
+ private function link(string $filename): void
+ {
+ if ($this->loaded) {
+ return;
+ }
+
+ $this->catalogue = require $filename;
+ $this->loaded = true;
+ }
+
+ /**
+ * @param callable $callback function(array &$messages, string $locale): void
+ */
+ public function addCompileCallback(callable $callback): void
+ {
+ $this->onCompile[] = $callback;
+ }
+
+ /**
+ * @param callable $callback function(string $locale): void
+ */
+ public function addCheckCallback(callable $callback): void
+ {
+ $this->onCheck[] = $callback;
+ }
+
+ public function setDebugMode(bool $debug): self
+ {
+ $this->debug = $debug;
+ return $this;
+ }
+}
diff --git a/src/Diagnostics/Diagnostics.php b/src/Diagnostics/Diagnostics.php
index b58a653..aae364c 100644
--- a/src/Diagnostics/Diagnostics.php
+++ b/src/Diagnostics/Diagnostics.php
@@ -14,69 +14,54 @@
namespace Bckp\Translator\Diagnostics;
-use Bckp\Translator\IDiagnostics;
+use Bckp\Translator\Interfaces;
use function array_unique;
-/**
- * Class Diagnostics
- *
- * @package Bckp\Translator\Diagnostics
- */
-class Diagnostics implements IDiagnostics
+class Diagnostics implements Interfaces\Diagnostics
{
- /** @var string */
- private $locale = '';
+ /** @var string */
+ private string $locale = '';
- /** @var array */
- private $messages = [];
+ /** @var array */
+ private array $messages = [];
- /** @var array */
- private $untranslated = [];
+ /** @var array */
+ private array $untranslated = [];
- /**
- * @return string
- */
- public function getLocale(): string
- {
- return $this->locale;
- }
+ public function getLocale(): string
+ {
+ return $this->locale;
+ }
- /**
- * @return array
- */
- public function getUntranslated(): array
- {
- return array_unique($this->untranslated);
- }
+ /**
+ * @return string[]
+ */
+ public function getUntranslated(): array
+ {
+ return array_unique($this->untranslated);
+ }
- /**
- * @return array
- */
- public function getWarnings(): array
- {
- return array_unique($this->messages);
- }
+ /**
+ * @return string[]
+ */
+ public function getWarnings(): array
+ {
+ return array_unique($this->messages);
+ }
- /** @param string $locale */
- public function setLocale(string $locale): void
- {
- $this->locale = $locale;
- }
+ public function setLocale(string $locale): void
+ {
+ $this->locale = $locale;
+ }
- /**
- * @param string $message
- */
- public function untranslated(string $message): void
- {
- $this->untranslated[] = $message;
- }
+ public function untranslated(string $message): void
+ {
+ $this->untranslated[] = $message;
+ }
- /**
- * @param string $message
- */
- public function warning(string $message): void
- {
- $this->messages[] = $message;
- }
+ public function warning(string $message): void
+ {
+ $this->messages[] = $message;
+ }
}
diff --git a/src/Exceptions/BuilderException.php b/src/Exceptions/BuilderException.php
index 8e2f47b..69ce201 100644
--- a/src/Exceptions/BuilderException.php
+++ b/src/Exceptions/BuilderException.php
@@ -12,8 +12,6 @@
declare(strict_types=1);
-namespace Bckp\Translator;
+namespace Bckp\Translator\Exceptions;
-class BuilderException extends TranslatorException
-{
-}
+class BuilderException extends TranslatorException {}
diff --git a/src/Exceptions/FileInvalidException.php b/src/Exceptions/FileInvalidException.php
index 030340d..56d4279 100644
--- a/src/Exceptions/FileInvalidException.php
+++ b/src/Exceptions/FileInvalidException.php
@@ -12,10 +12,8 @@
declare(strict_types=1);
-namespace Bckp\Translator;
+namespace Bckp\Translator\Exceptions;
use Nette\InvalidStateException;
-class FileInvalidException extends InvalidStateException
-{
-}
+class FileInvalidException extends InvalidStateException {}
diff --git a/src/Exceptions/PathInvalidException.php b/src/Exceptions/PathInvalidException.php
index 05e7838..79e0ded 100644
--- a/src/Exceptions/PathInvalidException.php
+++ b/src/Exceptions/PathInvalidException.php
@@ -12,12 +12,10 @@
declare(strict_types=1);
-namespace Bckp\Translator;
+namespace Bckp\Translator\Exceptions;
use Nette\FileNotFoundException;
use Nette\InvalidStateException;
use RuntimeException;
-class PathInvalidException extends FileNotFoundException
-{
-}
+class PathInvalidException extends FileNotFoundException {}
diff --git a/src/Exceptions/TranslatorException.php b/src/Exceptions/TranslatorException.php
index 1ec2b21..c88f8f5 100644
--- a/src/Exceptions/TranslatorException.php
+++ b/src/Exceptions/TranslatorException.php
@@ -12,10 +12,8 @@
declare(strict_types=1);
-namespace Bckp\Translator;
+namespace Bckp\Translator\Exceptions;
use RuntimeException;
-class TranslatorException extends RuntimeException
-{
-}
+class TranslatorException extends RuntimeException {}
diff --git a/src/Interfaces/Diagnostics.php b/src/Interfaces/Diagnostics.php
new file mode 100644
index 0000000..1f24369
--- /dev/null
+++ b/src/Interfaces/Diagnostics.php
@@ -0,0 +1,22 @@
+
+ */
+
+namespace Bckp\Translator\Interfaces;
+
+interface Diagnostics
+{
+ public function setLocale(string $locale): void;
+ public function untranslated(string $message): void;
+ public function warning(string $message): void;
+}
diff --git a/src/Interfaces/ICatalogue.php b/src/Interfaces/ICatalogue.php
deleted file mode 100644
index 9f7395c..0000000
--- a/src/Interfaces/ICatalogue.php
+++ /dev/null
@@ -1,56 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Bckp\Translator;
-
-/**
- * Interface ICatalogue
- *
- * @package Bckp\Translator
- */
-interface ICatalogue
-{
- /**
- * Get build time in seconds
- * @return int
- */
- public function buildTime(): int;
-
- /**
- * Get the message translation
- * @param string $message
- * @return string|string[] return array if plural is detected
- */
- public function get(string $message);
-
- /**
- * Check if catalogue has message translation
- * @param string $message
- * @return bool
- */
- public function has(string $message): bool;
-
- /**
- * Get locale
- * @return string
- */
- public function locale(): string;
-
- /**
- * Plural form getter
- * @param int $n
- * @return string
- */
- public function plural(int $n): string;
-}
diff --git a/src/Interfaces/IDiagnostics.php b/src/Interfaces/IDiagnostics.php
deleted file mode 100644
index 34e1548..0000000
--- a/src/Interfaces/IDiagnostics.php
+++ /dev/null
@@ -1,38 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Bckp\Translator;
-
-/**
- * Interface IDiagnostics
- *
- * @package Bckp\Translator
- */
-interface IDiagnostics
-{
- /**
- * @param string $locale
- */
- public function setLocale(string $locale): void;
-
- /**
- * @param string $message
- */
- public function untranslated(string $message): void;
-
- /**
- * @param string $message
- */
- public function warning(string $message): void;
-}
diff --git a/src/Interfaces/IPlural.php b/src/Interfaces/IPlural.php
deleted file mode 100644
index 3224784..0000000
--- a/src/Interfaces/IPlural.php
+++ /dev/null
@@ -1,41 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Bckp\Translator;
-
-/**
- * Interface IPlural
- *
- * @package Bckp\Translator
- */
-interface IPlural
-{
- /**
- * Plural variants
- */
- public const
- ZERO = 'zero',
- ONE = 'one',
- TWO = 'two',
- FEW = 'few',
- MANY = 'many',
- OTHER = 'other';
-
- /**
- * Get plural method
- * @param string $locale
- * @return callable
- */
- public function getPlural(string $locale): callable;
-}
diff --git a/src/Interfaces/ITranslator.php b/src/Interfaces/ITranslator.php
deleted file mode 100644
index c5865dd..0000000
--- a/src/Interfaces/ITranslator.php
+++ /dev/null
@@ -1,35 +0,0 @@
-
- */
-
-declare(strict_types=1);
-
-namespace Bckp\Translator;
-
-/**
- * Interface ITranslator
- *
- * @package Bckp\Translator
- */
-interface ITranslator
-{
- /**
- * @param callable $callback function(string $string): string
- */
- public function setNormalizeCallback(callable $callback): void;
-
- /**
- * @param array|string|object $message
- * @param mixed ...$params
- * @return string
- */
- public function translate($message, ...$params): string;
-}
diff --git a/src/Interfaces/Translator.php b/src/Interfaces/Translator.php
new file mode 100644
index 0000000..79c5c84
--- /dev/null
+++ b/src/Interfaces/Translator.php
@@ -0,0 +1,32 @@
+
+ */
+
+declare(strict_types=1);
+
+namespace Bckp\Translator\Interfaces;
+
+use Stringable;
+
+/**
+ * Interface ITranslator
+ *
+ * @package Bckp\Translator
+ */
+interface Translator
+{
+ /**
+ * @param callable $callback function(string $string): string
+ */
+ public function setNormalizeCallback(callable $callback): void;
+
+ public function translate(string|Stringable $message, mixed ...$params): string;
+}
diff --git a/src/Plural.php b/src/Plural.php
new file mode 100644
index 0000000..fa6bafb
--- /dev/null
+++ b/src/Plural.php
@@ -0,0 +1,15 @@
+ 'csPlural',
- 'en' => 'enPlural',
- 'id' => 'zeroPlural',
- 'ja' => 'zeroPlural',
- 'ka' => 'zeroPlural',
- 'ko' => 'zeroPlural',
- 'lo' => 'zeroPlural',
- 'ms' => 'zeroPlural',
- 'my' => 'zeroPlural',
- 'th' => 'zeroPlural',
- 'vi' => 'zeroPlural',
- 'zh' => 'zeroPlural',
- ];
-
- /**
- * Czech plural selector (zero-one-few-other)
- *
- * @param int|null $n
- * @return string
- */
- public static function csPlural(?int $n): string
- {
- return $n === 0
- ? IPlural::ZERO
- : ($n === 1
- ? IPlural::ONE
- : ($n >= 2 && $n < 5
- ? IPlural::FEW
- : IPlural::OTHER
- )
- );
- }
-
- /**
- * Default plural detector (zero-one-other)
- *
- * @param int|null $n
- * @return string
- */
- public static function enPlural(?int $n): string
- {
- return $n === 0
- ? IPlural::ZERO
- : ($n === 1
- ? IPlural::ONE
- : IPlural::OTHER
- );
- }
+ /**
+ * Czech plural selector (zero-one-few-other)
+ */
+ public static function csPlural(?int $n): Plural
+ {
+ return match (true) {
+ $n === 0 => Plural::Zero,
+ $n === 1 => Plural::One,
+ $n >= 2 && $n <= 4 => Plural::Few,
+ default => Plural::Other,
+ };
+ }
- /**
- * No plural detector (zero-other)
- *
- * @param int|null $n
- * @return string
- */
- public static function zeroPlural(?int $n): string
- {
- return $n === 0
- ? IPlural::ZERO
- : IPlural::OTHER;
- }
+ /**
+ * Default plural detector (zero-one-other)
+ */
+ public static function enPlural(?int $n): Plural
+ {
+ return match (true) {
+ $n === 0 => Plural::Zero,
+ $n === 1 => Plural::One,
+ default => Plural::Other,
+ };
+ }
- /**
- * Get plural method
- *
- * @param string $locale
- * @return callable(int|null $n): string
- */
- public function getPlural(string $locale): callable
- {
- $locale = strtolower($locale);
- $callable = [$this, $this->plurals[$locale] ?? null];
+ /**
+ * No plural detector (zero-other)
+ */
+ public static function zeroPlural(?int $n): Plural
+ {
+ return $n === 0
+ ? Plural::Zero
+ : Plural::Other;
+ }
- if ($callable[1] && is_callable($callable)) {
- return $callable;
- }
- return [$this, self::DEFAULT];
- }
+ public function getPlural(string $locale): callable
+ {
+ return match (strtolower($locale)) {
+ 'cs' => [$this, 'csPlural'],
+ 'id', 'ja', 'ka', 'ko', 'lo', 'ms', 'my', 'th', 'vi', 'zh' => [$this, 'zeroPlural'],
+ default => [$this, 'enPlural'],
+ };
+ }
}
diff --git a/src/Translator.php b/src/Translator.php
index 7309f7e..bc110d2 100644
--- a/src/Translator.php
+++ b/src/Translator.php
@@ -14,14 +14,12 @@
namespace Bckp\Translator;
+use Bckp\Translator\Interfaces\Diagnostics;
+use Stringable;
+
use function array_key_exists;
-use function end;
-use function gettype;
+use function array_key_last;
use function is_array;
-use function is_object;
-use function is_string;
-use function key;
-use function method_exists;
use function vsprintf;
/**
@@ -29,179 +27,98 @@
*
* @package Bckp\Translator
*/
-class Translator implements ITranslator
+class Translator implements Interfaces\Translator
{
- /** @var ICatalogue */
- private $catalogue;
-
- /** @var IDiagnostics|null */
- private $diagnostics;
-
- /** @var callable function(string $string): string */
- private $normalizeCallback;
-
- /**
- * Translator constructor.
- *
- * @param ICatalogue $catalogue
- * @param IDiagnostics|null $diagnostics
- */
- public function __construct(ICatalogue $catalogue, IDiagnostics $diagnostics = null)
- {
- $this->catalogue = $catalogue;
- $this->normalizeCallback = [$this, 'normalize'];
- if ($this->diagnostics = $diagnostics) {
- $this->diagnostics->setLocale($catalogue->locale());
- }
- }
-
- /**
- * Normalize string to preserve frameworks placeholders
- *
- * @param string $string
- * @return string
- */
- public function normalize(string $string): string
- {
- return str_replace(
- ['%label', '%value', '%name'],
- ['%%label', '%%value', '%%name'],
- $string
- );
- }
-
- /**
- * @param callable $callback
- * @return void
- */
- public function setNormalizeCallback(callable $callback): void
- {
- $this->normalizeCallback = $callback;
- }
-
- /**
- * @param mixed $message
- * @param mixed ...$parameters
- * @return string
- */
- public function translate($message, ...$parameters): string
- {
- if (empty($message)) {
- return '';
- }
-
- $form = null;
-
- $this->expandParameters($parameters, $message);
- $message = $this->getMessage($message, $form);
- if ($message === null) {
- return $this->warn('Expected string|array|object::__toString, but %s given.', gettype($message));
- }
- $result = $message;
-
- // process plural if any
- if ($translation = $this->catalogue->get($message)) {
- $result = $this->getVariant($message, $translation, $form);
-
- if ($parameters) {
- $result = ($this->normalizeCallback)($result);
- $result = @vsprintf($result, $parameters);
- // Intentionally @ as argument count can mismatch
- }
- } else {
- $this->untranslated((string)$message);
- }
-
- return $result;
- }
-
- /**
- * @param string $message
- * @param string|array $translation
- * @param string|null $form
- * @return string
- */
- private function getVariant(string $message, $translation, string $form = null): string
- {
- if (!is_array($translation)) {
- return $translation;
- }
-
- if ($form === null || !array_key_exists($form, $translation)) {
- $this->warn(
- 'Plural form not defined. (message: %s, form: %s)',
- (string)$message,
- (string)$form
- );
- end($translation);
- $form = key($translation);
- }
- return $translation[$form];
- }
-
- /**
- * @param mixed $message
- * @param string|null $plural
- * @return string|null
- */
- protected function getMessage($message, ?string &$plural): ?string
- {
- if (is_string($message)) {
- return $message;
- }
-
- if (is_array($message) && is_string($message[0])) {
- $plural = $this->catalogue->plural((int)$message[1] ?? 1);
- return $message[0];
- }
-
- if (is_object($message) && method_exists($message, '__toString')) {
- return (string)$message;
- }
-
- return null;
- }
-
- /**
- * @param string $message
- */
- protected function untranslated(string $message): void
- {
- if ($this->diagnostics !== null) {
- $this->diagnostics->untranslated($message);
- }
- }
-
- /**
- * @param string $message
- * @param mixed ...$parameters
- * @return string
- */
- protected function warn(string $message, ...$parameters): string
- {
- if (!empty($parameters)) {
- $message = @vsprintf($message, $parameters);
- } // Intentionally @ as parameter count can mismatch
-
- if ($this->diagnostics !== null) {
- $this->diagnostics->warning($message);
- }
-
- return $message;
- }
-
- /**
- * @param array $parameters
- * @param string|array|object $message
- */
- private function expandParameters(array &$parameters, $message): void
- {
- if (
- empty($parameters)
- && is_array($message)
- && is_numeric($message[1] ?? null)
- ) {
- $parameters[] = $message[1];
- }
- }
+ /** @var callable function(string $string): string */
+ private $normalizeCallback;
+
+ public function __construct(
+ private readonly Catalogue $catalogue,
+ private readonly ?Diagnostics $diagnostics = null
+ ) {
+ $this->normalizeCallback = [$this, 'normalize'];
+ $this->diagnostics?->setLocale($catalogue->locale());
+ }
+
+ public function normalize(string $string): string
+ {
+ return str_replace(
+ ['%label', '%value', '%name'],
+ ['%%label', '%%value', '%%name'],
+ $string
+ );
+ }
+
+ public function setNormalizeCallback(callable $callback): void
+ {
+ $this->normalizeCallback = $callback;
+ }
+
+ public function translate(string|Stringable $message, mixed ...$parameters): string
+ {
+ $message = (string) $message;
+
+ if (empty($message)) {
+ return '';
+ }
+
+ $translation = $this->catalogue->get($message);
+ if (!$translation) {
+ $this->untranslated($message);
+ return $message;
+ }
+
+ // Plural option is returned, we need to choose the right one
+ if (is_array($translation)) {
+ $plural = is_numeric($parameters[0] ?? null) ? $this->catalogue->plural((int) $parameters[0]) : Plural::Other;
+ $translation = $this->getVariant($message, $translation, $plural);
+ }
+
+ if (!empty($parameters)) {
+ $translation = ($this->normalizeCallback)($translation);
+ $translation = @vsprintf($translation, $parameters);
+ }
+
+ return $translation;
+ }
+
+ /**
+ * @param array $translations
+ */
+ public function getVariant(string $message, array $translations, Plural $plural): string
+ {
+ if (!array_key_exists($plural->value, $translations)) {
+ $this->warn(
+ 'Plural form not defined. (message: %s, form: %s)',
+ $message,
+ $plural->value,
+ );
+ }
+
+ return $translations[$plural->value] ?? $translations[array_key_last($translations)];
+ }
+
+ /**
+ * @param string $message
+ */
+ protected function untranslated(string $message): void
+ {
+ $this->diagnostics?->untranslated($message);
+ }
+
+ /**
+ * @param string $message
+ * @param mixed ...$parameters
+ * @return string
+ */
+ protected function warn(string $message, ...$parameters): string
+ {
+ if (!empty($parameters)) {
+ $message = @vsprintf($message, $parameters);
+ } // Intentionally @ as parameter count can mismatch
+
+ $this->diagnostics?->warning($message);
+
+ return $message;
+ }
}
diff --git a/src/TranslatorProvider.php b/src/TranslatorProvider.php
index b340bda..3e8b7cd 100644
--- a/src/TranslatorProvider.php
+++ b/src/TranslatorProvider.php
@@ -14,78 +14,60 @@
namespace Bckp\Translator;
-use Bckp\Translator\Builder\Catalogue as BuilderCatalogue;
+use Bckp\Translator\Exceptions\BuilderException;
+use Bckp\Translator\Exceptions\TranslatorException;
use function strtolower;
-/**
- * Class TranslatorProvider
- *
- * @package Bckp\Translator
- */
class TranslatorProvider
{
- /** @var BuilderCatalogue[] */
- protected $catalogues = [];
-
- /** @var IDiagnostics|null */
- protected $diagnostics = null;
-
- /** @var string[] */
- protected $languages = [];
+ /**
+ * @var array
+ */
+ protected array $catalogues = [];
- /** @var ITranslator[] */
- protected $translators = [];
+ /**
+ * @var array
+ */
+ protected array $translators = [];
- /**
- * TranslatorProvider constructor.
- *
- * @param string[] $languages
- * @param IDiagnostics|null $diagnostics
- */
- public function __construct(array $languages, IDiagnostics $diagnostics = null)
- {
- $this->diagnostics = $diagnostics;
- $this->languages = $languages;
- }
+ /**
+ * @param array $languages
+ */
+ public function __construct(
+ protected array $languages,
+ protected ?Interfaces\Diagnostics $diagnostics = null
+ ) {}
- /**
- * @param string $locale
- * @param BuilderCatalogue $builder
- * @return void
- */
- public function addCatalogue(string $locale, BuilderCatalogue $builder): void
- {
- $locale = strtolower($locale);
- $this->catalogues[$locale] = $builder;
- }
+ public function addCatalogue(
+ string $locale,
+ CatalogueBuilder $builder
+ ): void {
+ $this->catalogues[strtolower($locale)] = $builder;
+ }
- /**
- * @param string $locale
- * @return ITranslator
- * @throws \Throwable
- */
- public function getTranslator(string $locale): ITranslator
- {
- $locale = strtolower($locale);
- if (!isset($this->translators[$locale])) {
- $this->translators[$locale] = $this->createTranslator($locale);
- }
+ public function getTranslator(string $locale): Interfaces\Translator
+ {
+ $locale = strtolower($locale);
+ if (!isset($this->translators[$locale])) {
+ $this->translators[$locale] = $this->createTranslator($locale);
+ }
- return $this->translators[$locale];
- }
+ return $this->translators[$locale];
+ }
- /**
- * @param string $locale
- * @return ITranslator
- * @throws \Throwable
- */
- protected function createTranslator(string $locale): ITranslator
- {
- if (!isset($this->catalogues[$locale])) {
- throw new TranslatorException("Language {$locale} requested, but corresponding catalogue missing.");
- }
+ /**
+ * @throws BuilderException
+ */
+ protected function createTranslator(string $locale): Interfaces\Translator
+ {
+ if (!isset($this->catalogues[$locale])) {
+ throw new TranslatorException("Language {$locale} requested, but corresponding catalogue missing.");
+ }
- return new Translator($this->catalogues[$locale]->compile(), $this->diagnostics);
- }
+ return new Translator(
+ $this->catalogues[$locale]->compile(),
+ $this->diagnostics
+ );
+ }
}
diff --git a/tests/Catalogue/Catalogue.dynamic.phpt b/tests/Catalogue/Catalogue.dynamic.phpt
index 2123498..faf69a4 100644
--- a/tests/Catalogue/Catalogue.dynamic.phpt
+++ b/tests/Catalogue/Catalogue.dynamic.phpt
@@ -11,12 +11,11 @@ $plural = (new PluralProvider());
const LOCALE = 'dynamic';
const RESOURCE = 'test';
-$catalogue = new Catalogue($plural, TEMP_DIR, LOCALE);
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, LOCALE);
$catalogue->addDynamic(RESOURCE, function (array &$messages, string &$resource, string &$locale) {
# Verify callback is called properly
Assert::equal($locale, LOCALE);
Assert::equal($resource, RESOURCE);
- Assert::true(is_array($messages));
# Add string
$messages['string'] = 'test';
diff --git a/tests/Catalogue/Catalogue.onCheck.phpt b/tests/Catalogue/Catalogue.onCheck.phpt
index 835232f..d9285b7 100644
--- a/tests/Catalogue/Catalogue.onCheck.phpt
+++ b/tests/Catalogue/Catalogue.onCheck.phpt
@@ -2,7 +2,7 @@
namespace Bckp\Translator;
-use Bckp\Translator\Builder\Catalogue;
+use Bckp\Translator\Exceptions\BuilderException;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
@@ -11,7 +11,7 @@ $plural = (new PluralProvider());
const LOCALE = 'dynamic';
const RESOURCE = 'test';
-$catalogue = new Catalogue($plural, TEMP_DIR, LOCALE);
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, LOCALE);
$catalogue->addDynamic(RESOURCE, function (array &$messages) {
$messages['string'] = 'test';
});
@@ -19,21 +19,21 @@ $catalogue->addCheckCallback(function () {
throw new BuilderException('Rebuild required');
});
-Assert::noError(function () use ($catalogue) {
+Assert::noError(static function () use ($catalogue) {
$catalogue->compile();
});
-Assert::exception(function () use ($catalogue) {
+Assert::exception(static function () use ($catalogue) {
$catalogue->setDebugMode(true);
$catalogue->compile();
}, BuilderException::class);
-Assert::exception(function () use ($catalogue) {
+Assert::exception(static function () use ($catalogue) {
$catalogue->setDebugMode(true);
$catalogue->compile(2);
}, BuilderException::class);
-Assert::exception(function () use ($catalogue) {
+Assert::exception(static function () use ($catalogue) {
$catalogue->setDebugMode(true);
$catalogue->compile(3);
}, BuilderException::class);
diff --git a/tests/Catalogue/Catalogue.onCompile.phpt b/tests/Catalogue/Catalogue.onCompile.phpt
index aea49ff..07350da 100644
--- a/tests/Catalogue/Catalogue.onCompile.phpt
+++ b/tests/Catalogue/Catalogue.onCompile.phpt
@@ -11,12 +11,11 @@ $plural = (new PluralProvider());
const LOCALE = 'dynamic';
const RESOURCE = 'test';
-$catalogue = new Catalogue($plural, TEMP_DIR, LOCALE);
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, LOCALE);
$catalogue->addDynamic(RESOURCE, function (array &$messages, string &$resource, string &$locale) {
# Verify callback is called properly
Assert::equal($locale, LOCALE);
Assert::equal($resource, RESOURCE);
- Assert::true(is_array($messages));
# Add string
$messages['string'] = 'test';
diff --git a/tests/Catalogue/Catalogue.phpt b/tests/Catalogue/CatalogueBuilder.phpt
similarity index 72%
rename from tests/Catalogue/Catalogue.phpt
rename to tests/Catalogue/CatalogueBuilder.phpt
index a02fa5f..a202f55 100644
--- a/tests/Catalogue/Catalogue.phpt
+++ b/tests/Catalogue/CatalogueBuilder.phpt
@@ -2,41 +2,43 @@
namespace Bckp\Translator;
-use Bckp\Translator\Builder\Catalogue;
+use Bckp\Translator\Exceptions\BuilderException;
+use Bckp\Translator\Exceptions\FileInvalidException;
+use Bckp\Translator\Exceptions\PathInvalidException;
use Tester\Assert;
use Tester\Environment;
require __DIR__ . '/../bootstrap.php';
$plural = (new PluralProvider());
-Assert::exception(function () use ($plural) {
- new Catalogue($plural, '/no-exists', 'x1');
+Assert::exception(static function () use ($plural) {
+ new CatalogueBuilder($plural, '/no-exists', 'x1');
}, PathInvalidException::class);
@unlink(TEMP_DIR . '/x1Catalogue.php');
-Assert::exception(function () use ($plural) {
- $catalogue = new Catalogue($plural, TEMP_DIR, 'x2');
+Assert::exception(static function () use ($plural) {
+ $catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'x2');
$catalogue->addFile('not-exists');
$catalogue->compile();
}, PathInvalidException::class);
@unlink(TEMP_DIR . '/x2Catalogue.php');
-Assert::exception(function () use ($plural) {
- $catalogue = new Catalogue($plural, TEMP_DIR, 'x3');
+Assert::exception(static function () use ($plural) {
+ $catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'x3');
$catalogue->compile(4);
}, BuilderException::class);
@unlink(TEMP_DIR . '/x3Catalogue.php');
-Assert::exception(function () use ($plural) {
- $catalogue = new Catalogue($plural, TEMP_DIR, 'x4');
+Assert::exception(static function () use ($plural) {
+ $catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'x4');
$catalogue->addFile('./translations/broken.xx.neon');
$catalogue->compile(2);
}, FileInvalidException::class);
@unlink(TEMP_DIR . '/x4Catalogue.php');
-Assert::exception(function () use ($plural) {
- $catalogue = new Catalogue($plural, TEMP_DIR, 'x5');
+Assert::exception(static function () use ($plural) {
+ $catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'x5');
$catalogue->addFile('./translations/string.xx.neon');
$catalogue->compile(2);
}, FileInvalidException::class);
@unlink(TEMP_DIR . '/x5Catalogue.php');
-Assert::exception(function () use ($plural) {
+Assert::exception(static function () use ($plural) {
@unlink(TEMP_DIR . '/x6Catalogue.php');
file_put_contents(TEMP_DIR . '/x6Catalogue.php', 'compile(3);
}, BuilderException::class);
@unlink(TEMP_DIR . '/x7Catalogue.php');
@@ -62,31 +64,31 @@ return new Class{
}
};
');
-$catalogue = new Catalogue($plural, TEMP_DIR, 'x7');
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'x7');
$catalogue->addFile('./translations/test.cs.neon');
$compiled = $catalogue->compile(2);
-Assert::type(ICatalogue::class, $compiled);
+Assert::type(Catalogue::class, $compiled);
Assert::type('string', $compiled->get('test.welcome'));
@unlink(TEMP_DIR . '/x7Catalogue.php');
// Rebuild 2
-$catalogue = new Catalogue($plural, TEMP_DIR, 'x8');
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'x8');
$catalogue->addFile('./translations/test.cs.neon');
file_put_contents(TEMP_DIR . '/x8Catalogue.php', 'compile(2);
Assert::same('x8', $catalogue->getLocale());
-Assert::type(ICatalogue::class, $compiled);
+Assert::type(Catalogue::class, $compiled);
@unlink(TEMP_DIR . '/x8Catalogue.php');
-$catalogue = new Catalogue($plural, TEMP_DIR, 'CS');
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'CS');
$catalogue->addFile('./translations/test.cs.neon');
$catalogue->addFile('./translations/blank.cs.neon');
Assert::same('cs', $catalogue->getLocale());
Assert::same('cs', $catalogue->compile()->locale());
$compiled = $catalogue->compile();
-Assert::type(ICatalogue::class, $compiled);
-Assert::true(filemtime('./translations/test.cs.neon') < $compiled->buildTime());
+Assert::type(Catalogue::class, $compiled);
+Assert::true(filemtime('./translations/test.cs.neon') < $compiled->build());
Assert::true($compiled->has('test.welcome'));
Assert::false($compiled->has('not-exists'));
@@ -102,13 +104,13 @@ Assert::equal([
'other' => '%d lidí',
], $compiled->get('test.plural'));
-$catalogue = new Catalogue($plural, TEMP_DIR, 'EN');
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'EN');
$catalogue->addFile('./translations/test.en.neon');
Assert::same('en', $catalogue->getLocale());
$compiled = $catalogue->compile();
-Assert::type(ICatalogue::class, $compiled);
-Assert::true(filemtime('./translations/test.en.neon') < $compiled->buildTime());
+Assert::type(Catalogue::class, $compiled);
+Assert::true(filemtime('./translations/test.en.neon') < $compiled->build());
Assert::same('en', $compiled->locale());
// en catalogue
@@ -128,13 +130,13 @@ file_put_contents(TEMP_DIR . '/x9Catalogue.php', file_get_contents('assets/x9cat
touch(TEMP_DIR . '/x9Catalogue.php', $time);
if (filemtime(TEMP_DIR . '/x9Catalogue.php') === $time) {
touch('./translations/blank.cs.neon');
- $catalogue = new Catalogue($plural, TEMP_DIR, 'x9');
+ $catalogue = new CatalogueBuilder($plural, TEMP_DIR, 'x9');
$catalogue->setDebugMode(true);
$catalogue->addFile('./translations/test.cs.neon');
$catalogue->addFile('./translations/blank.cs.neon');
$compiled = $catalogue->compile();
- Assert::true($compiled->buildTime() > $time);
+ Assert::true($compiled->build() > $time);
} else {
Environment::skip('Skipped test touch and debug');
}
diff --git a/tests/Catalogue/Catalogue.rebuild.phpt b/tests/Catalogue/CatalogueBuilder.rebuild.phpt
similarity index 67%
rename from tests/Catalogue/Catalogue.rebuild.phpt
rename to tests/Catalogue/CatalogueBuilder.rebuild.phpt
index 7e1248a..1b3389b 100644
--- a/tests/Catalogue/Catalogue.rebuild.phpt
+++ b/tests/Catalogue/CatalogueBuilder.rebuild.phpt
@@ -2,7 +2,6 @@
namespace Bckp\Translator;
-use Bckp\Translator\Builder\Catalogue;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
@@ -11,14 +10,14 @@ $plural = (new PluralProvider());
const LOCALE = 'dynamic';
const RESOURCE = 'test';
-$catalogue = new Catalogue($plural, TEMP_DIR, LOCALE);
+$catalogue = new CatalogueBuilder($plural, TEMP_DIR, LOCALE);
$catalogue->addDynamic(RESOURCE, function (array &$messages) {
$messages['string'] = 'test';
});
$compiled = $catalogue->compile();
-$buildTime = $compiled->buildTime();
+$buildTime = $compiled->build();
sleep(2);
$compiled = $catalogue->rebuild();
-Assert::notEqual($buildTime, $compiled->buildTime());
+Assert::notEqual($buildTime, $compiled->build());
diff --git a/tests/Diagnostics/Panel.phpt b/tests/Diagnostics/Panel.phpt
index 35a30f3..c619d66 100644
--- a/tests/Diagnostics/Panel.phpt
+++ b/tests/Diagnostics/Panel.phpt
@@ -3,7 +3,6 @@
namespace Bckp\Translator;
use Bckp\Translator\Diagnostics\Diagnostics;
-use Bckp\Translator\Diagnostics\Panel;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
diff --git a/tests/Plural/Plural.phpt b/tests/Plural/Plural.phpt
index 58281d8..2494dd6 100644
--- a/tests/Plural/Plural.phpt
+++ b/tests/Plural/Plural.phpt
@@ -16,33 +16,33 @@ Assert::type('callable', $provider->getPlural('non-sense'));
$plural = $provider->getPlural('cs');
Assert::same('csPlural', $plural[1]);
Assert::same(PluralProvider::csPlural(0), $plural(0));
-Assert::same(IPlural::ZERO, $plural(0));
+Assert::same(Plural::Zero, $plural(0));
Assert::same(PluralProvider::csPlural(1), $plural(1));
-Assert::same(IPlural::ONE, $plural(1));
+Assert::same(Plural::One, $plural(1));
Assert::same(PluralProvider::csPlural(2), $plural(2));
-Assert::same(IPlural::FEW, $plural(2));
+Assert::same(Plural::Few, $plural(2));
Assert::same(PluralProvider::csPlural(3), $plural(3));
-Assert::same(IPlural::FEW, $plural(3));
+Assert::same(Plural::Few, $plural(3));
Assert::same(PluralProvider::csPlural(4), $plural(4));
-Assert::same(IPlural::FEW, $plural(4));
+Assert::same(Plural::Few, $plural(4));
Assert::same(PluralProvider::csPlural(5), $plural(5));
-Assert::same(IPlural::OTHER, $plural(5));
+Assert::same(Plural::Other, $plural(5));
Assert::same(PluralProvider::csPlural(-5), $plural(-5));
-Assert::same(IPlural::OTHER, $plural(-5));
+Assert::same(Plural::Other, $plural(-5));
# English
$plural = $provider->getPlural('en');
Assert::same('enPlural', $plural[1]);
Assert::same(PluralProvider::enPlural(0), $plural(0));
-Assert::same(IPlural::ZERO, $plural(0));
+Assert::same(Plural::Zero, $plural(0));
Assert::same(PluralProvider::enPlural(1), $plural(1));
-Assert::same(IPlural::ONE, $plural(1));
+Assert::same(Plural::One, $plural(1));
Assert::same(PluralProvider::enPlural(2), $plural(2));
-Assert::same(IPlural::OTHER, $plural(2));
+Assert::same(Plural::Other, $plural(2));
Assert::same(PluralProvider::enPlural(5), $plural(5));
-Assert::same(IPlural::OTHER, $plural(5));
+Assert::same(Plural::Other, $plural(5));
Assert::same(PluralProvider::enPlural(-5), $plural(-5));
-Assert::same(IPlural::OTHER, $plural(-5));
+Assert::same(Plural::Other, $plural(-5));
# Zero plural
foreach(['id','ja','ka','ko','lo','ms','my','th','vi','zh'] as $lang) {
@@ -50,27 +50,27 @@ foreach(['id','ja','ka','ko','lo','ms','my','th','vi','zh'] as $lang) {
Assert::type('callable', $plural);
Assert::same('zeroPlural', $plural[1]);
Assert::same(PluralProvider::zeroPlural(0), $plural(0));
- Assert::same(IPlural::ZERO, $plural(0));
+ Assert::same(Plural::Zero, $plural(0));
Assert::same(PluralProvider::zeroPlural(5), $plural(5));
- Assert::same(IPlural::OTHER, $plural(5));
+ Assert::same(Plural::Other, $plural(5));
Assert::same(PluralProvider::zeroPlural(-5), $plural(-5));
- Assert::same(IPlural::OTHER, $plural(-5));
+ Assert::same(Plural::Other, $plural(-5));
}
# csPlural
-Assert::same(IPlural::ZERO, PluralProvider::csPlural(0));
-Assert::same(IPlural::ONE, PluralProvider::csPlural(1));
-Assert::same(IPlural::FEW, PluralProvider::csPlural(2));
-Assert::same(IPlural::OTHER, PluralProvider::csPlural(5));
+Assert::same(Plural::Zero, PluralProvider::csPlural(0));
+Assert::same(Plural::One, PluralProvider::csPlural(1));
+Assert::same(Plural::Few, PluralProvider::csPlural(2));
+Assert::same(Plural::Other, PluralProvider::csPlural(5));
# enPlural
-Assert::same(IPlural::ZERO, PluralProvider::enPlural(0));
-Assert::same(IPlural::ONE, PluralProvider::enPlural(1));
-Assert::same(IPlural::OTHER, PluralProvider::enPlural(2));
-Assert::same(IPlural::OTHER, PluralProvider::enPlural(5));
+Assert::same(Plural::Zero, PluralProvider::enPlural(0));
+Assert::same(Plural::One, PluralProvider::enPlural(1));
+Assert::same(Plural::Other, PluralProvider::enPlural(2));
+Assert::same(Plural::Other, PluralProvider::enPlural(5));
# zeroPlural
-Assert::same(IPlural::ZERO, PluralProvider::zeroPlural(0));
-Assert::same(IPlural::OTHER, PluralProvider::zeroPlural(1));
-Assert::same(IPlural::OTHER, PluralProvider::zeroPlural(5));
-Assert::same(IPlural::OTHER, PluralProvider::zeroPlural(-1));
+Assert::same(Plural::Zero, PluralProvider::zeroPlural(0));
+Assert::same(Plural::Other, PluralProvider::zeroPlural(1));
+Assert::same(Plural::Other, PluralProvider::zeroPlural(5));
+Assert::same(Plural::Other, PluralProvider::zeroPlural(-1));
diff --git a/tests/Translator/Translator.phpt b/tests/Translator/Translator.phpt
index 8637ee7..25496f5 100644
--- a/tests/Translator/Translator.phpt
+++ b/tests/Translator/Translator.phpt
@@ -2,9 +2,10 @@
namespace Bckp\Translator;
-use Bckp\Translator\Builder\Catalogue;
use Bckp\Translator\Diagnostics\Diagnostics;
+use Bckp\Translator\Exceptions\TranslatorException;
use Nette\Utils\Html;
+use Stringable;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
@@ -13,12 +14,12 @@ require __DIR__ . '/../bootstrap.php';
$plural = new PluralProvider();
$panel = new Diagnostics();
$provider = new TranslatorProvider(['cs', 'en'], $panel);
-$provider->addCatalogue('cs', (new Catalogue($plural, TEMP_DIR, 'cs'))->addFile('./translations/test.cs.neon'));
-$provider->addCatalogue('en', (new Catalogue($plural, TEMP_DIR, 'en'))->addFile('./translations/test.en.neon'));
-$provider->addCatalogue('hu', (new Catalogue($plural, TEMP_DIR, 'hu')));
+$provider->addCatalogue('cs', (new CatalogueBuilder($plural, TEMP_DIR, 'cs'))->addFile('./translations/test.cs.neon'));
+$provider->addCatalogue('en', (new CatalogueBuilder($plural, TEMP_DIR, 'en'))->addFile('./translations/test.en.neon'));
+$provider->addCatalogue('hu', (new CatalogueBuilder($plural, TEMP_DIR, 'hu')));
// Test object
-$string = new class {
+$string = new class implements Stringable {
public function __toString(): string
{
return 'test.welcome';
@@ -34,17 +35,21 @@ $nonString = new class {
// cs translator
$translator = $provider->getTranslator('cs');
Assert::equal('Vítejte', $translator->translate('test.welcome'));
-Assert::equal('Vítejte', $translator->translate(['test.welcome', 3]));
+Assert::equal('Vítejte', $translator->translate('test.welcome', 3));
Assert::equal('Vítejte', $translator->translate('test.welcome', 3));
Assert::equal('Vítejte', $translator->translate($string));
Assert::equal('', $translator->translate(''));
Assert::equal('not.existing', $translator->translate('not.existing'));
Assert::equal('html', $translator->translate(Html::el()->setText('html')));
Assert::equal('test.blank', $translator->translate('test.blank'), 'Translation is empty');
-Assert::equal('Expected string|array|object::__toString, but NULL given.', $translator->translate($nonString));
Assert::equal('zapnuto', $translator->translate('test.options'));
-Assert::equal('zapnuto', $translator->translate(['test.options', 7]));
-Assert::equal('5 lidí', $translator->translate(['test.plural', 5]));
+Assert::equal('zapnuto', $translator->translate('test.options', 7));
+Assert::equal('5 lidí', $translator->translate('test.plural', 5, 5));
+
+Assert::equal('1 2 3 4 5', $translator->translate('test.numbers', 1,2,3,4,5));
+Assert::equal('5 4 3 2 1', $translator->translate('test.numbersReverse', 1,2,3,4,5));
+
+Assert::equal('1 + 1 = 2', $translator->translate('test.justSecond', 5, 1));
// en translator
$translator = $provider->getTranslator('en');
@@ -63,7 +68,7 @@ Assert::truthy($panel->getUntranslated());
Assert::truthy($panel->getWarnings());
// Exception on non-exists catalogue
-Assert::exception(function () use ($provider) {
+Assert::exception(static function () use ($provider) {
return $provider->getTranslator('jp');
}, TranslatorException::class);
@@ -75,7 +80,7 @@ $translator->setNormalizeCallback(function (string $string) use (&$callbackUsed)
$callbackUsed = true;
return str_replace('%value', '%%value', $string);
});
-Assert::equal('Hodnota prvku %value ma byt test.', $translator->translate('test.normalize', 'test'));
+Assert::equal('Hodnota prvku %value ma byt test.', $translator->translate('test.normalize', parameters: 'test'));
Assert::true($callbackUsed, 'Callback should be used.');
$callbackUsed = false;
diff --git a/tests/Translator/translations/test.cs.neon b/tests/Translator/translations/test.cs.neon
index a7fbcbf..388d3b2 100644
--- a/tests/Translator/translations/test.cs.neon
+++ b/tests/Translator/translations/test.cs.neon
@@ -10,3 +10,8 @@ options:
one: 'zapnuto'
normalize: 'Hodnota prvku %value ma byt %s.'
normalize2: 'Hodnota prvku %value.'
+numbers: '%d %d %d %d %d'
+numbersReverse: '%5$d %4$d %3$d %2$d %1$d'
+justSecond:
+ one: 'tahle ne'
+ other: '1 + %2$d = 2'
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 87faf8e..04ff334 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -4,19 +4,20 @@
namespace Bckp\Translator;
+use Nette\Utils\Random;
use Tester\Environment;
-use function lcg_value;
-
use const TEMP_DIR;
require __DIR__ . '/../vendor/autoload.php';
-define('TEMP_DIR', __DIR__ . '/../temp/' . (string)lcg_value());
+define('TEMP_DIR', __DIR__ . '/../temp/' . Random::generate(10));
if (file_exists(TEMP_DIR)) {
@unlink(TEMP_DIR);
}
-mkdir(TEMP_DIR, 0775, true);
+if (!mkdir($concurrentDirectory = TEMP_DIR, 0775, true) && !is_dir($concurrentDirectory)) {
+ throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
+}
Environment::setup();
@unlink(TEMP_DIR);