Passed
Push — master ( 6d2c79...4d8b4e )
by Roeland
23:36 queued 11:49
created

Factory::findAvailableLanguages()   C

Complexity

Conditions 14
Paths 26

Size

Total Lines 43
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 24
nc 26
nop 1
dl 0
loc 43
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright 2016 Roeland Jago Douma <[email protected]>
5
 * @copyright 2016 Lukas Reschke <[email protected]>
6
 *
7
 * @author Arthur Schiwon <[email protected]>
8
 * @author Bart Visscher <[email protected]>
9
 * @author Bjoern Schiessle <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Georg Ehrke <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author John Molakvoæ (skjnldsv) <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Robin McCorkell <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Thomas Citharel <[email protected]>
20
 *
21
 * @license AGPL-3.0
22
 *
23
 * This code is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License, version 3,
25
 * as published by the Free Software Foundation.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU Affero General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU Affero General Public License, version 3,
33
 * along with this program. If not, see <http://www.gnu.org/licenses/>
34
 *
35
 */
36
37
namespace OC\L10N;
38
39
use OCP\IConfig;
40
use OCP\IRequest;
41
use OCP\IUser;
42
use OCP\IUserSession;
43
use OCP\L10N\IFactory;
44
use OCP\L10N\ILanguageIterator;
45
46
/**
47
 * A factory that generates language instances
48
 */
49
class Factory implements IFactory {
50
51
	/** @var string */
52
	protected $requestLanguage = '';
53
54
	/**
55
	 * cached instances
56
	 * @var array Structure: Lang => App => \OCP\IL10N
57
	 */
58
	protected $instances = [];
59
60
	/**
61
	 * @var array Structure: App => string[]
62
	 */
63
	protected $availableLanguages = [];
64
65
	/**
66
	 * @var array
67
	 */
68
	protected $availableLocales = [];
69
70
	/**
71
	 * @var array Structure: string => callable
72
	 */
73
	protected $pluralFunctions = [];
74
75
	public const COMMON_LANGUAGE_CODES = [
76
		'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it',
77
		'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko'
78
	];
79
80
	/** @var IConfig */
81
	protected $config;
82
83
	/** @var IRequest */
84
	protected $request;
85
86
	/** @var IUserSession */
87
	protected $userSession;
88
89
	/** @var string */
90
	protected $serverRoot;
91
92
	/**
93
	 * @param IConfig $config
94
	 * @param IRequest $request
95
	 * @param IUserSession $userSession
96
	 * @param string $serverRoot
97
	 */
98
	public function __construct(IConfig $config,
99
								IRequest $request,
100
								IUserSession $userSession,
101
								$serverRoot) {
102
		$this->config = $config;
103
		$this->request = $request;
104
		$this->userSession = $userSession;
105
		$this->serverRoot = $serverRoot;
106
	}
107
108
	/**
109
	 * Get a language instance
110
	 *
111
	 * @param string $app
112
	 * @param string|null $lang
113
	 * @param string|null $locale
114
	 * @return \OCP\IL10N
115
	 */
116
	public function get($app, $lang = null, $locale = null) {
117
		return new LazyL10N(function () use ($app, $lang, $locale) {
118
			$app = \OC_App::cleanAppId($app);
119
			if ($lang !== null) {
120
				$lang = str_replace(['\0', '/', '\\', '..'], '', (string)$lang);
121
			}
122
123
			$forceLang = $this->config->getSystemValue('force_language', false);
124
			if (is_string($forceLang)) {
125
				$lang = $forceLang;
126
			}
127
128
			$forceLocale = $this->config->getSystemValue('force_locale', false);
129
			if (is_string($forceLocale)) {
130
				$locale = $forceLocale;
131
			}
132
133
			if ($lang === null || !$this->languageExists($app, $lang)) {
134
				$lang = $this->findLanguage($app);
135
			}
136
137
			if ($locale === null || !$this->localeExists($locale)) {
138
				$locale = $this->findLocale($lang);
139
			}
140
141
			if (!isset($this->instances[$lang][$app])) {
142
				$this->instances[$lang][$app] = new L10N(
143
					$this, $app, $lang, $locale,
144
					$this->getL10nFilesForApp($app, $lang)
145
				);
146
			}
147
148
			return $this->instances[$lang][$app];
149
		});
150
	}
151
152
	/**
153
	 * Find the best language
154
	 *
155
	 * @param string|null $app App id or null for core
156
	 * @return string language If nothing works it returns 'en'
157
	 */
158
	public function findLanguage($app = null) {
159
		$forceLang = $this->config->getSystemValue('force_language', false);
160
		if (is_string($forceLang)) {
161
			$this->requestLanguage = $forceLang;
162
		}
163
164
		if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) {
165
			return $this->requestLanguage;
166
		}
167
168
		/**
169
		 * At this point Nextcloud might not yet be installed and thus the lookup
170
		 * in the preferences table might fail. For this reason we need to check
171
		 * whether the instance has already been installed
172
		 *
173
		 * @link https://github.com/owncloud/core/issues/21955
174
		 */
175
		if ($this->config->getSystemValue('installed', false)) {
176
			$userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
177
			if (!is_null($userId)) {
178
				$userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
179
			} else {
180
				$userLang = null;
181
			}
182
		} else {
183
			$userId = null;
184
			$userLang = null;
185
		}
186
187
		if ($userLang) {
188
			$this->requestLanguage = $userLang;
189
			if ($this->languageExists($app, $userLang)) {
190
				return $userLang;
191
			}
192
		}
193
194
		try {
195
			// Try to get the language from the Request
196
			$lang = $this->getLanguageFromRequest($app);
197
			if ($userId !== null && $app === null && !$userLang) {
198
				$this->config->setUserValue($userId, 'core', 'lang', $lang);
199
			}
200
			return $lang;
201
		} catch (LanguageNotFoundException $e) {
202
			// Finding language from request failed fall back to default language
203
			$defaultLanguage = $this->config->getSystemValue('default_language', false);
204
			if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
205
				return $defaultLanguage;
206
			}
207
		}
