Passed
Push — master ( f0a9b1...5fb29c )
by MusikAnimal
04:44
created

I18nHelper   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Test Coverage

Coverage 85.86%

Importance

Changes 0
Metric Value
dl 0
loc 308
ccs 85
cts 99
cp 0.8586
rs 8.8
c 0
b 0
f 0
wmc 36

15 Methods

Rating   Name   Duplication   Size   Complexity  
A isRTL() 0 4 2
B getIntuition() 0 32 5
A __construct() 0 8 1
A getLangName() 0 5 2
A getLang() 0 3 1
A percentFormat() 0 16 3
A numberFormat() 0 10 2
A msgExists() 0 5 2
B getIntuitionLang() 0 13 5
A getRequest() 0 3 1
A getFallbacks() 0 11 1
A msg() 0 4 2
A dateFormat() 0 19 4
A msgIfExists() 0 11 3
A getAllLangs() 0 19 2
1
<?php
2
/**
3
 * This file contains only the I18nHelper.
4
 */
5
6
namespace AppBundle\Helper;
7
8
use DateTime;
9
use IntlDateFormatter;
10
use Intuition;
11
use NumberFormatter;
12
use Symfony\Component\Config\Definition\Exception\Exception;
13
use Symfony\Component\DependencyInjection\ContainerInterface;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Symfony\Component\HttpFoundation\Session\SessionInterface;
17
18
/**
19
 * The I18nHelper centralizes all methods for i18n and l10n,
20
 * and interactions with the Intution library.
21
 */
