*/ const LEGACY_LOCALE_MAP = [ 'af' => 'af-ZA', 'ar' => 'ar-SA', 'bn' => 'bn-BD', 'bs' => 'bs-BA', 'ca' => 'ca-ES', 'cs' => 'cs-CZ', 'cy' => 'cy-GB', 'da' => 'da-DK', 'de' => 'de-DE', 'el' => 'el-GR', 'en' => 'en-US', 'eo' => 'eo-UY', 'es' => 'es-ES', 'eu' => 'eu-ES', 'fa' => 'fa-IR', 'fi' => 'fi-FI', 'fr' => 'fr-FR', 'gd' => 'gd-GB', 'gl' => 'gl-ES', 'he' => 'he-IL', 'hi' => 'hi-IN', 'hr' => 'hr-HR', 'hu' => 'hu-HU', 'id' => 'id-ID', 'it' => 'it-IT', 'ja' => 'ja-JP', 'ko' => 'ko-KR', 'me' => 'me-ME', 'mk' => 'mk-MK', 'ms' => 'ms-MY', 'nl' => 'nl-NL', 'no' => 'no-NO', 'oc' => 'oc-FR', 'pl' => 'pl-PL', 'pt' => 'pt-PT', 'ro' => 'ro-RO', 'ru' => 'ru-RU', 'sk' => 'sk-SK', 'sr' => 'sr-CS', 'sv' => 'sv-SE', 'th' => 'th-TH', 'tr' => 'tr-TR', 'uk' => 'uk-UA', 'vi' => 'vi-VN', 'zh-cn' => 'zh-CN', 'zh-tw' => 'zh-TW', ]; /** * Normalize a configured locale to a current locale-coded value. * * Empty input returns "en-US"; a known legacy short code is mapped; * anything else (already-current or custom codes) is returned unchanged so * the framework's own fallback_locale still applies. Always returns a * plain string, so it is safe to use in config values that get cached. */ public static function normalizeLocale(?string $locale): string { $locale = is_string($locale) ? trim($locale) : ''; if ($locale === '') { return 'en-US'; } return self::LEGACY_LOCALE_MAP[strtolower($locale)] ?? $locale; } /** * List of available UI language codes. * * Reads the static manifest generated by `php artisan i18n:export` * (lang/locales.json) so the list is deterministic and never served from * a runtime cache that can go stale. Falls back to scanning the lang/ * directory if the manifest is missing. * * @return array */ public static function languages() { return static::localesFromManifest() ?? static::localesFromScan(); } /** * Full locale metadata (code, name, nativeName) from the manifest, * sorted by display name. Empty array if the manifest is missing. * * @return array */ public static function locales(): array { $path = lang_path('locales.json'); if (! is_file($path)) { return []; } $data = json_decode((string) file_get_contents($path), true); return is_array($data) ? $data : []; } /** * @return array|null */ protected static function localesFromManifest(): ?array { $locales = static::locales(); if ($locales === []) { return null; } return array_values(array_filter(array_map( fn ($l) => $l['code'] ?? null, $locales ))); } /** * @return array */ protected static function localesFromScan(): array { $dir = lang_path(); return Arr::flatten(array_diff(scandir($dir), ['..', '.', 'vendor', '.DS_Store', 'locales.json'])); } }