Files
Psychotoxical-psysonic/src/locales/en/settings.ts
T
Psychotoxical b950d4704b feat(cover): artist artwork from fanart.tv (off by default) (#1137)
* feat(cover): add artist_artwork_lookup table + accessors

Image-scraper P0 (design-review §12): additive library-SQLite migration 013
plus get/upsert/clear-per-server accessors for the external artist-artwork
lookup (fanart.tv). Render never reads it; the on-demand cover ensure path
and the mbid_ambiguous 24h negative cache use it. server_id = serverIndexKey.

* feat(cover): add fanart.tv + getArtistInfo2 provider layer

Image-scraper P0 (§7/§19/§23): new cover_cache/external.rs — Rust-side
getArtistInfo2 tag-MBID resolution plus fanart.tv v3/music URL + first
artistbackground fetch (BYOK client_key sent in addition to the project
api_key per fanart.tv ToS, §22). Extract a shared build_subsonic_url helper
in fetch.rs (cover URL behaviour unchanged). Add a dedicated low-concurrency
fanart_http_sem so external HTTP never starves Navidrome (§26). URL builders
unit-tested; wired into ensure_inner next.

* feat(cover): wire fanart.tv external branch into ensure_inner

Image-scraper P0 (§16): on-demand artist `fanart` ensures try fanart.tv
before the Navidrome fallback. MBID resolved Rust-side via getArtistInfo2
(§23, tag MBID); on a miss it falls through WITHOUT a .fetch-failed marker so
Navidrome stays the display fallback (§28). External tiers are written as
{tier}-fanart.webp in the same entity dir (same cacheKind, §16) — 2000 + 512
(matryoshka §17); peek prefers them for the fanart surface. Dedicated
low-concurrency fanart lane (§26); .miss-fanart ~30min negative marker.
Additive IPC args (externalArtworkEnabled, surfaceKind), off by default and
gated by PSYSONIC_FANART_KEY — inert until a render surface opts in (P1).
Quality gate (§11), name->MusicBrainz (§19), and lookup-table writes are P1.

* feat(cover): fanart-first peek for the fanart surface

Image-scraper P0: for an artist `fanart` ensure, the early peek serves only
the external {tier}-fanart.webp tiers; if none exist yet it returns None so
ensure runs the external branch (fetch fanart) instead of short-circuiting on
a cached Navidrome tier. Realises "fanart prioritised" (§18) for the opt-in
surface; Navidrome stays the fallback inside the branch's miss path.

* chore(cover): dev-only artist-fanart spike helper

DEV-only window.psyFanartSpike(name) — resolves an artist by name and fires
the real cover_cache_ensure with externalArtworkEnabled+surfaceKind=fanart to
verify the P0 pipeline against a live server (with PSYSONIC_FANART_KEY set).
Not wired in production.

* feat(cover): §11 quality gate for the fanart surface

Before an external fanart fetch, check whether a Navidrome tier already on
disk is an HQ ~16:9 image (width >= 1280, aspect 1.6-2.0) and skip the fetch
if so — square artist portraits never satisfy it, so the common case still
fetches. Reads tier dimensions only (no full decode). Rust consts per the
design review. Unit-tested predicate.

* feat(cover): persist fanart resolution in artist_artwork_lookup (§12)

Wire the lookup table as both the MBID resolution cache and the negative
cache: a cached MBID skips the getArtistInfo2 round-trip; no_mbid/mbid_ambiguous
back off 24h and miss 30min from updated_at before re-querying. Writes
hit/miss/no_mbid with mbid/mbid_source/provider; transient network errors are
not cached. All store reads/writes run off the async executor via
spawn_blocking and no-op before login. Store reached via app.try_state::<LibraryRuntime>().

* feat(cover): compile-time fanart key fallback + album/name IPC args

A runtime PSYSONIC_FANART_KEY still wins (dev), else the key baked in at build
time via option_env! (release). Add additive artistName/albumTitle ensure args
as context for the §19 name->MusicBrainz fallback (inert until the render
passes them). Library backfill passes None.

* feat(settings): add External Artwork Scraper toggle under Integrations

Master toggle (themeStore, off by default per §20) in a new Integrations
subsection, alongside the other opt-in third-party categories. Contacts
fanart.tv only when enabled. i18n across all 9 locales.

* feat(cover): wire fanart background into the fullscreen player (§28)

New useArtistFanart hook resolves a fanart.tv 16:9 background via a dedicated
cover_cache_ensure (surfaceKind=fanart) — it bypasses the shared peek/disk-src
cache (the {tier}-fanart.webp surface is keyed differently) and reuses
coverDiskUrl for the asset URL. Fullscreen background priority is now
fanart -> Navidrome artist image (cover pipeline) -> album cover; the live
useFsArtistPortrait probe is deleted (§28). Additive ensure opts
(surfaceKind/artistName/albumTitle); externalArtworkEnabled is derived in
ensureArgsFromRef from the master toggle and restricted to the artist fanart
surface, so plain cover ensures are unaffected.

* feat(cover): generalize external surface to fanart + banner (§13)

surfaceKind='banner' fetches the fanart.tv musicbanner array -> {tier}-banner.webp
in the same entity dir; fanart stays the 16:9 artistbackground. The ensure
branch, peek, lookup rows (per-surface surface_kind), miss marker
(.miss-<surface>) and tier suffix are all surface-parameterised. The §11
quality gate stays fanart-only (the banner strip has its own aspect). Unit
test for the surface->fanart JSON key map.

* feat(artist): fanart banner on the artist-detail header (§13, Option B)

The artist-detail header gets an album-detail-style background layer: fanart.tv
banner (musicbanner) -> the 16:9 fanart background cropped to the strip ->
empty. Both via a shared useArtistExternalImage hook (useArtistBanner /
useArtistFanart); each fetches on demand and shares the Rust cache, so the
header and the fullscreen player warm each other's images. The header is its
own stacking context (isolation) so a z-index:-1 banner clips behind the avatar
+ meta with no content wrapper; the album-style framing (padding/radius/clip)
is applied only when a banner is shown, so the off-by-default case stays
pixel-identical.

* refactor(artist): reuse album-detail header structure for the fanart banner

The artist-detail header now uses the same album-detail-* container classes as
AlbumHeader (header/bg/overlay/content/hero) with the fanart banner as the
background; the Back button moves inside the header. A surgical
`artist-detail-bleed` cancels the artist page's .content-body padding so the
banner is full-bleed to the container edges, matching the album header exactly
instead of the earlier inset card. Reverts the experimental artist-specific bg
CSS.

* feat(cover): drop artist_artwork_lookup rows on clear-cover-cache (§12/B.4)

cover_cache_clear_server already removed the server's whole cover dir (so the
{tier}-fanart.webp / -banner.webp tiers + .miss-* markers go with it); also
clear the artist_artwork_lookup rows for that server (off-thread) so no stale
resolution state lingers. Automatic toggle-off purge deferred — turning the
toggle off already hides external artwork (render is gated), and explicit
cache-clear now cleans external state too.

* feat(cover): name->MusicBrainz album-confirmed MBID resolution (§19)

When getArtistInfo2 has no tag MBID and the ensure carries the artist name +
an album in context (fullscreen), one MusicBrainz release-search query resolves
the artist MBID: the primary artist across score>=90 releases wins, conflicting
ids -> mbid_ambiguous (24h backoff), none -> no_mbid. Sends the required
User-Agent; a single-permit musicbrainz_sem + >=1s spacing holds us under MB's
rate limit. mbid_source=musicbrainz persisted. Banner surface (no album
context) correctly skips this. Pure classify/escape helpers unit-tested.

