Passed
Push — master ( 4108db...a3bd4e )
by Chauncey
41s
created

Translator::selector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Charcoal\Translator;
4
5
use RuntimeException;
6
7
// From 'symfony/translation'
8
use Symfony\Component\Translation\MessageSelector;
9
use Symfony\Component\Translation\Translator as SymfonyTranslator;
10
11
// From 'charcoal-translator'
12
use Charcoal\Translator\LocalesManager;
13
use Charcoal\Translator\Translation;
14
15
/**
16
 * Charcoal Translator.
17
 *
18
 * Extends the Symfony translator to allow returned values in a "Translation" oject,
19
 * containing localizations for all locales.
20
 */
21
class Translator extends SymfonyTranslator
22
{
23
    /**
24
     * The locales manager.
25
     *
26
     * @var LocalesManager
27
     */
28
    private $manager;
29
30
    /**
31
     * The message selector.
32
     *
33
     * @var MessageSelector
34
     */
35
    private $selector;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
36
37
    /**
38
     * The loaded domains.
39
     *
40
     * @var string[]
41
     */
42
    private $domains = [ 'messages' ];
43
44
    /**
45
     * @param array $data Constructor data.
46
     */
47
    public function __construct(array $data)
48
    {
49
        $this->setManager($data['manager']);
50
51
        // Ensure Charcoal has control of the message selector.
52
        $this->setSelector($data['message_selector']);
53
54
        $defaults = [
55
            'locale'    => $this->manager()->currentLocale(),
56
            'cache_dir' => null,
57
            'debug'     => false
58
        ];
59
        $data = array_merge($defaults, $data);
60
61
        // If 'symfony/config' is not installed, DON'T use cache.
62
        if (!class_exists('\Symfony\Component\Config\ConfigCacheFactory', false)) {
63
            $data['cache_dir'] = null;
64
        }
65
66
        parent::__construct(
67
            $data['locale'],
68
            $data['message_selector'],
69
            $data['cache_dir'],
70
            $data['debug']
71
        );
72
    }
73
74
    /**
75
     * Adds a resource.
76
     *
77
     * @see    SymfonyTranslator::addResource() Keep track of the translation domains.
78
     * @param  string      $format   The name of the loader (@see addLoader()).
79
     * @param  mixed       $resource The resource name.
80
     * @param  string      $locale   The locale.
81
     * @param  string|null $domain   The domain.
82
     * @return void
83
     */
84
    public function addResource($format, $resource, $locale, $domain = null)
85
    {
86
        if (null !== $domain) {
87
            $this->domains[] = $domain;
88
        }
89
90
        parent::addResource($format, $resource, $locale, $domain);
91
    }
92
93
    /**
94
     * Retrieve the loaded domains.
95
     *
96
     * @return string[]
97
     */
98
    public function availableDomains()
99
    {
100
        return $this->domains;
101
    }
102
103
    /**
104
     * Retrieve a translation object from a (mixed) message.
105
     *
106
     * @uses   SymfonyTranslator::trans()
107
     * @param  mixed       $val        The string or translation-object to retrieve.
108
     * @param  array       $parameters An array of parameters for the message.
109
     * @param  string|null $domain     The domain for the message or NULL to use the default.
110
     * @return Translation|null The translation object or NULL if the value is not translatable.
111
     */
112
    public function translation($val, array $parameters = [], $domain = null)
113
    {
114
        if ($this->isValidTranslation($val) === false) {
115
            return null;
116
        }
117
118
        $translation = new Translation($val, $this->manager());
119
        foreach ($this->availableLocales() as $lang) {
120
            if (!isset($translation[$lang]) || $translation[$lang] == $val) {
121
                $translation[$lang] = $this->trans((string)$translation, $parameters, $domain, $lang);
122
            }
123
        }
124
125
        return $translation;
126
    }
127
128
    /**
129
     * Translates the given (mixed) message.
130
     *
131
     * @uses   SymfonyTranslator::trans()
132
     * @uses   Translator::translation()
133
     * @param  mixed       $val        The string or translation-object to retrieve.
134
     * @param  array       $parameters An array of parameters for the message.
135
     * @param  string|null $domain     The domain for the message or NULL to use the default.
136
     * @param  string|null $locale     The locale or NULL to use the default.
137
     * @return string The translated string
138
     */
139
    public function translate($val, array $parameters = [], $domain = null, $locale = null)
140
    {
141
        if ($locale === null) {
142
            $locale = $this->getLocale();
143
        }
144
145
        if ($val instanceof Translation) {
146
            return strtr($val[$locale], $parameters);
147
        }
148
149
        if (is_object($val) && method_exists($val, '__toString')) {
150
            $val = (string)$val;
151
        }
152
153
        if (is_string($val)) {
154
            return $this->trans($val, $parameters, $domain, $locale);
155
        } else {
156
            $translation = $this->translation($val, $parameters, $domain);
157
            return $translation[$locale];
158
        }
159
    }
160
161
    /**
162
     * Retrieve a translation object from a (mixed) message by choosing a translation according to a number.
163
     *
164
     * @uses   SymfonyTranslator::transChoice()
165
     * @param  mixed       $val        The string or translation-object to retrieve.
166
     * @param  integer     $number     The number to use to find the indice of the message.
167
     * @param  array       $parameters An array of parameters for the message.
168
     * @param  string|null $domain     The domain for the message or NULL to use the default.
169
     * @return Translation|null The translation object or NULL if the value is not translatable.
170
     */
171
    public function translationChoice($val, $number, array $parameters = [], $domain = null)
172
    {
173
        if ($this->isValidTranslation($val) === false) {
174
            return null;
175
        }
176
177
        $parameters = array_merge([
178
            '%count%' => $number,
179
        ], $parameters);
180
181
        $translation = new Translation($val, $this->manager());
182
        foreach ($this->availableLocales() as $lang) {
183
            if (!isset($translation[$lang]) || $translation[$lang] == $val) {
184
                $translation[$lang] = $this->transChoice((string)$translation, $number, $parameters, $domain, $lang);
185
            } else {
186
                $translation[$lang] = strtr(
187
                    $this->selector()->choose($translation[$lang], (int)$number, $lang),
188
                    $parameters
189
                );
190
            }
191
        }
192
193
        return $translation;
194
    }
195
196
    /**
197
     * Translates the given (mixed) choice message by choosing a translation according to a number.
198
     *
199
     * @uses   SymfonyTranslator::transChoice()
200
     * @uses   Translator::translationChoice()
201
     * @param  mixed       $val        The string or translation-object to retrieve.
202
     * @param  integer     $number     The number to use to find the indice of the message.
203
     * @param  array       $parameters An array of parameters for the message.
204
     * @param  string|null $domain     The domain for the message or NULL to use the default.
205
     * @param  string|null $locale     The locale or NULL to use the default.
206
     * @return string The translated string
207
     */
208
    public function translateChoice($val, $number, array $parameters = [], $domain = null, $locale = null)
209
    {
210
        if ($locale === null) {
211
            $locale = $this->getLocale();
212
        }
213
214
        if ($val instanceof Translation) {
215
            $parameters = array_merge([
216
                '%count%' => $number,
217
            ], $parameters);
218
219
            return strtr(
220
                $this->selector()->choose($val[$locale], (int)$number, $locale),
221
                $parameters
222
            );
223
        }
224
225
        if (is_object($val) && method_exists($val, '__toString')) {
226
            $val = (string)$val;
227
        }
228
229
        if (is_string($val)) {
230
            return $this->transChoice($val, $number, $parameters, $domain, $locale);
231
        } else {
232
            $translation = $this->translationChoice($val, $number, $parameters, $domain);
233
            return $translation[$locale];
234
        }
235
    }
236
237
    /**
238
     * Retrieve the available locales information.
239
     *
240
     * @return array
241
     */
242
    public function locales()
243
    {
244
        return $this->manager()->locales();
245
    }
246
247
    /**
248
     * Retrieve the available locales (language codes).
249
     *
250
     * @return string[]
251
     */
252
    public function availableLocales()
253
    {
254
        return $this->manager()->availableLocales();
255
    }
256
257
    /**
258
     * Sets the current locale.
259
     *
260
     * @see    SymfonyTranslator::setLocale() Ensure that the method also changes the locales manager's language.
261
     * @param  string $locale The locale.
262
     * @return void
263
     */
264
    public function setLocale($locale)
265
    {
266
        parent::setLocale($locale);
267
268
        $this->manager()->setCurrentLocale($locale);
269
    }
270
271
    /**
272
     * Set the locales manager.
273
     *
274
     * @param  LocalesManager $manager The locales manager.
275
     * @return void
276
     */
277
    private function setManager(LocalesManager $manager)
278
    {
279
        $this->manager = $manager;
280
    }
281
282
    /**
283
     * Retrieve the locales manager.
284
     *
285
     * @return LocalesManager
286
     */
287
    protected function manager()
288
    {
289
        return $this->manager;
290
    }
291
292
    /**
293
     * Set the message selector.
294
     *
295
     * The {@see SymfonyTranslator} keeps the message selector private (as of 3.3.2),
296
     * thus we must explicitly require it in this class to guarantee access.
297
     *
298
     * @param  MessageSelector $selector The selector.
299
     * @return void
300
     */
301
    public function setSelector(MessageSelector $selector)
302
    {
303
        $this->selector = $selector;
304
    }
305
306
    /**
307
     * Retrieve the message selector.
308
     *
309
     * @return MessageSelector
310
     */
311
    protected function selector()
312
    {
313
        return $this->selector;
314
    }
315
316
    /**
317
     * Determine if the value is translatable.
318
     *
319
     * @param  mixed $val The value to be checked.
320
     * @return boolean
321
     */
322
    protected function isValidTranslation($val)
323
    {
324
        if (empty($val) && !is_numeric($val)) {
325
            return false;
326
        }
327
328
        if (is_string($val)) {
329
            return !empty(trim($val));
330
        }
331
332
        if ($val instanceof Translation) {
333
            return true;
334
        }
335
336
        if (is_array($val)) {
337
            return !!array_filter(
338
                $val,
339
                function ($v, $k) {
340
                    if (is_string($k) && strlen($k) > 0) {
341
                        if (is_string($v) && strlen($v) > 0) {
342
                            return true;
343
                        }
344
                    }
345
346
                    return false;
347
                },
348
                ARRAY_FILTER_USE_BOTH
349
            );
350
        }
351
        return false;
352
    }
353
}
354