// Note: User-facing toast and modal copy is centralized here. const APP_MESSAGES = { actions: { raw_torrent: 'Add torrent', add: 'Add torrent', start: 'Start torrent', pause: 'Pause torrent', stop: 'Stop torrent', resume: 'Resume torrent', remove: 'Remove torrent', erase: 'Delete torrent', delete: 'Delete torrent', move: 'Move torrent', recheck: 'Force recheck', reannounce: 'Reannounce', set_label: 'Update label', label: 'Update label' }, toast: { operationStarted: ({ action }) => `${actionLabel(action)} started`, operationDone: ({ action }) => `${actionLabel(action)} done`, operationFailed: ({ action, error }) => `${actionLabel(action)} failed: ${error || 'unknown error'}`, actionQueued: ({ action, parts }) => Number(parts || 1) > 1 ? `${actionLabel(action)} queued in ${parts} parts` : `${actionLabel(action)} queued`, moveQueued: ({ parts, physical }) => Number(parts || 1) > 1 ? `Move queued in ${parts} parts` : physical ? 'Physical move queued' : 'Move queued', addQueued: () => 'Torrent add queued', addQueuedSkipped: ({ count }) => `Torrent add queued, skipped ${count} duplicate torrent(s)`, addTooLarge: () => 'One or more .torrent files exceed the current rTorrent XML-RPC upload limit. Open rTorrent config and set network.xmlrpc.size_limit to e.g. 16M.', dropOnlyTorrents: () => 'Drop .torrent files only', droppedAddedSkipped: ({ queued, skipped }) => `Added ${queued} torrent(s), skipped ${skipped} duplicate(s)`, droppedAdded: ({ queued }) => `Added ${queued} torrent(s)`, droppedSkipped: ({ skipped }) => `Skipped ${skipped} duplicate torrent(s)`, droppedNone: () => 'No torrents were added', noTorrentsSelected: () => 'No torrents selected', noTorrentSelected: () => 'No torrent selected', noFilesSelected: () => 'No files selected', downloadStarted: () => 'Download started', chunkActionDone: ({ action }) => `${actionLabel(action)} done`, trackerActionDone: ({ action }) => `${actionLabel(action)} done`, pathPickerUnavailable: () => 'Path picker is unavailable', pathEmpty: () => 'Path is empty', columnsSaved: () => 'Columns saved', recommendedColumnsApplied: () => 'Recommended columns applied', jobLogsCleared: ({ deleted }) => `Cleared ${deleted || 0} finished job log(s)`, emergencyJobLogsCleared: ({ deleted }) => `Emergency cleanup removed ${deleted || 0} job log(s)`, rtorrentConfigSaved: ({ updated }) => `rTorrent config saved (${updated || 0})`, rtorrentConfigReset: ({ removed }) => `rTorrent config reset (${removed || 0} override(s) removed)`, automationLogsDeleted: ({ deleted }) => `Automation logs deleted: ${deleted || 0}`, cleanupDone: ({ deleted }) => `Cleanup done (${deleted})`, plannerApplied: ({ dryRun, paused, resumed, limitsChanged }) => `${dryRun ? 'Planner dry-run' : 'Planner applied'}: paused ${ paused || 0 }, resumed ${resumed || 0}, limits ${ limitsChanged ? 'changed' : 'unchanged' }`, rssQueued: ({ queued }) => `RSS queued ${queued || 0} item(s)`, smartQueueCheckQueued: () => 'Smart Queue check queued. It will continue in the background.', automationForceRunDone: ({ count }) => `Automation force run done (${count || 0} torrent item(s))`, automationsApplied: ({ count, batches }) => batches ? `Automations applied ${count || 0} torrent(s) in ${ batches || 0 } batch(es)` : `Automations applied ${count || 0} item(s)`, torrentStatsError: ({ error }) => `Torrent stats: ${error}`, startupConfigApplied: ({ count }) => `Startup rTorrent config applied (${count || 0})`, startupConfigFailed: ({ error }) => `Startup rTorrent config: ${error}`, plannerSocketResult: ({ paused, resumed, dryRun }) => `Planner: paused ${paused || 0}, resumed ${resumed || 0}${ dryRun ? ' dry-run' : '' }` } }; function actionLabel(action) { const key = String(action || '').trim(); if (APP_MESSAGES.actions[key]) { return APP_MESSAGES.actions[key]; } return key ? key.replace(/[_-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) : 'Operation'; } function appMessage(key, params = {}) { const fn = key .split('.') .reduce((acc, part) => acc && acc[part], APP_MESSAGES); return typeof fn === 'function' ? fn(params) : String(fn || key); } function toastMessage(key, type = 'secondary', params = {}) { toast(appMessage(key, params), type); }