1export const SYSTEM_LANGUAGE = "system";
2
3// Shown in their own language so a user can find theirs from any UI language.
4const NATIVE_LANGUAGE_NAMES: Readonly<Record<string, string>> = {
5 en: "English",
6 de: "Deutsch",
7 es: "Español",
8 fr: "Français",
9 ja: "日本語",
10 ko: "한국어",
11 "pt-BR": "Português (Brasil)",
12 ru: "Русский",
13 zh: "中文(简体)",
14};
15
16export function nativeLanguageName(locale: string): string {
17 return NATIVE_LANGUAGE_NAMES[locale] ?? locale;
18}
19
20// Our `zh` is Simplified, so Traditional tags only match an exact locale.
21const TRADITIONAL_CHINESE = /^zh[-_](hant|tw|hk|mo)\b/;
22
23function matchSupportedLocale(tag: string, supported: ReadonlyArray<string>): string | undefined {
24 const normalized = tag.trim().toLowerCase();
25 const language = normalized.split(/[-_]/)[0];
26 const exact = supported.find((locale) => locale.toLowerCase() === normalized);
27 if (exact || TRADITIONAL_CHINESE.test(normalized)) return exact;
28 return supported.find((locale) => locale.toLowerCase() === language);
29}
30
31/** Saved setting to active locale; "system" and unknown values follow the host's languages. */
32export function resolveLocale(
33 language: string,
34 systemLocales: ReadonlyArray<string>,
35 supported: ReadonlyArray<string>,
36 fallback: string,
37): string {
38 if (supported.includes(language)) return language;
39 for (const tag of systemLocales) {
40 const match = matchSupportedLocale(tag, supported);
41 if (match) return match;
42 }
43 return fallback;
44}