208
209
		// We could not find any language so fall back to english
210
		return 'en';
211
	}
212
213
	/**
214
	 * find the best locale
215
	 *
216
	 * @param string $lang
217
	 * @return null|string
218
	 */
219
	public function findLocale($lang = null) {
220
		$forceLocale = $this->config->getSystemValue('force_locale', false);
221
		if (is_string($forceLocale) && $this->localeExists($forceLocale)) {
222
			return $forceLocale;
223
		}
224
225
		if ($this->config->getSystemValue('installed', false)) {
226
			$userId = null !== $this->userSession->getUser() ? $this->userSession->getUser()->getUID() :  null;
227
			$userLocale = null;
228
			if (null !== $userId) {
229
				$userLocale = $this->config->getUserValue($userId, 'core', 'locale', null);
230
			}
231
		} else {
232
			$userId = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $userId is dead and can be removed.
Loading history...
233
			$userLocale = null;
234
		}
235
236
		if ($userLocale && $this->localeExists($userLocale)) {
237
			return $userLocale;
238
		}
239
240
		// Default : use system default locale
241
		$defaultLocale = $this->config->getSystemValue('default_locale', false);
242
		if ($defaultLocale !== false && $this->localeExists($defaultLocale)) {
243
			return $defaultLocale;
244
		}
245
246
		// If no user locale set, use lang as locale
247
		if (null !== $lang && $this->localeExists($lang)) {
248
			return $lang;
249
		}
250
251
		// At last, return USA
252
		return 'en_US';
253
	}
254
255
	/**
256
	 * find the matching lang from the locale
257
	 *
258
	 * @param string $app
259
	 * @param string $locale
260
	 * @return null|string
261
	 */
262
	public function findLanguageFromLocale(string $app = 'core', string $locale = null) {
263
		if ($this->languageExists($app, $locale)) {
264
			return $locale;
265
		}
266
267
		// Try to split e.g: fr_FR => fr
268
		$locale = explode('_', $locale)[0];
269
		if ($this->languageExists($app, $locale)) {
270
			return $locale;
271
		}
272
	}
273
274
	/**
275
	 * Find all available languages for an app
276
	 *
277
	 * @param string|null $app App id or null for core
278
	 * @return array an array of available languages
279
	 */
