Feature chore bug 007 #21

Merged
iamdoubz merged 68 commits from feature_chore_bug_007 into main 2026-07-12 12:59:04 -05:00
Showing only changes of commit a7af623753 - Show all commits
+56
View File
@@ -0,0 +1,56 @@
// Minimal hand-rolled i18n — no dependency. A reactive `locale` (persisted to
// localStorage, UI ephemera like the layout store) plus a `t(key)` lookup over
// per-language JSON dictionaries. English is the fallback for any missing key,
// so a partial translation degrades to English rather than showing raw keys.
//
// Adding a language: create `<code>.json` next to en.json, import it, and add
// it to DICTS + LOCALES below. Translating that one file is the whole job.
import en from "./en.json";
type Dict = Record<string, string>;
// Register languages here. en is the source-of-truth key set + fallback.
const DICTS: Record<string, Dict> = { en };
export const LOCALES: { code: string; label: string }[] = [{ code: "en", label: "English" }];
const KEY = "wa-locale-v1";
function load(): string {
try {
const saved = localStorage.getItem(KEY);
if (saved && saved in DICTS) return saved;
} catch {
/* localStorage unavailable — fall through to default */
}
return "en";
}
class I18n {
locale = $state(load());
setLocale(code: string) {
if (!(code in DICTS)) return;
this.locale = code;
try {
localStorage.setItem(KEY, code);
} catch {
/* non-fatal: preference just won't persist */
}
}
/** Look up `key` in the active locale, falling back to English then the key
* itself. `vars` fills `{name}` placeholders. Reads `locale` so components
* that call `t()` in markup re-render when the language changes. */
t = (key: string, vars?: Record<string, string | number>): string => {
const dict = DICTS[this.locale] ?? en;
let s = dict[key] ?? (en as Dict)[key] ?? key;
if (vars) {
for (const [k, v] of Object.entries(vars)) s = s.replaceAll(`{${k}}`, String(v));
}
return s;
};
}
export const i18n = new I18n();
export const t = i18n.t;