initial localization support

This commit is contained in:
Gabriele Pandocchi 2025-06-04 18:00:35 +02:00
parent c04507d1ee
commit 359c9a1e0d
13 changed files with 128 additions and 3 deletions

25
src/lib/locales/i18n.ts Normal file
View File

@ -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)
);

1
src/lib/locales/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./i18n";

View File

@ -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<Locales, Record<Dictionary, string>> = {
en: en,
it: it
}
export default translation;

View File

@ -0,0 +1,8 @@
import type { Dictionary } from "$lib/types/locales";
const en: Record<Dictionary, string> = {
'settings.languageTitle': 'Language',
'settings.chooseLanguage': 'Choose Language'
}
export default en;

View File

@ -0,0 +1,7 @@
import type { Dictionary } from "$lib/types/locales";
const it: Record<Dictionary, string> = {
'settings.languageTitle': 'Lingua',
'settings.chooseLanguage': 'Scegli Lingua'
}
export default it;

View File

@ -0,0 +1,53 @@
<script lang="ts">
import Dropdown from "$lib/components/functional/Dropdown.svelte";
import Panel from "$lib/components/visual/Panel.svelte";
import { language, setLanguage } from "$lib/store/language";
import { LanguagesIcon } from "lucide-svelte";
import { type Locales } from "$lib/types/locales";
import { t } from "$lib/locales";
export const supportedLanguages = {
en: "English",
it: "Italiano",
};
const handleSelect = (option: string) => {
console.log(option);
const lang = Object.keys(supportedLanguages)[
Object.values(supportedLanguages).findIndex(
(value) => value === option,
)
] as Locales;
if (lang) setLanguage(lang);
};
</script>
<Panel class="flex flex-col gap-8 p-6">
<div class="flex flex-col gap-3">
<h2 class="text-2xl font-bold">
<LanguagesIcon
size="40"
class="inline-block -mt-1 mr-2 bg-accent-purple p-2 rounded-full"
color="black"
/>
{$t("settings.languageTitle")}
</h2>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<p class="text-base font-bold">
{$t("settings.chooseLanguage")}
</p>
<p class="text-sm text-muted font-normal italic"></p>
</div>
<div class="flex flex-col gap-3 w-full">
<Dropdown
options={Object.values(supportedLanguages)}
onselect={(option) => handleSelect(option)}
selected={(()=>supportedLanguages[$language])()}
/>
</div>
</div>
</div>
</div>
</Panel>

View File

@ -5,6 +5,7 @@ export { default as Appearance } from "./Appearance.svelte";
export { default as Conversion } from "./Conversion.svelte"; export { default as Conversion } from "./Conversion.svelte";
export { default as Vertd } from "./Vertd.svelte"; export { default as Vertd } from "./Vertd.svelte";
export { default as Privacy } from "./Privacy.svelte"; export { default as Privacy } from "./Privacy.svelte";
export { default as Language } from "./Language.svelte";
export interface ISettings { export interface ISettings {
filenameFormat: string; filenameFormat: string;

View File

@ -3,9 +3,9 @@ import { converters } from "$lib/converters";
import { error, log } from "$lib/logger"; import { error, log } from "$lib/logger";
import { VertFile } from "$lib/types"; import { VertFile } from "$lib/types";
import { parseBlob, selectCover } from "music-metadata"; import { parseBlob, selectCover } from "music-metadata";
import PQueue from "p-queue";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { addDialog } from "./DialogProvider"; import { addDialog } from "./DialogProvider";
import PQueue from "p-queue";
class Files { class Files {
public files = $state<VertFile[]>([]); public files = $state<VertFile[]>([]);

11
src/lib/store/language.ts Normal file
View File

@ -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<Locales>("en");
export function setLanguage(locale: Locales) {
localStorage.setItem("language", locale);
log(["language"], `set to ${locale}`);
document.documentElement.lang = locale;
language.set(locale);
}

View File

@ -1,3 +1,4 @@
export * from "./file.svelte"; export * from "./file.svelte";
export * from "./util"; export * from "./util";
export * from "./conversion-worker"; export * from "./conversion-worker";
export * from "./locales";

2
src/lib/types/locales.ts Normal file
View File

@ -0,0 +1,2 @@
export type Locales = 'en' | 'it';
export type Dictionary = 'settings.languageTitle' | 'settings.chooseLanguage';

View File

@ -16,9 +16,11 @@
dropping, dropping,
vertdLoaded, vertdLoaded,
} from "$lib/store/index.svelte"; } from "$lib/store/index.svelte";
import { language } from "$lib/store/language";
import "$lib/css/app.scss"; import "$lib/css/app.scss";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { page } from "$app/state"; import { page } from "$app/state";
import type { Locales } from "$lib/types/locales.js";
let { children, data } = $props(); let { children, data } = $props();
let enablePlausible = $state(false); let enablePlausible = $state(false);
@ -66,6 +68,8 @@
(localStorage.getItem("theme") as "light" | "dark") || "light", (localStorage.getItem("theme") as "light" | "dark") || "light",
); );
language.set((localStorage.getItem("language") as Locales) || "en");
Settings.instance.load(); Settings.instance.load();
fetch(`${Settings.instance.settings.vertdURL}/api/version`).then( fetch(`${Settings.instance.settings.vertdURL}/api/version`).then(

View File

@ -62,6 +62,7 @@
</div> </div>
<div class="flex flex-col gap-4 flex-1"> <div class="flex flex-col gap-4 flex-1">
<Settings.Language />
<Settings.Appearance /> <Settings.Appearance />
{#if PUB_PLAUSIBLE_URL} {#if PUB_PLAUSIBLE_URL}
<Settings.Privacy {settings} /> <Settings.Privacy {settings} />