feat(phase2): localization EN/RU + additional widget types

- Add svelte-i18n with 224 translation keys (English + Russian)
- Language switcher in header (EN/RU toggle, persists to localStorage)
- Extract all hardcoded strings from 37 component/page files
- Add 4 new widget types: Bookmark, Note (markdown), Embed (iframe), Status
- WidgetRenderer dispatches by type, WidgetGrid supports full-width widgets
- Type-specific config forms in board editor
- Install marked for markdown rendering
This commit is contained in:
2026-03-24 23:18:05 +03:00
parent bf4e5089ee
commit 477c0e4d52
52 changed files with 1776 additions and 395 deletions
@@ -0,0 +1,42 @@
<script lang="ts">
import { marked } from 'marked';
interface NoteConfig {
content: string;
format: 'markdown' | 'text';
}
interface Props {
config: NoteConfig;
}
let { config }: Props = $props();
// Configure marked for security
marked.setOptions({
breaks: true,
gfm: true
});
const renderedContent = $derived.by(() => {
if (config.format === 'text') {
return config.content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
}
// Sanitize by stripping script tags and event handlers from markdown output
const raw = marked.parse(config.content, { async: false }) as string;
return raw
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/\s*on\w+\s*=\s*"[^"]*"/gi, '')
.replace(/\s*on\w+\s*=\s*'[^']*'/gi, '');
});
</script>
<div class="flex h-full flex-col rounded-xl border border-border bg-card p-4">
<div class="prose prose-sm prose-invert max-w-none flex-1 overflow-auto text-foreground">
{@html renderedContent}
</div>
</div>