22
class I18nHelper
23
{
24
    /** @var ContainerInterface The application's container interface. */
25
    protected $container;
26
27
    /** @var RequestStack The request stack. */
28
    protected $requestStack;
29
30
    /** @var SessionInterface User's current session. */
31
    protected $session;
32
33
    /** @var Intuition|null The i18n object. */
34
    private $intuition;
35
36
    /** @var NumberFormatter Instance of NumberFormatter class, used in localizing numbers. */
37
    protected $numFormatter;
38
39
    /** @var NumberFormatter Instance of NumberFormatter class for localizing percentages. */
40
    protected $percentFormatter;
41
42
    /** @var IntlDateFormatter Instance of IntlDateFormatter class, used in localizing dates. */
43
    protected $dateFormatter;
44
45
    /**
46
     * Constructor for the I18nHelper.
47
     * @param ContainerInterface $container
48
     * @param RequestStack $requestStack
49
     * @param SessionInterface $session
50
     */
51 60
    public function __construct(
52
        ContainerInterface $container,
53
        RequestStack $requestStack,
54
        SessionInterface $session
55
    ) {
56 60
        $this->container = $container;
57 60
        $this->requestStack = $requestStack;
58 60
        $this->session = $session;
59 60
    }
60
61
    /**
62
     * Get an Intuition object, set to the current language based on the query string or session
63
     * of the current request.
64
     * @return Intuition
65
     * @throws \Exception If the 'i18n/en.json' file doesn't exist (as it's the default).
66
     */
67 25
    public function getIntuition()
68
    {
69
        // Don't recreate the object.
70 25
        if ($this->intuition instanceof Intuition) {
71 13
            return $this->intuition;
72
        }
73
74
        // Find the path, and complain if English doesn't exist.
75 25
        $path = $this->container->getParameter('kernel.root_dir') . '/../i18n';
76 25
        if (!file_exists("$path/en.json")) {
77
            throw new Exception("Language directory doesn't exist: $path");
78
        }
79
80 25
        $useLang = 'en';
81
82
        // Current request doesn't exist in unit tests, in which case we'll fall back to English.
83 25
        if ($this->getRequest() !== null) {
84 11
            $useLang = $this->getIntuitionLang();
85
86
            // Save the language to the session.
87 11
            if ($this->session->get('lang') !== $useLang) {
88 11
                $this->session->set('lang', $useLang);
89
            }
90
        }
91
92
        // Set up Intuition, using the selected language.
93 25
        $intuition = new Intuition('xtools');
94 25
        $intuition->registerDomain('xtools', $path);
0 ignored issues
show
Bug introduced by
$path of type string is incompatible with the type array expected by parameter $dir of Intuition::registerDomain(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

94
        $intuition->registerDomain('xtools', /** @scrutinizer ignore-type */ $path);
Loading history...
95 25
        $intuition->setLang(strtolower($useLang));
96
97 25
        $this->intuition = $intuition;
98 25
        return $intuition;
99
    }
100
101
    /**
102
     * Get the current language code.
103
     * @return string
104
     */
105 13
    public function getLang()
106
    {
107 13
        return $this->getIntuition()->getLang();
108
    }
109
110
    /**
111
     * Get the current language name (defaults to 'English').
112
     * @return string
113
     */
114 12
    public function getLangName()
115
    {
116 12
        return in_array(ucfirst($this->getIntuition()->getLangName()), $this->getAllLangs())
117 12
            ? $this->getIntuition()->getLangName()
118 12
            : 'English';
119
    }
120
121
    /**
122
     * Get all available languages in the i18n directory
123
     * @return array Associative array of langKey => langName
124
     */
125 12
    public function getAllLangs()
126
    {
127 12
        $messageFiles = glob($this->container->getParameter('kernel.root_dir').'/../i18n/*.json');
128
129 12
        $languages = array_values(array_unique(array_map(
130
            function ($filename) {
131 12
                return basename($filename, '.json');
132 12
            },
133 12
            $messageFiles
134
        )));
135
136 12
        $availableLanguages = [];
137
138 12
        foreach ($languages as $lang) {
139 12
            $availableLanguages[$lang] = ucfirst($this->getIntuition()->getLangName($lang));
140
        }
141 12
        asort($availableLanguages);
142
143 12
        return $availableLanguages;
144
    }
145
146
    /**
147
     * Whether the current language is right-to-left.
148
     * @param string|null $lang Optionally provide a specific lanuage code.
149
     * @return bool
150
     */
151 13
    public function isRTL($lang = null)
152
    {
153 13
        return $this->getIntuition()->isRTL(
154 13
            null === $lang ? $this->getLang() : $lang
155
        );
156
    }
157
158
    /**
159
     * Get the fallback languages for the current language, so we know what to
160
     * load with jQuery.i18n. Languages for which no file exists are not returend.
161
     * @return string[]
162
     */
163 11
    public function getFallbacks()
164
    {
165 11
        $i18nPath = $this->container->getParameter('kernel.root_dir').'/../i18n/';
166
167 11
        $fallbacks = array_merge(
168 11
            [$this->getLang()],
169 11
            $this->getIntuition()->getLangFallbacks($this->getLang())
170
        );
171
172 11
        return array_filter($fallbacks, function ($lang) use ($i18nPath) {
173 11
            return is_file($i18nPath.$lang.'.json');
174 11
        });
175
    }
176
177
    /******************** MESSAGE HELPERS ********************/
178
179
    /**
180
     * Get an i18n message.
181
     * @param string $message
182
     * @param array $vars
183
     * @return mixed|null|string
184
     */
185 11
    public function msg($message = '', $vars = [])
186
    {
187 11
        $vars = is_array($vars) ? $vars : [];
0 ignored issues
show
introduced by
The condition is_array($vars) is always true.
Loading history...
188 11
        return $this->getIntuition()->msg($message, ['domain' => 'xtools', 'variables' => $vars]);
189
    }
190
191
    /**
192
     * See if a given i18n message exists.
193
     * @param string $message The message.
194
     * @param array $vars
195
     * @return bool
196
     */
197
    public function msgExists($message = '', $vars = [])
198
    {
199
        return $this->getIntuition()->msgExists($message, array_merge(
200
            ['domain' => 'xtools'],
201
            ['variables' => is_array($vars) ? $vars : []]
0 ignored issues
show
introduced by
The condition is_array($vars) is always true.
Loading history...
202
        ));
203
    }
204
205
    /**
206
     * Get an i18n message if it exists, otherwise just get the message key.
207
     * @param string $message
208
     * @param array $vars
209
     * @return mixed|null|string
210
     */
211
    public function msgIfExists($message = '', $vars = [])
212
    {
213
        if (is_array($message)) {
0 ignored issues
show
introduced by
The condition is_array($message) is always false.
Loading history...
214
            $vars = $message;
215
            $message = $message[0];
216
            $vars = array_slice($vars, 1);
217
        }
218
        if ($this->msgExists($message, $vars)) {
219
            return $this->msg($message, $vars);
220
        } else {
221
            return $message;
222
        }
223
    }
224
225
    /************************ NUMBERS ************************/
226
227
    /**
228
     * Format a number based on language settings.
229
     * @param  int|float $number
230
     * @param  int $decimals Number of decimals to format to.
231
     * @return string
232
     */
233 14
    public function numberFormat($number, $decimals = 0)
234
    {
235 14
        if (!isset($this->numFormatter)) {
236 14
            $lang = $this->getIntuition()->getLang();
237 14
            $this->numFormatter = new NumberFormatter($lang, NumberFormatter::DECIMAL);
238
        }
239
240 14
        $this->numFormatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
241
242 14
        return $this->numFormatter->format($number);
243
    }
244
245
    /**
246
     * Format a given number or fraction as a percentage.
247
     * @param  number  $numerator   Numerator or single fraction if denominator is ommitted.
248
     * @param  number  $denominator Denominator.
249
     * @param  integer $precision   Number of decimal places to show.
250
     * @return string               Formatted percentage.
251
     */
252 1
    public function percentFormat($numerator, $denominator = null, $precision = 1)
253
    {
254 1
        if (!isset($this->percentFormatter)) {
255 1
            $lang = $this->getIntuition()->getLang();
256 1
            $this->percentFormatter = new NumberFormatter($lang, NumberFormatter::PERCENT);
257
        }
258
259 1
        $this->percentFormatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $precision);
260
261 1
        if (!$denominator) {
262 1
            $quotient = $numerator / 100;
263
        } else {
264 1
            $quotient = $numerator / $denominator;
265
        }
266
267 1
        return $this->percentFormatter->format($quotient);
268
    }
269
270
    /************************ DATES ************************/
271
272
    /**
273
     * Localize the given date based on language settings.
274
     * @param string|int|DateTime $datetime
275
     * @param string $pattern Format according to this ICU date format.
276
     * @see http://userguide.icu-project.org/formatparse/datetime
277
     * @return string
278
     */
279 10
    public function dateFormat($datetime, $pattern = 'yyyy-MM-dd HH:mm')
280
    {
281 10
        if (!isset($this->dateFormatter)) {
282 10
            $this->dateFormatter = new IntlDateFormatter(
283 10
                $this->getIntuition()->getLang(),
284 10
                IntlDateFormatter::SHORT,
285 10
                IntlDateFormatter::SHORT
286
            );
287
        }
288
289 10
        if (is_string($datetime)) {
290 1
            $datetime = new DateTime($datetime);
291 10
        } elseif (is_int($datetime)) {
292 4
            $datetime = DateTime::createFromFormat('U', $datetime);
293
        }
294
295 10
        $this->dateFormatter->setPattern($pattern);
296
297 10
        return $this->dateFormatter->format($datetime);
298
    }
299
300
    /********************* PRIVATE MEHTODS *********************/
301
302
    /**
303
     * Determine the interface language, either from the current request or session.
304
     * @return string
305
     */
306 11
    private function getIntuitionLang()
307
    {
308 11
        $queryLang = $this->getRequest()->query->get('uselang');
309 11
        $sessionLang = $this->session->get('lang');
310
311 11
        if ($queryLang !== '' && $queryLang !== null) {
312 1
            return $queryLang;
313 11
        } elseif ($sessionLang !== '' && $sessionLang !== null) {
314
            return $sessionLang;
315
        }
316
317
        // English as default.
318 11
        return 'en';
319
    }
320
321
    /**
322
     * Shorthand to get the current request from the request stack.
323
     * @return Request
324
     * There is no request stack in the tests.
325
     * @codeCoverageIgnore
326
     */
327
    private function getRequest()
328
    {
329
        return $this->container->get('request_stack')->getCurrentRequest();
330
    }
331
}
332