Passed
Pull Request — master (#1)
by Team eFabrica
01:31
created

Translator::parseParameters()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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