280
	public function findAvailableLanguages($app = null) {
281
		$key = $app;
282
		if ($key === null) {
283
			$key = 'null';
284
		}
285
286
		// also works with null as key
287
		if (!empty($this->availableLanguages[$key])) {
288
			return $this->availableLanguages[$key];
289
		}
290
291
		$available = ['en']; //english is always available
292
		$dir = $this->findL10nDir($app);
293
		if (is_dir($dir)) {
294
			$files = scandir($dir);
295
			if ($files !== false) {
296
				foreach ($files as $file) {
297
					if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
298
						$available[] = substr($file, 0, -5);
299
					}
300
				}
301
			}
302
		}
303
304
		// merge with translations from theme
305
		$theme = $this->config->getSystemValue('theme');
306
		if (!empty($theme)) {
307
			$themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
308
309
			if (is_dir($themeDir)) {
310
				$files = scandir($themeDir);
311
				if ($files !== false) {
312
					foreach ($files as $file) {
313
						if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
314
							$available[] = substr($file, 0, -5);
315
						}
316
					}
317
				}
318
			}
319
		}
320
321
		$this->availableLanguages[$key] = $available;
322
		return $available;
323
	}
324
325
	/**
326
	 * @return array|mixed
327
	 */
328
	public function findAvailableLocales() {
329
		if (!empty($this->availableLocales)) {
330
			return $this->availableLocales;
331
		}
332
333
		$localeData = file_get_contents(\OC::$SERVERROOT . '/resources/locales.json');
334
		$this->availableLocales = \json_decode($localeData, true);
335
336
		return $this->availableLocales;
337
	}
338
339
	/**
340
	 * @param string|null $app App id or null for core
341
	 * @param string $lang
342
	 * @return bool
343
	 */
344
	public function languageExists($app, $lang) {
345
		if ($lang === 'en') {//english is always available
346
			return true;
347
		}
348
349
		$languages = $this->findAvailableLanguages($app);
350
		return array_search($lang, $languages) !== false;
351
	}
352
353
	public function getLanguageIterator(IUser $user = null): ILanguageIterator {
354
		$user = $user ?? $this->userSession->getUser();
355
		if ($user === null) {
356
			throw new \RuntimeException('Failed to get an IUser instance');
357
		}
358
		return new LanguageIterator($user, $this->config);
359
	}
360
361
	/**
362
	 * Return the language to use when sending something to a user
363
	 *
364
	 * @param IUser|null $user
365
	 * @return string
366
	 * @since 20.0.0
367
	 */
368
	public function getUserLanguage(IUser $user = null): string {
369
		$language = $this->config->getSystemValue('force_language', false);
370
		if ($language !== false) {
371
			return $language;
372
		}
373
374
		if ($user instanceof IUser) {
375
			$language = $this->config->getUserValue($user->getUID(), 'core', 'lang', null);
376
			if ($language !== null) {
0 ignored issues
show
introduced by
The condition $language !== null is always true.
Loading history...
377
				return $language;
378
			}
379
		}
380
381
		return $this->config->getSystemValue('default_language', 'en');
382
	}
383
384
	/**
385
	 * @param string $locale
386
	 * @return bool
387
	 */
388
	public function localeExists($locale) {
389
		if ($locale === 'en') { //english is always available
390
			return true;
391
		}
392
393
		$locales = $this->findAvailableLocales();
394
		$userLocale = array_filter($locales, function ($value) use ($locale) {
395
			return $locale === $value['code'];
396
		});
397
398
		return !empty($userLocale);
399
	}
400
401
	/**
402
	 * @param string|null $app
403
	 * @return string
404
	 * @throws LanguageNotFoundException
405
	 */
406
	private function getLanguageFromRequest($app) {
407
		$header = $this->request->getHeader('ACCEPT_LANGUAGE');
408
		if ($header !== '') {
409
			$available = $this->findAvailableLanguages($app);
410
411
			// E.g. make sure that 'de' is before 'de_DE'.
412
			sort($available);
413
414
			$preferences = preg_split('/,\s*/', strtolower($header));
415
			foreach ($preferences as $preference) {
416
				list($preferred_language) = explode(';', $preference);
417
				$preferred_language = str_replace('-', '_', $preferred_language);
418
419
				foreach ($available as $available_language) {
420
					if ($preferred_language === strtolower($available_language)) {
421
						return $this->respectDefaultLanguage($app, $available_language);
422
					}
423
				}
424
425
				// Fallback from de_De to de
426
				foreach ($available as $available_language) {
427
					if (substr($preferred_language, 0, 2) === $available_language) {
428
						return $available_language;
429
					}
430
				}
431
			}
432
		}
433
434
		throw new LanguageNotFoundException();
435
	}
