Translator::pgettext()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Gettext;
4
5
use Gettext\Generators\PhpArray;
6
7
class Translator extends BaseTranslator implements TranslatorInterface
8
{
9
    protected $domain;
10
    protected $dictionary = [];
11
    protected $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 static
19
     */
20
    public function loadTranslations($translations)
21
    {
22
        if (is_object($translations) && $translations instanceof Translations) {
23
            $translations = PhpArray::generate($translations, ['includeHeaders' => false]);
24
        } elseif (is_string($translations) && is_file($translations)) {
25
            $translations = include $translations;
26
        } elseif (!is_array($translations)) {
27
            throw new \InvalidArgumentException(
28
                'Invalid Translator: only arrays, files or instance of Translations are allowed'
29
            );
30
        }
31
32
        $this->addTranslations($translations);
33
34
        return $this;
35
    }
36
37
    /**
38
     * Set the default domain.
39
     *
40
     * @param string $domain
41
     *
42
     * @return static
43
     */
44
    public function defaultDomain($domain)
45
    {
46
        $this->domain = $domain;
47
48
        return $this;
49
    }
50
51
    /**
52
     * @see TranslatorInterface
53
     *
54
     * {@inheritdoc}
55
     */
56
    public function gettext($original)
57
    {
58
        return $this->dpgettext($this->domain, null, $original);
59
    }
60
61
    /**
62
     * @see TranslatorInterface
63
     *
64
     * {@inheritdoc}
65
     */
66
    public function ngettext($original, $plural, $value)
67
    {
68
        return $this->dnpgettext($this->domain, null, $original, $plural, $value);
69
    }
70
71
    /**
72
     * @see TranslatorInterface
73
     *
74
     * {@inheritdoc}
75
     */
76
    public function dngettext($domain, $original, $plural, $value)
77
    {
78
        return $this->dnpgettext($domain, null, $original, $plural, $value);
79
    }
80
81
    /**
82
     * @see TranslatorInterface
83
     *
84
     * {@inheritdoc}
85
     */
86
    public function npgettext($context, $original, $plural, $value)
87
    {
88
        return $this->dnpgettext($this->domain, $context, $original, $plural, $value);
89
    }
90
91
    /**
92
     * @see TranslatorInterface
93
     *
94
     * {@inheritdoc}
95
     */
96
    public function pgettext($context, $original)
97
    {
98
        return $this->dpgettext($this->domain, $context, $original);
99
    }
100
101
    /**
102
     * @see TranslatorInterface
103
     *
104
     * {@inheritdoc}
105
     */
106
    public function dgettext($domain, $original)
107
    {
108
        return $this->dpgettext($domain, null, $original);
109
    }
110
111
    /**
112
     * @see TranslatorInterface
113
     *
114
     * {@inheritdoc}
115
     */
116
    public function dpgettext($domain, $context, $original)
117
    {
118
        $translation = $this->getTranslation($domain, $context, $original);
119
120
        if (isset($translation[0]) && $translation[0] !== '') {
121
            return $translation[0];
122
        }
123
124
        return $original;
125
    }
126
127
    /**
128
     * @see TranslatorInterface
129
     *
130
     * {@inheritdoc}
131
     */
132
    public function dnpgettext($domain, $context, $original, $plural, $value)
133
    {
134
        $translation = $this->getTranslation($domain, $context, $original);
135
        $key = $this->getPluralIndex($domain, $value, $translation === false);
136
137
        if (isset($translation[$key]) && $translation[$key] !== '') {
138
            return $translation[$key];
139
        }
140
141
        return ($key === 0) ? $original : $plural;
142
    }
143
144
    /**
145
     * Set new translations to the dictionary.
146
     *
147
     * @param array $translations
148
     */
149
    protected function addTranslations(array $translations)
150
    {
151
        $domain = isset($translations['domain']) ? $translations['domain'] : '';
152
153
        //Set the first domain loaded as default domain
154
        if ($this->domain === null) {
155
            $this->domain = $domain;
156
        }
157
158
        if (isset($this->dictionary[$domain])) {
159
            $this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations['messages']);
160
161
            return;
162
        }
163
164
        if (!empty($translations['plural-forms'])) {
165
            list($count, $code) = array_map('trim', explode(';', $translations['plural-forms'], 2));
166
167
            // extract just the expression turn 'n' into a php variable '$n'.
168
            // Slap on a return keyword and semicolon at the end.
169
            $this->plurals[$domain] = [
170
                'count' => (int) str_replace('nplurals=', '', $count),
171
                'code' => str_replace('plural=', 'return ', str_replace('n', '$n', $code)).';',
172
            ];
173
        }
174
175
        $this->dictionary[$domain] = $translations['messages'];
176
    }