* fix(cover): enable banner surface in ensureArgsFromRef

externalEnsureFields only set externalArtworkEnabled for surfaceKind 'fanart',
so the 'banner' surface never fired — the artist-detail header always fell back
to the fanart image instead of the fanart.tv musicbanner. Both external artist
surfaces (fanart/banner) now enable the external branch.

* feat(settings): optional BYOK personal fanart key field

Add an optional personal fanart.tv API key field to the External Artwork
Scraper block (shown when the toggle is on): a masked input, a saved/in-use
status line, and the simple note that it is sent in addition to the app key.
Persisted in themeStore and plumbed through cover_cache_ensure
(externalArtworkByok); Rust prefers the settings key, falling back to the
PSYSONIC_FANART_CLIENT_KEY dev env. i18n x9.

* fix(cover): resolve artist-page fanart image collision on navigation

The artist-detail header keyed its fanart/banner hooks on the route `id`,
which flips immediately on navigation while `artist`/`albums` refetch a beat
later. The mismatched ensure wrote the previous artist's image under the new
artist's id (e.g. Sepultura's image under Lordi's id).

- key on the loaded `artist.id`, not the route `id`, so id/name/album always
  describe the same artist
- pick the §19 album context from an album that actually belongs to this
  artist (`albums.find(a => a.artistId === artist.id)`), so a stale album can't
  run a mismatched name→MusicBrainz query or cache a wrong `no_mbid`
- reset `src` on every input change in `useArtistExternalImage` so a previous
  artist's image never lingers while the new one resolves

* fix(cover): strip trailing album qualifier before MusicBrainz lookup

Library titles like "Show No Mercy (2004 Remastered)" or "Album [Deluxe
Edition]" failed the §19 MusicBrainz release query, blocking name-confirmed
MBID resolution. `normalize_album_for_mb` strips a single trailing
parenthetical/bracketed qualifier; leading qualifiers (e.g. "(What's the
Story) Morning Glory?") are left intact. Unit-tested.

* fix(cover): don't cache no_mbid when album context is unavailable

The banner ensure could fire before the artist's albums loaded, with no album
in context. The old code cached `no_mbid` there and the 24h backoff then
blocked the later ensure that arrived *with* album context. Could-not-attempt
is not tried-and-failed: the no-album branch now returns without persisting.

* fix(cover): don't emit tier-ready for external fanart/banner surfaces

`try_external_fanart` emitted `cover:tier-ready` with the `{tier}-{surface}.webp`
path. That event is keyed by the canonical cover key (cacheKind/cacheEntityId/
tier, no surface), so the frontend `useCoverArtBridge` listener seeded the
Navidrome artist cover's disk-src cache with the external image — leaking
fanart/banner into the plain artist cover (avatar, fullscreen "navidrome-artist"
fallback) even with the scraper toggled off.

Remove the emit: the fanart/banner hooks read the path from the
`cover_cache_ensure` return value, so no event is needed. (No disk-level
overwrite — the suffixed files are never matched by `tier_exists`; this was
frontend disk-src-cache cross-contamination.)

* fix(cover): wait for the final external background before showing it, with fade-in

The fullscreen player and artist-detail header flashed several backgrounds in
sequence while the fanart resolved (upscaled album cover → Navidrome artist
image → fanart), and the artist header could show the fanart first and then
swap to the banner.

- the album cover is no longer a background source — it only feeds the
  foreground thumbnail
- the external-artwork hooks return `{ src, pending }` so callers can tell
  "still resolving" (hold back) from "resolved, no image" (fall back now)
- fullscreen background: scraper on → fanart, empty while it resolves, Navidrome
  artist image only on a confirmed miss; scraper off → Navidrome artist image
- artist header: the banner is preferred — nothing shows while it resolves
  (no fanart flash), fanart is the fallback only once the banner misses
- both backgrounds preload the chosen image and fade it in (`onLoad` plus a
  `ref` `complete` check so an already-cached image, whose load event can fire
  before React attaches the handler, still appears). The header fade is a
  scoped inline opacity so the shared `album-detail-bg` class is untouched.

* ci(release): pass PSYSONIC_FANART_KEY into the macOS + Linux builds

* refactor(cover): extract external-artwork ensure into its own module

Pure code move: the on-demand fanart/banner fetch, the quality gate, the
surface-aware peek and the lookup-table cache move from cover_cache/mod.rs
into cover_cache/external_ensure.rs. Behaviour unchanged; mod.rs 1877 -> 1488.

* chore(cover): remove dev-only fanart spike helper

The real render wiring now exercises the external ensure branch, so the
dev-only window.psyFanartSpike helper is redundant.

* feat(cover): purge external artwork on opt-out (B3)

New cover_cache_purge_external command: when the External Artwork toggle is
turned off, drop every fetched {tier}-{provider}.webp, .miss-{provider}
marker and artist_artwork_lookup row across all configured servers, leaving
the canonical Navidrome covers intact. Opting out now removes the
third-party-sourced data instead of just hiding it (design-review §9/§12/B.4).

* docs: changelog, credits and what's new for artist fanart (PR #1137)
2026-06-20 21:04:21 +02:00

