fix: comprehensive API/UI review — 26 bug fixes and improvements
Backend: - Scheduler lifecycle sync: create/update/delete tracker now syncs APScheduler jobs - Test-periodic/test-memory endpoints render actual Jinja2 templates with sample data - Cascade cleanup on tracker delete (TrackerState removed, EventLog nullified) - Fix user_id=0 FK violation for system-owned TemplateConfig (removed FK constraint) - Fix API key leak: only attach x-api-key header for internal provider URLs - Validate config ownership in tracker_targets create/update - Fix _response() double-emit of created_at in template/tracking configs - Add per-target-link test endpoints (test, test-periodic, test-memory) Frontend: - Fix orphaned provider on test exception in providers/new - Add submitting guard + disabled state to targets save button - Move test buttons from tracker card to per-target-link rows - Fix Svelte 5 async $state reactivity (spread reassignment for all Record mutations) - i18n for dashboard timeAgo and event type badges (EN + RU) - Add required attribute to chat select dropdown in targets - Fix font CSS vars to prioritize imported DM Sans / JetBrains Mono - Standardize empty states with centered icon + text across all 6 list pages - Add stagger-children animation class to all list containers - Fix slide transition duration consistency (200ms everywhere) - Standardize border-radius to rounded-md across all form inputs - Fix providers/new page structure (h2 + mb-8 spacing) - Fix tracker card action row overflow (flex-wrap justify-end) - JinjaEditor dark mode reactivity (recreate editor on theme change) - Add aria-labels to mobile nav items - Make ConfirmModal confirm button label/icon configurable - Remove double error reporting on providers page - Add telegram bot edit functionality (name editing via PUT) - i18n for External Domain label on provider forms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import Modal from './Modal.svelte';
|
||||
import MdiIcon from './MdiIcon.svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
let { open = false, title = '', message = '', confirmLabel = '', confirmIcon = 'mdiDelete', onconfirm, oncancel } = $props<{
|
||||
open: boolean;
|
||||
title?: string;
|
||||
message?: string;
|
||||
confirmLabel?: string;
|
||||
confirmIcon?: string;
|
||||
onconfirm: () => void;
|
||||
oncancel: () => void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<Modal {open} title={title || t('common.confirm')} onclose={oncancel}>
|
||||
<div class="flex items-start gap-3 mb-5">
|
||||
<div class="flex items-center justify-center w-9 h-9 rounded-full flex-shrink-0"
|
||||
style="background: var(--color-error-bg); color: var(--color-error-fg);">
|
||||
<MdiIcon name="mdiAlertCircle" size={20} />
|
||||
</div>
|
||||
<p class="text-sm mt-1.5" style="color: var(--color-muted-foreground);">{message}</p>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button onclick={oncancel}
|
||||
class="confirm-btn-cancel">
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button onclick={onconfirm}
|
||||
class="confirm-btn-delete">
|
||||
<MdiIcon name={confirmIcon} size={15} />
|
||||
{confirmLabel || t('common.delete')}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.confirm-btn-cancel {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
border: 1px solid var(--color-border);
|
||||
background: transparent;
|
||||
color: var(--color-foreground);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.confirm-btn-cancel:hover {
|
||||
background: var(--color-muted);
|
||||
}
|
||||
|
||||
.confirm-btn-delete {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
background: var(--color-destructive);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.confirm-btn-delete:hover {
|
||||
box-shadow: 0 0 16px rgba(239, 68, 68, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { EditorView, Decoration, placeholder as cmPlaceholder, type DecorationSet } from '@codemirror/view';
|
||||
import { EditorState, StateField, StateEffect } from '@codemirror/state';
|
||||
import { StreamLanguage } from '@codemirror/language';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { getTheme } from '$lib/theme.svelte';
|
||||
|
||||
let { value = '', onchange, rows = 6, placeholder = '', errorLine = null } = $props<{
|
||||
value: string;
|
||||
onchange: (val: string) => void;
|
||||
rows?: number;
|
||||
placeholder?: string;
|
||||
errorLine?: number | null;
|
||||
}>();
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let view: EditorView;
|
||||
const theme = getTheme();
|
||||
|
||||
// Error line highlight effect and field
|
||||
const setErrorLine = StateEffect.define<number | null>();
|
||||
const errorLineField = StateField.define<DecorationSet>({
|
||||
create() { return Decoration.none; },
|
||||
update(decorations, tr) {
|
||||
for (const e of tr.effects) {
|
||||
if (e.is(setErrorLine)) {
|
||||
if (e.value === null) return Decoration.none;
|
||||
const lineNum = e.value;
|
||||
if (lineNum < 1 || lineNum > tr.state.doc.lines) return Decoration.none;
|
||||
const line = tr.state.doc.line(lineNum);
|
||||
return Decoration.set([
|
||||
Decoration.line({ class: 'cm-error-line' }).range(line.from),
|
||||
]);
|
||||
}
|
||||
}
|
||||
return decorations;
|
||||
},
|
||||
provide: f => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
// Simple Jinja2 stream parser for syntax highlighting
|
||||
const jinjaLang = StreamLanguage.define({
|
||||
token(stream) {
|
||||
if (stream.match('{#')) {
|
||||
stream.skipTo('#}') && stream.match('#}');
|
||||
return 'comment';
|
||||
}
|
||||
if (stream.match('{{')) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match('}}')) return 'variableName';
|
||||
stream.next();
|
||||
}
|
||||
return 'variableName';
|
||||
}
|
||||
if (stream.match('{%')) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match('%}')) return 'keyword';
|
||||
stream.next();
|
||||
}
|
||||
return 'keyword';
|
||||
}
|
||||
while (stream.next()) {
|
||||
if (stream.peek() === '{') break;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
const extensions = [
|
||||
jinjaLang,
|
||||
errorLineField,
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
onchange(update.state.doc.toString());
|
||||
}
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.theme({
|
||||
'&': { fontSize: '13px', fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace" },
|
||||
'.cm-content': { minHeight: `${rows * 1.5}em`, padding: '8px' },
|
||||
'.cm-editor': { borderRadius: '0.375rem', border: '1px solid var(--color-border)' },
|
||||
'.cm-focused': { outline: '2px solid var(--color-primary)', outlineOffset: '0px' },
|
||||
'.cm-error-line': { backgroundColor: 'rgba(239, 68, 68, 0.2)', outline: '1px solid rgba(239, 68, 68, 0.4)' },
|
||||
'.ͼc': { color: '#e879f9' },
|
||||
'.ͼd': { color: '#38bdf8' },
|
||||
'.ͼ5': { color: '#6b7280' },
|
||||
}),
|
||||
];
|
||||
|
||||
if (theme.isDark) {
|
||||
extensions.push(oneDark);
|
||||
}
|
||||
|
||||
if (placeholder) {
|
||||
extensions.push(cmPlaceholder(placeholder));
|
||||
}
|
||||
|
||||
view = new EditorView({
|
||||
state: EditorState.create({ doc: value, extensions }),
|
||||
parent: container,
|
||||
});
|
||||
|
||||
return () => view.destroy();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (view && view.state.doc.toString() !== value) {
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: value },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (view) {
|
||||
view.dispatch({ effects: setErrorLine.of(errorLine ?? null) });
|
||||
}
|
||||
});
|
||||
|
||||
// Recreate editor when theme changes
|
||||
let lastIsDark: boolean | undefined;
|
||||
$effect(() => {
|
||||
const isDark = theme.isDark;
|
||||
if (lastIsDark !== undefined && lastIsDark !== isDark && view) {
|
||||
const currentDoc = view.state.doc.toString();
|
||||
view.destroy();
|
||||
|
||||
const extensions = [
|
||||
jinjaLang,
|
||||
errorLineField,
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
onchange(update.state.doc.toString());
|
||||
}
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.theme({
|
||||
'&': { fontSize: '13px', fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace" },
|
||||
'.cm-content': { minHeight: `${rows * 1.5}em`, padding: '8px' },
|
||||
'.cm-editor': { borderRadius: '0.375rem', border: '1px solid var(--color-border)' },
|
||||
'.cm-focused': { outline: '2px solid var(--color-primary)', outlineOffset: '0px' },
|
||||
'.cm-error-line': { backgroundColor: 'rgba(239, 68, 68, 0.2)', outline: '1px solid rgba(239, 68, 68, 0.4)' },
|
||||
'.ͼc': { color: '#e879f9' },
|
||||
'.ͼd': { color: '#38bdf8' },
|
||||
'.ͼ5': { color: '#6b7280' },
|
||||
}),
|
||||
];
|
||||
if (isDark) extensions.push(oneDark);
|
||||
if (placeholder) extensions.push(cmPlaceholder(placeholder));
|
||||
|
||||
view = new EditorView({
|
||||
state: EditorState.create({ doc: currentDoc, extensions }),
|
||||
parent: container,
|
||||
});
|
||||
}
|
||||
lastIsDark = isDark;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={container}></div>
|
||||
@@ -0,0 +1,448 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "Notify Bridge",
|
||||
"tagline": "Service notifications"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"providers": "Providers",
|
||||
"trackers": "Trackers",
|
||||
"trackingConfigs": "Tracking",
|
||||
"templateConfigs": "Templates",
|
||||
"telegramBots": "Bots",
|
||||
"targets": "Targets",
|
||||
"users": "Users",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"auth": {
|
||||
"signIn": "Sign in",
|
||||
"signInTitle": "Sign in to your account",
|
||||
"signingIn": "Signing in...",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"confirmPassword": "Confirm password",
|
||||
"setupTitle": "Welcome",
|
||||
"setupDescription": "Create your admin account to get started",
|
||||
"createAccount": "Create account",
|
||||
"creatingAccount": "Creating account...",
|
||||
"passwordMismatch": "Passwords do not match",
|
||||
"passwordTooShort": "Password must be at least 6 characters",
|
||||
"or": "or"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"description": "Overview of your Notify Bridge setup",
|
||||
"providers": "Providers",
|
||||
"activeTrackers": "Active Trackers",
|
||||
"targets": "Targets",
|
||||
"recentEvents": "Recent Events",
|
||||
"noEvents": "No events yet. Create a tracker to start monitoring.",
|
||||
"loading": "Loading...",
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{n}m ago",
|
||||
"hoursAgo": "{n}h ago",
|
||||
"daysAgo": "{n}d ago",
|
||||
"assetsAdded": "assets added",
|
||||
"assetsRemoved": "assets removed",
|
||||
"collectionRenamed": "collection renamed",
|
||||
"collectionDeleted": "collection deleted",
|
||||
"sharingChanged": "sharing changed"
|
||||
},
|
||||
"providers": {
|
||||
"title": "Providers",
|
||||
"description": "Manage service provider connections",
|
||||
"addProvider": "Add Provider",
|
||||
"cancel": "Cancel",
|
||||
"type": "Provider Type",
|
||||
"name": "Name",
|
||||
"url": "Provider URL",
|
||||
"urlPlaceholder": "http://provider:2283",
|
||||
"apiKey": "API Key",
|
||||
"apiKeyKeep": "API Key (leave empty to keep current)",
|
||||
"connecting": "Connecting...",
|
||||
"noProviders": "No providers configured yet.",
|
||||
"delete": "Delete",
|
||||
"confirmDelete": "Delete this provider?",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"checking": "Checking...",
|
||||
"loadError": "Failed to load providers.",
|
||||
"externalDomain": "External Domain",
|
||||
"optional": "optional"
|
||||
},
|
||||
"trackers": {
|
||||
"title": "Trackers",
|
||||
"description": "Monitor albums for changes",
|
||||
"newTracker": "New Tracker",
|
||||
"cancel": "Cancel",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Family photos tracker",
|
||||
"server": "Provider",
|
||||
"selectServer": "Select provider...",
|
||||
"albums": "Albums",
|
||||
"eventTypes": "Event Types",
|
||||
"notificationTargets": "Notification Targets",
|
||||
"scanInterval": "Scan Interval (seconds)",
|
||||
"createTracker": "Create Tracker",
|
||||
"noTrackers": "No trackers yet. Add a provider first, then create a tracker.",
|
||||
"active": "Active",
|
||||
"paused": "Paused",
|
||||
"pause": "Pause",
|
||||
"resume": "Resume",
|
||||
"delete": "Delete",
|
||||
"confirmDelete": "Delete this tracker?",
|
||||
"albums_count": "album(s)",
|
||||
"every": "every",
|
||||
"trackImages": "Track images",
|
||||
"trackVideos": "Track videos",
|
||||
"favoritesOnly": "Favorites only",
|
||||
"includePeople": "Include people in notifications",
|
||||
"includeAssetDetails": "Include asset details",
|
||||
"maxAssetsToShow": "Max assets to show",
|
||||
"sortBy": "Sort by",
|
||||
"sortOrder": "Sort order",
|
||||
"sortNone": "Original order",
|
||||
"sortDate": "Date",
|
||||
"sortRating": "Rating",
|
||||
"sortName": "Name",
|
||||
"sortRandom": "Random",
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending",
|
||||
"quietHoursStart": "Quiet hours start",
|
||||
"quietHoursEnd": "Quiet hours end",
|
||||
"batchDuration": "Batch duration (seconds)",
|
||||
"linkedTargets": "targets",
|
||||
"noLinkedTargets": "No targets linked. Add a target below.",
|
||||
"addTarget": "Add target"
|
||||
},
|
||||
"templates": {
|
||||
"title": "Templates",
|
||||
"description": "Jinja2 message templates for notifications",
|
||||
"newTemplate": "New Template",
|
||||
"cancel": "Cancel",
|
||||
"name": "Name",
|
||||
"body": "Template Body (Jinja2)",
|
||||
"variables": "Variables",
|
||||
"preview": "Preview",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"confirmDelete": "Delete this template?",
|
||||
"create": "Create Template",
|
||||
"update": "Update Template",
|
||||
"noTemplates": "No templates yet. A default template will be used if none is configured.",
|
||||
"eventType": "Event type",
|
||||
"allEvents": "All events",
|
||||
"assetsAdded": "Assets added",
|
||||
"assetsRemoved": "Assets removed",
|
||||
"albumRenamed": "Album renamed",
|
||||
"albumDeleted": "Album deleted"
|
||||
},
|
||||
"targets": {
|
||||
"title": "Targets",
|
||||
"description": "Notification destinations (Telegram, webhooks)",
|
||||
"addTarget": "Add Target",
|
||||
"cancel": "Cancel",
|
||||
"type": "Type",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "My notifications",
|
||||
"botToken": "Bot Token",
|
||||
"chatId": "Chat ID",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"create": "Add Target",
|
||||
"test": "Test",
|
||||
"delete": "Delete",
|
||||
"confirmDelete": "Delete this target?",
|
||||
"noTargets": "No notification targets configured yet.",
|
||||
"testSent": "Test sent successfully!",
|
||||
"aiCaptions": "Enable AI captions",
|
||||
"telegramSettings": "Telegram Settings",
|
||||
"maxMedia": "Max media to send",
|
||||
"maxGroupSize": "Max group size",
|
||||
"chunkDelay": "Delay between groups (ms)",
|
||||
"maxAssetSize": "Max asset size (MB)",
|
||||
"videoWarning": "Video size warning",
|
||||
"disableUrlPreview": "Disable link previews",
|
||||
"sendLargeAsDocuments": "Send large photos as documents"
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"description": "Manage user accounts (admin only)",
|
||||
"addUser": "Add User",
|
||||
"cancel": "Cancel",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"role": "Role",
|
||||
"roleUser": "User",
|
||||
"roleAdmin": "Admin",
|
||||
"create": "Create User",
|
||||
"delete": "Delete",
|
||||
"confirmDelete": "Delete this user?",
|
||||
"joined": "joined"
|
||||
},
|
||||
"telegramBot": {
|
||||
"title": "Telegram Bots",
|
||||
"description": "Register and manage Telegram bots",
|
||||
"addBot": "Add Bot",
|
||||
"name": "Display name",
|
||||
"namePlaceholder": "Family notifications bot",
|
||||
"token": "Bot Token",
|
||||
"tokenPlaceholder": "123456:ABC-DEF...",
|
||||
"noBots": "No bots registered yet.",
|
||||
"chats": "Chats",
|
||||
"noChats": "No chats found. Send a message to the bot first.",
|
||||
"refreshChats": "Refresh",
|
||||
"selectBot": "Select bot",
|
||||
"selectChat": "Select chat",
|
||||
"private": "Private",
|
||||
"group": "Group",
|
||||
"supergroup": "Supergroup",
|
||||
"channel": "Channel",
|
||||
"confirmDelete": "Delete this bot?",
|
||||
"commands": "Commands",
|
||||
"enabledCommands": "Enabled Commands",
|
||||
"defaultCount": "Default result count",
|
||||
"responseMode": "Response mode",
|
||||
"modeMedia": "Media (send photos)",
|
||||
"modeText": "Text (send links)",
|
||||
"botLocale": "Bot language",
|
||||
"rateLimits": "Rate Limits",
|
||||
"rateSearch": "Search cooldown",
|
||||
"rateFind": "Find cooldown",
|
||||
"rateDefault": "Default cooldown",
|
||||
"syncCommands": "Sync to Telegram",
|
||||
"discoverChats": "Discover chats from Telegram",
|
||||
"clickToCopy": "Click to copy chat ID",
|
||||
"chatsDiscovered": "Chats discovered",
|
||||
"chatDeleted": "Chat removed"
|
||||
},
|
||||
"trackingConfig": {
|
||||
"title": "Tracking Configs",
|
||||
"description": "Define what events and assets to react to",
|
||||
"newConfig": "New Config",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Default tracking",
|
||||
"noConfigs": "No tracking configs yet.",
|
||||
"eventTracking": "Event Tracking",
|
||||
"assetsAdded": "Assets added",
|
||||
"assetsRemoved": "Assets removed",
|
||||
"albumRenamed": "Album renamed",
|
||||
"albumDeleted": "Album deleted",
|
||||
"sharingChanged": "Sharing changed",
|
||||
"trackImages": "Track images",
|
||||
"trackVideos": "Track videos",
|
||||
"favoritesOnly": "Favorites only",
|
||||
"assetDisplay": "Asset Display",
|
||||
"includePeople": "Include people",
|
||||
"includeDetails": "Include asset details",
|
||||
"maxAssets": "Max assets to show",
|
||||
"sortBy": "Sort by",
|
||||
"sortOrder": "Sort order",
|
||||
"periodicSummary": "Periodic Summary",
|
||||
"enabled": "Enabled",
|
||||
"intervalDays": "Interval (days)",
|
||||
"startDate": "Start date",
|
||||
"times": "Times (HH:MM)",
|
||||
"scheduledAssets": "Scheduled Assets",
|
||||
"albumMode": "Album mode",
|
||||
"limit": "Limit",
|
||||
"assetType": "Asset type",
|
||||
"minRating": "Min rating",
|
||||
"memoryMode": "Memory Mode (On This Day)",
|
||||
"test": "Test",
|
||||
"confirmDelete": "Delete this tracking config?",
|
||||
"sortNone": "None",
|
||||
"sortDate": "Date",
|
||||
"sortRating": "Rating",
|
||||
"sortName": "Name",
|
||||
"orderDesc": "Descending",
|
||||
"orderAsc": "Ascending",
|
||||
"albumModePerAlbum": "Per album",
|
||||
"albumModeCombined": "Combined",
|
||||
"albumModeRandom": "Random",
|
||||
"assetTypeAll": "All",
|
||||
"assetTypePhoto": "Photo",
|
||||
"assetTypeVideo": "Video"
|
||||
},
|
||||
"templateConfig": {
|
||||
"title": "Template Configs",
|
||||
"description": "Define how notification messages are formatted",
|
||||
"newConfig": "New Config",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Default EN",
|
||||
"descriptionPlaceholder": "e.g. English templates for family notifications",
|
||||
"noConfigs": "No template configs yet.",
|
||||
"eventMessages": "Event Messages",
|
||||
"assetsAdded": "Assets added",
|
||||
"assetsRemoved": "Assets removed",
|
||||
"albumRenamed": "Album renamed",
|
||||
"albumDeleted": "Album deleted",
|
||||
"sharingChanged": "Sharing changed",
|
||||
"assetFormatting": "Asset Formatting",
|
||||
"imageTemplate": "Image item",
|
||||
"videoTemplate": "Video item",
|
||||
"assetsWrapper": "Assets wrapper",
|
||||
"moreMessage": "More message",
|
||||
"peopleFormat": "People format",
|
||||
"dateLocation": "Date & Location",
|
||||
"dateFormat": "Date format",
|
||||
"commonDate": "Common date",
|
||||
"uniqueDate": "Per-asset date",
|
||||
"locationFormat": "Location format",
|
||||
"commonLocation": "Common location",
|
||||
"uniqueLocation": "Per-asset location",
|
||||
"favoriteIndicator": "Favorite indicator",
|
||||
"scheduledMessages": "Scheduled Messages",
|
||||
"periodicSummary": "Periodic summary",
|
||||
"periodicAlbum": "Per-album item",
|
||||
"scheduledAssets": "Scheduled assets",
|
||||
"memoryMode": "Memory mode",
|
||||
"settings": "Settings",
|
||||
"previewAs": "Preview as",
|
||||
"preview": "Preview",
|
||||
"variables": "Variables",
|
||||
"assetFields": "Asset fields (in {% for asset in added_assets %})",
|
||||
"albumFields": "Album fields (in {% for album in albums %})",
|
||||
"confirmDelete": "Delete this template config?"
|
||||
},
|
||||
"templateVars": {
|
||||
"message_assets_added": { "description": "Notification when new assets are added to an album" },
|
||||
"message_assets_removed": { "description": "Notification when assets are removed from an album" },
|
||||
"message_album_renamed": { "description": "Notification when an album is renamed" },
|
||||
"message_album_deleted": { "description": "Notification when an album is deleted" },
|
||||
"periodic_summary_message": { "description": "Periodic album summary (scheduler not yet implemented)" },
|
||||
"scheduled_assets_message": { "description": "Scheduled asset delivery (scheduler not yet implemented)" },
|
||||
"memory_mode_message": { "description": "\"On This Day\" memories (scheduler not yet implemented)" },
|
||||
"album_id": "Album ID (UUID)",
|
||||
"album_name": "Album name",
|
||||
"album_url": "Public share URL (empty if not shared)",
|
||||
"added_count": "Number of assets added",
|
||||
"removed_count": "Number of assets removed",
|
||||
"change_type": "Type of change (assets_added, assets_removed, album_renamed, album_deleted)",
|
||||
"people": "Detected people names (list, use {{ people | join(', ') }})",
|
||||
"added_assets": "List of asset dicts (use {% for asset in added_assets %})",
|
||||
"removed_assets": "List of removed asset IDs (strings)",
|
||||
"shared": "Whether album is shared (boolean)",
|
||||
"target_type": "Target type: 'telegram' or 'webhook'",
|
||||
"has_videos": "Whether added assets contain videos (boolean)",
|
||||
"has_photos": "Whether added assets contain photos (boolean)",
|
||||
"old_name": "Previous album name (rename events)",
|
||||
"new_name": "New album name (rename events)",
|
||||
"old_shared": "Was album shared before rename (boolean)",
|
||||
"new_shared": "Is album shared after rename (boolean)",
|
||||
"albums": "List of album dicts (use {% for album in albums %})",
|
||||
"assets": "List of asset dicts (use {% for asset in assets %})",
|
||||
"date": "Current date string",
|
||||
"asset_id": "Asset ID (UUID)",
|
||||
"asset_filename": "Original filename",
|
||||
"asset_type": "IMAGE or VIDEO",
|
||||
"asset_created_at": "Creation date/time (ISO 8601)",
|
||||
"asset_owner": "Owner display name",
|
||||
"asset_owner_id": "Owner user ID",
|
||||
"asset_description": "User or EXIF description",
|
||||
"asset_people": "People detected in this asset (list)",
|
||||
"asset_is_favorite": "Whether asset is favorited (boolean)",
|
||||
"asset_rating": "Star rating (1-5 or null)",
|
||||
"asset_latitude": "GPS latitude (float or null)",
|
||||
"asset_longitude": "GPS longitude (float or null)",
|
||||
"asset_city": "City name",
|
||||
"asset_state": "State/region name",
|
||||
"asset_country": "Country name",
|
||||
"asset_url": "Public viewer URL (if shared)",
|
||||
"asset_download_url": "Direct download URL (if shared)",
|
||||
"asset_photo_url": "Preview image URL (images only, if shared)",
|
||||
"asset_playback_url": "Video playback URL (videos only, if shared)",
|
||||
"album_name_field": "Album name (in album list)",
|
||||
"album_asset_count": "Total assets in album",
|
||||
"album_url_field": "Album share URL",
|
||||
"album_shared": "Whether album is shared"
|
||||
},
|
||||
"hints": {
|
||||
"periodicSummary": "Sends a scheduled summary of all tracked albums at specified times. Great for daily/weekly digests.",
|
||||
"scheduledAssets": "Sends random or selected photos from tracked albums on a schedule. Like a daily photo pick.",
|
||||
"memoryMode": "\"On This Day\" — sends photos taken on this date in previous years. Nostalgic flashbacks.",
|
||||
"favoritesOnly": "Only include assets marked as favorites.",
|
||||
"maxAssets": "Maximum number of asset details to include in a single notification message.",
|
||||
"periodicStartDate": "The reference date for calculating periodic intervals. Summaries are sent every N days from this date.",
|
||||
"times": "Time(s) of day to send notifications, in HH:MM format. Use commas for multiple times: 09:00,18:00",
|
||||
"albumMode": "Per album: separate notification per album. Combined: one notification with all albums. Random: pick one album randomly.",
|
||||
"minRating": "Only include assets with at least this star rating (0 = no filter).",
|
||||
"eventMessages": "Templates for real-time event notifications. Use {variables} for dynamic content.",
|
||||
"assetFormatting": "How individual assets are formatted within notification messages.",
|
||||
"dateLocation": "Date and location formatting in notifications. Uses strftime syntax for dates.",
|
||||
"scheduledMessages": "Templates for periodic summaries, scheduled photo picks, and On This Day memories.",
|
||||
"aiCaptions": "Use Claude AI to generate a natural-language caption for notifications instead of the template.",
|
||||
"maxMedia": "Maximum number of photos/videos to attach per notification (0 = text only).",
|
||||
"groupSize": "Telegram media groups can contain 2-10 items. Larger batches are split into chunks.",
|
||||
"chunkDelay": "Delay in milliseconds between sending media chunks. Prevents Telegram rate limiting.",
|
||||
"maxAssetSize": "Skip assets larger than this size in MB. Telegram limits files to 50 MB.",
|
||||
"trackingConfig": "Controls which events trigger notifications and how assets are filtered.",
|
||||
"templateConfig": "Controls the message format. Uses default templates if not set.",
|
||||
"scanInterval": "How often to poll the provider for changes, in seconds. Lower = faster detection but more API calls.",
|
||||
"batchDuration": "Time to accumulate changes before dispatching notifications. 0 = send immediately.",
|
||||
"defaultCount": "How many results to return when the user doesn't specify a count (1-20).",
|
||||
"responseMode": "Media: send actual photos. Text: send filenames/links only. Media mode uses more bandwidth.",
|
||||
"botLocale": "Language for command descriptions in Telegram's menu and bot response messages.",
|
||||
"rateLimits": "Cooldown in seconds between uses of each command category per chat. 0 = no limit."
|
||||
},
|
||||
"snack": {
|
||||
"providerSaved": "Provider saved",
|
||||
"providerDeleted": "Provider deleted",
|
||||
"trackerCreated": "Tracker created",
|
||||
"trackerUpdated": "Tracker updated",
|
||||
"trackerDeleted": "Tracker deleted",
|
||||
"trackerPaused": "Tracker paused",
|
||||
"trackerResumed": "Tracker resumed",
|
||||
"targetSaved": "Target saved",
|
||||
"targetDeleted": "Target deleted",
|
||||
"targetTestSent": "Test notification sent",
|
||||
"templateSaved": "Template config saved",
|
||||
"templateDeleted": "Template config deleted",
|
||||
"trackingConfigSaved": "Tracking config saved",
|
||||
"trackingConfigDeleted": "Tracking config deleted",
|
||||
"botRegistered": "Bot registered",
|
||||
"botDeleted": "Bot deleted",
|
||||
"userCreated": "User created",
|
||||
"userDeleted": "User deleted",
|
||||
"passwordChanged": "Password changed",
|
||||
"copied": "Copied to clipboard",
|
||||
"genericError": "Something went wrong",
|
||||
"commandsSaved": "Commands config saved",
|
||||
"commandsSynced": "Commands synced to Telegram",
|
||||
"targetLinked": "Target linked",
|
||||
"targetUnlinked": "Target unlinked",
|
||||
"botUpdated": "Bot updated"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"description": "Description",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"none": "None",
|
||||
"noneDefault": "None (default)",
|
||||
"loadError": "Failed to load data",
|
||||
"headersInvalid": "Invalid JSON",
|
||||
"language": "Language",
|
||||
"theme": "Theme",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System",
|
||||
"test": "Test",
|
||||
"create": "Create",
|
||||
"changePassword": "Change Password",
|
||||
"currentPassword": "Current password",
|
||||
"newPassword": "New password",
|
||||
"passwordChanged": "Password changed successfully",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"syntaxError": "Syntax error",
|
||||
"undefinedVar": "Unknown variable",
|
||||
"line": "line",
|
||||
"add": "Add"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "Notify Bridge",
|
||||
"tagline": "Уведомления о сервисах"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Главная",
|
||||
"providers": "Провайдеры",
|
||||
"trackers": "Трекеры",
|
||||
"trackingConfigs": "Отслеживание",
|
||||
"templateConfigs": "Шаблоны",
|
||||
"telegramBots": "Боты",
|
||||
"targets": "Получатели",
|
||||
"users": "Пользователи",
|
||||
"logout": "Выход"
|
||||
},
|
||||
"auth": {
|
||||
"signIn": "Войти",
|
||||
"signInTitle": "Вход в аккаунт",
|
||||
"signingIn": "Вход...",
|
||||
"username": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"confirmPassword": "Подтвердите пароль",
|
||||
"setupTitle": "Добро пожаловать",
|
||||
"setupDescription": "Создайте учётную запись администратора",
|
||||
"createAccount": "Создать аккаунт",
|
||||
"creatingAccount": "Создание...",
|
||||
"passwordMismatch": "Пароли не совпадают",
|
||||
"passwordTooShort": "Пароль должен быть не менее 6 символов",
|
||||
"or": "или"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Главная",
|
||||
"description": "Обзор настроек Notify Bridge",
|
||||
"providers": "Провайдеры",
|
||||
"activeTrackers": "Активные трекеры",
|
||||
"targets": "Получатели",
|
||||
"recentEvents": "Последние события",
|
||||
"noEvents": "Событий пока нет. Создайте трекер для отслеживания.",
|
||||
"loading": "Загрузка...",
|
||||
"justNow": "только что",
|
||||
"minutesAgo": "{n} мин назад",
|
||||
"hoursAgo": "{n} ч назад",
|
||||
"daysAgo": "{n} д назад",
|
||||
"assetsAdded": "добавлены файлы",
|
||||
"assetsRemoved": "удалены файлы",
|
||||
"collectionRenamed": "альбом переименован",
|
||||
"collectionDeleted": "альбом удалён",
|
||||
"sharingChanged": "изменение доступа"
|
||||
},
|
||||
"providers": {
|
||||
"title": "Провайдеры",
|
||||
"description": "Управление подключениями к сервисам",
|
||||
"addProvider": "Добавить провайдер",
|
||||
"cancel": "Отмена",
|
||||
"type": "Тип провайдера",
|
||||
"name": "Название",
|
||||
"url": "URL провайдера",
|
||||
"urlPlaceholder": "http://provider:2283",
|
||||
"apiKey": "API ключ",
|
||||
"apiKeyKeep": "API ключ (оставьте пустым, чтобы сохранить текущий)",
|
||||
"connecting": "Подключение...",
|
||||
"noProviders": "Провайдеры не настроены.",
|
||||
"delete": "Удалить",
|
||||
"confirmDelete": "Удалить этот провайдер?",
|
||||
"online": "В сети",
|
||||
"offline": "Не в сети",
|
||||
"checking": "Проверка...",
|
||||
"loadError": "Не удалось загрузить провайдеры.",
|
||||
"externalDomain": "Внешний домен",
|
||||
"optional": "необязательно"
|
||||
},
|
||||
"trackers": {
|
||||
"title": "Трекеры",
|
||||
"description": "Отслеживание изменений в альбомах",
|
||||
"newTracker": "Новый трекер",
|
||||
"cancel": "Отмена",
|
||||
"name": "Название",
|
||||
"namePlaceholder": "Трекер семейных фото",
|
||||
"server": "Провайдер",
|
||||
"selectServer": "Выберите провайдер...",
|
||||
"albums": "Альбомы",
|
||||
"eventTypes": "Типы событий",
|
||||
"notificationTargets": "Получатели уведомлений",
|
||||
"scanInterval": "Интервал проверки (секунды)",
|
||||
"createTracker": "Создать трекер",
|
||||
"noTrackers": "Трекеров пока нет. Сначала добавьте провайдер, затем создайте трекер.",
|
||||
"active": "Активен",
|
||||
"paused": "Приостановлен",
|
||||
"pause": "Пауза",
|
||||
"resume": "Возобновить",
|
||||
"delete": "Удалить",
|
||||
"confirmDelete": "Удалить этот трекер?",
|
||||
"albums_count": "альбом(ов)",
|
||||
"every": "каждые",
|
||||
"trackImages": "Отслеживать фото",
|
||||
"trackVideos": "Отслеживать видео",
|
||||
"favoritesOnly": "Только избранные",
|
||||
"includePeople": "Включать людей в уведомления",
|
||||
"includeAssetDetails": "Включать детали файлов",
|
||||
"maxAssetsToShow": "Макс. файлов в уведомлении",
|
||||
"sortBy": "Сортировка",
|
||||
"sortOrder": "Порядок",
|
||||
"sortNone": "Исходный порядок",
|
||||
"sortDate": "Дата",
|
||||
"sortRating": "Рейтинг",
|
||||
"sortName": "Имя",
|
||||
"sortRandom": "Случайный",
|
||||
"ascending": "По возрастанию",
|
||||
"descending": "По убыванию",
|
||||
"quietHoursStart": "Тихие часы начало",
|
||||
"quietHoursEnd": "Тихие часы конец",
|
||||
"batchDuration": "Длительность пакета (секунды)",
|
||||
"linkedTargets": "получатели",
|
||||
"noLinkedTargets": "Нет привязанных получателей. Добавьте получателя ниже.",
|
||||
"addTarget": "Добавить получателя"
|
||||
},
|
||||
"templates": {
|
||||
"title": "Шаблоны",
|
||||
"description": "Шаблоны сообщений Jinja2 для уведомлений",
|
||||
"newTemplate": "Новый шаблон",
|
||||
"cancel": "Отмена",
|
||||
"name": "Название",
|
||||
"body": "Текст шаблона (Jinja2)",
|
||||
"variables": "Переменные",
|
||||
"preview": "Предпросмотр",
|
||||
"edit": "Редактировать",
|
||||
"delete": "Удалить",
|
||||
"confirmDelete": "Удалить этот шаблон?",
|
||||
"create": "Создать шаблон",
|
||||
"update": "Обновить шаблон",
|
||||
"noTemplates": "Шаблонов пока нет. Без шаблона будет использован шаблон по умолчанию.",
|
||||
"eventType": "Тип события",
|
||||
"allEvents": "Все события",
|
||||
"assetsAdded": "Добавлены файлы",
|
||||
"assetsRemoved": "Удалены файлы",
|
||||
"albumRenamed": "Альбом переименован",
|
||||
"albumDeleted": "Альбом удалён"
|
||||
},
|
||||
"targets": {
|
||||
"title": "Получатели",
|
||||
"description": "Адреса уведомлений (Telegram, вебхуки)",
|
||||
"addTarget": "Добавить получателя",
|
||||
"cancel": "Отмена",
|
||||
"type": "Тип",
|
||||
"name": "Название",
|
||||
"namePlaceholder": "Мои уведомления",
|
||||
"botToken": "Токен бота",
|
||||
"chatId": "ID чата",
|
||||
"webhookUrl": "URL вебхука",
|
||||
"create": "Добавить",
|
||||
"test": "Тест",
|
||||
"delete": "Удалить",
|
||||
"confirmDelete": "Удалить этого получателя?",
|
||||
"noTargets": "Получатели уведомлений не настроены.",
|
||||
"testSent": "Тестовое уведомление отправлено!",
|
||||
"aiCaptions": "Включить AI подписи",
|
||||
"telegramSettings": "Настройки Telegram",
|
||||
"maxMedia": "Макс. медиафайлов",
|
||||
"maxGroupSize": "Макс. размер группы",
|
||||
"chunkDelay": "Задержка между группами (мс)",
|
||||
"maxAssetSize": "Макс. размер файла (МБ)",
|
||||
"videoWarning": "Предупреждение о размере видео",
|
||||
"disableUrlPreview": "Отключить превью ссылок",
|
||||
"sendLargeAsDocuments": "Отправлять большие фото как документы"
|
||||
},
|
||||
"users": {
|
||||
"title": "Пользователи",
|
||||
"description": "Управление аккаунтами (только админ)",
|
||||
"addUser": "Добавить пользователя",
|
||||
"cancel": "Отмена",
|
||||
"username": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"role": "Роль",
|
||||
"roleUser": "Пользователь",
|
||||
"roleAdmin": "Администратор",
|
||||
"create": "Создать",
|
||||
"delete": "Удалить",
|
||||
"confirmDelete": "Удалить этого пользователя?",
|
||||
"joined": "зарегистрирован"
|
||||
},
|
||||
"telegramBot": {
|
||||
"title": "Telegram боты",
|
||||
"description": "Регистрация и управление Telegram ботами",
|
||||
"addBot": "Добавить бота",
|
||||
"name": "Отображаемое имя",
|
||||
"namePlaceholder": "Бот семейных уведомлений",
|
||||
"token": "Токен бота",
|
||||
"tokenPlaceholder": "123456:ABC-DEF...",
|
||||
"noBots": "Ботов пока нет.",
|
||||
"chats": "Чаты",
|
||||
"noChats": "Чатов не найдено. Сначала отправьте сообщение боту.",
|
||||
"refreshChats": "Обновить",
|
||||
"selectBot": "Выберите бота",
|
||||
"selectChat": "Выберите чат",
|
||||
"private": "Личный",
|
||||
"group": "Группа",
|
||||
"supergroup": "Супергруппа",
|
||||
"channel": "Канал",
|
||||
"confirmDelete": "Удалить этого бота?",
|
||||
"commands": "Команды",
|
||||
"enabledCommands": "Включённые команды",
|
||||
"defaultCount": "Кол-во результатов",
|
||||
"responseMode": "Режим ответа",
|
||||
"modeMedia": "Медиа (отправка фото)",
|
||||
"modeText": "Текст (ссылки)",
|
||||
"botLocale": "Язык бота",
|
||||
"rateLimits": "Ограничения частоты",
|
||||
"rateSearch": "Кулдаун поиска",
|
||||
"rateFind": "Кулдаун поиска файлов",
|
||||
"rateDefault": "Кулдаун по умолчанию",
|
||||
"syncCommands": "Синхронизировать с Telegram",
|
||||
"discoverChats": "Обнаружить чаты из Telegram",
|
||||
"clickToCopy": "Нажмите, чтобы скопировать ID чата",
|
||||
"chatsDiscovered": "Чаты обнаружены",
|
||||
"chatDeleted": "Чат удалён"
|
||||
},
|
||||
"trackingConfig": {
|
||||
"title": "Конфигурации отслеживания",
|
||||
"description": "Определите, на какие события и файлы реагировать",
|
||||
"newConfig": "Новая конфигурация",
|
||||
"name": "Название",
|
||||
"namePlaceholder": "Основное отслеживание",
|
||||
"noConfigs": "Конфигураций отслеживания пока нет.",
|
||||
"eventTracking": "Отслеживание событий",
|
||||
"assetsAdded": "Добавлены файлы",
|
||||
"assetsRemoved": "Удалены файлы",
|
||||
"albumRenamed": "Альбом переименован",
|
||||
"albumDeleted": "Альбом удалён",
|
||||
"sharingChanged": "Изменение доступа",
|
||||
"trackImages": "Фото",
|
||||
"trackVideos": "Видео",
|
||||
"favoritesOnly": "Только избранные",
|
||||
"assetDisplay": "Отображение файлов",
|
||||
"includePeople": "Включать людей",
|
||||
"includeDetails": "Включать детали",
|
||||
"maxAssets": "Макс. файлов",
|
||||
"sortBy": "Сортировка",
|
||||
"sortOrder": "Порядок",
|
||||
"periodicSummary": "Периодическая сводка",
|
||||
"enabled": "Включено",
|
||||
"intervalDays": "Интервал (дни)",
|
||||
"startDate": "Дата начала",
|
||||
"times": "Время (ЧЧ:ММ)",
|
||||
"scheduledAssets": "Запланированные фото",
|
||||
"albumMode": "Режим альбомов",
|
||||
"limit": "Лимит",
|
||||
"assetType": "Тип файлов",
|
||||
"minRating": "Мин. рейтинг",
|
||||
"memoryMode": "Воспоминания (В этот день)",
|
||||
"test": "Тест",
|
||||
"confirmDelete": "Удалить эту конфигурацию отслеживания?",
|
||||
"sortNone": "Нет",
|
||||
"sortDate": "Дата",
|
||||
"sortRating": "Рейтинг",
|
||||
"sortName": "Имя",
|
||||
"orderDesc": "По убыванию",
|
||||
"orderAsc": "По возрастанию",
|
||||
"albumModePerAlbum": "По альбомам",
|
||||
"albumModeCombined": "Объединённый",
|
||||
"albumModeRandom": "Случайный",
|
||||
"assetTypeAll": "Все",
|
||||
"assetTypePhoto": "Фото",
|
||||
"assetTypeVideo": "Видео"
|
||||
},
|
||||
"templateConfig": {
|
||||
"title": "Конфигурации шаблонов",
|
||||
"description": "Определите формат уведомлений",
|
||||
"newConfig": "Новая конфигурация",
|
||||
"name": "Название",
|
||||
"namePlaceholder": "По умолчанию RU",
|
||||
"descriptionPlaceholder": "напр. Русские шаблоны для семейных уведомлений",
|
||||
"noConfigs": "Конфигураций шаблонов пока нет.",
|
||||
"eventMessages": "Сообщения о событиях",
|
||||
"assetsAdded": "Добавлены файлы",
|
||||
"assetsRemoved": "Удалены файлы",
|
||||
"albumRenamed": "Альбом переименован",
|
||||
"albumDeleted": "Альбом удалён",
|
||||
"sharingChanged": "Изменение доступа",
|
||||
"assetFormatting": "Форматирование файлов",
|
||||
"imageTemplate": "Шаблон фото",
|
||||
"videoTemplate": "Шаблон видео",
|
||||
"assetsWrapper": "Обёртка списка",
|
||||
"moreMessage": "Сообщение \"ещё\"",
|
||||
"peopleFormat": "Формат людей",
|
||||
"dateLocation": "Дата и место",
|
||||
"dateFormat": "Формат даты",
|
||||
"commonDate": "Общая дата",
|
||||
"uniqueDate": "Дата файла",
|
||||
"locationFormat": "Формат места",
|
||||
"commonLocation": "Общее место",
|
||||
"uniqueLocation": "Место файла",
|
||||
"favoriteIndicator": "Индикатор избранного",
|
||||
"scheduledMessages": "Запланированные сообщения",
|
||||
"periodicSummary": "Периодическая сводка",
|
||||
"periodicAlbum": "Элемент альбома",
|
||||
"scheduledAssets": "Запланированные фото",
|
||||
"memoryMode": "Воспоминания",
|
||||
"settings": "Настройки",
|
||||
"previewAs": "Предпросмотр как",
|
||||
"preview": "Предпросмотр",
|
||||
"variables": "Переменные",
|
||||
"assetFields": "Поля файла (в {% for asset in added_assets %})",
|
||||
"albumFields": "Поля альбома (в {% for album in albums %})",
|
||||
"confirmDelete": "Удалить эту конфигурацию шаблона?"
|
||||
},
|
||||
"templateVars": {
|
||||
"message_assets_added": { "description": "Уведомление о добавлении файлов в альбом" },
|
||||
"message_assets_removed": { "description": "Уведомление об удалении файлов из альбома" },
|
||||
"message_album_renamed": { "description": "Уведомление о переименовании альбома" },
|
||||
"message_album_deleted": { "description": "Уведомление об удалении альбома" },
|
||||
"periodic_summary_message": { "description": "Периодическая сводка альбомов (планировщик не реализован)" },
|
||||
"scheduled_assets_message": { "description": "Запланированная подборка фото (планировщик не реализован)" },
|
||||
"memory_mode_message": { "description": "«В этот день» — воспоминания (планировщик не реализован)" },
|
||||
"album_id": "ID альбома (UUID)",
|
||||
"album_name": "Название альбома",
|
||||
"album_url": "Публичная ссылка (пусто, если не расшарен)",
|
||||
"added_count": "Количество добавленных файлов",
|
||||
"removed_count": "Количество удалённых файлов",
|
||||
"change_type": "Тип изменения (assets_added, assets_removed, album_renamed, album_deleted)",
|
||||
"people": "Обнаруженные люди (список, {{ people | join(', ') }})",
|
||||
"added_assets": "Список файлов ({% for asset in added_assets %})",
|
||||
"removed_assets": "Список ID удалённых файлов (строки)",
|
||||
"shared": "Общий альбом (boolean)",
|
||||
"target_type": "Тип получателя: 'telegram' или 'webhook'",
|
||||
"has_videos": "Содержат ли добавленные файлы видео (boolean)",
|
||||
"has_photos": "Содержат ли добавленные файлы фото (boolean)",
|
||||
"old_name": "Прежнее название альбома (при переименовании)",
|
||||
"new_name": "Новое название альбома (при переименовании)",
|
||||
"old_shared": "Был ли общим до переименования (boolean)",
|
||||
"new_shared": "Является ли общим после переименования (boolean)",
|
||||
"albums": "Список альбомов ({% for album in albums %})",
|
||||
"assets": "Список файлов ({% for asset in assets %})",
|
||||
"date": "Текущая дата",
|
||||
"asset_id": "ID файла (UUID)",
|
||||
"asset_filename": "Имя файла",
|
||||
"asset_type": "IMAGE или VIDEO",
|
||||
"asset_created_at": "Дата создания (ISO 8601)",
|
||||
"asset_owner": "Имя владельца",
|
||||
"asset_owner_id": "ID владельца",
|
||||
"asset_description": "Описание (EXIF или пользовательское)",
|
||||
"asset_people": "Люди на этом файле (список)",
|
||||
"asset_is_favorite": "В избранном (boolean)",
|
||||
"asset_rating": "Рейтинг (1-5 или null)",
|
||||
"asset_latitude": "GPS широта (float или null)",
|
||||
"asset_longitude": "GPS долгота (float или null)",
|
||||
"asset_city": "Город",
|
||||
"asset_state": "Регион",
|
||||
"asset_country": "Страна",
|
||||
"asset_url": "Ссылка для просмотра (если расшарен)",
|
||||
"asset_download_url": "Ссылка для скачивания (если расшарен)",
|
||||
"asset_photo_url": "URL превью (только фото, если расшарен)",
|
||||
"asset_playback_url": "URL видео (только видео, если расшарен)",
|
||||
"album_name_field": "Название альбома (в списке альбомов)",
|
||||
"album_asset_count": "Всего файлов в альбоме",
|
||||
"album_url_field": "Ссылка на альбом",
|
||||
"album_shared": "Общий альбом"
|
||||
},
|
||||
"hints": {
|
||||
"periodicSummary": "Отправляет плановую сводку по всем отслеживаемым альбомам в указанное время. Подходит для ежедневных/еженедельных дайджестов.",
|
||||
"scheduledAssets": "Отправляет случайные или выбранные фото из альбомов по расписанию. Как ежедневная подборка фото.",
|
||||
"memoryMode": "\"В этот день\" — отправляет фото, сделанные в этот день в прошлые годы. Ностальгические воспоминания.",
|
||||
"favoritesOnly": "Включать только ассеты, отмеченные как избранные.",
|
||||
"maxAssets": "Максимальное количество ассетов в одном уведомлении.",
|
||||
"periodicStartDate": "Опорная дата для расчёта интервалов. Сводки отправляются каждые N дней от этой даты.",
|
||||
"times": "Время отправки уведомлений в формате ЧЧ:ММ. Для нескольких значений через запятую: 09:00,18:00",
|
||||
"albumMode": "По альбому: отдельное уведомление для каждого. Объединённый: одно уведомление со всеми. Случайный: выбирается один альбом.",
|
||||
"minRating": "Включать только ассеты с рейтингом не ниже указанного (0 = без фильтра).",
|
||||
"eventMessages": "Шаблоны уведомлений о событиях в реальном времени. Используйте {переменные} для динамического контента.",
|
||||
"assetFormatting": "Форматирование отдельных ассетов в сообщениях уведомлений.",
|
||||
"dateLocation": "Форматирование даты и местоположения. Использует синтаксис strftime для дат.",
|
||||
"scheduledMessages": "Шаблоны для периодических сводок, подборок фото и воспоминаний «В этот день».",
|
||||
"aiCaptions": "Использовать Claude AI для генерации описания уведомления вместо шаблона.",
|
||||
"maxMedia": "Максимальное количество фото/видео в одном уведомлении (0 = только текст).",
|
||||
"groupSize": "Медиагруппы Telegram содержат 2-10 элементов. Большие пакеты разбиваются на части.",
|
||||
"chunkDelay": "Задержка в миллисекундах между отправкой порций медиа. Предотвращает ограничение Telegram.",
|
||||
"maxAssetSize": "Пропускать файлы больше указанного размера в МБ. Лимит Telegram — 50 МБ.",
|
||||
"trackingConfig": "Управляет тем, какие события вызывают уведомления и как фильтруются ассеты.",
|
||||
"templateConfig": "Управляет форматом сообщений. Используются шаблоны по умолчанию, если не задано.",
|
||||
"scanInterval": "Как часто опрашивать провайдер на предмет изменений (в секундах). Меньше = быстрее обнаружение, но больше запросов к API.",
|
||||
"batchDuration": "Время накопления изменений перед отправкой уведомлений. 0 = отправлять сразу.",
|
||||
"defaultCount": "Сколько результатов возвращать, если пользователь не указал количество (1-20).",
|
||||
"responseMode": "Медиа: отправка фото. Текст: только имена файлов/ссылки. Медиа-режим использует больше трафика.",
|
||||
"botLocale": "Язык описаний команд в меню Telegram и ответов бота.",
|
||||
"rateLimits": "Кулдаун в секундах между использованиями команд в каждом чате. 0 = без ограничений."
|
||||
},
|
||||
"snack": {
|
||||
"providerSaved": "Провайдер сохранён",
|
||||
"providerDeleted": "Провайдер удалён",
|
||||
"trackerCreated": "Трекер создан",
|
||||
"trackerUpdated": "Трекер обновлён",
|
||||
"trackerDeleted": "Трекер удалён",
|
||||
"trackerPaused": "Трекер приостановлен",
|
||||
"trackerResumed": "Трекер возобновлён",
|
||||
"targetSaved": "Цель сохранена",
|
||||
"targetDeleted": "Цель удалена",
|
||||
"targetTestSent": "Тестовое уведомление отправлено",
|
||||
"templateSaved": "Шаблон сохранён",
|
||||
"templateDeleted": "Шаблон удалён",
|
||||
"trackingConfigSaved": "Конфигурация сохранена",
|
||||
"trackingConfigDeleted": "Конфигурация удалена",
|
||||
"botRegistered": "Бот зарегистрирован",
|
||||
"botDeleted": "Бот удалён",
|
||||
"userCreated": "Пользователь создан",
|
||||
"userDeleted": "Пользователь удалён",
|
||||
"passwordChanged": "Пароль изменён",
|
||||
"copied": "Скопировано",
|
||||
"genericError": "Что-то пошло не так",
|
||||
"commandsSaved": "Конфигурация команд сохранена",
|
||||
"commandsSynced": "Команды синхронизированы с Telegram",
|
||||
"targetLinked": "Получатель привязан",
|
||||
"targetUnlinked": "Получатель отвязан",
|
||||
"botUpdated": "Бот обновлён"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Загрузка...",
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить",
|
||||
"edit": "Редактировать",
|
||||
"description": "Описание",
|
||||
"close": "Закрыть",
|
||||
"confirm": "Подтвердить",
|
||||
"error": "Ошибка",
|
||||
"success": "Успешно",
|
||||
"none": "Нет",
|
||||
"noneDefault": "Нет (по умолчанию)",
|
||||
"loadError": "Не удалось загрузить данные",
|
||||
"headersInvalid": "Невалидный JSON",
|
||||
"language": "Язык",
|
||||
"theme": "Тема",
|
||||
"light": "Светлая",
|
||||
"dark": "Тёмная",
|
||||
"system": "Системная",
|
||||
"test": "Тест",
|
||||
"create": "Создать",
|
||||
"changePassword": "Сменить пароль",
|
||||
"currentPassword": "Текущий пароль",
|
||||
"newPassword": "Новый пароль",
|
||||
"passwordChanged": "Пароль успешно изменён",
|
||||
"expand": "Развернуть",
|
||||
"collapse": "Свернуть",
|
||||
"syntaxError": "Ошибка синтаксиса",
|
||||
"undefinedVar": "Неизвестная переменная",
|
||||
"line": "строка",
|
||||
"add": "Добавить"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user