Completed
Push — v4.0-dev ( 9a8f25...7f8453 )
by Oscar
02:11
created

Translator   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 34
c 4
b 1
f 0
lcom 1
cbo 2
dl 0
loc 254
rs 9.2

14 Methods

Rating   Name   Duplication   Size   Complexity  
B loadTranslations() 0 14 6
A defaultDomain() 0 4 1
A gettext() 0 4 1
A ngettext() 0 4 1
A dngettext() 0 4 1
A npgettext() 0 4 1
A pgettext() 0 4 1
A dgettext() 0 4 1
A dpgettext() 0 10 3
A dnpgettext() 0 11 4
B addTranslations() 0 28 3
A getTranslation() 0 4 2
B isPlural() 0 17 6
B fixTerseIfs() 0 31 3
1
<?php
2
3
namespace Gettext;
4
5
use Gettext\Generators\PhpArray;
6
7
class Translator extends BaseTranslator implements TranslatorInterface
8
{
9
    private $domain;
10
    private $dictionary = [];
11
    private $plurals = [];
12
13
    /**
14
     * Loads translation from a Translations instance, a file on an array.
15
     *
16
     * @param Translations|string|array $translations
17
     *
18
     * @return self
19
     */
20
    public function loadTranslations($translations)
21
    {
22
        if (is_object($translations) && $translations instanceof Translations) {
23
            $translations = PhpArray::toArray($translations);
24
        } elseif (is_string($translations) && is_file($translations)) {
25
            $translations = include $translations;
26
        } elseif (!is_array($translations)) {
27
            throw new \InvalidArgumentException('Invalid Translator: only arrays, files or instance of Translations are allowed');
28
        }
29
30
        $this->addTranslations($translations);
31
32
        return $this;
33
    }
34
35
    /**
36
     * Set the default domain.
37
     * 
38
     * @param string $domain
39
     */
40
    public function defaultDomain($domain)
41
    {
42
        $this->domain = $domain;
43
    }
44
45
    /**
46
     * @see TranslatorInterface
47
     *
48
     * {@inheritdoc}
49
     */
50
    public function gettext($original)
51
    {
52
        return $this->dpgettext($this->domain, null, $original);
53
    }
54
55
    /**
56
     * @see TranslatorInterface
57
     *
58
     * {@inheritdoc}
59
     */
60
    public function ngettext($original, $plural, $value)
61
    {
62
        return $this->dnpgettext($this->domain, null, $original, $plural, $value);
63
    }
64
65
    /**
66
     * @see TranslatorInterface
67
     *
68
     * {@inheritdoc}
69
     */
70
    public function dngettext($domain, $original, $plural, $value)
71
    {
72
        return $this->dnpgettext($domain, null, $original, $plural, $value);
73
    }
74
75
    /**
76
     * @see TranslatorInterface
77
     *
78
     * {@inheritdoc}
79
     */
80
    public function npgettext($context, $original, $plural, $value)
81
    {
82
        return $this->dnpgettext($this->domain, $context, $original, $plural, $value);
83
    }
84
85
    /**
86
     * @see TranslatorInterface
87
     *
88
     * {@inheritdoc}
89
     */
90
    public function pgettext($context, $original)
91
    {
92
        return $this->dpgettext($this->domain, $context, $original);
93
    }
94
95
    /**
96
     * @see TranslatorInterface
97
     *
98
     * {@inheritdoc}
99
     */
100
    public function dgettext($domain, $original)
101
    {
102
        return $this->dpgettext($domain, null, $original);
103
    }
104
105
    /**
106
     * @see TranslatorInterface
107
     *
108
     * {@inheritdoc}
109
     */
110
    public function dpgettext($domain, $context, $original)
111
    {
112
        $translation = $this->getTranslation($domain, $context, $original);
113
114
        if (isset($translation[0]) && $translation[0] !== '') {
115
            return $translation[0];
116
        }
117
118
        return $original;
119
    }
120
121
    /**
122
     * @see TranslatorInterface
123
     *
124
     * {@inheritdoc}
125
     */
126
    public function dnpgettext($domain, $context, $original, $plural, $value)
127
    {
128
        $key = $this->isPlural($domain, $value);
129
        $translation = $this->getTranslation($domain, $context, $original);
130
131
        if (isset($translation[$key]) && $translation[$key] !== '') {
132
            return $translation[$key];
133
        }
134
135
        return ($key === 0) ? $original : $plural;
136
    }
137
138
    /**
139
     * Set new translations to the dictionary.
140
     *
141
     * @param array $translations
142
     */
143
    protected function addTranslations(array $translations)
144
    {
145
        $domain = $translations['domain'];
146
147
        //Set the first domain loaded as default domain
148
        if (!$this->domain) {
149
            $this->domain = $domain;
150
        }
151
152
        if (isset($this->dictionary[$domain])) {
153
            $this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations['messages']);
154
155
            return;
156
        }
157
158
        $pluralForms = $translations['plural-forms'];
159
160
        list($count, $code) = explode(';', $pluralForms, 2);
161
162
        // extract just the expression turn 'n' into a php variable '$n'.
163
        // Slap on a return keyword and semicolon at the end.
164
        $this->plurals[$domain] = [
165
            'count' => (int) str_replace('nplurals=', '', $count),
166
            'code' => str_replace('plural=', 'return ', str_replace('n', '$n', $code)).';',
167
        ];
168
169
        $this->dictionary[$domain] = $translations['messages'];
170
    }
