Skip to content

Commit 4673964

Browse files
committed
initial commit
0 parents  commit 4673964

14 files changed

+737
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
tests/temp
2+
vendor
3+
composer.lock
4+
.idea/

LICENSE

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2018, Miroslav Koula
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
* Neither the name of the copyright holder nor the names of its
17+
contributors may be used to endorse or promote products derived from
18+
this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

composer.json

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "elcheco/translator",
3+
"type": "library",
4+
"minimum-stability": "dev",
5+
"require": {
6+
"php": ">=7.2",
7+
"ext-intl": "*",
8+
"ext-tokenizer": "*",
9+
"nette/di": "^2.4",
10+
"nette/neon": "v3.0.0-beta1",
11+
"nette/safe-stream": "^2.3",
12+
"nette/utils": "^2.5",
13+
"psr/log": "^1.0"
14+
},
15+
"require-dev": {
16+
"nette/tester": "^2.0",
17+
"mockery/mockery": "^1.0@dev",
18+
"phpstan/phpstan": "^0.9.0@dev"
19+
},
20+
"suggest": {
21+
"monolog/monolog": "To log translation errors."
22+
},
23+
"autoload": {
24+
"psr-4": {
25+
"ElCheco\\": [
26+
"src"
27+
]
28+
}
29+
}
30+
}

