Passed
Push — master ( 1a0d99...e5153a )
by Radovan
02:10
created

Translator::getVariant()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 16
rs 9.9332
1
<?php
2
3
/**
4
 * BCKP Translator
5
 * (c) Radovan Kepák
6
 *
7
 * For the full copyright and license information, please view
8
 * the file license.md that was distributed with this source code.
9
 *
10
 * @author Radovan Kepak <[email protected]>
11
 */
12
13
declare(strict_types=1);
14
15
namespace Bckp\Translator;
16
17
use Nette\Utils\Strings;
18
use function array_key_exists;
19
use function end;
20
use function gettype;
21
use function is_array;
22
use function is_object;
23
use function is_string;
24
use function key;
25
use function method_exists;
26
use function vsprintf;
27
28
/**
29
 * Class Translator
30
 *
31
 * @package Bckp\Translator
32
 */
33
class Translator implements ITranslator
34
{
35
    /** @var ICatalogue */
36
    private $catalogue;
37
38
    /** @var IDiagnostics|null */
39
    private $diagnostics;
40
41
    /** @var callable function(string $string): string */
42
    private $normalizeCallback;
43
44
    /**
45
     * Translator constructor.
46
     *
47
     * @param ICatalogue $catalogue
48
     * @param IDiagnostics|null $diagnostics
49
     */
50
    public function __construct(ICatalogue $catalogue, IDiagnostics $diagnostics = null)
51
    {
52
        $this->catalogue = $catalogue;
53
        $this->normalizeCallback = [$this, 'normalize'];
54
        if ($this->diagnostics = $diagnostics) {
55
            $this->diagnostics->setLocale($catalogue->locale());
56
        }
57
    }
58
59
    /**
60
     * Normalize string to preserve frameworks placeholders
61
     *
62
     * @param string $string
63
     * @return string
64
     */
65
    public function normalize(string $string): string
66
    {
67
        return str_replace(
68
            ['%label', '%value', '%name'],
69
            ['%%label', '%%value', '%%name'],
70
            $string
71
        );
72
    }
73
74
    /**
75
     * @param callable $callback
76
     * @return void
77
     */
78
    public function setNormalizeCallback(callable $callback): void
79
    {
80
        $this->normalizeCallback = $callback;
81
    }
82
83
    /**
84
     * @param mixed $message
85
     * @param mixed ...$parameters
86
     * @return string
87
     */
88
    public function translate($message, ...$parameters): string
89
    {
90
        if (empty($message)) {
91
            return '';
92
        }
93
94
        $form = null;
95
        $message = $this->getMessage($message, $form);
96
        if ($message === null) {
97
            return $this->warn('Expected string|array|object::__toString, but %s given.', gettype($message));
98
        }
99
        $result = $message;
100
        $this->expandParameters($parameters, $message);
101
102
        // process plural if any
103
        if ($translation = $this->catalogue->get($message)) {
104
            $result = $this->getVariant($message, $translation, $form);
105
106
            if ($parameters) {
107
                $result = ($this->normalizeCallback)($result);
108
                $result = @vsprintf($result, $parameters);
109
                // Intentionally @ as argument count can mismatch
110
            }
111
        } else {
112
            $this->untranslated((string)$message);
113
        }
114
115
        return $result;
116
    }
117
118
    /**
119
     * @param string $message
120
     * @param string|array<string> $translation
121
     * @param string|null $form
122
     * @return string
123
     */
124
    private function getVariant(string $message, $translation, string $form = null): string
125
    {
126
        if (!is_array($translation)) {
127
            return $translation;
128
        }
129
130
        if ($form === null || !array_key_exists($form, $translation)) {
131
            $this->warn(
132
                'Plural form not defined. (message: %s, form: %s)',
133
                (string)$message,
134
                (string)$form
135
            );
136
            end($translation);
137
            $form = key($translation);
138
        }
139
        return $translation[$form];
140
    }
141
142
    /**
143
     * @param mixed $message
144
     * @param string|null $plural
145
     * @return string|null
146
     */
147
    protected function getMessage($message, ?string &$plural): ?string
148
    {
149
        if (is_string($message)) {
150
            return $message;
151
        }
152
153
        if (is_array($message) && is_string($message[0])) {
154
            $plural = $this->catalogue->plural((int)$message[1] ?? 1);
155
            return $message[0];
156
        }
157
158
        if (is_object($message) && method_exists($message, '__toString')) {
159
            return (string)$message;
160
        }
161
162
        return null;
163
    }
164
165
    /**
166
     * @param string $message
167
     */
168
    protected function untranslated(string $message): void
169
    {
170
        if ($this->diagnostics !== null) {
171
            $this->diagnostics->untranslated($message);
172
        }
173
    }
174
175
    /**
176
     * @param string $message
177
     * @param mixed ...$parameters
178
     * @return string
179
     */
180
    protected function warn(string $message, ...$parameters): string
181
    {
182
        if (!empty($parameters)) {
183
            $message = @vsprintf($message, $parameters);
184
        } // Intentionally @ as parameter count can mismatch
185
186
        if ($this->diagnostics !== null) {
187
            $this->diagnostics->warning($message);
188
        }
189
190
        return $message;
191
    }
192
193
    /**
194
     * @param array<mixed> $parameters
195
     * @param string|array<string|int> $message
196
     */
197
    private function expandParameters(array &$parameters, $message): void
198
    {
199
        if (
200
            empty($parameters)
201
            && is_array($message)
202
            && is_numeric($message[1] ?? null)
203
        ) {
204
            $parameters[] = $message[1];
205
        }
206
    }
207
}
208