Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
164 / 164 |
|
100.00% |
34 / 34 |
CRAP | |
100.00% |
1 / 1 |
Language | |
100.00% |
164 / 164 |
|
100.00% |
34 / 34 |
62 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
addFindedLocale | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addLines | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
currency | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
date | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
findFilenames | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
getCurrentLocale | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCurrentLocaleDirection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDefaultLocale | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDirectories | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFallbackLevel | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFallbackLine | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
6 | |||
getFileLines | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getLine | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
findLines | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getLines | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
resetLines | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getSupportedLocales | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isFindedLocale | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
lang | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
ordinal | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
render | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
getRenderedLine | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
hasLine | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
formatMessage | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
setCurrentLocale | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
setDefaultLocale | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
setDirectories | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
addDirectory | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
reindex | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
setFallbackLevel | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setSupportedLocales | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
setDebugCollector | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getLocaleDirection | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
2 |
1 | <?php declare(strict_types=1); |
2 | /* |
3 | * This file is part of Aplus Framework Language Library. |
4 | * |
5 | * (c) Natan Felles <natanfelles@gmail.com> |
6 | * |
7 | * For the full copyright and license information, please view the LICENSE |
8 | * file that was distributed with this source code. |
9 | */ |
10 | namespace Framework\Language; |
11 | |
12 | use Framework\Helpers\Isolation; |
13 | use Framework\Language\Debug\LanguageCollector; |
14 | use InvalidArgumentException; |
15 | use JetBrains\PhpStorm\ArrayShape; |
16 | use JetBrains\PhpStorm\Pure; |
17 | |
18 | /** |
19 | * Class Language. |
20 | * |
21 | * @see https://www.sitepoint.com/localization-demystified-understanding-php-intl/ |
22 | * @see https://unicode-org.github.io/icu-docs/#/icu4c/classMessageFormat.html |
23 | * |
24 | * @package language |
25 | */ |
26 | class Language |
27 | { |
28 | /** |
29 | * The current locale. |
30 | */ |
31 | protected string $currentLocale; |
32 | /** |
33 | * The default locale. |
34 | */ |
35 | protected string $defaultLocale; |
36 | /** |
37 | * List of directories to find for files. |
38 | * |
39 | * @var array<int,string> |
40 | */ |
41 | protected array $directories = []; |
42 | /** |
43 | * The locale fallback level. |
44 | */ |
45 | protected FallbackLevel $fallbackLevel = FallbackLevel::default; |
46 | /** |
47 | * List with locales of already scanned directories. |
48 | * |
49 | * @var array<int,string> |
50 | */ |
51 | protected array $findedLocales = []; |
52 | /** |
53 | * Language lines. |
54 | * |
55 | * List of "locale" => "file" => "line" => "text" |
56 | * |
57 | * @var array<string,array<string,array<string,string>>> |
58 | */ |
59 | protected array $languages = []; |
60 | /** |
61 | * Supported locales. Any other will be ignored. |
62 | * |
63 | * The default locale is always supported. |
64 | * |
65 | * @var array<int,string> |
66 | */ |
67 | protected array $supportedLocales = []; |
68 | protected LanguageCollector $debugCollector; |
69 | |
70 | /** |
71 | * Language constructor. |
72 | * |
73 | * @param string $locale The default (and current) locale code |
74 | * @param array<int,string> $directories List of directory paths to find for language files |
75 | */ |
76 | public function __construct(string $locale = 'en', array $directories = []) |
77 | { |
78 | $this->setDefaultLocale($locale); |
79 | $this->setCurrentLocale($locale); |
80 | if ($directories) { |
81 | $this->setDirectories($directories); |
82 | } |
83 | } |
84 | |
85 | /** |
86 | * Adds a locale to the list of already scanned directories. |
87 | * |
88 | * @param string $locale |
89 | * |
90 | * @return static |
91 | */ |
92 | protected function addFindedLocale(string $locale) : static |
93 | { |
94 | $this->findedLocales[] = $locale; |
95 | return $this; |
96 | } |
97 | |
98 | /** |
99 | * Adds custom lines for a specific locale. |
100 | * |
101 | * Useful to set lines from a database or any parsed file. |
102 | * |
103 | * NOTE: This function will always replace the old lines, as given from files. |
104 | * |
105 | * @param string $locale The locale code |
106 | * @param string $file The file name |
107 | * @param array<string> $lines An array of "line" => "text" |
108 | * |
109 | * @return static |
110 | */ |
111 | public function addLines(string $locale, string $file, array $lines) : static |
112 | { |
113 | if ( ! $this->isFindedLocale($locale)) { |
114 | // Certify that all directories are scanned first |
115 | // So, this method always have priority on replacements |
116 | $this->getLine($locale, $file, ''); |
117 | } |
118 | $this->languages[$locale][$file] = isset($this->languages[$locale][$file]) |
119 | ? \array_replace($this->languages[$locale][$file], $lines) |
120 | : $lines; |
121 | return $this; |
122 | } |
123 | |
124 | /** |
125 | * Gets a currency value formatted in a given locale. |
126 | * |
127 | * @param float $value The money value |
128 | * @param string $currency The Currency code. i.e. USD, BRL, JPY |
129 | * @param string|null $locale A custom locale or null to use the current |
130 | * |
131 | * @see https://en.wikipedia.org/wiki/ISO_4217#Active_codes |
132 | * |
133 | * @return string |
134 | */ |
135 | public function currency(float $value, string $currency, string $locale = null) : string |
136 | { |
137 | // @phpstan-ignore-next-line |
138 | return \NumberFormatter::create( |
139 | $locale ?? $this->getCurrentLocale(), |
140 | \NumberFormatter::CURRENCY |
141 | )->formatCurrency($value, $currency); |
142 | } |
143 | |
144 | /** |
145 | * Gets a formatted date in a given locale. |
146 | * |
147 | * @param int $time An Unix timestamp |
148 | * @param string|null $style One of: short, medium, long or full. Leave null to use short |
149 | * @param string|null $locale A custom locale or null to use the current |
150 | * |
151 | * @throws InvalidArgumentException for invalid style format |
152 | * |
153 | * @return string |
154 | */ |
155 | public function date(int $time, string $style = null, string $locale = null) : string |
156 | { |
157 | if ($style && ! \in_array($style, ['short', 'medium', 'long', 'full'], true)) { |
158 | throw new InvalidArgumentException('Invalid date style format: ' . $style); |
159 | } |
160 | $style = $style ?: 'short'; |
161 | // @phpstan-ignore-next-line |
162 | return \MessageFormatter::formatMessage( |
163 | $locale ?? $this->getCurrentLocale(), |
164 | "{time, date, {$style}}", |
165 | ['time' => $time] |
166 | ); |
167 | } |
168 | |
169 | /** |
170 | * Find for absolute file paths from where language lines can be loaded. |
171 | * |
172 | * @param string $locale The required locale |
173 | * @param string $file The required file |
174 | * |
175 | * @return array<int,string> a list of valid filenames |
176 | */ |
177 | #[Pure] |
178 | protected function findFilenames(string $locale, string $file) : array |
179 | { |
180 | $filenames = []; |
181 | foreach ($this->getDirectories() as $directory) { |
182 | $path = $directory . $locale . \DIRECTORY_SEPARATOR . $file . '.php'; |
183 | if (\is_file($path)) { |
184 | $filenames[] = $path; |
185 | } |
186 | } |
187 | return $filenames; |
188 | } |
189 | |
190 | /** |
191 | * Gets the current locale. |
192 | * |
193 | * @return string |
194 | */ |
195 | #[Pure] |
196 | public function getCurrentLocale() : string |
197 | { |
198 | return $this->currentLocale; |
199 | } |
200 | |
201 | /** |
202 | * Gets the current locale directionality. |
203 | * |
204 | * @return string 'ltr' for Left-To-Right ot 'rtl' for Right-To-Left |
205 | */ |
206 | #[Pure] |
207 | public function getCurrentLocaleDirection() : string |
208 | { |
209 | return static::getLocaleDirection($this->getCurrentLocale()); |
210 | } |
211 | |
212 | /** |
213 | * Gets the default locale. |
214 | * |
215 | * @return string |
216 | */ |
217 | #[Pure] |
218 | public function getDefaultLocale() : string |
219 | { |
220 | return $this->defaultLocale; |
221 | } |
222 | |
223 | /** |
224 | * Gets the list of directories where language files can be finded. |
225 | * |
226 | * @return array<int,string> |
227 | */ |
228 | #[Pure] |
229 | public function getDirectories() : array |
230 | { |
231 | return $this->directories; |
232 | } |
233 | |
234 | /** |
235 | * Gets the Fallback Level. |
236 | * |
237 | * @return FallbackLevel |
238 | */ |
239 | #[Pure] |
240 | public function getFallbackLevel() : FallbackLevel |
241 | { |
242 | return $this->fallbackLevel; |
243 | } |
244 | |
245 | /** |
246 | * Gets a text line and locale according the Fallback Level. |
247 | * |
248 | * @param string $locale The locale to get his fallback line |
249 | * @param string $file The file |
250 | * @param string $line The line |
251 | * |
252 | * @return array<int,string|null> Two numeric keys containg the used locale and text |
253 | */ |
254 | #[ArrayShape(['string', 'string|null'])] |
255 | protected function getFallbackLine(string $locale, string $file, string $line) : array |
256 | { |
257 | $text = null; |
258 | $level = $this->getFallbackLevel()->value; |
259 | // Fallback to parent |
260 | if ($level > FallbackLevel::none->value && \strpos($locale, '-') > 1) { |
261 | [$locale] = \explode('-', $locale, 2); |
262 | $text = $this->getLine($locale, $file, $line); |
263 | } |
264 | // Fallback to default |
265 | if ($text === null |
266 | && $level > FallbackLevel::parent->value |
267 | && $locale !== $this->getDefaultLocale() |
268 | ) { |
269 | $locale = $this->getDefaultLocale(); |
270 | $text = $this->getLine($locale, $file, $line); |
271 | } |
272 | return [ |
273 | $locale, |
274 | $text, |
275 | ]; |
276 | } |
277 | |
278 | /** |
279 | * @param string $filename |
280 | * |
281 | * @return array<int,string> |
282 | */ |
283 | protected function getFileLines(string $filename) : array |
284 | { |
285 | return Isolation::require($filename); |
286 | } |
287 | |
288 | /** |
289 | * Gets a language line text. |
290 | * |
291 | * @param string $locale The required locale |
292 | * @param string $file The required file |
293 | * @param string $line The required line |
294 | * |
295 | * @return string|null The text of the line or null if the line is not found |
296 | */ |
297 | protected function getLine(string $locale, string $file, string $line) : ?string |
298 | { |
299 | if (isset($this->languages[$locale][$file][$line])) { |
300 | return $this->languages[$locale][$file][$line]; |
301 | } |
302 | if ( ! \in_array($locale, $this->getSupportedLocales(), true)) { |
303 | return null; |
304 | } |
305 | $this->addFindedLocale($locale); |
306 | $this->findLines($locale, $file); |
307 | return $this->languages[$locale][$file][$line] ?? null; |
308 | } |
309 | |
310 | /** |
311 | * Find and add lines. |
312 | * |
313 | * This method can be overridden to find lines in custom storage, such as |
314 | * in a database table. |
315 | * |
316 | * @param string $locale |
317 | * @param string $file |
318 | * |
319 | * @return static |
320 | */ |
321 | protected function findLines(string $locale, string $file) : static |
322 | { |
323 | foreach ($this->findFilenames($locale, $file) as $filename) { |
324 | $this->addLines($locale, $file, $this->getFileLines($filename)); |
325 | } |
326 | return $this; |
327 | } |
328 | |
329 | /** |
330 | * Gets the list of available locales, lines and texts. |
331 | * |
332 | * @return array<string,array<string,array<string,string>>> |
333 | */ |
334 | #[Pure] |
335 | public function getLines() : array |
336 | { |
337 | return $this->languages; |
338 | } |
339 | |
340 | public function resetLines() : static |
341 | { |
342 | $this->languages = []; |
343 | return $this; |
344 | } |
345 | |
346 | /** |
347 | * Gets the list of Supported Locales. |
348 | * |
349 | * @return array<int,string> |
350 | */ |
351 | #[Pure] |
352 | public function getSupportedLocales() : array |
353 | { |
354 | return $this->supportedLocales; |
355 | } |
356 | |
357 | /** |
358 | * Tells if a locale already was found in the directories. |
359 | * |
360 | * @param string $locale The locale |
361 | * |
362 | * @see \Framework\Language\Language::getLine() |
363 | * |
364 | * @return bool |
365 | */ |
366 | #[Pure] |
367 | protected function isFindedLocale(string $locale) : bool |
368 | { |
369 | return \in_array($locale, $this->findedLocales, true); |
370 | } |
371 | |
372 | /** |
373 | * Renders a language file line with dot notation format. |
374 | * |
375 | * E.g. home.hello matches home for file and hello for line. |
376 | * |
377 | * @param string $line The dot notation file line |
378 | * @param array<mixed> $args The arguments to be used in the formatted text |
379 | * @param string|null $locale A custom locale or null to use the current |
380 | * |
381 | * @return string|null The rendered text or null if not found |
382 | */ |
383 | public function lang(string $line, array $args = [], string $locale = null) : ?string |
384 | { |
385 | [$file, $line] = \explode('.', $line, 2); |
386 | return $this->render($file, $line, $args, $locale); |
387 | } |
388 | |
389 | /** |
390 | * Gets an ordinal number in a given locale. |
391 | * |
392 | * @param int $number The number to be converted to ordinal |
393 | * @param string|null $locale A custom locale or null to use the current |
394 | * |
395 | * @return string |
396 | */ |
397 | public function ordinal(int $number, string $locale = null) : string |
398 | { |
399 | // @phpstan-ignore-next-line |
400 | return \MessageFormatter::formatMessage( |
401 | $locale ?? $this->getCurrentLocale(), |
402 | '{number, ordinal}', |
403 | ['number' => $number] |
404 | ); |
405 | } |
406 | |
407 | /** |
408 | * Renders a language file line. |
409 | * |
410 | * @param string $file The file |
411 | * @param string $line The file line |
412 | * @param array<mixed> $args The arguments to be used in the formatted text |
413 | * @param string|null $locale A custom locale or null to use the current |
414 | * |
415 | * @return string The rendered text or file.line expression |
416 | */ |
417 | public function render( |
418 | string $file, |
419 | string $line, |
420 | array $args = [], |
421 | string $locale = null |
422 | ) : string { |
423 | if (isset($this->debugCollector)) { |
424 | $start = \microtime(true); |
425 | $rendered = $this->getRenderedLine($file, $line, $args, $locale); |
426 | $end = \microtime(true); |
427 | $this->debugCollector->adddata([ |
428 | 'start' => $start, |
429 | 'end' => $end, |
430 | 'file' => $file, |
431 | 'line' => $line, |
432 | 'locale' => $rendered['locale'], |
433 | 'message' => $rendered['message'], |
434 | ]); |
435 | return $rendered['message']; |
436 | } |
437 | return $this->getRenderedLine($file, $line, $args, $locale)['message']; |
438 | } |
439 | |
440 | /** |
441 | * @param string $file |
442 | * @param string $line |
443 | * @param array<mixed> $args |
444 | * @param string|null $locale |
445 | * |
446 | * @return array<string,string> |
447 | */ |
448 | #[ArrayShape(['locale' => 'string', 'message' => 'string'])] |
449 | protected function getRenderedLine( |
450 | string $file, |
451 | string $line, |
452 | array $args = [], |
453 | string $locale = null |
454 | ) : array { |
455 | $locale ??= $this->getCurrentLocale(); |
456 | $text = $this->getLine($locale, $file, $line); |
457 | if ($text === null) { |
458 | [$locale, $text] = $this->getFallbackLine($locale, $file, $line); |
459 | } |
460 | if ($text !== null) { |
461 | $text = $this->formatMessage($text, $args, $locale); |
462 | } |
463 | return [ |
464 | 'locale' => $locale, |
465 | 'message' => $text ?? ($file . '.' . $line), |
466 | ]; |
467 | } |
468 | |
469 | /** |
470 | * Checks if Language has a line. |
471 | * |
472 | * @param string $file The file |
473 | * @param string $line The file line |
474 | * @param string|null $locale A custom locale or null to use the current |
475 | * |
476 | * @return bool True if the line is found, otherwise false |
477 | */ |
478 | public function hasLine(string $file, string $line, string $locale = null) : bool |
479 | { |
480 | $locale ??= $this->getCurrentLocale(); |
481 | $text = $this->getLine($locale, $file, $line); |
482 | if ($text === null) { |
483 | $text = $this->getFallbackLine($locale, $file, $line)[1]; |
484 | } |
485 | return $text !== null; |
486 | } |
487 | |
488 | /** |
489 | * @param string $text |
490 | * @param array<mixed> $args |
491 | * @param string|null $locale |
492 | * |
493 | * @return string |
494 | */ |
495 | public function formatMessage(string $text, array $args = [], string $locale = null) : string |
496 | { |
497 | $args = \array_map(static function ($arg) : string { |
498 | return (string) $arg; |
499 | }, $args); |
500 | $locale ??= $this->getCurrentLocale(); |
501 | return \MessageFormatter::formatMessage($locale, $text, $args) ?: $text; |
502 | } |
503 | |
504 | /** |
505 | * Sets the current locale. |
506 | * |
507 | * @param string $locale The current locale. This automatically is set as |
508 | * one of Supported Locales. |
509 | * |
510 | * @return static |
511 | */ |
512 | public function setCurrentLocale(string $locale) : static |
513 | { |
514 | $this->currentLocale = $locale; |
515 | $locales = $this->getSupportedLocales(); |
516 | $locales[] = $locale; |
517 | $this->setSupportedLocales($locales); |
518 | return $this; |
519 | } |
520 | |
521 | /** |
522 | * Sets the default locale. |
523 | * |
524 | * @param string $locale The default locale. This automatically is set as |
525 | * one of Supported Locales. |
526 | * |
527 | * @return static |
528 | */ |
529 | public function setDefaultLocale(string $locale) : static |
530 | { |
531 | $this->defaultLocale = $locale; |
532 | $locales = $this->getSupportedLocales(); |
533 | $locales[] = $locale; |
534 | $this->setSupportedLocales($locales); |
535 | return $this; |
536 | } |
537 | |
538 | /** |
539 | * Sets a list of directories where language files can be found. |
540 | * |
541 | * @param array<string> $directories a list of valid directory paths |
542 | * |
543 | * @throws InvalidArgumentException if a directory path is inaccessible |
544 | * |
545 | * @return static |
546 | */ |
547 | public function setDirectories(array $directories) : static |
548 | { |
549 | $dirs = []; |
550 | foreach ($directories as $directory) { |
551 | $path = \realpath($directory); |
552 | if ( ! $path || ! \is_dir($path)) { |
553 | throw new InvalidArgumentException('Directory path inaccessible: ' . $directory); |
554 | } |
555 | $dirs[] = $path . \DIRECTORY_SEPARATOR; |
556 | } |
557 | $this->directories = $dirs ? \array_unique($dirs) : []; |
558 | $this->reindex(); |
559 | return $this; |
560 | } |
561 | |
562 | /** |
563 | * @param string $directory |
564 | * |
565 | * @return static |
566 | */ |
567 | public function addDirectory(string $directory) : static |
568 | { |
569 | $this->setDirectories(\array_merge([ |
570 | $directory, |
571 | ], $this->getDirectories())); |
572 | return $this; |
573 | } |
574 | |
575 | protected function reindex() : void |
576 | { |
577 | $this->findedLocales = []; |
578 | foreach ($this->languages as $locale => $files) { |
579 | foreach (\array_keys($files) as $file) { |
580 | $this->findLines($locale, $file); |
581 | } |
582 | $this->addFindedLocale($locale); |
583 | } |
584 | } |
585 | |
586 | /** |
587 | * Sets the Fallback Level. |
588 | * |
589 | * @param FallbackLevel $level |
590 | * |
591 | * @return static |
592 | */ |
593 | public function setFallbackLevel(FallbackLevel $level) : static |
594 | { |
595 | $this->fallbackLevel = $level; |
596 | return $this; |
597 | } |
598 | |
599 | /** |
600 | * Sets a list of Supported Locales. |
601 | * |
602 | * NOTE: the default locale always is supported. But the current can be exclude |
603 | * if this function is called after {@see Language::setCurrentLocale()}. |
604 | * |
605 | * @param array<string> $locales the supported locales |
606 | * |
607 | * @return static |
608 | */ |
609 | public function setSupportedLocales(array $locales) : static |
610 | { |
611 | $locales[] = $this->getDefaultLocale(); |
612 | $locales = \array_unique($locales); |
613 | \sort($locales); |
614 | $this->supportedLocales = $locales; |
615 | $this->reindex(); |
616 | return $this; |
617 | } |
618 | |
619 | public function setDebugCollector(LanguageCollector $debugCollector) : static |
620 | { |
621 | $this->debugCollector = $debugCollector; |
622 | $this->debugCollector->setLanguage($this); |
623 | return $this; |
624 | } |
625 | |
626 | /** |
627 | * Gets text directionality based on locale. |
628 | * |
629 | * @param string $locale The locale code |
630 | * |
631 | * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir |
632 | * @see https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code |
633 | * |
634 | * @return string 'ltr' for Left-To-Right ot 'rtl' for Right-To-Left |
635 | */ |
636 | #[Pure] |
637 | public static function getLocaleDirection(string $locale) : string |
638 | { |
639 | $locale = \strtolower($locale); |
640 | $locale = \strtr($locale, ['_' => '-']); |
641 | if (\in_array($locale, [ |
642 | 'ar', |
643 | 'arc', |
644 | 'ckb', |
645 | 'dv', |
646 | 'fa', |
647 | 'ha', |
648 | 'he', |
649 | 'khw', |
650 | 'ks', |
651 | 'ps', |
652 | 'ur', |
653 | 'uz-af', |
654 | 'yi', |
655 | ], true)) { |
656 | return 'rtl'; |
657 | } |
658 | return 'ltr'; |
659 | } |
660 | } |