const UI_TEXT_EN = { appName: 'Twitch VOD Manager', static: { navVods: 'Twitch VODs', navClips: 'Twitch Clips', navCutter: 'Video Cutter', navMerge: 'Merge Videos', navSettings: 'Settings', queueTitle: 'Queue', retryFailed: 'Retry', retryFailedHint: 'Retry failed downloads only', healthUnknown: 'System: Unknown', healthGood: 'System: Stable', healthWarn: 'System: Warning', healthBad: 'System: Problem', clearQueue: 'Clear', refresh: 'Refresh', streamerPlaceholder: 'Add streamer...', clipsHeading: 'Twitch Clip Download', clipsInfoTitle: 'Info', clipsInfoText: 'Supported formats:\n- https://clips.twitch.tv/ClipName\n- https://www.twitch.tv/streamer/clip/ClipName\n\nClips are saved in your download folder under "Clips/StreamerName/".', cutterSelectTitle: 'Select video', cutterBrowse: 'Browse', mergeTitle: 'Merge videos', mergeDesc: 'Select multiple videos to merge into one file. You can change the order before merging.', mergeAdd: '+ Add videos', designTitle: 'Design', themeLabel: 'Theme', themeLight: 'Light', languageLabel: 'Language', languageDe: 'German', languageEn: 'English', apiTitle: 'Twitch API', clientIdLabel: 'Client ID', clientSecretLabel: 'Client Secret', saveSettings: 'Save & Connect', downloadSettingsTitle: 'Download Settings', storageLabel: 'Storage Path', openFolder: 'Open', modeLabel: 'Download Mode', modeFull: 'Full VOD', modeParts: 'Split into parts', partMinutesLabel: 'Part Length (Minutes)', parallelDownloadsLabel: 'Parallel Downloads', parallelDownloads1: '1 (Default)', parallelDownloads2: '2 (Parallel)', performanceModeLabel: 'Performance Profile', performanceModeStability: 'Max Stability', performanceModeBalanced: 'Balanced', performanceModeSpeed: 'Max Speed', smartSchedulerLabel: 'Enable smart queue scheduler', smartSchedulerHint: 'Prefers shorter VODs and older queue entries first so the queue throughput stays steady. Disable to drain in strict insertion order.', streamerInvalid: 'Invalid Twitch username (4-25 chars, letters/digits/underscore).', apiHelpIntro: 'You need a Client ID and Client Secret from Twitch.', apiHelpLinkText: 'dev.twitch.tv/console/apps', openDebugLogFile: 'Open log file', storageCardTitle: 'Storage', storageCardIntro: 'Per-streamer disk usage in the current download folder. Live recordings are surfaced separately.', storageRefresh: 'Refresh', storageEmpty: 'Download folder is empty or unreadable.', storageScanning: 'Scanning...', storageSummary: 'Total: {files} files, {size} — Free disk: {free}', storageColumnFolder: 'Folder', storageColumnFiles: 'Files', storageColumnTotal: 'Total', storageColumnLive: 'Live', storageColumnChat: 'Chat', storageOpen: 'Open', storageOtherFolders: 'Other folders in download path', cleanupTitle: 'Auto-cleanup', cleanupIntro: 'Move recordings older than N days to an archive folder, or delete them outright. Sibling chat files (.chat.json/.chat.jsonl) travel with the video.', cleanupEnabledLabel: 'Enable auto-cleanup', cleanupDaysLabel: 'Age threshold (days)', cleanupTargetLabel: 'Scope', cleanupTargetLive: 'Live recordings only', cleanupTargetAll: 'All recordings', cleanupActionLabel: 'Action', cleanupActionArchive: 'Move to archive folder', cleanupActionDelete: 'Delete', cleanupDryRun: 'Preview', cleanupRunNow: 'Run now', cleanupReportPreview: 'Would touch {count} files (~{size}). No files have been moved or deleted.', cleanupReportDone: 'Processed {count} files, freed ~{size}.{failed}', cleanupReportFailedSuffix: ' {failed} failed.', cleanupReportEmpty: 'No recordings older than {days} days found.', discordCardTitle: 'Discord webhook', discordCardIntro: 'Send notifications to a Discord channel via webhook — handy for multi-device setups or a dedicated archive machine.', discordWebhookUrlLabel: 'Webhook URL', discordNotifyLiveStartLabel: 'Notify on live recording start', discordNotifyLiveEndLabel: 'Notify on live recording end', discordNotifyVodCompleteLabel: 'Notify on completed VOD download', autoResumeLiveRecordingLabel: 'Auto-resume live recording if streamlink crashes (max 5 retries)', autoMergeResumedPartsLabel: 'Auto-merge resumed-recording parts into one file (ffmpeg concat, no re-encode)', deletePartsAfterMergeLabel: 'Delete individual parts after successful merge', discordNotifyVodAutoQueuedLabel: 'Notify when a VOD gets auto-queued', autoVodCardTitle: 'Auto-VOD download', autoVodCardIntro: 'Streamers with the VOD toggle on are scanned for new Twitch VODs at the interval set here. New VODs within the age window are added to the download queue automatically.', autoVodPollMinutesLabel: 'Poll interval (minutes)', autoVodMaxAgeHoursLabel: 'Max age (hours)', autoVodScanNow: 'Scan now', autoRecordScanNow: 'Check live status', statsTitle: 'Archive statistics', statsIntro: 'Aggregated across the download folder. Live recordings live under {streamer}/live/, VOD downloads under {streamer}/. Scan time scales with file count.', statsRefresh: 'Refresh', statsScanning: 'Scanning...', statsScannedAt: 'Last scan', statsScannedAtNever: 'Not yet scanned', statsSummaryTitle: 'Overview', statsTopStreamersTitle: 'Top streamers (by size)', statsActivityTitle: 'Activity (last 30 days)', statsSizeBucketsTitle: 'Recording-size distribution', statsTotalRecordings: 'Recordings total', statsLiveRecordings: 'Live recordings', statsVodRecordings: 'VOD downloads', statsStreamers: 'Streamers', statsAvgSize: 'Avg. recording size', statsChatFiles: 'Chat files', statsFiles: 'files', statsActivityEmpty: 'No recordings in the last 30 days.', statsActivitySummary: '{count} recordings - {size} in the last 30 days', statsEmpty: 'No data.', statsNoRoot: 'Download folder not found. Set a download path in Settings first.', navStats: 'Statistics', navArchive: 'Archive', archiveTitle: 'Search archive', archiveIntro: 'Search by filename, streamer, or date string. Hits show recordings (Live + VOD); related chat and events files appear as companion buttons.', archiveAllTypes: 'All types', archiveTypeLive: 'Live recordings', archiveTypeVod: 'VOD downloads', archiveAllStreamers: 'All streamers', archiveSortDateDesc: 'Newest first', archiveSortDateAsc: 'Oldest first', archiveSortSizeDesc: 'Largest first', archiveSortSizeAsc: 'Smallest first', archiveSortNameAsc: 'Name (A-Z)', archiveSearchBtn: 'Search', archiveSearching: 'Scanning...', archiveSummary: '{matchCount} matches (scanned {scanned} files)', archiveSummaryTruncated: '{matchCount} matches (scanned {scanned} files, showing {shown} - tighten the query for more)', archiveNoMatches: 'No matches.', archiveNoRoot: 'Download folder not found. Set a download path in Settings first.', archiveSearchPlaceholder: 'Search...', archiveOpen: 'Open', archiveShowInFolder: 'Folder', archiveViewChat: 'Chat', archiveViewEvents: 'Events', backupCardTitle: 'Backup & Maintenance', backupCardIntro: 'Back up your configuration, restore it on another machine, or reset the list of already-downloaded VODs.', exportConfig: 'Export config', importConfig: 'Import config', resetDownloadedIds: 'Reset downloaded list', configExported: 'Configuration exported.', configExportFailed: 'Configuration export failed.', configImported: 'Configuration imported. Some changes may need a restart.', configImportFailed: 'Configuration import failed.', resetDownloadedConfirm: 'Reset the downloaded-VODs list? Cards will lose the green check mark, but no files are deleted.', resetDownloadedDone: 'Cleared {count} entries from the downloaded list.', duplicatePreventionLabel: 'Prevent duplicate queue entries', persistQueueLabel: 'Keep queue between app restarts', autoResumeQueueLabel: 'Auto-resume the queue on startup', autoResumeQueueHint: 'When enabled and the persisted queue has pending entries, downloads kick off ~5 seconds after the window opens. Disable to require an explicit Start click.', notifyEachCompletionLabel: 'Notify on every completed download', notifyEachCompletionHint: 'Off by default — long queues would otherwise spam the OS notifications panel. The end-of-queue summary notification fires either way.', streamlinkDisableAdsLabel: 'Skip Twitch ads while downloading', streamlinkDisableAdsHint: 'Passes --twitch-disable-ads to streamlink so mid-roll ads do not get embedded into the VOD output. Recommended on.', downloadChatReplayLabel: 'Save chat replay alongside each VOD (.chat.json)', downloadChatReplayHint: 'After a VOD download completes, fetches the public chat replay via Twitch GQL and saves it as JSON next to the video. Twitch keeps chat replay only as long as the VOD itself.', captureLiveChatLabel: 'Capture live chat during recording (.chat.jsonl)', captureLiveChatHint: 'Opens an anonymous IRC connection to Twitch chat during a live recording and appends every message to a sibling .chat.jsonl file (JSON Lines, one message per line) so a long capture can be killed mid-stream without corrupting earlier data.', logStreamEventsLabel: 'Log stream events during live recording (.events.jsonl)', logStreamEventsHint: 'Polls the streamer once a minute and writes title / game changes to a sibling .events.jsonl file. Useful for seeking inside long archived streams ("when did he switch to CS:GO?"). Cheap — one extra Helix/GQL hit per minute per active recording.', streamlinkQualityLabel: 'Stream quality', streamlinkQualityHint: 'Streamlink will try this quality first; if the VOD does not offer it, falls back to "best".', streamlinkQualityBest: 'Best (default)', streamlinkQualitySource: 'Source (original)', streamlinkQualityAudio: 'Audio only', downloadPathNotWritable: 'Download folder is not writable. Pick another folder or grant write permission.', streamerSectionTitle: 'Streamer', streamerListFilterPlaceholder: 'Filter...', streamerBulkRemoveTitle: 'Remove all (or filtered)', streamerBulkRemoveAll: 'Remove all {count} streamers from the list?', streamerBulkRemoveFiltered: 'Remove the {count} matching streamer(s) from the list?', cutterDropHint: 'Drop a video file here to load it.', metadataCacheMinutesLabel: 'Metadata Cache (Minutes)', filenameTemplatesTitle: 'Filename Templates', vodTemplateLabel: 'VOD Template', partsTemplateLabel: 'VOD Part Template', defaultClipTemplateLabel: 'Clip Template', filenameTemplateHint: 'Placeholders: {title} {id} {channel} {date} {part} {part_padded} {trim_start} {trim_end} {trim_length} {date_custom="yyyy-MM-dd"}', vodTemplatePlaceholder: '{title}.mp4', partsTemplatePlaceholder: '{date}_Part{part_padded}.mp4', defaultClipTemplatePlaceholder: '{date}_{part}.mp4', templateLintOk: 'Template check: OK', templateLintWarn: 'Unknown placeholder(s)', templateGuideButton: 'Template Guide', templateGuideTitle: 'Filename Template Guide', templateGuideIntro: 'Use placeholders for filenames and test your pattern with a live preview.', templateGuideTemplateLabel: 'Template', templateGuideOutputLabel: 'Live preview', templateGuideVarsTitle: 'Available placeholders', templateGuideVarCol: 'Placeholder', templateGuideDescCol: 'Description', templateGuideExampleCol: 'Example', templateGuideUseVod: 'Use VOD template', templateGuideUseParts: 'Use part template', templateGuideUseClip: 'Use clip template', templateGuideClose: 'Close', templateGuideContextVod: 'Context: Sample full VOD download', templateGuideContextParts: 'Context: Sample split VOD part', templateGuideContextClip: 'Context: Sample clip trim', templateGuideContextClipLive: 'Context: Current clip dialog selection', runtimeMetricsTitle: 'Runtime Metrics', runtimeMetricsRefresh: 'Refresh', runtimeMetricsExport: 'Export JSON', runtimeMetricsAutoRefresh: 'Auto refresh', runtimeMetricsLoading: 'Loading metrics...', runtimeMetricsError: 'Could not load runtime metrics.', runtimeMetricsExportDone: 'Runtime metrics exported successfully.', runtimeMetricsExportCancelled: 'Runtime metrics export cancelled.', runtimeMetricsExportFailed: 'Runtime metrics export failed.', runtimeMetricQueue: 'Queue', runtimeMetricMode: 'Mode', runtimeMetricRetries: 'Retries', runtimeMetricIntegrity: 'Integrity failures', runtimeMetricCache: 'Cache', runtimeMetricBandwidth: 'Bandwidth', runtimeMetricDownloads: 'Downloads', runtimeMetricActive: 'Active item', runtimeMetricLastError: 'Last error class', runtimeMetricUpdated: 'Updated', updateTitle: 'Updates', checkUpdates: 'Check for updates', preflightTitle: 'System Check', preflightRun: 'Run check', preflightFix: 'Auto-fix tools', preflightEmpty: 'No checks run yet.', preflightChecking: 'Checking...', preflightFixing: 'Fixing...', preflightReady: 'Everything is ready.', preflightInternet: 'Internet', preflightStreamlink: 'Streamlink', preflightFfmpeg: 'FFmpeg', preflightFfprobe: 'FFprobe', preflightPath: 'Download path', debugLogTitle: 'Live Debug Log', refreshLog: 'Refresh', autoRefresh: 'Auto refresh', notConnected: 'Not connected' }, status: { noLogin: 'No login (public mode)', connecting: 'Connecting...', connected: 'Connected', connectFailedPublic: 'Connection failed - public mode active' }, tabs: { vods: 'VODs', clips: 'Clips', cutter: 'Video Cutter', merge: 'Merge Videos', stats: 'Statistics', archive: 'Archive', settings: 'Settings' }, queue: { empty: 'No downloads in queue', start: 'Start', stop: 'Pause', resume: 'Resume', statusDone: 'Completed', statusFailed: 'Failed', statusRunning: 'Running', statusPaused: 'Paused', statusWaiting: 'Waiting', progressError: 'Error', progressReady: 'Ready', progressLoading: 'Loading...', readyToDownload: 'Ready to download', started: 'Download started', done: 'Done', failed: 'Download failed', speed: 'Speed', eta: 'ETA', part: 'Part', emptyAlert: 'Queue is empty. Add a VOD or clip first.', duplicateSkipped: 'This item is already active in the queue.', openFile: 'Open file', showInFolder: 'Show in folder', openFileFailed: 'Could not open the file (it may have been moved or deleted).', outputFilesLabel: '{count} output files', retryItem: 'Retry this item', viewChat: 'View chat', viewChatLoading: 'Loading chat...', viewChatFailed: 'Could not read chat file', viewChatCount: '{count} messages', viewChatTruncatedSuffix: ' (truncated)', viewEvents: 'View events', viewEventsCount: '{count} events', viewEventsEmpty: 'No events recorded.', eventStartedAs: 'Started as', eventEndedAfter: 'Ended after', eventTitleFromTo: 'Title: {from} -> {to}', eventGameFromTo: 'Game: {from} -> {to}', statusBarSummary: '{downloading} dl, {pending} queued', ctxMoveTop: 'Move to top', ctxMoveBottom: 'Move to bottom', ctxCopyUrl: 'Copy URL', ctxOpenOnTwitch: 'Open on Twitch', ctxRemove: 'Remove from queue', ctxCopiedUrl: 'URL copied to clipboard.', liveRecordingTitle: 'Live recording — captures until the stream ends', recordingHealth: { ok: 'Healthy — bytes flowing', stale: 'Stalled — no bytes recently (network blip or stream ending)', unknown: 'Waiting for first segment' }, eventRecordingResume: 'Recording resumed — starting part {part}' }, profile: { liveBadge: 'LIVE', partner: 'Partner', affiliate: 'Affiliate', followers: 'Followers', vods: 'VODs', vodsTooltip: 'VODs visible via Twitch API for this channel', lastStream: 'Last stream', openTwitch: 'Open on Twitch', openTwitchTooltip: 'Open this channel on twitch.tv', liveCardTooltip: 'Click to start a live recording right now', recordNow: 'Record now', refresh: 'Refresh', agoMinutes: '{n} min ago', agoHours: '{n} h ago', agoDays: '{n} d ago', agoMonths: '{n} mo ago', agoYears: '{n} y ago' }, streamers: { recordLiveTitle: 'Record this streamer live (captures until stream ends)', liveRecordingStarted: 'Live recording started for {streamer}.', liveRecordingOffline: '{streamer} is offline right now.', liveRecordingAlreadyActive: 'Already recording {streamer}.', liveRecordingFailed: 'Could not start live recording', autoRecordTitle: 'Auto-record: when this streamer goes live the app records automatically', autoRecordEnabled: 'Auto-record enabled for {streamer}. Polling for live state...', autoRecordDisabled: 'Auto-record disabled for {streamer}.', autoVodTitle: 'Auto-download new VODs (recently published) for this streamer', autoVodEnabled: 'Auto-VOD enabled for {streamer}. Will pick up new VODs.', autoVodDisabled: 'Auto-VOD disabled for {streamer}.', autoVodScanQueued: '{count} new VOD(s) auto-queued.', autoVodScanEmpty: 'No new VODs found.', autoRecordScanTriggered: 'Manual scan: {count} live recording(s) started.', autoRecordScanEmpty: 'Manual scan: no streamers currently live.', liveNowTooltip: 'Currently live on Twitch' }, vods: { noneTitle: 'No VODs', noneText: 'Select a streamer from the list.', loading: 'Loading VODs...', notFound: 'Streamer not found', noResultsTitle: 'No VODs found', noResultsText: 'This streamer has no VODs.', untitled: 'Untitled VOD', views: 'views', addQueue: '+ Queue', trimButton: 'Trim VOD', filterPlaceholder: 'Filter by title... (Ctrl+F)', filterClearTitle: 'Clear filter (Esc)', filterNoMatchTitle: 'No matches', filterNoMatchText: 'No VODs match the current filter.', filterMatchCount: '{shown} of {total} VODs', sortLabel: 'Sort:', sortDateDesc: 'Newest first', sortDateAsc: 'Oldest first', sortViewsDesc: 'Most viewed', sortDurationDesc: 'Longest first', sortDurationAsc: 'Shortest first', bulkSelectedCount: '{count} selected', bulkAddToQueue: '+ Queue', bulkAdding: 'Adding...', bulkClear: 'Clear', bulkAddedToQueue: 'Added {count} VODs to the queue.', bulkAddSkipped: 'No VODs were added (already in queue or invalid).', bulkMarkDownloaded: 'Mark as downloaded', bulkUnmark: 'Unmark', bulkMarkedDownloaded: 'Marked {count} VODs as downloaded.', bulkUnmarkedDownloaded: 'Removed {count} VODs from the downloaded list.', alreadyDownloaded: 'Already downloaded', hideDownloaded: 'Hide downloaded', hideDownloadedTitle: 'Hide VODs that are marked as already downloaded', openOnTwitch: 'Open on Twitch', ctxOpenOnTwitch: 'Open on Twitch', ctxCopyUrl: 'Copy VOD URL', ctxCopiedUrl: 'URL copied to clipboard.', ctxMarkDownloaded: 'Mark as downloaded', ctxUnmarkDownloaded: 'Unmark downloaded' }, clips: { dialogTitle: 'Trim VOD', dialogStart: 'Start:', dialogStartTime: 'Start time (HH:MM:SS):', dialogEnd: 'End:', dialogEndTime: 'End time (HH:MM:SS):', dialogDuration: 'Duration: ', dialogPartLabel: 'Start part number (optional, for continuation):', dialogPartHint: 'Leave empty = part 1', dialogFormatLabel: 'Filename format:', dialogConfirm: 'Add to queue', invalidDuration: 'Invalid!', endBeforeStart: 'End time must be greater than start time!', outOfRange: 'Time is outside VOD range!', enterUrl: 'Please enter a URL', loadingButton: 'Loading...', loadingStatus: 'Downloading...', downloadButton: 'Download clip', success: 'Download successful!', errorPrefix: 'Error: ', unknownError: 'Unknown error', formatSimple: '(default)', formatTimestamp: '(with timestamp)', formatParts: '(parts naming)', formatTemplate: '(custom template)', templateEmpty: 'Template cannot be empty in custom template mode.', templatePlaceholder: '{date}_{part}.mp4', templateHelp: 'Placeholders: {title} {id} {channel} {date} {part} {part_padded} {trim_start} {trim_end} {trim_length} {date_custom="yyyy-MM-dd"}' }, cutter: { videoInfoFailed: 'Could not read video info. Is FFprobe installed?', previewLoading: 'Loading preview...', previewUnavailable: 'Preview unavailable', cutting: 'Cutting...', cut: 'Cut', cutSuccess: 'Video cut successfully!', cutFailed: 'Failed to cut video.', infoDuration: 'Duration', infoResolution: 'Resolution', infoFps: 'FPS', infoSelection: 'Selection', startLabel: 'Start:', endLabel: 'End:' }, merge: { empty: 'No videos selected', merging: 'Merging...', merge: 'Merge', success: 'Videos merged successfully!', failed: 'Failed to merge videos.' }, mergeGroup: { btn: 'Merge & Split', phaseDownloading: 'Downloading VOD', phaseMerging: 'Merging...', phaseSplitting: 'Splitting Part', phaseCleanup: 'Cleaning up...', needMinTwo: 'Select at least 2 VODs', titleTwo: 'Merge: {title1} + {title2}', titleMany: 'Merge: {title1} + {count} more', metaLabel: '{count} VODs', }, updates: { bannerDefault: 'New version available!', latest: 'You are on the latest version!', checking: 'Checking for updates...', checkInProgress: 'Update check is already running.', readyToInstall: 'Update is ready to install.', checkFailed: 'Update check failed.', downloading: 'Downloading...', downloadInProgress: 'Update download is already running.', downloadFailed: 'Update download failed.', available: 'available!', downloadNow: 'Download now', downloadLabel: 'Download', ready: 'ready to install!', installNow: 'Install now & restart', modalAvailableTitle: 'Update available', modalAvailableMessage: 'Version {version} is available. Download it now?', modalReadyTitle: 'Update ready', modalReadyMessage: 'Version {version} has been downloaded. Install and restart now?', modalDismiss: 'No', modalDownloadConfirm: 'Yes, download', modalInstallConfirm: 'Yes, install', modalSkipVersion: 'Skip this version', changelogLabel: 'Changelog', showChangelog: 'Show changelog', hideChangelog: 'Hide changelog', noChangelog: 'No changelog available.', releasedLabel: 'Release' } } as const;