Passed
Branch master (1a0d99)
by Radovan
03:25 queued 01:22
created

Translator::getMessage()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 8
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 16
rs 9.2222
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
        // expand parameters
95
        if (empty($parameters) && is_array($message) && is_numeric($message[1] ?? null)) {
96
            $parameters[] = $message[1];
97
        }
98
99
        // get message and plural if any
100
        $form = null;
101
        $message = $this->getMessage($message, $form);
102
        if ($message === null) {
103
            return $this->warn('Expected string|array|object::__toString, but %s given.', gettype($message));
104
        }
105
        $result = $message;
106
107
        // process plural if any
108
        if ($translation = $this->catalogue->get($message)) {
109
            $result = $this->getVariant($message, $translation, $form);
110
111
            if ($parameters) {
112
                $result = ($this->normalizeCallback)($result);
113
                $result = @vsprintf($result, $parameters);
114
                // Intentionally @ as argument count can mismatch
115
            }
116
        } else {
117
            $this->untranslated((string)$message);
118
        }
119
120
        return $result;
121
    }
122
123
    /**
124
     * @param string $message
125
     * @param string|array<string> $translation
126
     * @param string|null $form
127
     * @return string
128
     */
129
    protected function getVariant(string $message, $translation, string $form = null): string
130
    {
131
        if (!is_array($translation)) {
132
            return $translation;
133
        }
134
135
        if ($form === null || !array_key_exists($form, $translation)) {
136
            $this->warn(
137
                'Plural form not defined. (message: %s, form: %s)',
138
                (string)$message,
139
                (string)$form
140
            );
141
            end($translation);
142
            $form = key($translation);
143
        }
144
        return $translation[$form];
145
    }
146
147
    /**
148
     * @param mixed $message
149
     * @param string|null $plural
150
     * @return string|null
151
     */
152
    protected function getMessage($message, ?string &$plural): ?string
153
    {
154
        if (is_string($message)) {
155
            return $message;
156
        }
157
158
        if (is_array($message) && is_string($message[0])) {
159
            $plural = $this->catalogue->plural((int)$message[1] ?? 1);
160
            return $message[0];
161
        }
162
163
        if (is_object($message) && method_exists($message, '__toString')) {
164
            return (string)$message;
165
        }
166
167
        return null;
168
    }
169
170
    /**
171
     * @param string $message
172
     */
173
    protected function untranslated(string $message): void
174
    {
175
        if ($this->diagnostics !== null) {
176
            $this->diagnostics->untranslated($message);
177
        }
178
    }
179
180
    /**
181
     * @param string $message
182
     * @param mixed ...$parameters
183
     * @return string
184
     */
185
    protected function warn(string $message, ...$parameters): string
186
    {
187
        if (!empty($parameters)) {
188
            $message = @vsprintf($message, $parameters);
189
        } // Intentionally @ as parameter count can mismatch
190
191
        if ($this->diagnostics !== null) {
192
            $this->diagnostics->warning($message);
193
        }
194
195
        return $message;
196
    }
197
}
198