Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
96 / 96
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
I18n
100.00% covered (success)
100.00%
96 / 96
100.00% covered (success)
100.00%
9 / 9
53
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 getLanguage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLocale
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 loadFile
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
22
 __
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 _e
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLanguages
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
8
 translate
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
10
 loadCurrentLanguage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\I18n;
15
16/**
17 * I18n and l10n class
18 *
19 * @category   Pop
20 * @package    Pop_I18n
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    3.2.0
25 */
26class I18n
27{
28
29    /**
30     * Directory with language files in it
31     * @var string
32     */
33    protected $directory = null;
34
35    /**
36     * Default system language
37     * @var string
38     */
39    protected $language = null;
40
41    /**
42     * Default system locale
43     * @var string
44     */
45    protected $locale = null;
46
47    /**
48     * Language content
49     * @var array
50     */
51    protected $content = [
52        'source' => [],
53        'output' => []
54    ];
55
56    /**
57     * Constructor
58     *
59     * Instantiate the I18n object
60     *
61     * @param  string $lang
62     * @param  string $dir
63     */
64    public function __construct($lang = null, $dir = null)
65    {
66        if (null === $lang) {
67            $lang = (defined('POP_LANG')) ? POP_LANG : 'en_US';
68        }
69
70        if (strpos($lang, '_') !== false) {
71            [$language, $locale] = explode('_', $lang);
72            $this->language = $language;
73            $this->locale   = $locale;
74        } else {
75            $this->language = $lang;
76            $this->locale   = strtoupper($lang);
77        }
78
79        $this->directory = ((null !== $dir) && file_exists($dir)) ? realpath($dir) . DIRECTORY_SEPARATOR
80            : __DIR__ . DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR;
81
82        $this->loadCurrentLanguage();
83    }
84
85    /**
86     * Get current language setting
87     *
88     * @return string
89     */
90    public function getLanguage()
91    {
92        return $this->language;
93    }
94
95    /**
96     * Get current locale setting
97     *
98     * @return string
99     */
100    public function getLocale()
101    {
102        return $this->locale;
103    }
104
105    /**
106     * Load language content from an XML file
107     *
108     * @param  string $langFile
109     * @throws Exception
110     * @return void
111     */
112    public function loadFile($langFile)
113    {
114        // If an XML file
115        if (file_exists($langFile) && (stripos($langFile, '.xml') !== false)) {
116            if (($xml =@ new \SimpleXMLElement($langFile, LIBXML_NOWARNING, true)) !== false) {
117                $key    = 0;
118                $length = count($xml->locale);
119
120                // Find the locale node key
121                for ($i = 0; $i < $length; $i++) {
122                    if ($this->locale == (string)$xml->locale[$i]->attributes()->region) {
123                        $key = $i;
124                    }
125                }
126
127                // If the locale node matches the current locale
128                if ($this->locale == (string)$xml->locale[$key]->attributes()->region) {
129                    foreach ($xml->locale[$key]->text as $text) {
130                        if (isset($text->source) && isset($text->output)) {
131                            $this->content['source'][] = (string)$text->source;
132                            if (isset($text->output->output)) {
133                                $alternates = [];
134
135                                foreach ($text->output->output as $output) {
136                                    $alt = $output->attributes()->alt;
137                                    if (null !== $alt) {
138                                        $alternates[(string)$alt] = (string)$output;
139                                    } else {
140                                        $alternates[] = (string)$output;
141                                    }
142                                }
143
144                                $this->content['output'][] = $alternates;
145                            } else {
146                                $this->content['output'][] = (string)$text->output;
147                            }
148                        }
149                    }
150                }
151            }
152        // Else if a JSON file
153        } else if (file_exists($langFile) && (stripos($langFile, '.json') !== false)) {
154            $json = json_decode(file_get_contents($langFile), true);
155
156            $key    = 0;
157            $length = count($json['language']['locale']);
158
159            // Find the locale node key
160            for ($i = 0; $i < $length; $i++) {
161                if ($this->locale == $json['language']['locale'][$i]['region']) {
162                    $key = $i;
163                }
164            }
165
166            if ($this->locale == $json['language']['locale'][$key]['region']) {
167                foreach ($json['language']['locale'][$key]['text'] as $text) {
168                    if (isset($text['source']) && isset($text['output'])) {
169                        $this->content['source'][] = (string)$text['source'];
170                        $this->content['output'][] = (is_array($text['output'])) ? $text['output'] : (string)$text['output'];
171                    }
172                }
173            }
174        } else {
175            throw new Exception('Error: The language file ' . $langFile . ' does not exist or is not valid.');
176        }
177    }
178
179    /**
180     * Return the translated string
181     *
182     * @param  string       $str
183     * @param  string|array $params
184     * @param  mixed        $variation
185     * @return string
186     */
187    public function __($str, $params = null, $variation = null)
188    {
189        return $this->translate($str, $params, $variation);
190    }
191
192    /**
193     * Echo the translated string
194     *
195     * @param  string       $str
196     * @param  string|array $params
197     * @param  mixed        $variation
198     * @return void
199     */
200    public function _e($str, $params = null, $variation = null)
201    {
202        echo $this->translate($str, $params, $variation);
203    }
204
205    /**
206     * Get languages from the XML files
207     *
208     * @param  string $dir
209     * @return array
210     */
211    public static function getLanguages($dir)
212    {
213        $langsAry      = [];
214        $langDirectory = $dir;
215
216        if (file_exists($langDirectory)) {
217            $files = scandir($langDirectory);
218            foreach ($files as $file) {
219                if (stripos($file, '.xml')) {
220                    if (($xml =@ new \SimpleXMLElement($langDirectory . DIRECTORY_SEPARATOR . $file, LIBXML_NOWARNING, true)) !== false) {
221                        $lang       = (string)$xml->attributes()->output;
222                        $langName   = (string)$xml->attributes()->name;
223                        $langNative = (string)$xml->attributes()->native;
224
225                        foreach ($xml->locale as $locale) {
226                            $region = (string)$locale->attributes()->region;
227                            $name   = (string)$locale->attributes()->name;
228                            $native = (string)$locale->attributes()->native;
229                            $native .= ' (' . $langName . ', ' . $name . ')';
230                            $langsAry[$lang . '_' . $region] = $langNative . ', ' . $native;
231                        }
232                    }
233                } else if (stripos($file, '.json')) {
234                    $json = json_decode(file_get_contents($langDirectory . DIRECTORY_SEPARATOR . $file), true);
235                    $lang       = $json['language']['output'];
236                    $langName   = $json['language']['name'];
237                    $langNative = $json['language']['native'];
238
239                    foreach ($json['language']['locale'] as $locale) {
240                        $region = $locale['region'];
241                        $name   = $locale['name'];
242                        $native = $locale['native'];
243                        $native .= ' (' . $langName . ', ' . $name . ')';
244                        $langsAry[$lang . '_' . $region] = $langNative . ', ' . $native;
245                    }
246                }
247            }
248        }
249
250        ksort($langsAry);
251        return $langsAry;
252    }
253
254    /**
255     * Translate and return the string
256     *
257     * @param  string       $str
258     * @param  string|array $params
259     * @param  mixed        $variation
260     * @return string
261     */
262    protected function translate($str, $params = null, $variation = null)
263    {
264        $key   = array_search($str, $this->content['source']);
265        $trans = null;
266
267        if (($key !== false) && isset($this->content['output'][$key])) {
268            if ((null !== $variation) && isset($this->content['output'][$key][$variation])) {
269                $trans = $this->content['output'][$key][$variation];
270            } else {
271                $trans = (is_array($this->content['output'][$key])) ?
272                    reset($this->content['output'][$key]) : $this->content['output'][$key];
273            }
274        }
275
276        if (null === $trans) {
277            $trans = $str;
278        }
279
280        if (null !== $params) {
281            if (is_array($params)) {
282                foreach ($params as $key => $value) {
283                    $trans = str_replace('%' . ($key + 1), $value, $trans);
284                }
285            } else {
286                $trans = str_replace('%1', $params, $trans);
287            }
288        }
289
290        return $trans;
291    }
292
293    /**
294     * Get language content from the XML file
295     *
296     * @return void
297     */
298    protected function loadCurrentLanguage()
299    {
300        if (file_exists($this->directory . $this->language . '.xml')) {
301            $this->loadFile($this->directory . $this->language . '.xml');
302        } else if (file_exists($this->directory . $this->language . '.json')) {
303            $this->loadFile($this->directory . $this->language . '.json');
304        }
305    }
306
307}