Passed
Push — master ( c88656...e5f428 )
by MusikAnimal
08:08
created

I18nHelper::msgExists()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 1
nop 2
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 6
rs 10
c 0
b 0
f 0
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 63
    public function __construct(
52
        ContainerInterface $container,
53
        RequestStack $requestStack,
54
        SessionInterface $session
55
    ) {
56 63
        $this->container = $container;
57 63
        $this->requestStack = $requestStack;
58 63
        $this->session = $session;
59 63
    }
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 27
    public function getIntuition()
68
    {
69
        // Don't recreate the object.
70 27
        if ($this->intuition instanceof Intuition) {
71 15
            return $this->intuition;
72
        }
73
74
        // Find the path, and complain if English doesn't exist.
75 27
        $path = $this->container->getParameter('kernel.root_dir') . '/../i18n';
76 27
        if (!file_exists("$path/en.json")) {
77
            throw new Exception("Language directory doesn't exist: $path");
78
        }
79
80 27
        $useLang = 'en';
81
82
        // Current request doesn't exist in unit tests, in which case we'll fall back to English.
83 27
        if ($this->getRequest() !== null) {
84 13
            $useLang = $this->getIntuitionLang();
85
86
            // Save the language to the session.
87 13
            if ($this->session->get('lang') !== $useLang) {
88 13
                $this->session->set('lang', $useLang);
89
            }
90
        }
91
92
        // Set up Intuition, using the selected language.
93 27
        $intuition = new Intuition('xtools');
94 27
        $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 Krinkle\Intuition\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 27
        $intuition->setLang(strtolower($useLang));
96
97 27
        $this->intuition = $intuition;
98 27
        return $intuition;
99
    }
100
101
    /**
102
     * Get the current language code.
103
     * @return string
104
     */
105 15
    public function getLang()
106
    {
107 15
        return $this->getIntuition()->getLang();
108
    }
109
110
    /**
111
     * Get the current language name (defaults to 'English').
112
     * @return string
113
     */
114 14
    public function getLangName()
115
    {
116 14
        return in_array(ucfirst($this->getIntuition()->getLangName()), $this->getAllLangs())
117 14
            ? $this->getIntuition()->getLangName()
118 14
            : 'English';
119
    }
120
121
    /**
122
     * Get all available languages in the i18n directory
123
     * @return array Associative array of langKey => langName
124
     */
125 14
    public function getAllLangs()
126
    {
127 14
        $messageFiles = glob($this->container->getParameter('kernel.root_dir').'/../i18n/*.json');
128
129 14
        $languages = array_values(array_unique(array_map(
130 14
            function ($filename) {
131 14
                return basename($filename, '.json');
132 14
            },
133 14
            $messageFiles
134
        )));
135
136 14
        $availableLanguages = [];
137
138 14
        foreach ($languages as $lang) {
139 14
            $availableLanguages[$lang] = ucfirst($this->getIntuition()->getLangName($lang));
140
        }
141 14
        asort($availableLanguages);
142
143 14
        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 15
    public function isRTL($lang = null)
152
    {
153 15
        return $this->getIntuition()->isRTL(
154 15
            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 13
    public function getFallbacks()
164
    {
165 13
        $i18nPath = $this->container->getParameter('kernel.root_dir').'/../i18n/';
166
167 13
        $fallbacks = array_merge(
168 13
            [$this->getLang()],
169 13
            $this->getIntuition()->getLangFallbacks($this->getLang())
170
        );
171
172 13
        return array_filter($fallbacks, function ($lang) use ($i18nPath) {
173 13
            return is_file($i18nPath.$lang.'.json');
174 13
        });
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 13
    public function msg($message = '', $vars = [])
186
    {
187 13
        $vars = is_array($vars) ? $vars : [];
0 ignored issues
show
introduced by
The condition is_array($vars) is always true.
Loading history...
188 13
        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 16
    public function numberFormat($number, $decimals = 0)
234
    {
235 16
        if (!isset($this->numFormatter)) {
236 16
            $lang = $this->getIntuition()->getLang();
237 16
            $this->numFormatter = new NumberFormatter($lang, NumberFormatter::DECIMAL);
238
        }
239
240 16
        $this->numFormatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
241
242 16
        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 13
    private function getIntuitionLang()
307
    {
308 13
        $queryLang = $this->getRequest()->query->get('uselang');
309 13
        $sessionLang = $this->session->get('lang');
310
311 13
        if ($queryLang !== '' && $queryLang !== null) {
312 1
            return $queryLang;
313 13
        } elseif ($sessionLang !== '' && $sessionLang !== null) {
314 1
            return $sessionLang;
315
        }
316
317
        // English as default.
318 13
        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