Ariver
2026-06-30 abdb22f7391d4128a4f89d8b668781061aedf86d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import zh from "./zh.json";
import en from "./en.json";
import fr from "./fr.json";
import de from "./de.json";
import es from "./es.json";
import it from "./it.json";
import pt from "./pt.json";
import ja from "./ja.json";
import ko from "./ko.json";
import { writable } from "svelte/store";
 
export type Locale = "zh" | "en" | "fr" | "de" | "es" | "it" | "pt" | "ja" | "ko";
 
const translations: Record<Locale, Record<string, any>> = { zh, en, fr, de, es, it, pt, ja, ko };
export const localeRevision = writable(0);
 
// Detect system language, default to Chinese
function detectLocale(): Locale {
  const lang = navigator.language.toLowerCase();
  if (lang.startsWith("zh")) return "zh";
  if (lang.startsWith("fr")) return "fr";
  if (lang.startsWith("de")) return "de";
  if (lang.startsWith("es")) return "es";
  if (lang.startsWith("it")) return "it";
  if (lang.startsWith("pt")) return "pt";
  if (lang.startsWith("ja")) return "ja";
  if (lang.startsWith("ko")) return "ko";
  return "en";
}
 
let currentLocale: Locale = detectLocale();
 
function normalizeLocale(locale: string): Locale {
  const normalized = locale.toLowerCase();
  if (normalized.startsWith("zh")) return "zh";
  for (const candidate of ["fr", "de", "es", "it", "pt", "ja", "ko"] as Locale[]) {
    if (normalized.startsWith(candidate)) return candidate;
  }
  return "en";
}
 
export function setLocale(locale: Locale | string) {
  const next = normalizeLocale(locale);
  if (currentLocale !== next) {
    currentLocale = next;
    localeRevision.update((value) => value + 1);
  }
}
 
export function getLocale(): Locale {
  return currentLocale;
}
 
/**
 * Translate a key path like "status.ready" with optional interpolation.
 * Example: t("indicator.holdToSpeak", { key: "Ctrl" }) => "按住Ctrl说话"
 */
export function t(key: string, params?: Record<string, string>): string {
  const parts = key.split(".");
  let value: any = readPath(translations[currentLocale], parts);
 
  if (typeof value !== "string" && currentLocale !== "en") {
    value = readPath(translations.en, parts);
  }
 
  if (typeof value !== "string") return key;
 
  if (params) {
    for (const [k, v] of Object.entries(params)) {
      value = value.replace(`{${k}}`, v);
    }
  }
 
  return value;
}
 
function readPath(source: Record<string, any>, parts: string[]): any {
  let value: any = source;
  for (const part of parts) {
    if (value == null) return undefined;
    value = value[part];
  }
  return value;
}