177
178
    /**
179
     * Search and returns a translation.
180
     *
181
     * @param string $domain
182
     * @param string $context
183
     * @param string $original
184
     *
185
     * @return string|false
186
     */
187
    protected function getTranslation($domain, $context, $original)
188
    {
189
        return isset($this->dictionary[$domain][$context][$original])
190
             ? $this->dictionary[$domain][$context][$original]
191
             : false;
192
    }
193
194
    /**
195
     * Executes the plural decision code given the number to decide which
196
     * plural version to take.
197
     *
198
     * @param string $domain
199
     * @param string $n
200
     * @param bool   $fallback set to true to get fallback plural index
201
     *
202
     * @return int
203
     */
204
    protected function getPluralIndex($domain, $n, $fallback)
205
    {
206
        //Not loaded domain or translation, use a fallback
207
        if (!isset($this->plurals[$domain]) || $fallback === true) {
208
            return $n == 1 ? 0 : 1;
209
        }
210
211
        if (!isset($this->plurals[$domain]['function'])) {
212
            $code = static::fixTerseIfs($this->plurals[$domain]['code']);
0 ignored issues
show
Bug introduced by
Since fixTerseIfs() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of fixTerseIfs() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
213
            $this->plurals[$domain]['function'] = eval("return function (\$n) { $code };");
214
        }
215
216
        if ($this->plurals[$domain]['count'] <= 2) {
217
            return call_user_func($this->plurals[$domain]['function'], $n) ? 1 : 0;
218
        }
219
220
        return call_user_func($this->plurals[$domain]['function'], $n);
221
    }
222
223
    /**
224
     * This function will recursively wrap failure states in brackets if they contain a nested terse if.
225
     *
226
     * This because PHP can not handle nested terse if's unless they are wrapped in brackets.
227
     *
228
     * This code probably only works for the gettext plural decision codes.
229
     *
230
     * return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
231
     * becomes
232
     * return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
233
     *
234
     * @param string $code  the terse if string
235
     * @param bool   $inner If inner is true we wrap it in brackets
236
     *
237
     * @return string A formatted terse If that PHP can work with.
238
     */
239
    private static function fixTerseIfs($code, $inner = false)
240
    {
241
        /*
242
         * (?P<expression>[^?]+)   Capture everything up to ? as 'expression'
243
         * \?                      ?
244
         * (?P<success>[^:]+)      Capture everything up to : as 'success'
245
         * :                       :
246
         * (?P<failure>[^;]+)      Capture everything up to ; as 'failure'
247
         */
248
        preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/', $code, $matches);
249
250
        // If no match was found then no terse if was present
251
        if (!isset($matches[0])) {
252
            return $code;
253
        }
254
255
        $expression = $matches['expression'];
256
        $success = $matches['success'];
257
        $failure = $matches['failure'];
258
259
        // Go look for another terse if in the failure state.
260
        $failure = static::fixTerseIfs($failure, true);
0 ignored issues
show
Bug introduced by
Since fixTerseIfs() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of fixTerseIfs() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
261
        $code = $expression.' ? '.$success.' : '.$failure;
262
263
        if ($inner) {
264
            return "($code)";
265
        }
266
267
        // note the semicolon. We need that for executing the code.
268
        return "$code;";
269
    }
270
}
271