refactor(vue): migrate app, routes, and tooling from Svelte

Replace the Svelte runtime, Vite plugin, Storybook adapter, and component files with Vue 3 equivalents while preserving the existing route table, page markup, styling, assets, deployment shape, and npm workflows.

Move shared and page-level state onto Vue refs, computed values, lifecycle hooks, and script setup components. Recreate the custom async router and translation composable as Vue-native modules so navigation and bilingual content continue to work without introducing a router or state-management dependency.

Adopt the maintained @lucide/vue package, remove the Svelte icon and map wrappers, and initialize MapLibre directly on the Helpdesk page. Replace the Svelte CSF story with a Vue Storybook story, remove obsolete aliases and generation/config files, and update the audited lockfile with no known vulnerabilities.
This commit is contained in:
topsinoty
2026-07-04 15:51:11 +03:00
parent 33eac3b896
commit 13322e11cb
55 changed files with 3684 additions and 2456 deletions

View File

@@ -1,103 +1,65 @@
import { writable } from 'svelte/store';
import { onScopeDispose, ref, watch } from 'vue';
import { getLanguageFromRoute } from '../routes';
const localeModules = import.meta.glob('./locales/*/*.json');
// Get initial language from URL or localStorage
function getInitialLanguage() {
if (typeof window === 'undefined') return 'est';
const path = window.location.pathname;
const langFromRoute = getLanguageFromRoute(path);
// If we detected a language from route, use it and save it
const langFromRoute = getLanguageFromRoute(window.location.pathname);
if (langFromRoute) {
localStorage.setItem('language', langFromRoute);
return langFromRoute;
}
// Otherwise use saved language or default to 'est'
return localStorage.getItem('language') || 'est';
}
const initialLang = getInitialLanguage();
export const currentLang = writable(initialLang);
// Store for translations
export const text = writable({});
export const currentLang = ref(getInitialLanguage());
export const text = ref({});
async function loadTranslations(lang) {
try {
const translations = {};
// Dynamically import all translation files for the specific language
// Vite will code-split these and only bundle the ones actually used
for (const path in localeModules) {
if (path.includes(`/locales/${lang}/`) || path.includes(`\\locales\\${lang}\\`)) {
const module = await localeModules[path]();
Object.assign(translations, module.default || module);
}
}
text.set(translations);
const matchingModules = Object.entries(localeModules).filter(([path]) =>
path.includes(`/locales/${lang}/`),
);
const modules = await Promise.all(matchingModules.map(([, loader]) => loader()));
modules.forEach((module) => Object.assign(translations, module.default || module));
text.value = translations;
} catch (error) {
console.error(`Failed to load translations for ${lang}:`, error);
text.value = {};
}
}
loadTranslations(initialLang);
watch(currentLang, loadTranslations, { immediate: true });
/**
* Create a translation store that only loads one page file per language.
* Example: createPageTextStore('Home') -> ./locales/est/Home.json or ./locales/en/Home.json
*/
export function createPageTextStore(pageName) {
const pageText = writable({});
async function loadPageTranslations(lang) {
export function usePageText(pageName) {
const pageText = ref({});
const stop = watch(currentLang, async (lang) => {
try {
const filePath = `./locales/${lang}/${pageName}.json`;
const loader = localeModules[filePath];
if (!loader) {
pageText.set({});
return;
}
const module = await loader();
pageText.set(module.default || module);
const loader = localeModules[`./locales/${lang}/${pageName}.json`];
const module = loader ? await loader() : null;
pageText.value = module?.default || module || {};
} catch (error) {
console.error(`Failed to load ${pageName} translations for ${lang}:`, error);
pageText.set({});
pageText.value = {};
}
}
}, { immediate: true });
const unsubscribe = currentLang.subscribe((lang) => {
loadPageTranslations(lang);
});
return {
subscribe: pageText.subscribe,
destroy: unsubscribe,
};
onScopeDispose(stop);
return pageText;
}
export function switchLang(lang) {
currentLang.set(lang);
loadTranslations(lang);
if (typeof window !== 'undefined') {
localStorage.setItem('language', lang);
}
currentLang.value = lang;
if (typeof window !== 'undefined') localStorage.setItem('language', lang);
}
// Update language when route changes
if (typeof window !== 'undefined') {
window.addEventListener('popstate', () => {
const path = window.location.pathname;
const lang = getLanguageFromRoute(path);
if (lang) {
switchLang(lang);
}
const lang = getLanguageFromRoute(window.location.pathname);
if (lang) switchLang(lang);
});
}
}