Completed
Push — master ( 90a26d...3e0ba1 )
by El
13:40 queued 03:59
created

lib/i18n.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
10
 * @version   0.22
11
 */
12
13
/**
14
 * i18n
15
 *
16
 * provides internationalization tools like translation, browser language detection, etc.
17
 */
18
class i18n
19
{
20
    /**
21
     * language
22
     *
23
     * @access protected
24
     * @static
25
     * @var    string
26
     */
27
    protected static $_language = 'en';
28
29
    /**
30
     * language fallback
31
     *
32
     * @access protected
33
     * @static
34
     * @var    string
35
     */
36
    protected static $_languageFallback = 'en';
37
38
    /**
39
     * language labels
40
     *
41
     * @access protected
42
     * @static
43
     * @var    array
44
     */
45
    protected static $_languageLabels = array();
46
47
    /**
48
     * available languages
49
     *
50
     * @access protected
51
     * @static
52
     * @var    array
53
     */
54
    protected static $_availableLanguages = array();
55
56
    /**
57
     * path to language files
58
     *
59
     * @access protected
60
     * @static
61
     * @var    string
62
     */
63
    protected static $_path = '';
64
65
    /**
66
     * translation cache
67
     *
68
     * @access protected
69
     * @static
70
     * @var    array
71
     */
72
    protected static $_translations = array();
73
74
    /**
75
     * translate a string, alias for translate()
76
     *
77
     * @access public
78
     * @static
79
     * @param  string $messageId
80
     * @param  mixed $args one or multiple parameters injected into placeholders
81
     * @return string
82
     */
83 16
    public static function _($messageId)
0 ignored issues
show
The parameter $messageId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
    {
85 16
        return call_user_func_array(array('i18n', 'translate'), func_get_args());
86
    }
87
88
    /**
89
     * translate a string
90
     *
91
     * @access public
92
     * @static
93
     * @param  string $messageId
94
     * @param  mixed $args one or multiple parameters injected into placeholders
95
     * @return string
96
     */
97 16
    public static function translate($messageId)
98
    {
99 16
        if (empty($messageId)) return $messageId;
100 16
        if (count(self::$_translations) === 0) self::loadTranslations();
101 16
        $messages = $messageId;
102 16
        if (is_array($messageId))
103 16
        {
104 3
            $messageId = count($messageId) > 1 ? $messageId[1] : $messageId[0];
105 3
        }
106 16
        if (!array_key_exists($messageId, self::$_translations))
107 16
        {
108 9
            self::$_translations[$messageId] = $messages;
109 9
        }
110 16
        $args = func_get_args();
111 16
        if (is_array(self::$_translations[$messageId]))
112 16
        {
113 7
            $number = (int) $args[1];
114 7
            $key = self::_getPluralForm($number);
115 7
            $max = count(self::$_translations[$messageId]) - 1;
116 7
            if ($key > $max) $key = $max;
117
118 7
            $args[0] = self::$_translations[$messageId][$key];
119 7
            $args[1] = $number;
120 7
        }
121
        else
122
        {
123 15
            $args[0] = self::$_translations[$messageId];
124
        }
125 16
        return call_user_func_array('sprintf', $args);
126
    }
127
128
    /**
129
     * loads translations
130
     *
131
     * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
132
     *
133
     * @access public
134
     * @static
135
     * @return void
136
     */
137 10
    public static function loadTranslations()
138
    {
139 10
        $availableLanguages = self::getAvailableLanguages();
140
141
        // check if the lang cookie was set and that language exists
142 10
        if (array_key_exists('lang', $_COOKIE) && in_array($_COOKIE['lang'], $availableLanguages))
143 10
        {
144 1
            $match = $_COOKIE['lang'];
145 1
        }
146
        // find a translation file matching the browsers language preferences
147
        else
148
        {
149 9
            $match = self::_getMatchingLanguage(
150 9
                self::getBrowserLanguages(), $availableLanguages
151 9
            );
152
        }
153
154
        // load translations
155 10
        self::$_language = $match;
156 10
        self::$_translations = ($match == 'en') ? array() : json_decode(
157 5
            file_get_contents(self::_getPath($match . '.json')),
158
            true
159 10
        );
160 10
    }
161
162
    /**
163
     * get list of available translations based on files found
164
     *
165
     * @access public
166
     * @static
167
     * @return array
168
     */
169 11
    public static function getAvailableLanguages()
170
    {
171 11
        if (count(self::$_availableLanguages) == 0)
172 11
        {
173 3
            $i18n = dir(self::_getPath());
174 3
            while (false !== ($file = $i18n->read()))
175
            {
176 3
                if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1)
177 3
                {
178 3
                    self::$_availableLanguages[] = $match[1];
179 3
                }
180 3
            }
181 3
            self::$_availableLanguages[] = 'en';
182 3
        }
183 11
        return self::$_availableLanguages;
184
    }
185
186
    /**
187
     * detect the clients supported languages and return them ordered by preference
188
     *
189
     * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
190
     *
191
     * @access public
192
     * @static
193
     * @return array
194
     */
195 9
    public static function getBrowserLanguages()
196
    {
197 9
        $languages = array();
198 9
        if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER))
199 9
        {
200 6
            $languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
201 6 View Code Duplication
            foreach ($languageRanges as $languageRange)
202
            {
203 6
                if (preg_match(
204 6
                    '/(\*|[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})))?/',
205 6
                    trim($languageRange), $match
206 6
                ))
207 6
                {
208 6
                    if (!isset($match[2]))
209 6
                    {
210 5
                        $match[2] = '1.0';
211 5
                    }
212
                    else
213
                    {
214 3
                        $match[2] = (string) floatval($match[2]);
215
                    }
216 6
                    if (!isset($languages[$match[2]]))
217 6
                    {
218 6
                        $languages[$match[2]] = array();
219 6
                    }
220 6
                    $languages[$match[2]][] = strtolower($match[1]);
221 6
                }
222 6
            }
223 6
            krsort($languages);
224 6
        }
