feat: locale-aware command templates, debounced auto-sync, entity pickers

- Locale-aware templates: CommandTemplateSlot now has a locale column,
  allowing each slot to have per-language variants (EN/RU). Templates
  are resolved at runtime from the Telegram user's language_code.

- Merged system configs: "Default Commands (EN)" and "(RU)" merged
  into a single "Default Commands" config with locale-aware slots.
  Migration handles existing data automatically.

- Configurable command descriptions: hardcoded COMMAND_DESCRIPTIONS
  replaced with desc_* template slots (desc_status, desc_help, etc.)
  that users can customize per locale. setMyCommands registers all
  locales explicitly.

- Removed locale from CommandConfig: no longer needed since locale
  is derived from the Telegram user's language at runtime.

- Debounced command auto-sync: after command config/tracker changes,
  affected bots are marked dirty and synced after a 30s debounce
  window. Manual "Sync with Telegram" button still works.

- Entity pickers in LinkedTargetsSection: replaced 6 plain <select>
  elements with EntitySelect components (search, icons, keyboard nav).
  Added onselect callback and size="sm" props to EntitySelect.
This commit is contained in:
2026-03-22 03:14:51 +03:00
parent 751097b347
commit 1167d138a3
47 changed files with 604 additions and 230 deletions
@@ -13,7 +13,7 @@
import IconButton from '$lib/components/IconButton.svelte';
import CrossLink from '$lib/components/CrossLink.svelte';
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
import { providerTypeItems, localeItems, responseModeItems } from '$lib/grid-items';
import { providerTypeItems, responseModeItems } from '$lib/grid-items';
import EntitySelect from '$lib/components/EntitySelect.svelte';
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
import { highlightFromUrl } from '$lib/highlight';
@@ -59,7 +59,6 @@
icon: '',
provider_type: 'immich',
enabled_commands: ['help', 'status', 'albums', 'events', 'latest', 'random', 'favorites', 'summary', 'memory'] as string[],
locale: 'en',
response_mode: 'media',
default_count: 5,
rate_limits: { search: 30, default: 10 },
@@ -92,7 +91,6 @@
icon: cfg.icon || '',
provider_type: cfg.provider_type || 'immich',
enabled_commands: [...(cfg.enabled_commands || [])],
locale: cfg.locale || 'en',
response_mode: cfg.response_mode || 'media',
default_count: cfg.default_count ?? 5,
rate_limits: { search: cfg.rate_limits?.search ?? 30, default: cfg.rate_limits?.default ?? 10 },
@@ -192,11 +190,7 @@
<EntitySelect items={templateItems} bind:value={form.command_template_config_id} placeholder={t('commandConfig.responseTemplate')} />
</div>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div>
<label class="block text-xs mb-1">{t('commandConfig.locale')}</label>
<IconGridSelect items={localeItems()} bind:value={form.locale} columns={2} />
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
<div>
<label class="block text-xs mb-1">{t('commandConfig.responseMode')}</label>
<IconGridSelect items={responseModeItems(t)} bind:value={form.response_mode} columns={2} />
@@ -244,7 +238,6 @@
<span class="text-xs px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-500 font-mono">
{(cfg.enabled_commands || []).length} {t('commandConfig.commands')}
</span>
<span class="text-xs text-[var(--color-muted-foreground)]">{cfg.locale?.toUpperCase()}</span>
</div>
<div class="flex items-center gap-2 mt-0.5">
<span class="text-xs text-[var(--color-muted-foreground)]">