171
172
    /**
173
     * Search and returns a translation.
174
     *
175
     * @param string $domain
176
     * @param string $context
177
     * @param string $original
178
     *
179
     * @return string|false
180
     */
181
    protected function getTranslation($domain, $context, $original)
182
    {
183
        return isset($this->dictionary[$domain][$context][$original]) ? $this->dictionary[$domain][$context][$original] : false;
184
    }
185
186
    /**
187
     * Executes the plural decision code given the number to decide which
188
     * plural version to take.
189
     *
190
     * @param string $domain
191
     * @param string $n
192
     *
193
     * @return int
194
     */
195
    protected function isPlural($domain, $n)
196
    {
197
        //Not loaded domain, use a fallback
198
        if (!isset($this->plurals[$domain])) {
199
            return $n == 1 ? 0 : 1;
200
        }
201
202
        if (!isset($this->plurals[$domain]['function'])) {
203
            $this->plurals[$domain]['function'] = create_function('$n', self::fixTerseIfs($this->plurals[$domain]['code']));
204
        }
205
206
        if ($this->plurals[$domain]['count'] <= 2) {
207
            return call_user_func($this->plurals[$domain]['function'], $n) ? 1 : 0;
208
        }
209
210
        return call_user_func($this->plurals[$domain]['function'], $n);
211
    }
212
213
    /**
214
     * This function will recursively wrap failure states in brackets if they contain a nested terse if.
215
     *
216
     * This because PHP can not handle nested terse if's unless they are wrapped in brackets.
217
     *
218
     * This code probably only works for the gettext plural decision codes.
219
     *
220
     * return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
221
     * becomes
222
     * return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
223
     *
224
     * @param string $code  the terse if string
225
     * @param bool   $inner If inner is true we wrap it in brackets
226
     *
227
     * @return string A formatted terse If that PHP can work with.
228
     */
229
    private static function fixTerseIfs($code, $inner = false)
230
    {
231
        /*
232
         * (?P<expression>[^?]+)   Capture everything up to ? as 'expression'
233
         * \?                      ?
234
         * (?P<success>[^:]+)      Capture everything up to : as 'success'
235
         * :                       :
236
         * (?P<failure>[^;]+)      Capture everything up to ; as 'failure'
237
         */
238
        preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/', $code, $matches);
239
240
        // If no match was found then no terse if was present
241
        if (!isset($matches[0])) {
242
            return $code;
243
        }
244
245
        $expression = $matches['expression'];
246
        $success = $matches['success'];
247
        $failure = $matches['failure'];
248
249
        // Go look for another terse if in the failure state.
250
        $failure = self::fixTerseIfs($failure, true);
251
        $code = $expression.' ? '.$success.' : '.$failure;
252
253
        if ($inner) {
254
            return "($code)";
255
        }
256
257
        // note the semicolon. We need that for executing the code.
258
        return "$code;";
259
    }
260
}
261