feat: locale-aware notification templates + UX improvements

- Add locale support to notification templates (matching command template
  pattern): TemplateSlot now has locale field with (config_id, slot_name,
  locale) uniqueness, nested API format {slot: {locale: template}}
- Migration merges separate EN/RU system configs into unified per-provider
  configs; seeds create one config per provider with multi-locale slots
- Locale-aware dispatch with EN fallback in NotificationDispatcher
- Frontend locale tabs (EN/RU) on template config editor
- Fix tracking config cards not showing default provider icons
- Global provider filter, search palette, and various UX polish
This commit is contained in:
2026-03-23 19:08:48 +03:00
parent 6a559bfcd2
commit 37388c430c
30 changed files with 628 additions and 318 deletions
+5 -3
View File
@@ -13,7 +13,8 @@
import ConfirmModal from '$lib/components/ConfirmModal.svelte';
import IconButton from '$lib/components/IconButton.svelte';
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
import { providerTypeItems } from '$lib/grid-items';
import { providerTypeItems, providerDefaultIcon } from '$lib/grid-items';
import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
import { highlightFromUrl } from '$lib/highlight';
import type { ServiceProvider } from '$lib/types';
@@ -21,7 +22,8 @@
let allProviders = $derived(providersCache.items);
let filterText = $state('');
let providers = $derived(allProviders.filter(p =>
!filterText || p.name.toLowerCase().includes(filterText.toLowerCase()) || p.type.toLowerCase().includes(filterText.toLowerCase())
(!filterText || p.name.toLowerCase().includes(filterText.toLowerCase()) || p.type.toLowerCase().includes(filterText.toLowerCase())) &&
(!globalProviderFilter.id || p.id === globalProviderFilter.id)
));
let showForm = $state(false);
let editing = $state<number | null>(null);
@@ -249,7 +251,7 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="health-dot {health[provider.id] === true ? 'online' : health[provider.id] === false ? 'offline' : 'checking'}"></div>
<span style="color: var(--color-primary);"><MdiIcon name={provider.icon || 'mdiServer'} size={20} /></span>
<span style="color: var(--color-primary);"><MdiIcon name={providerDefaultIcon(provider)} size={20} /></span>
<div>
<div class="flex items-center gap-2">
<p class="font-medium">{provider.name}</p>