From 359c9a1e0dca0658cc64c844311d501670d1d3a7 Mon Sep 17 00:00:00 2001 From: Gabriele Pandocchi Date: Wed, 4 Jun 2025 18:00:35 +0200 Subject: [PATCH] initial localization support --- src/lib/locales/i18n.ts | 25 +++++++++++ src/lib/locales/index.ts | 1 + src/lib/locales/translations.ts | 11 +++++ src/lib/locales/translations/en.ts | 8 ++++ src/lib/locales/translations/it.ts | 7 +++ src/lib/sections/settings/Language.svelte | 53 +++++++++++++++++++++++ src/lib/sections/settings/index.svelte.ts | 1 + src/lib/store/index.svelte.ts | 4 +- src/lib/store/language.ts | 11 +++++ src/lib/types/index.ts | 3 +- src/lib/types/locales.ts | 2 + src/routes/+layout.svelte | 4 ++ src/routes/settings/+page.svelte | 1 + 13 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 src/lib/locales/i18n.ts create mode 100644 src/lib/locales/index.ts create mode 100644 src/lib/locales/translations.ts create mode 100644 src/lib/locales/translations/en.ts create mode 100644 src/lib/locales/translations/it.ts create mode 100644 src/lib/sections/settings/Language.svelte create mode 100644 src/lib/store/language.ts create mode 100644 src/lib/types/locales.ts diff --git a/src/lib/locales/i18n.ts b/src/lib/locales/i18n.ts new file mode 100644 index 0000000..0abbc4f --- /dev/null +++ b/src/lib/locales/i18n.ts @@ -0,0 +1,25 @@ +import { language } from "$lib/store/language"; +import { derived } from "svelte/store"; +import translations from "./translations"; +import type { Dictionary, Locales } from "$lib/types/locales"; + +export const locales = Object.keys(translations); + +function translate(locale: Locales, key: Dictionary, vars: any) { + if (!key) throw new Error("no key provided to $t()"); + if (!locale) throw new Error(`no translation for key "${key}"`); + + let text = translations[locale][key]; + + if (!text) throw new Error(`no translation found for ${locale}.${key}`); + + Object.keys(vars).map((k) => { + text = text.replaceAll(`{{${k}}}`, vars[k]); + }); + + return text; +} + +export const t = derived(language, ($locale) => (key: Dictionary, vars = {}) => + translate($locale, key, vars) +); diff --git a/src/lib/locales/index.ts b/src/lib/locales/index.ts new file mode 100644 index 0000000..ac55c11 --- /dev/null +++ b/src/lib/locales/index.ts @@ -0,0 +1 @@ +export * from "./i18n"; \ No newline at end of file diff --git a/src/lib/locales/translations.ts b/src/lib/locales/translations.ts new file mode 100644 index 0000000..7138691 --- /dev/null +++ b/src/lib/locales/translations.ts @@ -0,0 +1,11 @@ +import type { Dictionary, Locales } from "$lib/types/locales"; +import en from "./translations/en"; +import it from "./translations/it"; + + +const translation: Record> = { + en: en, + it: it +} + +export default translation; \ No newline at end of file diff --git a/src/lib/locales/translations/en.ts b/src/lib/locales/translations/en.ts new file mode 100644 index 0000000..bb13702 --- /dev/null +++ b/src/lib/locales/translations/en.ts @@ -0,0 +1,8 @@ +import type { Dictionary } from "$lib/types/locales"; + +const en: Record = { + 'settings.languageTitle': 'Language', + 'settings.chooseLanguage': 'Choose Language' +} + +export default en; \ No newline at end of file diff --git a/src/lib/locales/translations/it.ts b/src/lib/locales/translations/it.ts new file mode 100644 index 0000000..51d8bd7 --- /dev/null +++ b/src/lib/locales/translations/it.ts @@ -0,0 +1,7 @@ +import type { Dictionary } from "$lib/types/locales"; + +const it: Record = { + 'settings.languageTitle': 'Lingua', + 'settings.chooseLanguage': 'Scegli Lingua' +} +export default it; diff --git a/src/lib/sections/settings/Language.svelte b/src/lib/sections/settings/Language.svelte new file mode 100644 index 0000000..52cbfe5 --- /dev/null +++ b/src/lib/sections/settings/Language.svelte @@ -0,0 +1,53 @@ + + + +
+

