I18n::_getMatchingLanguage()   C
last analyzed

Complexity

Conditions 13
Paths 18

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 13.0085

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 39
ccs 26
cts 27
cp 0.963
rs 6.6166
c 0
b 0
f 0
cc 13
nc 18
nop 2
crap 13.0085

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * PrivateBin
4
 *
5
 * a zero-knowledge paste bin
6
 *
7
 * @link      https://github.com/PrivateBin/PrivateBin
8
 * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
9
 * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
10
 * @version   1.2.1
11
 */
12
13
namespace PrivateBin;
14
15
/**
16
 * I18n
17
 *
18
 * provides internationalization tools like translation, browser language detection, etc.
19
 */
20
class I18n
21
{
22
    /**
23
     * language
24
     *
25
     * @access protected
26
     * @static
27
     * @var    string
28
     */
29
    protected static $_language = 'en';
30
31
    /**
32
     * language fallback
33
     *
34
     * @access protected
35
     * @static
36
     * @var    string
37
     */
38
    protected static $_languageFallback = 'en';
39
40
    /**
41
     * language labels
42
     *
43
     * @access protected
44
     * @static
45
     * @var    array
46
     */
47
    protected static $_languageLabels = array();
48
49
    /**
50
     * available languages
51
     *
52
     * @access protected
53
     * @static
54
     * @var    array
55
     */
56
    protected static $_availableLanguages = array();
57
58
    /**
59
     * path to language files
60
     *
61
     * @access protected
62
     * @static
63
     * @var    string
64
     */
65
    protected static $_path = '';
66
67
    /**
68
     * translation cache
69
     *
70
     * @access protected
71
     * @static
72
     * @var    array
73
     */
74
    protected static $_translations = array();
75
76
    /**
77
     * translate a string, alias for translate()
78
     *
79
     * @access public
80
     * @static
81
     * @param  string $messageId
82
     * @param  mixed $args one or multiple parameters injected into placeholders
83
     * @return string
84
     */
85 71
    public static function _($messageId)
86
    {
87 71
        return forward_static_call_array('self::translate', func_get_args());
88
    }
89
90
    /**
91
     * translate a string
92
     *
93
     * @access public
94
     * @static
95
     * @param  string $messageId
96
     * @param  mixed $args one or multiple parameters injected into placeholders
97
     * @return string
98
     */
99 71
    public static function translate($messageId)
100
    {
101 71
        if (empty($messageId)) {
102 20
            return $messageId;
103
        }
104 71
        if (count(self::$_translations) === 0) {
105 53
            self::loadTranslations();
106
        }
107 71
        $messages = $messageId;
108 71
        if (is_array($messageId)) {
109 21
            $messageId = count($messageId) > 1 ? $messageId[1] : $messageId[0];
110
        }
111 71
        if (!array_key_exists($messageId, self::$_translations)) {
112 58
            self::$_translations[$messageId] = $messages;
113
        }
114 71
        $args = func_get_args();
115 71
        if (is_array(self::$_translations[$messageId])) {
116 30
            $number = (int) $args[1];
117 30
            $key    = self::_getPluralForm($number);
118 30
            $max    = count(self::$_translations[$messageId]) - 1;
119 30
            if ($key > $max) {
120
                $key = $max;
121
            }
122
123 30
            $args[0] = self::$_translations[$messageId][$key];
124 30
            $args[1] = $number;
125
        } else {
126 70
            $args[0] = self::$_translations[$messageId];
127
        }
128 71
        return call_user_func_array('sprintf', $args);
129
    }
130
131
    /**
132
     * loads translations
133
     *
134
     * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
135
     *
136
     * @access public
137
     * @static
138
     */
139 63
    public static function loadTranslations()
140
    {
141 63
        $availableLanguages = self::getAvailableLanguages();
142
143
        // check if the lang cookie was set and that language exists
144
        if (
145 63
            array_key_exists('lang', $_COOKIE) &&
146 63
            ($key = array_search($_COOKIE['lang'], $availableLanguages)) !== false
147
        ) {
148 7
            $match = $availableLanguages[$key];
149
        }
150
        // find a translation file matching the browsers language preferences
151
        else {
152 56
            $match = self::_getMatchingLanguage(
153 56
                self::getBrowserLanguages(), $availableLanguages
154
            );
155
        }
156
157
        // load translations
158 63
        self::$_language     = $match;
159 63
        self::$_translations = ($match == 'en') ? array() : json_decode(
160 16
            file_get_contents(self::_getPath($match . '.json')),
161 63
            true
162
        );
163 63
    }
164
165
    /**
166
     * get list of available translations based on files found
167
     *
168
     * @access public
169
     * @static
170
     * @return array
171
     */
172 103
    public static function getAvailableLanguages()
173
    {
174 103
        if (count(self::$_availableLanguages) == 0) {
175 89
            $i18n = dir(self::_getPath());
176 89
            while (false !== ($file = $i18n->read())) {
177 89
                if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1) {
178 89
                    self::$_availableLanguages[] = $match[1];
179
                }
180
            }
181 89
            self::$_availableLanguages[] = 'en';
182
        }
183 103
        return self::$_availableLanguages;
184
    }
185
186
    /**
187
     * detect the clients supported languages and return them ordered by preference
188
     *
189
     * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
190
     *
191
     * @access public
192
     * @static
193
     * @return array
194
     */
195 56
    public static function getBrowserLanguages()
