From d8bf73ac7f87a9d25edef6414f34003783426e54 Mon Sep 17 00:00:00 2001 From: liyachen Date: Mon, 13 Jul 2026 19:45:26 -0400 Subject: [PATCH] feat: i18n module with bundled en/zh locale files --- src/i18n.ts | 28 ++++++++++++++++++++++++++++ src/locales/en.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/locales/zh.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/i18n.test.ts | 37 +++++++++++++++++++++++++++++++++++++ tsconfig.json | 2 ++ 5 files changed, 157 insertions(+) create mode 100644 src/i18n.ts create mode 100644 src/locales/en.json create mode 100644 src/locales/zh.json create mode 100644 tests/i18n.test.ts diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..3ef93c3 --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,28 @@ +import en from './locales/en.json'; +import zh from './locales/zh.json'; + +export type Language = 'en' | 'zh'; +export type LocaleKey = keyof typeof en; + +// Typed as Record so a key missing from zh.json is a compile error. +const locales: Record> = { en, zh }; + +let current: Language = 'en'; + +export function setLanguage(lang: Language): void { + current = lang; +} + +export function t(key: LocaleKey, vars?: Record): string { + let s = locales[current][key]; + if (vars) { + for (const [k, v] of Object.entries(vars)) s = s.split(`{${k}}`).join(String(v)); + } + return s; +} + +// Every language's value for a key — parsing must recognize notes written +// under any language setting, not just the current one. +export function variants(key: LocaleKey): string[] { + return (Object.keys(locales) as Language[]).map((l) => locales[l][key]); +} diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 0000000..b3d2cde --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,45 @@ +{ + "settings.language": "Language", + "settings.storageHeading": "Storage locations", + "settings.videoNotesFolder.name": "Video notes folder", + "settings.videoNotesFolder.desc": "One note per video: cover, hook, and keyframe clips all merge into the same note. Default: Clips/Videos", + "settings.coverFolder.name": "Cover images folder", + "settings.coverFolder.desc": "Video cover images (