Translator   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 341
rs 8.3673
c 0
b 0
f 0
wmc 45

15 Methods

Rating   Name   Duplication   Size   Complexity  
B translation() 0 20 5
A setSelector() 0 3 1
A selector() 0 3 1
A setManager() 0 3 1
B translateChoice() 0 26 6
B __construct() 0 27 3
D isValidTranslation() 0 30 10
B translate() 0 19 6
B translationChoice() 0 24 5
A addResource() 0 7 2
A locales() 0 3 1
A setLocale() 0 5 1
A availableDomains() 0 3 1
A manager() 0 3 1
A availableLocales() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Translator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Translator, and based on these observations, apply Extract Interface, too.

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