Passed
Pull Request — master (#7)
by
unknown
13:53
created

Translator::currentTranslateStore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 8
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 5
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Efabrica\Translatte;
6
7
use Efabrica\Translatte\Cache\ICache;
8
use Efabrica\Translatte\Cache\NullCache;
9
use Efabrica\Translatte\CurrentTranslate\DumbCurrentTranslate;
10
use Efabrica\Translatte\CurrentTranslate\ICurrentTranslate;
11
use Efabrica\Translatte\Resolver\IResolver;
12
use Efabrica\Translatte\Resolver\StaticResolver;
13
use Efabrica\Translatte\Resource\IResource;
14
use InvalidArgumentException;
15
use Nette\Localization\ITranslator;
16
17
class Translator implements ITranslator
18
{
19
    /** @var string */
20
    private $defaultLang;
21
22
    /** @var string */
23
    private $lang;
24
25
    /** @var IResolver */
26
    private $resolver;
27
28
    /** @var ICache */
29
    private $cache;
30
31
    /** @var ICurrentTranslate */
32
    private $currentTranslate;
33
34
    /** @var array */
35
    private $resources = [];
36
37
    /** @var array */
38 15
    private $fallbackLanguages = [];
39
40
    /** @var Dictionary[] */
41
    private $dictionaries = [];
42
43 15
    public function __construct(
44 15
        string $defaultLang,
45 15
        IResolver $resolver = null,
46 15
        ICache $cache = null,
47
        ICurrentTranslate $currentTranslate = null
48
    ) {
49
        $this->defaultLang = $defaultLang;
50
        $this->resolver = $resolver ?: new StaticResolver($defaultLang);
51
        $this->cache = $cache ?: new NullCache();
52
        $this->currentTranslate = $currentTranslate ?: new DumbCurrentTranslate();
53
        $this->currentTranslate->init();
54
    }
55
56
    /**
57
     * Fallback languages will be used as waterfall, first with valid result is used
58
     * @param array $fallbackLanguages
59
     */
60
    public function setFallbackLanguages(array $fallbackLanguages): void
61
    {
62 15
        $this->fallbackLanguages = $fallbackLanguages;
63
    }
64 15
65 15
    /**
66
     * Add new resource to parse translations from
67
     * @param IResource $resource
68
     * @return Translator
69
     */
70
    public function addResource(IResource $resource): self
71
    {
72
        $this->resources[] = $resource;
73
        return $this;
74 15
    }
75
76
    /**
77
     * Provide translation
78
     * @param string|int $message
79 15
     * @param mixed ...$parameters
80 15
     * @return string
81
     */
82
    public function translate($message, ...$parameters): string
83 15
    {
84
        // translate($message, int $count, array $params, string $lang = null)
85
        // translate($message, array $params, string $lang = null)
86
87 15
        $message = (string) $message;
88 15
        list($count, $params, $lang) = array_values($this->parseParameters($parameters));
89
90
        // If wrong input arguments passed, return message key
91
        if (!is_int($count) || !is_array($params) || !is_string($lang)) {
92
            $this->currentTranslateStore($message, $message, $lang, (int) strval($count), (array) $params);
93
            return $message; // @ maybe throw exception?
94
        }
95
96
        $translation = $this->getDictionary($lang)->findTranslation($message);
97
        if ($translation === null) {
98
            // Try find translation in fallback languages
99
            foreach ($this->fallbackLanguages as $fallbackLanguage) {
100
                $translation = $this->getDictionary($fallbackLanguage)->findTranslation($message);
101
                if ($translation !== null) {
102
                    break;
103 15
                }
104 15
            }
105
106 15
            // If translation not found in base either fallback languages return message key
107
            if ($translation === null) {
108
                $this->currentTranslateStore($message, $translation, $lang, $count, $params);
109
                return $message;
110
            }
111
        }
112
113
        $translation = $this->selectRightPluralForm($translation, $lang, $count);
114
        $translation = $this->replaceParams($translation, $params);
115
116 15
        $this->currentTranslateStore($message, $translation, $lang, $count, $params);
117
        return $translation;
118 15
    }
119 15
120 6
    /**
121
     * Store current translate parameters
122
     * @param string $message
123 9
     * @param string|null $translation
124 9
     * @param string $lang
125
     * @param int $count
126
     * @param array $params
127
     * @return void
128
     */
129
    private function currentTranslateStore(string $message, ?string $translation, string $lang, int $count, array $params): void
130
    {
131
        $this->currentTranslate->store([
132
            'message' => $message,
133 15
            'translation' => $translation ?? '',
134
            'lang' => $lang,
135 15
            'count' => $count,
136 15
            'params' => $params
137 15
        ]);
138
    }
139
140 15
    /**
141
     * Select right plural form based on selected language
142
     * @param string $translation
143
     * @param string $lang
144
     * @param int $count
145
     * @return string
146
     */
147
    private function selectRightPluralForm(string $translation, string $lang, int $count): string
148 15
    {
149
        $exploded = explode('|', $translation);
150 15
        if (count($exploded) === 1) {
151
            return $translation;
152 9
        }
153
154 9
        $pluralForm = PluralForm::get($count, $lang);
155
        return isset($exploded[$pluralForm]) ? $exploded[$pluralForm] : $exploded[0];
156
    }
157
158 15
    /**
159
     * Replace parameters in translation string
160 12
     * @param string $translation
161 12
     * @param array $params
162 12
     * @return string
163
     */
164
    private function replaceParams(string $translation, array $params): string
165
    {
166 15
        $transParams = [];
167 15
        foreach ($params as $key => $value) {
168 15
            $transParams['%' . $key . '%'] = $value;
169
        }
170
171
        return strtr($translation, $transParams);
172 15
    }
173 15
174 15
    /**
175
     * Parse translation input parameters
176
     * @param array $parameters
177
     * @return array
178
     */
179
    private function parseParameters(array $parameters): array
180
    {
181
        if (!count($parameters)) {
182 15
            return [
183
                'count' => 1,
184 15
                'params' => ['count' => 1],
185 15
                'lang' => $this->getResolvedLang()
186 15
            ];
187
        }
188 15
189
        if (is_array($parameters[0])) {
190
            return [
191
                'count' => isset($parameters[0]['count']) ? $parameters[0]['count'] : 1,
192
                'params' => $parameters[0],
193
                'lang' => array_key_exists(1, $parameters) ? $parameters[1] : $this->getResolvedLang()
194
            ];
195
        }
196 15
197
        $params = array_key_exists(1, $parameters) ? $parameters[1] : [];
198 15
        if (!isset($params['count'])) {
199 12
            $params['count'] = $parameters[0];
200
        }
201
202 15
        return [
203 15
            'count' => $parameters[0],
204
            'params' => $params,
205
            'lang' => array_key_exists(2, $parameters) ? $parameters[2] : $this->getResolvedLang()
206
        ];
207
    }
208 15
209 15
    /**
210 15
     * Use resolvers to resolve language to use
211 15
     * @return string
212 15
     */
213
    private function getResolvedLang(): string
214
    {
215 15
        if ($this->lang === null) {
216
            $resolvedLang = $this->resolver->resolve();
217
            $this->lang = $resolvedLang !== null ? $resolvedLang : $this->defaultLang;
218
        }
219 15
        return $this->lang;
220
    }
221
222
    /**
223
     * Prepare and return dictionary ready to use
224
     * @param string $lang
225
     * @return Dictionary
226
     */
227
    private function getDictionary(string $lang): Dictionary
228
    {
229
        if (array_key_exists($lang, $this->dictionaries)) {
230
            return $this->dictionaries[$lang];
231
        }
232
233
        $dictionary = $this->cache->load($lang);
234
        if ($dictionary !== null) {
235
            $this->dictionaries[$lang] = $dictionary;
236
            return $this->dictionaries[$lang];
237
        }
238
239
        $this->dictionaries[$lang] = new Dictionary($lang);
240
        foreach ($this->resources as $resource) {
241
            $dictionaries = $resource->load($lang);
242
            foreach ($dictionaries as $dictionary) {
243
                $this->cache->store($lang, $dictionary->getRecords());
244
                if (!$dictionary instanceof Dictionary) {
245
                    throw new InvalidArgumentException(sprintf('%s expected. Resource returned %s', Dictionary::class, get_class($dictionary)));
246
                }
247
                $this->dictionaries[$lang]->extend($dictionary);
248
            }
249
        }
250
251
        return $this->dictionaries[$lang];
252
    }
253
}
254