225 9
        return $languages;
226
    }
227
228
    /**
229
     * get currently loaded language
230
     *
231
     * @access public
232
     * @static
233
     * @return string
234
     */
235
    public static function getLanguage()
236
    {
237
        return self::$_language;
238
    }
239
240
    /**
241
     * get list of language labels
242
     *
243
     * Only for given language codes, otherwise all labels.
244
     *
245
     * @access public
246
     * @static
247
     * @param  array $languages
248
     * @return array
249
     */
250 4
    public static function getLanguageLabels($languages = array())
251
    {
252 4
        $file = self::_getPath('languages.json');
253 4
        if (count(self::$_languageLabels) == 0 && is_readable($file))
254 4
        {
255 3
            self::$_languageLabels = json_decode(file_get_contents($file), true);
256 3
        }
257 4
        if (count($languages) == 0) return self::$_languageLabels;
258 4
        return array_intersect_key(self::$_languageLabels, array_flip($languages));
259
    }
260
261
    /**
262
     * set the default language
263
     *
264
     * @access public
265
     * @static
266
     * @param  string $lang
267
     * @return void
268
     */
269 2
    public static function setLanguageFallback($lang)
270
    {
271 2
        if (in_array($lang, self::getAvailableLanguages()))
272 2
            self::$_languageFallback = $lang;
273 2
    }
274
275
    /**
276
     * get language file path
277
     *
278
     * @access protected
279
     * @static
280
     * @param  string $file
281
     * @return string
282
     */
283 9
    protected static function _getPath($file = '')
284
    {
285 9
        if (strlen(self::$_path) == 0)
286 9
        {
287 3
            self::$_path = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'i18n';
288 3
        }
289 9
        return self::$_path . (strlen($file) ? DIRECTORY_SEPARATOR . $file : '');
290
    }
291
292
    /**
293
     * determines the plural form to use based on current language and given number
294
     *
295
     * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
296
     *
297
     * @access protected
298
     * @static
299
     * @param  int $n
300
     * @return int
301
     */
302 7
    protected static function _getPluralForm($n)
303
    {
304 7
        switch (self::$_language) {
305 7
            case 'fr':
306 7
            case 'zh':
307 1
                return ($n > 1 ? 1 : 0);
308 6
            case 'pl':
309 1
                return ($n == 1 ? 0 : $n%10 >= 2 && $n %10 <=4 && ($n%100 < 10 || $n%100 >= 20) ? 1 : 2);
310
            // en, de
311 5
            default:
312 5
                return ($n != 1 ? 1 : 0);
313 5
        }
314
    }
315
316
    /**
317
     * compares two language preference arrays and returns the preferred match
318
     *
319
     * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
320
     *
321
     * @access protected
322
     * @static
323
     * @param  array $acceptedLanguages
324
     * @param  array $availableLanguages
325
     * @return string
326
     */
327 9
    protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) {
328 9
        $matches = array();
329 9
        $any = false;
330 9
        foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues)
331
        {
332 6
            $acceptedQuality = floatval($acceptedQuality);
333 6
            if ($acceptedQuality === 0.0) continue;
334 6
            foreach ($availableLanguages as $availableValue)
335
            {
336 6
                $availableQuality = 1.0;
337 6
                foreach ($acceptedValues as $acceptedValue)
338
                {
339 6
                    if ($acceptedValue === '*')
340 6
                    {
341 1
                        $any = true;
342 1
                    }
343 6
                    $matchingGrade = self::_matchLanguage($acceptedValue, $availableValue);
344 6
                    if ($matchingGrade > 0)
345 6
                    {
346 3
                        $q = (string) ($acceptedQuality * $availableQuality * $matchingGrade);
347 3
                        if (!isset($matches[$q]))
348 3
                        {
349 3
                            $matches[$q] = array();
350 3
                        }
351 3
                        if (!in_array($availableValue, $matches[$q]))
352 3
                        {
353 3
                            $matches[$q][] = $availableValue;
354 3
                        }
355 3
                    }
356 6
                }
357 6
            }
358 9
        }
359 9
        if (count($matches) === 0 && $any)
360 9
        {
361 1
            if (count($availableLanguages) > 0)
362 1
            {
363 1
                $matches['1.0'] = $availableLanguages;
364 1
            }
365 1
        }
366 9
        if (count($matches) === 0)
367 9
        {
368 5
            return self::$_languageFallback;
369
        }
370 4
        krsort($matches);
371 4
        $topmatches = current($matches);
372 4
        return current($topmatches);
373
    }
374
375
    /**
376
     * compare two language IDs and return the degree they match
377
     *
378
     * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
379
     *
380
     * @access protected
381
     * @static
382
     * @param  string $a
383
     * @param  string $b
384
     * @return float
385
     */
386 6
    protected static function _matchLanguage($a, $b) {
387 6
        $a = explode('-', $a);
388 6
        $b = explode('-', $b);
389 6
        for ($i=0, $n=min(count($a), count($b)); $i<$n; $i++)
390
        {
391 6
            if ($a[$i] !== $b[$i]) break;
392 3
        }
393 6
        return $i === 0 ? 0 : (float) $i / count($a);
394
    }
395
}
396