Completed
Push — master ( a4c536...c246ca )
by Oscar
02:29
created

Translator::fixTerseIfs()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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