436
437
	/**
438
	 * if default language is set to de_DE (formal German) this should be
439
	 * preferred to 'de' (non-formal German) if possible
440
	 *
441
	 * @param string|null $app
442
	 * @param string $lang
443
	 * @return string
444
	 */
445
	protected function respectDefaultLanguage($app, $lang) {
446
		$result = $lang;
447
		$defaultLanguage = $this->config->getSystemValue('default_language', false);
448
449
		// use formal version of german ("Sie" instead of "Du") if the default
450
		// language is set to 'de_DE' if possible
451
		if (is_string($defaultLanguage) &&
452
			strtolower($lang) === 'de' &&
453
			strtolower($defaultLanguage) === 'de_de' &&
454
			$this->languageExists($app, 'de_DE')
455
		) {
456
			$result = 'de_DE';
457
		}
458
459
		return $result;
460
	}
461
462
	/**
463
	 * Checks if $sub is a subdirectory of $parent
464
	 *
465
	 * @param string $sub
466
	 * @param string $parent
467
	 * @return bool
468
	 */
469
	private function isSubDirectory($sub, $parent) {
470
		// Check whether $sub contains no ".."
471
		if (strpos($sub, '..') !== false) {
472
			return false;
473
		}
474
475
		// Check whether $sub is a subdirectory of $parent
476
		if (strpos($sub, $parent) === 0) {
477
			return true;
478
		}
479
480
		return false;
481
	}
482
483
	/**
484
	 * Get a list of language files that should be loaded
485
	 *
486
	 * @param string $app
487
	 * @param string $lang
488
	 * @return string[]
489
	 */
490
	// FIXME This method is only public, until OC_L10N does not need it anymore,
491
	// FIXME This is also the reason, why it is not in the public interface
492
	public function getL10nFilesForApp($app, $lang) {
493
		$languageFiles = [];
494
495
		$i18nDir = $this->findL10nDir($app);
496
		$transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
497
498
		if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
499
				|| $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
500
				|| $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

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

500
				|| $this->isSubDirectory($transFile, /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($app) . '/l10n/')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
Bug introduced by
Are you sure OC_App::getAppPath($app) of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

500
				|| $this->isSubDirectory($transFile, /** @scrutinizer ignore-type */ \OC_App::getAppPath($app) . '/l10n/')
Loading history...
501
			)
502
			&& file_exists($transFile)) {
503
			// load the translations file
504
			$languageFiles[] = $transFile;
505
		}
506
507
		// merge with translations from theme
508
		$theme = $this->config->getSystemValue('theme');
509
		if (!empty($theme)) {
510
			$transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
511
			if (file_exists($transFile)) {
512
				$languageFiles[] = $transFile;
513
			}
514
		}
515
516
		return $languageFiles;
517
	}
518
519
	/**
520
	 * find the l10n directory
521
	 *
522
	 * @param string $app App id or empty string for core
523
	 * @return string directory
524
	 */
525
	protected function findL10nDir($app = null) {
526
		if (in_array($app, ['core', 'lib'])) {
527
			if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
528
				return $this->serverRoot . '/' . $app . '/l10n/';
529
			}
530
		} elseif ($app && \OC_App::getAppPath($app) !== false) {
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

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

530
		} elseif ($app && /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($app) !== false) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
531
			// Check if the app is in the app folder
532
			return \OC_App::getAppPath($app) . '/l10n/';
0 ignored issues
show
Bug introduced by
Are you sure OC_App::getAppPath($app) of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

532
			return /** @scrutinizer ignore-type */ \OC_App::getAppPath($app) . '/l10n/';
Loading history...
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

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

532
			return /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($app) . '/l10n/';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
533
		}
534
		return $this->serverRoot . '/core/l10n/';
535
	}
536
537
538
	/**
539
	 * Creates a function from the plural string
540
	 *
541
	 * Parts of the code is copied from Habari:
542
	 * https://github.com/habari/system/blob/master/classes/locale.php
543
	 * @param string $string
544
	 * @return string
545
	 */