196
    {
197 56
        $languages = array();
198 56
        if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) {
199 11
            $languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
200 11
            foreach ($languageRanges as $languageRange) {
201 11
                if (preg_match(
202 11
                    '/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/',
203 11
                    trim($languageRange), $match
204
                )) {
205 11
                    if (!isset($match[2])) {
206 5
                        $match[2] = '1.0';
207
                    } else {
208 8
                        $match[2] = (string) floatval($match[2]);
209
                    }
210 11
                    if (!isset($languages[$match[2]])) {
211 11
                        $languages[$match[2]] = array();
212
                    }
213 11
                    $languages[$match[2]][] = strtolower($match[1]);
214
                }
215
            }
216 11
            krsort($languages);
217
        }
218 56
        return $languages;
219
    }
220
221
    /**
222
     * get currently loaded language
223
     *
224
     * @access public
225
     * @static
226
     * @return string
227
     */
228 2
    public static function getLanguage()
229
    {
230 2
        return self::$_language;
231
    }
232
233
    /**
234
     * get list of language labels
235
     *
236
     * Only for given language codes, otherwise all labels.
237
     *
238
     * @access public
239
     * @static
240
     * @param  array $languages
241
     * @return array
242
     */
243 22
    public static function getLanguageLabels($languages = array())
244
    {
245 22
        $file = self::_getPath('languages.json');
246 22
        if (count(self::$_languageLabels) == 0 && is_readable($file)) {
247 21
            self::$_languageLabels = json_decode(file_get_contents($file), true);
248
        }
249 22
        if (count($languages) == 0) {
250
            return self::$_languageLabels;
251
        }
252 22
        return array_intersect_key(self::$_languageLabels, array_flip($languages));
253
    }
254
255
    /**
256
     * set the default language
257
     *
258
     * @access public
259
     * @static
260
     * @param  string $lang
261
     */
262 88
    public static function setLanguageFallback($lang)
263
    {
264 88
        if (in_array($lang, self::getAvailableLanguages())) {
265 2
            self::$_languageFallback = $lang;
266
        }
267 88
    }
268
269
    /**
270
     * get language file path
271
     *
272
     * @access protected
273
     * @static
274
     * @param  string $file
275
     * @return string
276
     */
277 101
    protected static function _getPath($file = '')
278
    {
279 101
        if (strlen(self::$_path) == 0) {
280 89
            self::$_path = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'i18n';
281
        }
282 101
        return self::$_path . (strlen($file) ? DIRECTORY_SEPARATOR . $file : '');
283
    }
284
285
    /**
286
     * determines the plural form to use based on current language and given number
287
     *
288
     * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
289
     *
290
     * @access protected
291
     * @static
292
     * @param  int $n
293
     * @return int
294
     */
295 30
    protected static function _getPluralForm($n)
296
    {
297 30
        switch (self::$_language) {
298 30
            case 'fr':
299 27
            case 'oc':
300 26
            case 'zh':
301 5
                return $n > 1 ? 1 : 0;
302 25
            case 'pl':
303 1
                return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
304 24
            case 'ru':
305 1
                return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
306 23
            case 'sl':
307 1
                return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0));
308
            // de, en, es, hu, it, nl, no, pt
309
            default:
310 22
                return $n != 1 ? 1 : 0;
311
        }
312
    }
313
314
    /**
315
     * compares two language preference arrays and returns the preferred match
316
     *
317
     * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
318
     *
319
     * @access protected
320
     * @static
321
     * @param  array $acceptedLanguages
322
     * @param  array $availableLanguages
323
     * @return string
324
     */
325 56
    protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages)
326
    {
327 56
        $matches = array();
328 56
        $any     = false;
329 56
        foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) {
330 11
            $acceptedQuality = floatval($acceptedQuality);
331 11
            if ($acceptedQuality === 0.0) {
332
                continue;
333
            }
334 11
            foreach ($availableLanguages as $availableValue) {
335 11
                $availableQuality = 1.0;
336 11
                foreach ($acceptedValues as $acceptedValue) {
337 11
                    if ($acceptedValue === '*') {
338 1
                        $any = true;
339
                    }
340 11
                    $matchingGrade = self::_matchLanguage($acceptedValue, $availableValue);
341 11
                    if ($matchingGrade > 0) {
342 8
                        $q = (string) ($acceptedQuality * $availableQuality * $matchingGrade);
343 8
                        if (!isset($matches[$q])) {
344 8
                            $matches[$q] = array();
345
                        }
346 8
                        if (!in_array($availableValue, $matches[$q])) {
347 11
                            $matches[$q][] = $availableValue;
348
                        }
349
                    }
350
                }
351
            }
352
        }
353 56
        if (count($matches) === 0 && $any) {
354 1
            if (count($availableLanguages) > 0) {
355 1
                $matches['1.0'] = $availableLanguages;
356
            }
357
        }
358 56
        if (count($matches) === 0) {
359 47
            return self::$_languageFallback;
360
        }
361 9
        krsort($matches);
362 9
        $topmatches = current($matches);
363 9
        return current($topmatches);
364
    }
365
366
    /**
367
     * compare two language IDs and return the degree they match
368
     *
369
     * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
370
     *
371
     * @access protected
372
     * @static
373
     * @param  string $a
374
     * @param  string $b
375
     * @return float
376
     */
377 11
    protected static function _matchLanguage($a, $b)
378
    {
379 11
        $a = explode('-', $a);
380 11
        $b = explode('-', $b);
381 11
        for ($i = 0, $n = min(count($a), count($b)); $i < $n; ++$i) {
382 11
            if ($a[$i] !== $b[$i]) {
383 11
                break;
384
            }
385
        }
386 11
        return $i === 0 ? 0 : (float) $i / count($a);
387
    }
388
}
389