+ + {$t("settings.languageTitle")} +

+
+
+
+

+ {$t("settings.chooseLanguage")} +

+

+
+
+ handleSelect(option)} + selected={(()=>supportedLanguages[$language])()} + /> +
+
+
+
+
diff --git a/src/lib/sections/settings/index.svelte.ts b/src/lib/sections/settings/index.svelte.ts index a53b844..e224d05 100644 --- a/src/lib/sections/settings/index.svelte.ts +++ b/src/lib/sections/settings/index.svelte.ts @@ -5,6 +5,7 @@ export { default as Appearance } from "./Appearance.svelte"; export { default as Conversion } from "./Conversion.svelte"; export { default as Vertd } from "./Vertd.svelte"; export { default as Privacy } from "./Privacy.svelte"; +export { default as Language } from "./Language.svelte"; export interface ISettings { filenameFormat: string; diff --git a/src/lib/store/index.svelte.ts b/src/lib/store/index.svelte.ts index bf99690..4456313 100644 --- a/src/lib/store/index.svelte.ts +++ b/src/lib/store/index.svelte.ts @@ -3,9 +3,9 @@ import { converters } from "$lib/converters"; import { error, log } from "$lib/logger"; import { VertFile } from "$lib/types"; import { parseBlob, selectCover } from "music-metadata"; +import PQueue from "p-queue"; import { writable } from "svelte/store"; import { addDialog } from "./DialogProvider"; -import PQueue from "p-queue"; class Files { public files = $state([]); @@ -18,7 +18,7 @@ class Files { this.files.length === 0 ? false : this.requiredConverters.every((f) => f?.ready) && - this.files.every((f) => !f.processing), + this.files.every((f) => !f.processing), ); public results = $derived( this.files.length === 0 ? false : this.files.every((f) => f.result), diff --git a/src/lib/store/language.ts b/src/lib/store/language.ts new file mode 100644 index 0000000..54acf33 --- /dev/null +++ b/src/lib/store/language.ts @@ -0,0 +1,11 @@ +import { log } from "$lib/logger"; +import type { Locales } from "$lib/types/locales"; +import { writable } from "svelte/store"; + +export const language = writable("en"); +export function setLanguage(locale: Locales) { + localStorage.setItem("language", locale); + log(["language"], `set to ${locale}`); + document.documentElement.lang = locale; + language.set(locale); +} diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index dc857ca..bc3ea9f 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -1,3 +1,4 @@ export * from "./file.svelte"; export * from "./util"; -export * from "./conversion-worker"; \ No newline at end of file +export * from "./conversion-worker"; +export * from "./locales"; \ No newline at end of file diff --git a/src/lib/types/locales.ts b/src/lib/types/locales.ts new file mode 100644 index 0000000..490ce13 --- /dev/null +++ b/src/lib/types/locales.ts @@ -0,0 +1,2 @@ +export type Locales = 'en' | 'it'; +export type Dictionary = 'settings.languageTitle' | 'settings.chooseLanguage'; \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 4d37589..98764e3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -16,9 +16,11 @@ dropping, vertdLoaded, } from "$lib/store/index.svelte"; + import { language } from "$lib/store/language"; import "$lib/css/app.scss"; import { browser } from "$app/environment"; import { page } from "$app/state"; + import type { Locales } from "$lib/types/locales.js"; let { children, data } = $props(); let enablePlausible = $state(false); @@ -66,6 +68,8 @@ (localStorage.getItem("theme") as "light" | "dark") || "light", ); + language.set((localStorage.getItem("language") as Locales) || "en"); + Settings.instance.load(); fetch(`${Settings.instance.settings.vertdURL}/api/version`).then( diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index 429c98c..dece5ad 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -62,6 +62,7 @@
+ {#if PUB_PLAUSIBLE_URL}