546
	public function createPluralFunction($string) {
547
		if (isset($this->pluralFunctions[$string])) {
548
			return $this->pluralFunctions[$string];
549
		}
550
551
		if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) {
552
			// sanitize
553
			$nplurals = preg_replace('/[^0-9]/', '', $matches[1]);
554
			$plural = preg_replace('#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2]);
555
556
			$body = str_replace(
557
				[ 'plural', 'n', '$n$plurals', ],
558
				[ '$plural', '$n', '$nplurals', ],
559
				'nplurals='. $nplurals . '; plural=' . $plural
560
			);
561
562
			// add parents
563
			// important since PHP's ternary evaluates from left to right
564
			$body .= ';';
565
			$res = '';
566
			$p = 0;
567
			$length = strlen($body);
568
			for ($i = 0; $i < $length; $i++) {
569
				$ch = $body[$i];
570
				switch ($ch) {
571
					case '?':
572
						$res .= ' ? (';
573
						$p++;
574
						break;
575
					case ':':
576
						$res .= ') : (';
577
						break;
578
					case ';':
579
						$res .= str_repeat(')', $p) . ';';
580
						$p = 0;
581
						break;
582
					default:
583
						$res .= $ch;
584
				}
585
			}
586
587
			$body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
588
			$function = create_function('$n', $body);
0 ignored issues
show
Deprecated Code introduced by
The function create_function() has been deprecated: 7.2 ( Ignorable by Annotation )

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

588
			$function = /** @scrutinizer ignore-deprecated */ create_function('$n', $body);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
589
			$this->pluralFunctions[$string] = $function;
590
			return $function;
591
		} else {
592
			// default: one plural form for all cases but n==1 (english)
593
			$function = create_function(
0 ignored issues
show
Deprecated Code introduced by
The function create_function() has been deprecated: 7.2 ( Ignorable by Annotation )

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

593
			$function = /** @scrutinizer ignore-deprecated */ create_function(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
594
				'$n',
595
				'$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
596
			);
597
			$this->pluralFunctions[$string] = $function;
598
			return $function;
599
		}
600
	}
601
602
	/**
603
	 * returns the common language and other languages in an
604
	 * associative array
605
	 *
606
	 * @return array
607
	 */
608
	public function getLanguages() {
609
		$forceLanguage = $this->config->getSystemValue('force_language', false);
610
		if ($forceLanguage !== false) {
611
			$l = $this->get('lib', $forceLanguage);
612
			$potentialName = (string) $l->t('__language_name__');
613
614
			return [
615
				'commonlanguages' => [[
616
					'code' => $forceLanguage,
617
					'name' => $potentialName,
618
				]],
619
				'languages' => [],
620
			];
621
		}
622
623
		$languageCodes = $this->findAvailableLanguages();
624
625
		$commonLanguages = [];
626
		$languages = [];
627
628
		foreach ($languageCodes as $lang) {
629
			$l = $this->get('lib', $lang);
630
			// TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version
631
			$potentialName = (string) $l->t('__language_name__');
632
			if ($l->getLanguageCode() === $lang && $potentialName[0] !== '_') {//first check if the language name is in the translation file
633
				$ln = [
634
					'code' => $lang,
635
					'name' => $potentialName
636
				];
637
			} elseif ($lang === 'en') {
638
				$ln = [
639
					'code' => $lang,
640
					'name' => 'English (US)'
641
				];
642
			} else {//fallback to language code
643
				$ln = [
644
					'code' => $lang,
645
					'name' => $lang
646
				];
647
			}
648
649
			// put appropriate languages into appropriate arrays, to print them sorted
650
			// common languages -> divider -> other languages
651
			if (in_array($lang, self::COMMON_LANGUAGE_CODES)) {
652
				$commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)] = $ln;
653
			} else {
654
				$languages[] = $ln;
655
			}
656
		}
657
658
		ksort($commonLanguages);
659
660
		// sort now by displayed language not the iso-code
661
		usort($languages, function ($a, $b) {
662
			if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) {
663
				// If a doesn't have a name, but b does, list b before a
664
				return 1;
665
			}
666
			if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) {
667
				// If a does have a name, but b doesn't, list a before b
668
				return -1;
669
			}
670
			// Otherwise compare the names
671
			return strcmp($a['name'], $b['name']);
672
		});
673
674
		return [
675
			// reset indexes
676
			'commonlanguages' => array_values($commonLanguages),
677
			'languages' => $languages
678
		];
679
	}
680
}
681