715 lines
43 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const settings = {
title: 'Settings',
language: 'Language',
languageEn: 'English',
languageDe: 'Deutsch',
languageEs: 'Español',
languageFr: 'Français',
languageNl: 'Nederlands',
languageNb: 'Norsk',
languageRu: 'Русский',
languageZh: '中文',
languageRo: 'Română',
languageJa: '日本語',
font: 'Font',
fontHintOpenDyslexic: 'Dyslexia-friendly · no Chinese support',
theme: 'Theme',
appearance: 'Appearance',
servers: 'Servers',
serverName: 'Server Name',
serverUrl: 'Server URL',
serverUrlPlaceholder: '192.168.1.100:4533 or https://music.example.com',
serverAlternateUrl: 'Second address (optional)',
serverAlternateUrlPlaceholderPublic: 'https://music.example.com',
serverAlternateUrlPlaceholderLocal: '192.168.1.100:4533',
serverAlternateUrlHintAddPublic: 'You can add a public address for access outside your home network.',
serverAlternateUrlHintAddLocal: 'You can add a local address for faster connection at home.',
serverBothLanError: 'Both addresses are local — keep one or add a public address.',
dualAddressVerifying: 'Checking that both addresses point at the same server…',
dualAddressMismatch: 'The two addresses point at different servers. Check spelling and try again.',
dualAddressInsufficient: 'Could not confirm both addresses are the same server. Remove one address to continue.',
dualAddressUnreachable: 'Could not reach {{host}}. Check the address and your network, then try again.',
urlRemigrationTitle: 'Move your library data?',
urlRemigrationMessage: 'Changing the address will move all library, analysis, and cover-cache data from {{oldKey}} to {{newKey}}. This cannot be undone.',
urlRemigrationConfirm: 'Move data',
urlRemigrationProgress: 'Moving library data…',
urlRemigrationFailureInspect: 'Could not check what would be moved. Try again, or open the debug log.',
urlRemigrationFailureRun: 'Moving the library data failed. Your saved data is unchanged — try again.',
urlRemigrationFailureCoverRename: 'Library data was moved, but cover files could not be renamed. Covers will rebuild on next access.',
shareUsesLocalUrl: 'Use local address in share links',
shareUsesLocalUrlDesc: 'Affects Orbit invites and library shares. Default: public address.',
serverUsername: 'Username',
serverPassword: 'Password',
addServer: 'Add Server',
addServerTitle: 'Add New Server',
editServer: 'Edit',
editServerTitle: 'Edit Server',
useServer: 'Use',
deleteServer: 'Delete',
noServers: 'No servers saved.',
serverActive: 'Active',
serverVersionUnknown: 'Server version unknown',
confirmDeleteServer: 'Delete server "{{name}}"?',
confirmDeleteServerLibrary: 'Also delete this server\'s local library index? Click Cancel to keep the cached copy for offline use.',
serverConnecting: 'Connecting…',
serverConnected: 'Connected!',
serverFailed: 'Connection failed.',
testBtn: 'Test Connection',
testingBtn: 'Testing…',
serverCompatible: 'Built for Navidrome. Other Subsonic-compatible servers (Gonic, Airsonic, …) may work with reduced functionality, since Psysonic uses many Navidrome-specific API endpoints.',
userMgmtTitle: 'User Management',
userMgmtDesc: 'Manage users on this server. Requires admin privileges.',
userMgmtNoAdmin: 'You need admin privileges to manage users on this server.',
userMgmtLoadError: 'Failed to load users.',
userMgmtLoadFriendly: 'Server didn\'t answer — this is usually a one-off.',
userMgmtRetry: 'Retry',
userMgmtEmpty: 'No users found.',
userMgmtYouBadge: 'You',
userMgmtAdminBadge: 'Admin',
userMgmtAddUser: 'Add User',
userMgmtAddUserTitle: 'Add New User',
userMgmtEditUserTitle: 'Edit User',
userMgmtUsername: 'Username',
userMgmtName: 'Display Name',
userMgmtEmail: 'Email',
userMgmtPassword: 'Password',
userMgmtPasswordEditHint: 'Enter a new password to update it.',
userMgmtRoleAdmin: 'Admin',
userMgmtLibraries: 'Libraries',
userMgmtLibrariesAdminHint: 'Admin users automatically have access to all libraries.',
userMgmtLibrariesEmpty: 'No libraries available on this server.',
userMgmtLibrariesValidation: 'Select at least one library.',
userMgmtLibrariesUpdateError: 'User saved, but library assignment failed',
userMgmtNoLibraries: 'No libraries assigned',
userMgmtNeverSeen: 'Never',
userMgmtSave: 'Save',
userMgmtCancel: 'Cancel',
userMgmtDelete: 'Delete',
userMgmtEdit: 'Edit',
userMgmtConfirmDelete: 'Delete user "{{username}}"? This cannot be undone.',
userMgmtCreateError: 'Failed to create user.',
userMgmtUpdateError: 'Failed to update user.',
userMgmtDeleteError: 'Failed to delete user.',
userMgmtCreated: 'User created.',
userMgmtUpdated: 'User updated.',
userMgmtDeleted: 'User deleted.',
userMgmtValidationMissing: 'Username, display name and password are required.',
userMgmtValidationMissingIdentity: 'Username and display name are required.',
userMgmtMagicStringGenerate: 'Generate magic string',
userMgmtSaveAndMagicString: 'Save and get magic string',
userMgmtMagicStringPasswordNavHint:
'Navidrome will save this password for the user. If it differs from the current one, the server will update the login password.',
userMgmtMagicStringPlaintextWarning:
'Share the magic string carefully: it contains an unencrypted password (encoding is not encryption). Anyone with the full string can sign in as this user.',
userMgmtMagicStringCopied: 'Magic string copied to clipboard.',
userMgmtMagicStringCopyFailed: 'Could not copy to clipboard.',
userMgmtMagicStringLoginFailed: 'Password check failed — credentials could not be verified.',
userMgmtMagicStringModalTitle: 'Generate magic string',
userMgmtMagicStringModalDesc: 'Enter the Subsonic password for "{{username}}". It is included in the copied magic string.',
userMgmtMagicStringModalConfirm: 'Copy string',
audiomuseTitle: 'AudioMuse-AI (Navidrome)',
audiomuseBadge: 'AudioMuse-AI',
audiomuseDesc:
'Turn on if this server has the <pluginLink>AudioMuse-AI Navidrome plugin</pluginLink> configured. Enables Instant Mix from tracks and uses server-side similar artists instead of Last.fm on artist pages.',
audiomuseStatusActive: 'Active',
audiomuseStatusChecking: 'Checking…',
audiomuseStatusNotDetected: 'Not detected',
audiomuseStatusProbeFailed: 'Probe failed',
audiomuseIssueHint:
'Instant Mix failed recently — check the Navidrome plugin and AudioMuse API. Similar artists fall back to Last.fm when the server returns none.',
connected: 'Connected',
failed: 'Failed',
eqTitle: 'Equalizer',
eqEnabled: 'Enable Equalizer',
eqPreset: 'Preset',
eqPresetCustom: 'Custom',
eqPresetBuiltin: 'Built-in Presets',
eqPresetCustomGroup: 'My Presets',
eqSavePreset: 'Save as Preset',
eqPresetName: 'Preset name…',
eqDeletePreset: 'Delete preset',
eqResetBands: 'Reset to Flat',
eqPreGain: 'Pre-gain',
eqResetPreGain: 'Reset pre-gain',
eqAutoEqTitle: 'AutoEQ Headphone Lookup',
eqAutoEqPlaceholder: 'Search headphone / IEM model…',
eqAutoEqSearching: 'Searching…',
eqAutoEqNoResults: 'No results found',
eqAutoEqError: 'Search failed',
eqAutoEqRateLimit: 'GitHub rate limit reached — try again in a minute',
eqAutoEqFetchError: 'Failed to fetch EQ profile',
behavior: 'App Behavior',
groupTray: 'Tray',
groupLinuxRendering: 'Linux rendering',
groupClock: 'Clock',
groupDisplay: 'Display',
groupWindow: 'Window',
cacheTitle: 'Max. Storage Size',
cacheDesc: 'In-browser cache for cover art and artist images (IndexedDB). Oldest entries are removed automatically when space runs low.',
cacheUsedImages: 'Images:',
cacheUsedOffline: 'Offline tracks:',
cacheUsedHot: 'Size on disk:',
hotCacheTrackCount: 'Tracks in cache:',
cacheMaxLabel: 'Max. size',
imageCacheScopeLabel: 'Scope',
imageCacheSubTitle: 'In-browser previews',
coverDiskCacheSubTitle: 'On-disk cover cache',
coverCacheStrategyTitle: 'Cover art cache',
coverCacheStrategyDesc: 'You can choose a strategy for each server: Lazy — load covers as needed; Aggressive — load as needed plus extra background preloading.',
coverCacheStrategyLabel: 'Strategy',
coverCacheStrategyServerLabel: 'Server',
coverCacheStrategyProgressLabel: 'Cache',
coverCacheStrategyActionsLabel: 'Actions',
coverCacheStrategyLazy: 'Lazy',
coverCacheStrategyLazyDesc: 'Loads covers only when they are needed.',
coverCacheStrategyAggressive: 'Aggressive',
coverCacheStrategyAggressiveDesc: 'Lazy behaviour plus extra background preloading.',
coverCacheStrategyDiskUsage: 'On disk: {{size}}',
coverCacheStrategyProgressValue: '{{percent}}% ({{done}}/{{total}}) · {{size}}',
coverCacheStrategyClearAction: 'Clear cover cache',
coverCacheStrategyClearTitle: 'Clear cover cache?',
coverCacheStrategyClearDesc: 'Remove all cached cover images for {{server}}. They will be re-downloaded when needed.',
coverCacheStrategyClearConfirm: 'Clear',
coverCacheStrategyClearCancel: 'Cancel',
coverCacheStrategyClearSuccess: 'Cover cache cleared for this server.',
coverCacheStrategyClearError: 'Failed to clear cover cache.',
coverCacheStrategyServerRemoved: 'removed server',
cacheClearBtn: 'Clear Cache',
cacheClearWarning: 'Clears all cached cover and artist images from in-browser storage. They will be re-downloaded when needed.',
cacheClearConfirm: 'Clear image cache',
cacheClearCancel: 'Cancel',
waveformCacheCleared: 'Waveform cache cleared ({{count}} rows).',
waveformCacheClearFailed: 'Failed to clear waveform cache.',
storageTabTitle: 'Offline & cache',
mediaDirTitle: 'Media folder',
mediaDirDesc: 'Root folder for local playback. Offline library uses library/; queue prefetch uses cache/.',
mediaDirDefault: 'Default (App Data)',
mediaDirChange: 'Change folder',
mediaDirClear: 'Reset to default',
mediaDirHint: 'New downloads use this location. Existing files stay at their original paths until removed.',
offlineStorageTitle: 'Offline library storage',
offlineDirTitle: 'Offline Library (In-App)',
offlineDirDesc: 'Storage location for tracks you make available offline within Psysonic.',
offlineDirDefault: 'Default (App Data)',
offlineDirChange: 'Change Directory',
offlineDirClear: 'Reset to Default',
offlineDirHint: 'New downloads will use this location. Existing downloads remain at their original path.',
hotCacheTitle: 'Hot playback cache',
hotCacheDisclaimer: 'Preloads upcoming queue tracks and keeps previous ones. When enabled, uses disk space and network.',
hotCacheDirDefault: 'Default (App Data)',
hotCacheDirChange: 'Change folder',
hotCacheDirClear: 'Reset to default',
hotCacheDirHint: 'Changing the folder resets the in-app index; files already on disk stay where they are until you remove them.',
hotCacheEnabled: 'Enable hot playback cache',
hotCacheMaxMb: 'Maximum cache size',
hotCacheDebounce: 'Queue change debounce',
hotCacheDebounceImmediate: 'Immediate',
hotCacheDebounceSeconds: '{{n}} s',
hotCacheClearBtn: 'Clear hot cache',
audioOutputDevice: 'Audio Output Device',
audioOutputDeviceDesc: 'Select which audio device Psysonic plays through. Changes take effect immediately and restart the current track.',
audioOutputDeviceDefault: 'System Default',
audioOutputDeviceRefresh: 'Refresh device list',
audioOutputDeviceOsDefaultNow: 'current system output',
audioOutputDeviceListError: 'Could not load the audio device list.',
audioOutputDeviceNotInCurrentList: 'not in current list',
hiResTitle: 'Native Hi-Res Playback',
hiResEnabled: 'Enable native hi-res playback',
hiResDesc: "Plays each track at its original sample rate instead of resampling everything to 44.1 kHz, switching the audio device to match the file (88.2 kHz and up). Enable only if your hardware and network reliably handle high sample rates.",
showArtistImages: 'Show Artist Images',
showArtistImagesDesc: 'Load and display artist images in the Artists overview. Disabled by default to reduce server disk I/O and network load on large libraries.',
showOrbitTrigger: 'Show "Orbit" in the header',
showOrbitTriggerDesc: 'The top-bar trigger for starting or joining a shared listening session. Hide it if you don\'t use Orbit — you can turn it back on here.',
showTrayIcon: 'Show Tray Icon',
showTrayIconDesc: 'Display the Psysonic icon in the system notification area / menu bar.',
minimizeToTray: 'Minimize to Tray',
minimizeToTrayDesc: 'When closing the window, keep Psysonic running in the system tray instead of quitting.',
clockFormat: 'Clock Format',
clockFormatDesc: 'Wall-clock display used by the queue ETA and the sleep-timer preview.',
clockFormatAuto: 'Auto (system locale)',
clockFormatTwentyFour: '24-hour',
clockFormatTwelve: '12-hour (AM/PM)',
preloadMiniPlayer: 'Preload mini player',
preloadMiniPlayerDesc: 'Build the mini player window in the background at app start so it shows content instantly on first open. Uses a little extra memory.',
discordRichPresence: 'Discord Rich Presence',
discordRichPresenceDesc: 'Show the currently playing track on your Discord profile. Requires Discord to be running.',
discordRichPresenceNotice: 'Heads up: this is Psysonic\'s built-in Discord Rich Presence. If you\'d rather use the official Navidrome Discord Rich Presence plugin, leave this switched off and enable "Show in Now Playing" further down this page instead.',
useCustomTitlebar: 'Custom title bar',
useCustomTitlebarDesc: 'Replace the system title bar with a built-in one that matches the app theme. Disable to use the native GNOME/GTK title bar.',
windowButtonStyle: 'Window buttons',
windowButtonStyleDesc: 'Pick the look of the minimize, maximize and close buttons.',
windowButtonsDots: 'Dots',
windowButtonsDotsGlyph: 'Dots + icons',
windowButtonsFlat: 'Flat',
windowButtonsPill: 'Pill',
windowButtonsOutline: 'Outline',
windowButtonsGlyph: 'Minimal',
showMinimizeButton: 'Show minimize button',
showMinimizeButtonDesc: 'Turn off to show only the maximize and close buttons.',
linuxWebkitSmoothScroll: 'Smooth wheel (Linux)',
linuxWebkitSmoothScrollDesc: 'On: inertial scroll. Off: line-by-line, GTK-style.',
linuxWebkitInputForceRepaint: 'Repaint inputs on focus (Linux)',
linuxWebkitInputForceRepaintDesc: 'Workaround for WebKitGTK 2.50.x where text fields freeze when clicked. Search icons flicker briefly on focus.',
linuxWaylandTextRender: 'Wayland text rendering (Linux)',
linuxWaylandTextRenderDesc:
'Font smoothing in the UI updates immediately. WebKit hardware acceleration (sharp / GPU presets) is saved and applied on the next app start — changing it live can freeze WebKitGTK on some setups.',
linuxWaylandTextRenderBalanced: 'Balanced',
linuxWaylandTextRenderSharp: 'Sharp (CPU-friendly)',
linuxWaylandTextRenderGpu: 'GPU-first',
linuxWaylandTextRenderMinimal: 'Minimal (default CSS smoothing)',
discordCoverTitle: 'Cover art source',
discordCoverDesc: 'Where the album artwork on your Discord profile comes from.',
discordCoverNone: 'None (app icon only)',
discordCoverServer: 'Server (via album info)',
discordCoverApple: 'Apple Music',
discordOptions: 'Advanced Discord options',
discordTemplates: 'Custom text templates',
discordTemplatesDesc: 'Customize what information is shown on your Discord profile. Variables: {title}, {artist}, {album}',
discordTemplateDetails: 'Primary line (details)',
discordTemplateState: 'Secondary line (state)',
discordTemplateLargeText: 'Album tooltip (largeText)',
discordTemplateName: 'User list line (name)',
nowPlayingEnabled: 'Show in Now Playing',
nowPlayingEnabledDesc: 'Broadcast your currently playing track to the server\'s live listener view. Disable to stop sending playback data.',
nowPlayingPluginNote: 'Required when using the official Navidrome Discord Rich Presence plugin.',
externalArtwork: 'External Artwork Scraper',
externalArtworkDesc: 'Fetch high-resolution artist backgrounds from fanart.tv for the fullscreen player and artist pages. Off by default.',
externalArtworkNote: "Contacts fanart.tv, sending the artist's MusicBrainz id.",
externalArtworkByokTitle: 'Personal API key (BYOK)',
externalArtworkByokDesc: 'Optional. Enter your own fanart.tv personal API key here — it is sent in addition to the app key.',
externalArtworkByokSaved: 'Saved — sent with the app key while the scraper is on.',
enableBandsintown: 'Bandsintown tour dates',
enableBandsintownDesc: 'Show upcoming concerts for the current artist in the Info tab. Data is fetched from the public Bandsintown API.',
lyricsServerFirst: 'Prefer server lyrics',
lyricsServerFirstDesc: 'Check server-provided lyrics (embedded tags, sidecar files) before querying LRCLIB. Disable to use LRCLIB first.',
enableNeteaselyrics: 'Netease Cloud Music lyrics',
enableNeteaselyricsDesc: 'Use Netease Cloud Music as a last-resort lyrics source when server and LRCLIB both return nothing. Best coverage for Asian and international music.',
lyricsSourcesTitle: 'Lyrics Sources',
lyricsSourcesDesc: 'Choose which sources to query for lyrics and in what order. Drag to reorder. Disabled sources are skipped entirely.',
lyricsSourceServer: 'Server',
lyricsSourceLrclib: 'LRCLIB',
lyricsSourceNetease: 'Netease Cloud Music',
lyricsYouLyPlus: 'YouLyPlus (Karaoke)',
lyricsYouLyPlusDesc: 'Word-by-word sync from a community backend (Apple Music, Spotify, Musixmatch, QQ). Tried first; the enabled sources below act as fallback.',
lyricsSourcesFallbackHint: 'Used as fallback when YouLyPlus finds nothing. Drag to reorder.',
lyricsSourcesPrimaryHint: 'Tried in order. Drag to reorder. All off = lyrics disabled.',
lyricsStaticOnly: 'Show lyrics as static text only',
lyricsStaticOnlyDesc: 'Render synced lyrics without auto-scroll and without word highlighting.',
downloadsTitle: 'ZIP Export & Archiving',
downloadsFolderDesc: 'Destination folder for albums you download as a ZIP file to your computer.',
downloadsDefault: 'Default Downloads Folder',
pickFolder: 'Select',
pickFolderTitle: 'Select Download Folder',
clearFolder: 'Clear download folder',
logout: 'Logout',
aboutTitle: 'About Psysonic',
aboutDesc: 'A modern desktop music player built for Navidrome. Uses the Subsonic API plus Navidrome-specific extensions. Built on Tauri v2 with a native Rust audio engine — lightweight and fast, yet packed with features: waveform seekbar, synchronized lyrics, Last.fm integration, 10-band EQ, crossfade, gapless playback, Replay Gain, genre browsing, and a large library of themes.',
aboutLicense: 'License',
aboutLicenseText: 'GNU GPL v3 — free to use, modify, and distribute under the same license.',
aboutRepo: 'Source Code on GitHub',
aboutVersion: 'Version',
aboutBuiltWith: 'Built with Tauri · React · TypeScript · Rust/rodio',
aboutMaintainersLabel: 'Maintainers',
aboutReleaseNotesLabel: 'Release notes',
aboutReleaseNotesLink: "Open this version's what's-new",
aboutContributorsLabel: 'Contributors',
showChangelogOnUpdate: "Show 'What's New' on update",
showChangelogOnUpdateDesc: "Show a discreet changelog banner above Now Playing after an update. Click opens the release notes; X dismisses it.",
libraryGridMaxColumnsTitle: 'Library card grids',
libraryGridMaxColumnsPerfHint: 'Higher values pack more tiles per row and can increase layout and painting work—noticeable on very large libraries or slower devices.',
libraryGridMaxColumnsRangeLabel: 'Maximum columns ({{min}}{{max}})',
libraryGridMaxColumnsDesc: 'Applies to album, artist, playlist, radio, offline, and other card-based library views. Lower values use larger tiles and are usually easier on the CPU.',
libraryIndexTitle: 'Local library index (preview)',
libraryIndexDesc: 'Keep a local copy of each server\'s track database so browsing and search stay fast and work offline. Initial sync runs for every configured server; offline servers are retried automatically.',
libraryIndexDeltaHint: 'Background delta sync checks bound servers every 30 seconds and runs when due — typically every 545 minutes depending on library size and load.',
libraryIndexEnable: 'Enable local library index',
libraryIndexEnableAllDesc: 'Index and sync all configured servers in the background.',
libraryIndexNoServer: 'Add a server first.',
libraryIndexServerListTitle: 'Indexed servers',
libraryIndexAllExcluded: 'All servers are excluded from sync. Re-enable the index or add a server.',
libraryIndexServerOffline: 'Server offline — sync deferred',
libraryIndexServerSyncing: 'Syncing…',
libraryIndexFullResync: 'Full resync',
libraryIndexDeltaSync: 'Delta sync',
libraryIndexExcludeServer: 'Exclude from sync',
libraryIndexExcludingServer: 'Excluding…',
libraryIndexExcludedTitle: 'Excluded from sync',
libraryIndexIncludeServer: 'Include again',
libraryIndexIncludingServer: 'Including…',
libraryIndexStatus: 'Status',
libraryIndexStatusIdle: 'Idle',
libraryIndexStatusProbing: 'Checking server…',
libraryIndexStatusInitial: 'Initial sync…',
libraryIndexStatusReady: 'Ready ({{count}} tracks)',
libraryIndexStatusError: 'Error — see logs',
libraryIndexProgressIngest: 'Indexed {{count}} tracks…',
libraryIndexProgressVerify: 'Verified {{checked}} ({{deleted}} removed)',
libraryIndexSyncNow: 'Sync now',
libraryIndexVerify: 'Verify library integrity',
libraryIndexCancel: 'Cancel',
libraryIndexAutoReconcile: 'Auto-reconcile on count drop',
libraryIndexAutoReconcileDesc: 'Automatically check for removed tracks when the server reports fewer than expected.',
libraryIndexSyncError: 'Library sync failed: {{error}}',
libraryIndexBindError: 'Could not enable index: {{error}}',
analyticsStrategyTitle: 'Analytics strategies',
analyticsStrategyDesc: 'Choose the analysis style for the library.',
analyticsStrategyLabel: 'Strategy',
analyticsStrategyLazy: 'Lazy',
analyticsStrategyLazyDesc: 'Analyze only when tracks play or enter the hot/offline cache — minimal CPU and network use.',
analyticsStrategyAdvanced: 'Aggressive',
analyticsStrategyAdvancedDesc: 'More aggressive background scan. Once finished, you get BPM for every track plus instant loudness + waveform loads.',
analyticsStrategyPriorityTitle: 'Analysis priority (always enforced)',
analyticsStrategyPriorityHigh: '1 — Currently playing: download + analyze first (playback bytes, loudness backfill, or HTTP when needed).',
analyticsStrategyPriorityMiddle: '2 — Up next: the next ~5 queue tracks and hot-cache neighbours — analyze from cached/downloaded bytes when possible; HTTP backfill only on miss.',
analyticsStrategyPriorityLow: '3 — Library batch (Aggressive only): automatic background queue at the tail; refilled by watermark (~3× workers) without waiting for an empty queue.',
analyticsStrategyServerLabel: 'Server',
analyticsStrategyProgressLabel: 'Analysis progress:',
analyticsStrategyProgressValue: '{{percent}}% ({{done}}/{{total}})',
analyticsStrategyActionsLabel: 'Actions',
analyticsStrategyParallelismLabel: 'Parallel workers',
analyticsStrategyParallelismValue: '{{n}} threads',
analyticsStrategyParallelismDesc: 'How many tracks to download and analyze at once (HTTP + CPU). Higher values speed up library catch-up but use more network and CPU.',
analyticsStrategyAdvancedWarning: 'Aggressive uses more CPU and network bandwidth while the library batch runs in the background.',
analyticsStrategyServerRemoved: 'Removed server',
analyticsStrategyClearAction: 'Clear analysis',
analyticsStrategyClearTitle: 'Clear analysis?',
analyticsStrategyClearDesc: 'Clearing analysis for {{server}} removes cached waveforms and loudness. Re-adding this server later may require long re-indexing.',
analyticsStrategyClearCancel: 'Cancel',
analyticsStrategyClearConfirm: 'Clear analysis',
analyticsStrategyClearSuccess: 'Analysis cleared for the removed server.',
analyticsStrategyClearError: 'Failed to clear analysis for the removed server.',
analyticsStrategyProgressEmptyAfterFailed: '100% (0/0, blocked by failed tracks)',
analyticsFailedTracksOpenTitle: 'Failed tracks: {{count}}',
analyticsFailedTracksTitle: 'Failed analysis tracks — {{server}}',
analyticsFailedTracksDesc: 'These tracks repeatedly failed decode/enrichment and are excluded from automatic background analysis.',
analyticsFailedTracksLoading: 'Loading failed track list…',
analyticsFailedTracksEmpty: 'No failed tracks for this server.',
analyticsFailedTracksClose: 'Close',
analyticsFailedTracksExport: 'Export list',
analyticsFailedTracksExportSuccess: 'Failed track list exported ({{count}}).',
analyticsFailedTracksExportError: 'Could not export failed track list.',
analyticsFailedTracksRescan: 'Rescan failed tracks',
analyticsFailedTracksRescanSuccess: 'Rescan scheduled for {{count}} failed tracks.',
analyticsFailedTracksRescanError: 'Could not rescan failed tracks.',
analyticsFailedTracksLoadError: 'Could not load failed tracks.',
randomMixTitle: 'Random Mix Blacklist',
luckyMixMenuTitle: 'Show Lucky Mix in menu',
luckyMixMenuDesc: 'Enables Lucky Mix in Build a Mix and as a separate menu item when split navigation is on. Visible only when AudioMuse is enabled on the active server.',
randomMixBlacklistTitle: 'Custom Filter Keywords',
randomMixBlacklistDesc: 'Songs are excluded when any keyword matches their genre, title, album, or artist (active when the checkbox above is on).',
randomMixBlacklistPlaceholder: 'Add keyword…',
randomMixBlacklistAdd: 'Add',
randomMixBlacklistEmpty: 'No custom keywords added yet.',
randomMixHardcodedTitle: 'Built-in keywords (active when checkbox is on)',
tabAudio: 'Audio',
tabStorage: 'Offline & Cache',
tabAppearance: 'Appearance',
tabThemes: 'Themes',
themesYourThemesTitle: 'Your Themes',
themesCvdTooltip: 'Colour-blind safe — deuteranopia, protanopia, tritanopia',
themeAnimationWarning: 'This theme uses animations that may raise CPU usage on your setup (Nvidia/Linux or compositing off).',
themeStoreTitle: 'Theme Store',
themeStoreSubmitText: 'Made your own theme? Share it with the community — more info in the themes repository.',
themeStoreSubmitLink: 'Open the themes repository',
themeStoreNetworkNotice: 'The Theme Store loads its catalogue and previews from GitHub. No personal data is sent.',
themeStoreStatsNotice: 'Last-changed dates refresh once a day (around 04:17 UTC).',
themeStoreSearchPlaceholder: 'Search themes…',
themeStoreFilterMode: 'Filter by mode',
themeStoreModeAll: 'All',
themeStoreModeDark: 'Dark',
themeStoreModeLight: 'Light',
themeStoreRefresh: 'Refresh',
themeStoreLoading: 'Loading themes…',
themeStoreError: 'Could not load the theme store. Check your connection.',
themeStoreRetry: 'Retry',
themeStoreOfflineTitle: 'The Theme Store is offline',
themeStoreEmpty: 'No themes match your filters.',
themeStoreActive: 'Active',
themeStoreEnlarge: 'Enlarge preview',
themeStoreSortNewest: 'Newest',
themeStoreSortName: 'Alphabetical',
themeStoreAnimAll: 'All',
themeStoreAnimAnimated: 'Animated',
themeStoreAnimStatic: 'Static',
themeStoreByAuthor: 'by {{author}}',
themeStoreLastChanged: 'Last changed',
themeMigrationNoticeTitle: 'Theme moved to the Store',
themeMigrationNoticeBody: '{{themes}} is no longer bundled with the app. You can reinstall it from the Theme Store (Settings → Themes), or pick another theme.',
themeMigrationNoticeOpen: 'Open Theme Store',
themeStoreOffline: 'Showing the last cached catalogue — you appear to be offline.',
themeStoreInstall: 'Install',
themeStoreInstalling: 'Installing…',
themeStoreApply: 'Apply',
themeStoreUpdate: 'Update',
themeStoreUpdating: 'Updating…',
themeStoreUninstall: 'Uninstall',
themeStoreInstallFailed: 'Install failed',
themeStorePagePrev: 'Previous page',
themeStorePageNext: 'Next page',
themeStorePageStatus: 'Page {{page}} of {{total}}',
themeImportTitle: 'Import a theme',
themeImportButton: 'Import theme…',
themeImporting: 'Importing…',
themeImportHint: 'Load a theme packaged as a .zip (manifest.json + theme.css).',
themeImportSuccess: '"{{name}}" imported.',
themeImportErrorTitle: 'This theme could not be imported',
themeImportErrorBody: "It doesn't match the Psysonic theme format. It may have been made for a different version, or the file is incomplete — check that you picked the right .zip, or ask the theme's author.",
themeImportErrorDetails: 'Technical details',
themeImportConfirmTitle: 'Install theme?',
themeImportConfirmBody: 'Install "{{name}}" by {{author}}?',
themeImportConfirmRisk: "Imported themes aren't reviewed — only install themes you trust.",
tabLibrary: 'Library',
tabServers: 'Servers',
tabLyrics: 'Lyrics',
tabPersonalisation: 'Personalisation',
tabIntegrations: 'Integrations',
inputKeybindingsTitle: 'Keyboard shortcuts',
aboutContributorsCount_one: '{{count}} contribution',
aboutContributorsCount_other: '{{count}} contributions',
searchPlaceholder: 'Search settings…',
searchNoResults: 'No settings match your search.',
integrationsPrivacyTitle: 'Privacy notice',
integrationsPrivacyBody: 'All integrations on this tab are <strong>opt-in</strong> and send data to external services or to your Navidrome server when enabled. Last.fm receives your listening history, Discord shows the currently playing track in your profile, Bandsintown is queried per artist to fetch tour dates, and the Now-Playing share publishes your current track to other users of your Navidrome server. If you don\'t want any of this, simply leave the corresponding section disabled.',
homeCustomizerTitle: 'Mainstage',
queueSettingsTitle: 'Queue Settings',
queueModeTitle: 'Queue Display Mode',
queueModeQueueSub: 'Show only upcoming tracks. The current track stays in the header and leaves the list once it has played.',
queueModePlaylistSub: 'Keep the whole queue in the list with the current track highlighted at the top; played tracks stay.',
queueModeTimelineSub: 'Center the current track, with played history above and upcoming tracks (in shuffle order) below.',
queueToolbarTitle: 'Queue Toolbar',
queueToolbarReset: 'Reset to default',
queueToolbarSeparator: 'Separator',
sidebarTitle: 'Sidebar',
sidebarReset: 'Reset to default',
artistLayoutTitle: 'Artist page sections',
artistLayoutDesc: 'Drag to reorder, toggle to hide individual sections of the artist page. Sections without data are skipped automatically.',
artistLayoutReset: 'Reset to default',
artistLayoutBio: 'Artist biography',
artistLayoutTopTracks: 'Top tracks',
artistLayoutSimilar: 'Similar artists',
artistLayoutAlbums: 'Albums',
artistLayoutFeatured: 'Also featured on',
playlistLayoutTitle: 'Playlist page layout',
playlistLayoutDesc: 'Show or hide individual elements on the playlist page.',
playlistLayoutReset: 'Reset to default',
playerBarTitle: 'Player bar',
playerBarReset: 'Reset to default',
playerBarStarRating: 'Star rating',
playerBarFavorite: 'Favorite (heart)',
playerBarLastfmLove: 'Love button',
playerBarPlaybackRate: 'Playback speed',
playerBarEqualizer: 'Equalizer',
playerBarMiniPlayer: 'Mini player',
playbackRateTitle: 'Playback speed',
playbackRateEnabled: 'Enable playback speed',
playbackRateEnabledDesc: 'Change speed globally for all tracks. Not applied to radio or previews.',
playbackRateStrategy: 'Strategy',
playbackRateStrategySpeed: 'Speed',
playbackRateStrategyVarispeed: 'Varispeed',
playbackRateStrategyVarispeedSemitones: 'Semitones',
playbackRateStrategyPreserve: 'Pitch shift',
playbackRateStrategySpeedTip: 'Changes tempo; pitch is corrected automatically.',
playbackRateStrategyVarispeedTip: 'Speed and pitch move together, like tape. Pitch follows speed: 12 × log2(speed).',
playbackRateStrategyVarispeedSemitonesTip: 'Varispeed dialled directly in semitones. Speed = 2^(st/12).',
playbackRateStrategyPreserveTip: 'Adjustable tempo plus a manual pitch offset (±12 st).',
playbackRateSpeed: 'Speed',
playbackRatePitch: 'Pitch',
playbackRateDerivedPitch: 'Pitch shift from speed: {{value}}',
playbackRateDerivedSpeed: 'Speed from semitones: {{value}}',
playbackRateFineStep: 'Fine adjustment',
playbackRateFineStepDesc: 'Smaller slider steps: 0.01× for speed and 0.01 st for pitch.',
playbackRateAutoPitch: 'Pitch is corrected automatically.',
playbackRateHint: 'Speed keeps pitch natural. Varispeed changes pitch with speed. Varispeed (semitones) sets that pitch change directly in semitones. Pitch shift adds a manual offset. Uses more CPU than varispeed. Not for radio, previews, or Orbit.',
playbackRateNeutral: 'At 1.0× with no pitch offset, playback is normal.',
playbackRateOrbitPaused: 'Not applied during Orbit sessions — playback stays at 1.0× for sync.',
playbackRateOrbitPausedShort: 'Orbit: 1.0× for sync.',
advancedMode: 'Advanced',
advancedModeTooltip: 'Show advanced options across all Settings tabs. Community contributions that do not necessarily reflect the design philosophy of the Psysonic maintainers.',
advancedBadge: 'Advanced',
sidebarDrag: 'Drag to reorder',
sidebarFixed: 'Always visible',
nowPlayingTopTitle: 'Now Playing at top',
nowPlayingTopDesc: 'Pin the "Now Playing" entry to the top of the sidebar instead of the bottom.',
randomNavSplitTitle: 'Split Mix navigation',
randomNavSplitDesc: 'Show "Random Mix", "Random Albums", and "Lucky Mix" as separate sidebar entries instead of the "Build a Mix" hub.',
tabInput: 'Input',
tabUsers: 'Users',
tabSystem: 'System',
loggingTitle: 'Logging',
loggingModeDesc: 'Controls backend log verbosity in the terminal.',
loggingModeOff: 'Off',
loggingModeNormal: 'Normal',
loggingModeDebug: 'Debug',
loggingExport: 'Export logs',
loggingExportSuccess: 'Logs exported ({{count}} lines).',
loggingExportError: 'Could not export logs.',
ratingsSectionTitle: 'Ratings',
ratingsSkipStarTitle: 'Skip for 1 star',
ratingsSkipStarDesc:
'After several skips in a row, set the track to 1★. Only for tracks not yet rated.',
ratingsSkipStarThresholdLabel: 'Skips',
ratingsMixFilterTitle: 'Filter by rating',
ratingsMixFilterDesc:
'Filter low-rated items in {{mix}} and {{albums}}. Click the selected star again to turn off the threshold.',
ratingsMixMinSong: 'Songs',
ratingsMixMinAlbum: 'Albums',
ratingsMixMinArtist: 'Artists',
ratingsMixMinThresholdAria: 'Minimum stars: {{label}}',
backupTitle: 'Backup & Restore',
backupExport: 'Export settings',
backupExportDesc: 'Saves all settings, server profiles, Last.fm config, theme, EQ and keybindings to a .psybkp file. Passwords are stored in plaintext — keep the file secure.',
backupImport: 'Import settings',
backupImportDesc: 'Restores settings from a .psybkp file. The app will reload after import.',
backupImportConfirm: 'This will overwrite all current settings. Continue?',
backupImportAny: 'Import backup',
backupImportAnyConfirm: 'Import selected backup file and apply its contents (settings, library databases, or full). Continue?',
backupSuccess: 'Backup saved',
backupImportSuccess: 'Settings restored — reloading…',
backupImportError: 'Invalid or corrupted backup file.',
backupModeFull: 'Full',
backupModeLibrary: 'Library databases',
backupModeConfig: 'Config',
backupFullDesc: 'Archives settings and library databases into one backup file.',
backupFullExport: 'Export full backup',
backupFullImport: 'Import full backup',
backupFullImportConfirm: 'This will overwrite settings, replace current library databases, and move them to .bak. Continue?',
backupFullExportSuccess: 'Full backup exported.',
backupFullImportSuccess: 'Full backup imported — reloading…',
backupFullImportError: 'Could not import full backup.',
backupLibraryExport: 'Export databases',
backupLibraryExportDesc: 'Creates a compressed archive with clean SQLite snapshots of library databases.',
backupLibraryImport: 'Import databases',
backupLibraryImportDesc: 'Restores library databases from an archive file. Current databases are moved to .bak before switch.',
backupLibraryImportConfirm: 'This will replace current library databases and move them to .bak. Continue?',
backupLibraryExportSuccess: 'Databases backup exported.',
backupLibraryImportSuccess: 'Databases backup imported.',
backupLibraryImportError: 'Could not import databases backup.',
backupOverlayExportTitle: 'Creating backup…',
backupOverlayImportTitle: 'Restoring backup…',
backupOverlayHint: 'You can keep the window open while we process files. Input is temporarily locked.',
shortcutsReset: 'Reset to defaults',
shortcutListening: 'Press a key…',
shortcutUnbound: '—',
globalShortcutsTitle: 'Global Shortcuts',
globalShortcutsNote: 'Work system-wide even when Psysonic is in the background. Requires Ctrl, Alt, or Super as a modifier.',
shortcutClear: 'Clear',
shortcutPlayPause: 'Play / Pause',
shortcutNext: 'Next track',
shortcutPrev: 'Previous track',
shortcutVolumeUp: 'Volume up',
shortcutVolumeDown: 'Volume down',
shortcutSeekForward: 'Seek forward 10s',
shortcutSeekBackward: 'Seek backward 10s',
shortcutToggleQueue: 'Toggle queue',
shortcutOpenFolderBrowser: 'Open {{folderBrowser}}',
shortcutFullscreenPlayer: 'Fullscreen player',
shortcutNativeFullscreen: 'Native fullscreen',
shortcutOpenMiniPlayer: 'Open mini player',
shortcutStartSearch: 'Start a search',
shortcutStartAdvancedSearch: 'Start an advanced search',
shortcutToggleSidebar: 'Toggle Sidebar',
shortcutMuteSound: 'Mute sound',
shortcutToggleEqualizer: 'Open / Toggle Equalizer',
shortcutToggleRepeat: 'Toggle Repeat',
shortcutOpenNowPlaying: 'Open "Now Playing"',
shortcutShowLyrics: 'Show Lyrics',
shortcutFavoriteCurrentTrack: 'Add current track to favorites',
shortcutOpenHelp: 'Help',
playbackTitle: 'Playback',
replayGain: 'Replay Gain',
replayGainDesc: 'Normalize track volume using ReplayGain metadata',
replayGainMode: 'Mode',
replayGainAuto: 'Auto',
replayGainAutoDesc: 'Picks album gain when neighbouring queue tracks are from the same album, otherwise track gain.',
replayGainTrack: 'Track',
replayGainAlbum: 'Album',
replayGainPreGain: 'Pre-Gain (tagged files)',
replayGainPreGainDesc: 'Universal boost added on top of every ReplayGain calculation. Use to compensate if your library feels too quiet overall.',
replayGainFallback: 'Fallback (untagged / radio)',
replayGainFallbackDesc: 'Applied to tracks (and radio streams) that have no ReplayGain tags at all, so untagged content does not pop louder than the rest.',
normalization: 'Normalization',
normalizationDesc: 'Even out perceived loudness across tracks, albums and radio.',
normalizationOff: 'Off',
normalizationReplayGain: 'ReplayGain',
normalizationLufs: 'LUFS',
loudnessTargetLufs: 'Target LUFS',
loudnessTargetLufsDesc: 'Reference loudness all tracks are matched to. Lower numbers = louder output. Streaming services typically sit around -14 LUFS.',
loudnessPreAnalysisAttenuation: 'Pre-analysis attenuation',
loudnessPreAnalysisAttenuationDesc:
'Extra quieting until loudness for this track is saved. Streaming then uses rough guesses, not a full measurement. 0 dB = off; lower = quieter. The number on the right is the effective dB for your current target.',
loudnessPreAnalysisAttenuationRef: 'Effective {{eff}} dB with target {{tgt}} LUFS.',
loudnessPreAnalysisAttenuationReset: 'Reset to default',
loudnessFirstPlayNote:
'First play of a brand-new track may briefly drift in volume while it is being measured. The next play uses the cached measurement and is rock-solid. Upcoming queue tracks are usually pre-analysed during the previous song, so this rarely happens in practice.',
crossfade: 'Crossfade',
crossfadeSecs: '{{n}} s',
autoDj: 'AutoDJ',
autoDjDesc: 'No fixed duration — AutoDJ follows the actual audio, blending real fade-outs and intros instead of a set number of seconds. For reliable results, enable the Hot playback cache.',
autodjSmoothSkip: 'Smooth skip',
autodjSmoothSkipDesc: 'When you skip to the next or previous track while playing, blend into it with the same AutoDJ rules (overlap length, intro trim) from your current position instead of an abrupt cut. Works best when the next track is already buffered.',
gapless: 'Gapless Playback',
transitionsTitle: 'Track transitions',
transitionsDesc: 'How consecutive tracks blend together. Only one mode can be active at a time.',
transitionOff: 'Off',
queueBehaviourTitle: 'Queue behaviour',
preservePlayNextOrder: 'Preserve "Play Next" order',
preservePlayNextOrderDesc: 'Newly added Play Next items queue up behind earlier ones instead of jumping in front.',
trackPreviewsTitle: 'Track Previews',
trackPreviewsToggle: 'Enable track previews',
trackPreviewsDesc: 'Show inline Play and Preview buttons in tracklists for a quick mid-song sample.',
trackPreviewStart: 'Start position',
trackPreviewStartDesc: 'How far into the track the preview starts (% of track length).',
trackPreviewDuration: 'Duration',
trackPreviewDurationDesc: 'How long each preview plays before it stops.',
trackPreviewDurationSecs: '{{n}} s',
trackPreviewLocationsTitle: 'Where previews appear',
trackPreviewLocationsDesc: 'Pick which lists show inline preview buttons.',
trackPreviewLocation_suggestions: 'In playlist suggestions',
trackPreviewLocation_albums: 'In album tracklists',
trackPreviewLocation_playlists: 'In playlists',
trackPreviewLocation_favorites: 'In favorites',
trackPreviewLocation_artist: 'In artist top tracks',
trackPreviewLocation_randomMix: 'In Random Mix',
nextTrackBufferingTitle: 'Buffering',
infiniteQueue: 'Infinite Queue',
infiniteQueueDesc: 'Automatically append random tracks when the queue runs out',
experimental: 'Experimental',
sidebarLyricsStyle: 'Lyrics scroll style',
sidebarLyricsStyleClassic: 'Classic',
sidebarLyricsStyleClassicDesc: 'Scroll active line to center.',
sidebarLyricsStyleApple: 'Apple Music-like',
sidebarLyricsStyleAppleDesc: 'Active line anchors near the top with smooth spring transitions.',
seekbarStyle: 'Seekbar Style',
seekbarStyleDesc: 'Choose the look of the player seek bar',
seekbarTruewave: 'Truewave',
seekbarPseudowave: 'Pseudowave',
seekbarLinedot: 'Line & Dot',
seekbarBar: 'Bar',
seekbarThick: 'Thick Bar',
seekbarSegmented: 'Segmented',
seekbarNeon: 'Neon Glow',
seekbarPulsewave: 'Pulse Wave',
seekbarParticletrail: 'Particle Trail',
seekbarLiquidfill: 'Liquid Fill',
seekbarRetrotape: 'Retro Tape',
themeSchedulerTitle: 'Auto-Switch Theme',
themeSchedulerEnable: 'Enable Theme Scheduler',
themeSchedulerEnableSub: 'Automatically switch between two themes based on the time of day',
themeSchedulerDayTheme: 'Day Theme',
themeSchedulerDayStart: 'Day Starts At',
themeSchedulerNightTheme: 'Night Theme',
themeSchedulerNightStart: 'Night Starts At',
themeSchedulerActiveHint: 'Theme Scheduler is active - theme changes are managed automatically.',
visualOptionsTitle: 'Visual Options',
coverArtBackground: 'Cover Art Background',
coverArtBackgroundSub: 'Show blurred cover art as background in album/playlist headers',
playlistCoverPhoto: 'Playlist Cover Photo',
playlistCoverPhotoSub: 'Show cover photo grid in playlist detail view',
showBitrate: 'Show Bitrate',
showBitrateSub: 'Display audio bitrate in track listings',
floatingPlayerBar: 'Floating Player Bar',
floatingPlayerBarSub: 'Keep the player bar floating above content',
uiScaleTitle: 'Interface Scale',
uiScaleLabel: 'Zoom',
};