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
+13 -12
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import { t } from 'svelte-i18n';
import { ui } from '$lib/stores/ui.svelte.js';
import { page } from '$app/stores';
import DynamicIcon from '$lib/components/ui/DynamicIcon.svelte';
@@ -46,10 +47,10 @@
<rect x="14" y="14" width="7" height="7" />
<rect x="3" y="14" width="7" height="7" />
</svg>
<span class="text-sm font-semibold">App Launcher</span>
<span class="text-sm font-semibold">{$t('app_name')}</span>
</a>
{:else}
<a href="/" class="mx-auto text-sidebar-primary" title="App Launcher">
<a href="/" class="mx-auto text-sidebar-primary" title={$t('app_name')}>
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
@@ -75,7 +76,7 @@
<div class="mb-3">
{#if !collapsed}
<p class="mb-1 px-2 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/50">
Navigation
{$t('nav.navigation')}
</p>
{/if}
@@ -84,7 +85,7 @@
class="flex items-center gap-2 rounded-md px-2 py-2 text-sm transition-colors {isActive('/boards')
? 'bg-sidebar-accent text-sidebar-accent-foreground'
: 'text-sidebar-foreground hover:bg-sidebar-accent/50'}"
title={collapsed ? 'Boards' : undefined}
title={collapsed ? $t('nav.boards') : undefined}
>
<svg
class="h-4 w-4 shrink-0"
@@ -100,7 +101,7 @@
<line x1="3" y1="9" x2="21" y2="9" />
<line x1="9" y1="21" x2="9" y2="9" />
</svg>
{#if !collapsed}<span>Boards</span>{/if}
{#if !collapsed}<span>{$t('nav.boards')}</span>{/if}
</a>
<a
@@ -108,7 +109,7 @@
class="flex items-center gap-2 rounded-md px-2 py-2 text-sm transition-colors {isActive('/apps')
? 'bg-sidebar-accent text-sidebar-accent-foreground'
: 'text-sidebar-foreground hover:bg-sidebar-accent/50'}"
title={collapsed ? 'Apps' : undefined}
title={collapsed ? $t('nav.apps') : undefined}
>
<svg
class="h-4 w-4 shrink-0"
@@ -126,7 +127,7 @@
d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"
/>
</svg>
{#if !collapsed}<span>Apps</span>{/if}
{#if !collapsed}<span>{$t('nav.apps')}</span>{/if}
</a>
</div>
@@ -137,7 +138,7 @@
<p
class="mb-1 px-2 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/50"
>
Boards
{$t('nav.boards')}
</p>
{/if}
@@ -174,7 +175,7 @@
<p
class="mb-1 px-2 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/50"
>
Admin
{$t('nav.admin')}
</p>
{/if}
@@ -183,7 +184,7 @@
class="flex items-center gap-2 rounded-md px-2 py-2 text-sm transition-colors {isActive('/admin')
? 'bg-sidebar-accent text-sidebar-accent-foreground'
: 'text-sidebar-foreground hover:bg-sidebar-accent/50'}"
title={collapsed ? 'Admin Panel' : undefined}
title={collapsed ? $t('nav.admin_panel') : undefined}
>
<svg
class="h-4 w-4 shrink-0"
@@ -200,7 +201,7 @@
/>
<circle cx="12" cy="12" r="3" />
</svg>
{#if !collapsed}<span>Admin Panel</span>{/if}
{#if !collapsed}<span>{$t('nav.admin_panel')}</span>{/if}
</a>
</div>
{/if}
@@ -213,7 +214,7 @@
type="button"
onclick={() => ui.toggleSidebar()}
class="flex w-full items-center justify-center rounded-md p-2 text-sidebar-foreground transition-colors hover:bg-sidebar-accent"
title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
title={collapsed ? $t('sidebar.expand') : $t('sidebar.collapse')}
>
<svg
class="h-4 w-4 transition-transform duration-200"