Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.93% covered (warning)
84.93%
62 / 73
63.64% covered (warning)
63.64%
7 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
L11nManager
84.93% covered (warning)
84.93%
62 / 73
63.64% covered (warning)
63.64%
7 / 11
35.50
0.00% covered (danger)
0.00%
0 / 1
 isLanguageLoaded
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 loadLanguage
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 loadLanguageFile
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 loadLanguageFromFile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getModuleLanguage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getText
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
4.84
 getHtml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNumeric
58.33% covered (warning)
58.33%
7 / 12
0.00% covered (danger)
0.00%
0 / 1
2.29
 getPercentage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getCurrency
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
9.05
 getDateTime
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Localization
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Localization;
16
17use phpOMS\Autoloader;
18use phpOMS\Log\FileLogger;
19use phpOMS\Module\ModuleAbstract;
20use phpOMS\Stdlib\Base\FloatInt;
21
22/**
23 * Localization class.
24 *
25 * @package phpOMS\Localization
26 * @license OMS License 2.0
27 * @link    https://jingga.app
28 * @since   1.0.0
29 */
30final class L11nManager
31{
32    /**
33     * Language.
34     *
35     * @var array<string, array<int|string, array<string, string>>>
36     * @since 1.0.0
37     */
38    private array $language = [];
39
40    /**
41     * Verify if language is loaded.
42     *
43     * @param string $language Language iso code
44     *
45     * @return bool
46     *
47     * @since 1.0.0
48     */
49    public function isLanguageLoaded(string $language) : bool
50    {
51        return isset($this->language[$language]);
52    }
53
54    /**
55     * Load language.
56     *
57     * One module can only be loaded once. Once the module got loaded it's not
58     * possible to load more language files later on.
59     *
60     * @param string                               $language    Language iso code
61     * @param string                               $from        Module name
62     * @param array<string, array<string, string>> $translation Language files content
63     *
64     * @return void
65     *
66     * @since 1.0.0
67     */
68    public function loadLanguage(string $language, string $from, array $translation) : void
69    {
70        if (!isset($translation[$from])) {
71            return;
72        }
73
74        $this->language[$language][$from] = isset($this->language[$language][$from])
75            ? $translation[$from] + $this->language[$language][$from]
76            : $translation[$from];
77    }
78
79    /**
80     * Load language file which contains multiple languages.
81     *
82     * @param string $from Module name
83     * @param string $file File to import language from
84     *
85     * @return void
86     *
87     * @since 1.0.0
88     */
89    public function loadLanguageFile(string $from, string $file) : void
90    {
91        if (!\is_file($file)) {
92            return;
93        }
94
95        /** @noinspection PhpIncludeInspection */
96        $lang = include $file;
97
98        foreach ($lang as $code => $translation) {
99            $this->loadLanguage($code, $from, $translation);
100        }
101    }
102
103    /**
104     * Load language from file.
105     *
106     * One module can only be loaded once. Once the module got loaded it's not
107     * possible to load more language files later on.
108     *
109     * @param string $language Language iso code
110     * @param string $from     Module name
111     * @param string $file     File to import language from
112     *
113     * @return void
114     *
115     * @since 1.0.0
116     */
117    public function loadLanguageFromFile(string $language, string $from, string $file) : void
118    {
119        if (!\is_file($file)) {
120            return;
121        }
122
123        /** @noinspection PhpIncludeInspection */
124        $lang = include $file;
125        $this->loadLanguage($language, $from, $lang);
126    }
127
128    /**
129     * Get application language.
130     *
131     * @param string $language Language iso code
132     * @param string $module   Module name
133     *
134     * @return array<int|string, array<string, string>>|array<string, string>
135     *
136     * @since 1.0.0
137     */
138    public function getModuleLanguage(string $language, string $module = null) : array
139    {
140        if ($module === null && isset($this->language[$language])) {
141            return $this->language[$language];
142        } elseif (isset($this->language[$language], $this->language[$language][$module])) {
143            return $this->language[$language][$module];
144        }
145
146        return [];
147    }
148
149    /**
150     * Get translation.
151     *
152     * @param string $code        Language code
153     * @param string $module      Module name
154     * @param string $theme       Theme
155     * @param string $translation Text
156     *
157     * @return string In case the language element couldn't be found 'ERROR' will be returned
158     *
159     * @since 1.0.0
160     */
161    public function getText(string $code, string $module, string $theme, string $translation) : string
162    {
163        if (isset($this->language[$code][$module][$translation])) {
164            return $this->language[$code][$module][$translation];
165        }
166
167        try {
168            /** @var ModuleAbstract $class */
169            $class = '\Modules\\' . $module . '\\Controller\\Controller';
170
171            /** @var string $class */
172            if (!Autoloader::exists($class)) {
173                return 'ERROR-' . $translation;
174            }
175
176            $this->loadLanguage($code, $module, $class::getLocalization($code, $theme));
177        } catch (\Throwable $_) {
178            // @codeCoverageIgnoreStart
179            FileLogger::getInstance()->warning(FileLogger::MSG_FULL, [
180                'message' => 'Undefined translation for \'' . $code . '/' . $module . '/' . $translation . '\'.',
181            ]);
182            // @codeCoverageIgnoreEnd
183        }
184
185        return $this->language[$code][$module][$translation] ?? 'ERROR-' . $translation;
186    }
187
188    /**
189     * Get translation html escaped.
190     *
191     * @param string $code        Language code
192     * @param string $module      Module name
193     * @param string $theme       Theme
194     * @param string $translation Text
195     *
196     * @return string In case the language element couldn't be found 'ERROR' will be returned
197     *
198     * @since 1.0.0
199     */
200    public function getHtml(string $code, string $module, string $theme, string $translation) : string
201    {
202        return \htmlspecialchars($this->getText($code, $module, $theme, $translation));
203    }
204
205    /**
206     * Print a numeric value
207     *
208     * @param Localization       $l11n    Localization
209     * @param int|float|FloatInt $numeric Numeric value to print
210     * @param null|string        $format  Format type to use
211     *
212     * @return string
213     *
214     * @since 1.0.0
215     */
216    public function getNumeric(Localization $l11n, int | float | FloatInt $numeric, string $format = null) : string
217    {
218        if (!($numeric instanceof FloatInt)) {
219            return \number_format(
220                $numeric,
221                $l11n->getPrecision()[$format ?? 'medium'],
222                $l11n->getDecimal(),
223                $l11n->getThousands()
224            );
225        }
226
227        $numeric->setLocalization(
228            $l11n->getThousands(),
229            $l11n->getDecimal()
230        );
231
232        return $numeric->getAmount($l11n->getPrecision()[$format ?? 'medium']);
233    }
234
235    /**
236     * Print a percentage value
237     *
238     * @param Localization $l11n       Localization
239     * @param float        $percentage Percentage value to print
240     * @param null|string  $format     Format type to use
241     *
242     * @return string
243     *
244     * @since 1.0.0
245     */
246    public function getPercentage(Localization $l11n, float $percentage, string $format = null) : string
247    {
248        return \number_format(
249            $percentage, $l11n->getPrecision()[$format ?? 'medium'],
250            $l11n->getDecimal(),
251            $l11n->getThousands()
252        ) . '%';
253    }
254
255    /**
256     * Print a currency
257     *
258     * @param Localization             $l11n     Localization
259     * @param int|float|FloatInt|Money $currency Currency value to print
260     * @param null|string              $symbol   Currency name/symbol
261     * @param null|string              $format   Format type to use
262     * @param int                      $divide   Divide currency by divisor
263     *
264     * @return string
265     *
266     * @since 1.0.0
267     */
268    public function getCurrency(
269        Localization $l11n,
270        int | float | Money | FloatInt $currency,
271        string $symbol = null,
272        string $format = null,
273        int $divide = 1
274    ) : string
275    {
276        $language = $l11n->language;
277        $symbol ??= $l11n->getCurrency();
278
279        if (\is_float($currency)) {
280            $currency = (int) ($currency * \pow(10, Money::MAX_DECIMALS));
281        }
282
283        if ($divide > 1 && !empty($symbol)) {
284            if ($divide === 1000) {
285                $symbol = $this->getHtml($language, '0', '0', 'CurrencyK') . $symbol;
286            } elseif ($divide === 1000000) {
287                $symbol = $this->getHtml($language, '0', '0', 'CurrencyM') . $symbol;
288            } elseif ($divide === 1000000000) {
289                $symbol = $this->getHtml($language, '0', '0', 'CurrencyB') . $symbol;
290            }
291        }
292
293        $money = null;
294        if ($currency instanceof Money) {
295            $money = $currency;
296        } elseif ($currency instanceof FloatInt) {
297            $money = new Money((int) ($currency->value / $divide));
298        } else {
299            $money = new Money((int) ($currency / $divide));
300        }
301
302        $money->setLocalization(
303            $l11n->getThousands(),
304            $l11n->getDecimal(),
305            $symbol,
306            (int) $l11n->getCurrencyFormat()
307        );
308
309        return $money->getCurrency($l11n->getPrecision()[$format ?? 'medium']);
310    }
311
312    /**
313     * Print a datetime
314     *
315     * @param Localization            $l11n     Localization
316     * @param null|\DateTimeInterface $datetime DateTime to print
317     * @param string                  $format   Format type to use
318     *
319     * @return string
320     *
321     * @since 1.0.0
322     */
323    public function getDateTime(Localization $l11n, \DateTimeInterface $datetime = null, string $format = null) : string
324    {
325        return $datetime === null
326            ? ''
327            : $datetime->format($l11n->getDateTime()[$format ?? 'medium']);
328    }
329}