src/Translator/Dictionary.php

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* @author Miroslav Koula
4+
* @copyright Copyright (c) 2018 Miroslav Koula, https://elcheco.it
5+
* @created 01/10/18 14:02
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ElCheco\Translator;
11+
12+
13+
abstract class Dictionary implements DictionaryInterface
14+
{
15+
16+
/**
17+
* @var array
18+
*/
19+
private $messages;
20+
21+
22+
public function has(string $message): bool
23+
{
24+
$this->lazyLoad();
25+
26+
return array_key_exists($message, $this->messages);
27+
}
28+
29+
30+
public function get(string $message)
31+
{
32+
$this->lazyLoad();
33+
34+
return $this->messages[$message];
35+
}
36+
37+
38+
abstract protected function lazyLoad();
39+
40+
41+
protected function isReady()
42+
{
43+
return is_array($this->messages);
44+
}
45+
46+
47+
protected function setMessages(array $messages): DictionaryInterface
48+
{
49+
$this->messages = $messages;
50+
51+
return $this;
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
/**
3+
* @author Miroslav Koula
4+
* @copyright Copyright (c) 2018 Miroslav Koula, https://elcheco.it
5+
* @created 01/10/18 14:02
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ElCheco\Translator;
11+
12+
13+
interface DictionaryFactoryInterface
14+
{
15+
16+
public function create(string $locale, ?string $fallbackLocale): DictionaryInterface;
17+
18+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* @author Miroslav Koula
4+
* @copyright Copyright (c) 2018 Miroslav Koula, https://elcheco.it
5+
* @created 01/10/18 14:02
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ElCheco\Translator;
11+
12+
13+
interface DictionaryInterface
14+
{
15+
16+
/**
17+
* @param string $message
18+
* @return string|array
19+
*/
20+
public function get(string $message);
21+
22+
23+
public function has(string $message): bool;
24+
25+
}

src/Translator/Extension.php

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
/**
3+
* @author Miroslav Koula
4+
* @copyright Copyright (c) 2018 Miroslav Koula, https://elcheco.it
5+
* @created 01/10/18 14:02
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ElCheco\Translator;
11+
12+
13+
use Nette\DI\CompilerExtension;
14+
use Nette\DI\Helpers;
15+
use ElCheco\Translator\NeonDictionary\NeonDictionaryFactory;
16+
17+
class Extension extends CompilerExtension
18+
{
19+
20+
private $defaults = [
21+
'default' => 'en_US',
22+
'fallback' => 'en_US',
23+
'dictionary' => [
24+
'factory' => NeonDictionaryFactory::class,
25+
'args' => [
26+
'directory' => '%appDir%/translations',
27+
'cache' => '%tempDir%/cache/translations',
28+
]
29+
],
30+
];
31+
32+
33+
public function loadConfiguration()
34+
{
35+
$builder = $this->getContainerBuilder();
36+
37+
// expand options
38+
$config = Helpers::expand($this->validateConfig($this->defaults), $builder->parameters);
39+
40+
// add default neon dictionary factory
41+
$builder
42+
->addDefinition($this->prefix('dictionaryFactory'))
43+
->setFactory($config['dictionary']['factory'], array_values($config['dictionary']['args']))
44+
->setAutowired(true);
45+
46+
// add translator
47+
$builder
48+
->addDefinition($this->prefix('translator'))
49+
->setFactory(Translator::class)
50+
->addSetup('setFallbackLocale', [(isset($config['fallback']) ? $config['fallback'] : $config['default'])])
51+
->addSetup('setLocale', [$config['default']])
52+
// ->addSetup('setLocale', [$config['default']])
53+
->setAutowired(true);
54+
55+
}
56+
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
/**
3+
* @author Miroslav Koula
4+
* @copyright Copyright (c) 2018 Miroslav Koula, https://elcheco.it
5+
* @created 01/10/18 14:02
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ElCheco\Translator\NeonDictionary;
11+
12+
13+
use function is_array;
14+
use Nette\Neon\Neon;
15+
use ElCheco\Translator\Dictionary;
16+
17+
final class NeonDictionary extends Dictionary
18+
{
19+
20+
/**
21+
* @var string
22+
*/
23+
private $filename;
24+
25+
/**
26+
* @var string
27+
*/
28+
private $cacheFilename;
29+
30+
/**
31+
* @var string
32+
*/
33+
private $fallbackFilename;
34+
35+
36+
public function __construct(string $filename, string $cacheFilename, ?string $fallbackFilename = null)
37+
{
38+
if (!is_file($filename)) {
39+
40+
throw NeonDictionaryException::fileNotFound($filename);
41+
}
42+
43+
if ($fallbackFilename && !is_file($fallbackFilename)) {
44+
45+
throw NeonDictionaryException::fileNotFound($fallbackFilename);
46+
}
47+
48+
$this->filename = $filename;
49+
$this->cacheFilename = $cacheFilename;
50+
51+
$this->fallbackFilename = $fallbackFilename;
52+
}
53+
54+
55+
protected function lazyLoad()
56+
{
57+
if (!$this->isReady()) {
58+
59+
if (is_file($this->cacheFilename)) {
60+
61+
// load cache
62+
$this->setMessages(require $this->cacheFilename);
63+
64+
} else {
65+
66+
// load translations from neon file
67+
$fallbackDecoded = Neon::decode(file_get_contents($this->fallbackFilename));
68+
$fallbackTranslations = is_array($fallbackDecoded) ? $fallbackDecoded: [];
69+
70+
// load translations from neon file
71+
$decoded = Neon::decode(file_get_contents($this->filename));
72+
$translations = is_array($decoded) ? $decoded: [];
73+
74+
$translations = \array_merge($fallbackTranslations, $translations);
75+
76+
// save cache
77+
$content = '<?php ' . PHP_EOL . 'return ' . var_export($translations, true) . ';' . PHP_EOL;
78+
file_put_contents("safe://$this->cacheFilename", $content);
79+
80+
$this->setMessages($translations);
81+
}
82+
}
83+
}
84+
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/**
3+
* @author Miroslav Koula
4+
* @copyright Copyright (c) 2018 Miroslav Koula, https://elcheco.it
5+
* @created 01/10/18 14:02
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ElCheco\Translator\NeonDictionary;
11+
12+
13+
use ElCheco\Translator\TranslatorException;
14+
15+
final class NeonDictionaryException extends TranslatorException
16+
{
17+
18+
public static function cacheDirIsNotWritable(string $cacheDir)
19+
{
20+
return new static(sprintf("Cache directory %s is not writable.", $cacheDir));
21+
}
22+
23+
24+
public static function fileNotFound(string $filename)
25+
{
26+
return new static(sprintf("Translation file %s not found.", $filename));
27+
}
28+
29+
30+
public static function translationDirNotFound(string $dir)
31+
{
32+
return new static(sprintf("Translation directory %s not found.", $dir));
33+
}
34+
35+
}

0 commit comments

Comments
 (0)