Files
psysonic/src/locales/en.ts
T
Frank Stellmacher 59744601d4 feat(composer): Browse by Composer page (issue #465) (#487)
* feat(composer): Browse by Composer page (issue #465)

New library section listing every artist credited as composer on at
least one track, with a detail page showing all works they're credited
on in that role. Targeted at classical-music libraries where the
"recording artist" tag carries the orchestra and the "composer" tag
carries Bach / Mozart / Chopin.

Hits Navidrome's native /api/artist?_filters={"role":"composer"} for
the listing and /api/album?_filters={"role_composer_id":"…"} for the
works grid — Subsonic getArtist only follows AlbumArtist relations and
returns 0 albums for composer-only credits, so the native API is the
only path that works. Requires Navidrome 0.55+ (uses
library_artist.stats role aggregation); on older / pure-Subsonic
servers the page shows a one-line capability banner.

- Two new Tauri commands: nd_list_artists_by_role +
  nd_list_albums_by_artist_role, generic over participant role so
  conductor / lyricist / arranger pages are trivial to add later.
- Composers grid: text-only compact tiles (name + participation count
  pulled from stats[role].albumCount). No avatars — composer libraries
  carry no useful imagery and the listing endpoint exposes no image
  URLs anyway.
- ComposerDetail: hero with Last.fm bio (via getArtistInfo2) plus the
  full work grid, with a graceful fallback when the artist has no
  external info synced.
- Sidebar entry default off (Feather icon) — opt-in for the niche
  classical use case.
- nd_retry backoffs widened from [500] to [300, 800, 1800] — helps
  every nd_* call survive intermittent TLS-handshake-EOF errors that
  some reverse-proxy setups produce when keep-alive pools churn.
- Distinguishes "server can't do this" (HTTP 400/404/422/501) from
  transient errors so the capability banner only fires when the server
  actually rejects the request shape; everything else gets a retry
  button.
- i18n in all 8 supported locales.

* fix(composer): address review feedback on detail page + role queries

- Re-fetch ComposerDetail when music-library scope changes; previously
  the album grid stayed stale until navigation while the list refreshed.
- Thread library_id through nd_list_artists_by_role and
  nd_list_albums_by_artist_role so role queries respect the active
  Navidrome library, matching the Subsonic musicFolderId already piped
  through libraryFilterParams().
- Fix CachedImage cache-key mismatch on ComposerDetail: a Last.fm header
  image was stored under the Subsonic cover-art key, aliasing cache
  entries and risking cross-source pollution.
- Consolidate the two contradictory composer-imagery comments in
  Composers.tsx into a single accurate one (the older one referenced an
  Images toggle that was never implemented).
- Align openLink toast duration with ArtistDetail (1500ms -> 2500ms).

* fix(composer): keep bio across scope changes, add share, degrade gracefully

Three remaining items from the latest review pass on the composer flow.

1. Bio survives a music-library scope change.
   The previous fix added musicLibraryFilterVersion to the load effect,
   but that effect also did setInfo(null) while the getArtistInfo effect
   still depended on [id] alone — so a scope bump on the open page
   wiped the bio without re-fetching it. Move the info reset into the
   bio effect (keyed on id) and out of the load effect: the album grid
   still refreshes on scope change; the Last.fm header image and
   biography survive untouched, since both are library-independent.

2. Composers join the share pipeline as a first-class entity kind.
   Extend EntityShareKind with 'composer' (and isEntityKind), branch
   applySharePastePayload to validate via getArtist (same id pool) and
   navigate to /composer/:id, and wire a Share button into
   ComposerDetail. A pasted composer link now opens the composer view
   instead of the artist view, matching what was copied. i18n added in
   all 8 locales (sharePaste.composerUnavailable, openedComposer;
   composerDetail.shareComposer, unknownComposer).

3. Partial server failure no longer hides the works.
   If getArtist rejects but ndListAlbumsByArtistRole succeeds, the page
   used to show full "not found" despite having data to display. Switch
   the not-found gate to require both empty (`!artist && !albums`) and
   render a degraded header (placeholder name, no Wikipedia / favourite
   / share / Last.fm image) when only metadata is missing.

* fix(composer): right-click share copies a composer link, not an artist link

The context menu opened from a composer card / row uses type='artist'
because every composer-action (radio, favourite, rating, add-to-playlist)
is identical to the artist counterpart — they share an id space and a
backend representation. Sharing was the one exception: the "Share Link"
entry produced a 'psysonic2-' string with k='artist', so a paste opened
/artist/:id even though the user came from /composers.

Add an optional shareKindOverride to openContextMenu (default: undefined,
preserves existing behaviour) and have the artist-typed branch consult
it when calling copyShareLink. Composers.tsx now passes 'composer' on
both right-click sites; nothing else changes downstream because the
override only affects the share kind.

* polish(composer): show Last.fm avatar even without server metadata

Two minor follow-ups from the latest review.

- ComposerDetail: drop the `&& artist` guard on the header-avatar render
  path. info?.largeImageUrl can resolve through getArtistInfo(id) without
  ever needing the SubsonicArtist record, so the previous gate hid a
  perfectly good Last.fm portrait whenever getArtist failed but the
  bio fetch succeeded. Replace artist.name with displayName so the
  alt / aria-label degrade to the localised "Composer" placeholder
  instead of empty strings.
- copyEntityShareLink: doc comment now mentions composer alongside
  track / album / artist.

* fix(composer): derive Last.fm cache key from route id, not from artist record

Follow-up to the previous polish: the avatar render path no longer
requires `artist` to be populated, but the cache-key gate still did. So
when getArtist failed but getArtistInfo returned a Last.fm portrait, the
key fell through to coverKey — which is empty without an artist record,
re-creating the very aliasing bug the earlier Subsonic-vs-Last.fm fix
was meant to close.

Switch the Last.fm branch to the route id (same id namespace as the
SubsonicArtist record), so the key stays stable whenever Last.fm art is
shown, independent of getArtist succeeding.

* docs: CHANGELOG + Contributors entry for composer browsing (PR #487)
2026-05-07 00:36:09 +02:00

1844 lines
97 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 enTranslation = {
sidebar: {
library: 'Library',
mainstage: 'Mainstage',
newReleases: 'New Releases',
allAlbums: 'All Albums',
randomAlbums: 'Random Albums',
randomPicker: 'Build a Mix',
artists: 'Artists',
composers: 'Composers',
randomMix: 'Random Mix',
favorites: 'Favorites',
nowPlaying: 'Now Playing',
system: 'System',
statistics: 'Statistics',
settings: 'Settings',
help: 'Help',
expand: 'Expand Sidebar',
collapse: 'Collapse Sidebar',
downloadingTracks: 'Caching {{n}} tracks…',
syncingTracks: 'Syncing {{done}}/{{total}}…',
cancelDownload: 'Cancel download',
offlineLibrary: 'Offline Library',
genres: 'Genres',
tracks: 'Tracks',
playlists: 'Playlists',
smartPlaylists: 'Smart Playlists',
mostPlayed: 'Most Played',
radio: 'Internet Radio',
folderBrowser: 'Folder Browser',
deviceSync: 'Device Sync',
libraryScope: 'Library scope',
allLibraries: 'All libraries',
expandPlaylists: 'Expand playlists',
collapsePlaylists: 'Collapse playlists',
more: 'More',
feelingLucky: 'Lucky Mix',
},
home: {
hero: 'Featured',
starred: 'Personal Favorites',
recent: 'Recently Added',
mostPlayed: 'Most Played',
recentlyPlayed: 'Recently Played',
discover: 'Discover',
discoverSongs: 'Discover Songs',
loadMore: 'Load More',
discoverMore: 'Discover More',
discoverArtists: 'Discover Artists',
discoverArtistsMore: 'All Artists'
},
hero: {
eyebrow: 'Featured Album',
playAlbum: 'Play Album',
enqueue: 'Enqueue',
enqueueTooltip: 'Add entire album to queue',
},
search: {
placeholder: 'Search for artist, album or song…',
noResults: 'No results for "{{query}}"',
artists: 'Artists',
albums: 'Albums',
songs: 'Songs',
clearLabel: 'Clear search',
addedToQueueToast: 'Added "{{title}}" to queue',
title: 'Search',
resultsFor: 'Results for "{{query}}"',
album: 'Album',
advanced: 'Advanced Search',
advancedSearchTerm: 'Search term',
advancedSearchPlaceholder: 'Title, album, artist…',
advancedGenre: 'Genre',
advancedAllGenres: 'All genres',
advancedYear: 'Year',
advancedYearFrom: 'from',
advancedYearTo: 'to',
advancedAll: 'All',
advancedSearch: 'Search',
advancedEmpty: 'Enter a search term or select a filter to begin.',
advancedNoResults: 'No results found.',
advancedGenreNote: 'Songs are randomly selected from this genre.',
recentSearches: 'Recent Searches',
browse: 'Browse',
emptyHint: 'What do you want to hear?',
genres: 'Genres',
},
nowPlaying: {
tooltip: 'Who is listening?',
title: 'Who is listening?',
loading: 'Loading…',
nobody: 'Nobody is currently listening.',
minutesAgo: '{{n}}m ago',
nothingPlaying: 'Nothing playing yet. Start a track!',
aboutArtist: 'About the Artist',
fromAlbum: 'From this Album',
viewAlbum: 'View Album',
goToArtist: 'Go to Artist',
readMore: 'Read more',
showLess: 'Show less',
genreInfo: 'Genre',
trackInfo: 'Track Info',
topSongs: 'Most played by this artist',
topSongsCredit: 'Top tracks from {{name}}',
trackPosition: 'Track {{pos}}',
playsCount_one: '{{count}} play',
playsCount_other: '{{count}} plays',
releasedYearsAgo_one: '{{count}} year ago',
releasedYearsAgo_other: '{{count}} years ago',
discography: 'Discography',
lastfmStats: 'Last.fm stats',
thisTrack: 'This track',
thisArtist: 'This artist',
listeners: 'listeners',
scrobbles: 'scrobbles',
yourScrobbles: 'by you',
listenersN: '{{n}} listeners',
scrobblesN: '{{n}} scrobbles',
playsByYouN: 'played {{n}}× by you',
openTrackOnLastfm: 'Track on Last.fm',
openArtistOnLastfm: 'Artist on Last.fm',
rgTrackTooltip: 'ReplayGain (track)',
rgAlbumTooltip: 'ReplayGain (album)',
rgAutoTooltip: 'ReplayGain (auto)',
showMoreTracks: 'Show {{count}} more',
showLessTracks: 'Show less',
hideCard: 'Hide card',
layoutMenu: 'Layout',
visibleCards: 'Visible cards',
hiddenCards: 'Hidden cards',
noHiddenCards: 'No hidden cards',
resetLayout: 'Reset layout',
emptyColumn: 'Drop cards here',
},
contextMenu: {
playNow: 'Play Now',
playNext: 'Play Next',
addToQueue: 'Add to Queue',
enqueueAlbum: 'Enqueue Album',
enqueueAlbums_one: 'Enqueue {{count}} Album',
enqueueAlbums_other: 'Enqueue {{count}} Albums',
startRadio: 'Start Radio',
instantMix: 'Instant Mix',
instantMixFailed: 'Could not build Instant Mix — server or plugin error.',
cliMixNeedsTrack: 'Nothing is playing — start playback first, then run the mix command again.',
lfmLove: 'Love on Last.fm',
lfmUnlove: 'Unlove on Last.fm',
favorite: 'Favorite',
favoriteArtist: 'Favorite Artist',
favoriteAlbum: 'Favorite Album',
unfavorite: 'Remove from Favorites',
unfavoriteArtist: 'Remove Artist from Favorites',
unfavoriteAlbum: 'Remove Album from Favorites',
removeFromQueue: 'Remove from Queue',
removeFromPlaylist: 'Remove from Playlist',
openAlbum: 'Open Album',
goToArtist: 'Go to Artist',
download: 'Download (ZIP)',
addToPlaylist: 'Add to Playlist',
selectedPlaylists: '{{count}} playlists selected',
selectedAlbums: '{{count}} albums selected',
selectedArtists: '{{count}} artists selected',
songInfo: 'Song Info',
shareLink: 'Copy share link',
shareCopied: 'Share link copied to the clipboard.',
shareCopyFailed: 'Could not copy to the clipboard.',
},
sharePaste: {
notLoggedIn: 'Sign in and add the server before pasting a share link.',
noMatchingServer: 'No saved server matches this link. Add a server with this address: {{url}}',
trackUnavailable: 'This track was not found on the server.',
albumUnavailable: 'This album was not found on the server.',
artistUnavailable: 'This artist was not found on the server.',
composerUnavailable: 'This composer was not found on the server.',
openedTrack: 'Playing shared track.',
openedAlbum: 'Opening shared album.',
openedArtist: 'Opening shared artist.',
openedComposer: 'Opening shared composer.',
openedQueue_one: 'Playing {{count}} track from the share link.',
openedQueue_other: 'Playing {{count}} tracks from the share link.',
openedQueuePartial:
'Playing {{played}} of {{total}} tracks from the link ({{skipped}} not found on this server).',
queueAllUnavailable: 'None of the tracks from this link were found on the server.',
genericError: 'Could not open the share link.',
},
albumDetail: {
back: 'Back',
orbitDoubleClickHint: 'Double-click to add this track to the Orbit queue',
playAll: 'Play All',
shareAlbum: 'Share album',
enqueue: 'Enqueue',
enqueueTooltip: 'Add entire album to queue',
artistBio: 'Artist Bio',
download: 'Download (ZIP)',
downloading: 'Loading…',
cacheOffline: 'Make available offline',
offlineCached: 'Available offline',
offlineDownloading: 'Caching… ({{n}}/{{total}})',
removeOffline: 'Remove offline cache',
offlineStorageFull: 'Offline storage full (limit: {{mb}} MB). Free up space by deleting an album from your Offline Library, or increase the limit in Settings.',
offlineStorageGoToLibrary: 'Offline Library',
offlineStorageGoToSettings: 'Settings',
favoriteAdd: 'Add to Favorites',
favoriteRemove: 'Remove from Favorites',
favorite: 'Favorite',
noBio: 'No biography available.',
moreByArtist: 'More by {{artist}}',
tracksCount: '{{n}} Tracks',
goToArtist: 'Go to {{artist}}',
moreLabelAlbums: 'More albums on {{label}}',
trackTitle: 'Title',
trackAlbum: 'Album',
trackArtist: 'Artist',
trackGenre: 'Genre',
trackFormat: 'Format',
trackFavorite: 'Favorite',
trackRating: 'Rating',
trackDuration: 'Duration',
trackTotal: 'Total',
columns: 'Columns',
resetColumns: 'Reset to defaults',
notFound: 'Album not found.',
bioModal: 'Artist Biography',
bioClose: 'Close',
ratingLabel: 'Rating',
enlargeCover: 'Enlarge',
filterSongs: 'Filter songs…',
sortNatural: 'Natural',
sortByTitle: 'AZ (Title)',
sortByArtist: 'AZ (Artist)',
sortByAlbum: 'AZ (Album)',
},
entityRating: {
albumShort: 'Album rating',
artistShort: 'Artist rating',
albumAriaLabel: 'Album rating',
artistAriaLabel: 'Artist rating',
selectedArtistsRatingAriaLabel: 'Star rating for {{count}} selected artists',
selectedAlbumsRatingAriaLabel: 'Star rating for {{count}} selected albums',
saveFailed: 'Could not save rating.',
},
artistDetail: {
back: 'Back',
albums: 'Albums',
album: 'Album',
playAll: 'Play All',
shareArtist: 'Share artist',
shuffle: 'Shuffle',
radio: 'Radio',
loading: 'Loading…',
noRadio: 'No similar tracks found for this artist.',
notFound: 'Artist not found.',
albumsBy: 'Albums by {{name}}',
topTracks: 'Top Tracks',
noAlbums: 'No albums found.',
trackTitle: 'Title',
trackAlbum: 'Album',
trackDuration: 'Duration',
favoriteAdd: 'Add to Favorites',
favoriteRemove: 'Remove from Favorites',
favorite: 'Favorite',
albumCount_one: '{{count}} Album',
albumCount_other: '{{count}} Albums',
openedInBrowser: 'Opened in browser',
featuredOn: 'Also Featured On',
similarArtists: 'Similar Artists',
cacheOffline: 'Save discography offline',
offlineCached: 'Discography cached',
offlineDownloading: 'Caching… ({{done}}/{{total}} albums)',
uploadImage: 'Upload artist image',
uploadImageError: 'Failed to upload image',
releaseTypes: {
album: 'Album',
ep: 'EP',
single: 'Single',
compilation: 'Compilation',
live: 'Live',
soundtrack: 'Soundtrack',
remix: 'Remix',
other: 'Other',
},
},
favorites: {
title: 'Favorites',
empty: "You haven't saved any favorites yet.",
artists: 'Artists',
albums: 'Albums',
songs: 'Songs',
enqueueAll: 'Add all to queue',
playAll: 'Play all',
removeSong: 'Remove from favorites',
stations: 'Radio Stations',
showingFiltered: 'Showing {{filtered}} of {{total}} ({{artist}})',
showingCount: 'Showing {{filtered}} of {{total}}',
clearArtistFilter: 'Clear artist filter',
noFilterResults: 'No results with selected filters.',
allArtists: 'All Artists',
topArtists: 'Top Artists by Favorites',
topArtistsSongCount_one: '{{count}} song',
topArtistsSongCount_other: '{{count}} songs',
},
randomLanding: {
title: 'Build a Mix',
mixByTracks: 'Mix by Tracks',
mixByTracksDesc: 'Random selection of tracks from your entire library',
mixByAlbums: 'Mix by Albums',
mixByAlbumsDesc: 'Random album picks for your next discovery',
mixByLucky: 'Lucky Mix',
mixByLuckyDesc: 'Smart instant mix from your top artists, albums, and ratings',
},
randomAlbums: {
title: 'Random Albums',
refresh: 'Refresh',
},
genres: {
title: 'Genres',
genreCount: 'Genres',
albumCount_one: '{{count}} album',
albumCount_other: '{{count}} albums',
loading: 'Loading genres…',
empty: 'No genres found.',
albumsLoading: 'Loading albums…',
albumsEmpty: 'No albums found for this genre.',
loadMore: 'Load more',
back: 'Back',
},
randomMix: {
title: 'Random Mix',
remix: 'Remix',
remixTooltip: 'Load new random songs',
remixGenre: 'Remix {{genre}}',
remixTooltipGenre: 'Load new {{genre}} songs',
playAll: 'Play All',
trackTitle: 'Title',
trackArtist: 'Artist',
trackAlbum: 'Album',
trackFavorite: 'Favorite',
trackDuration: 'Duration',
favoriteAdd: 'Add to Favorites',
favoriteRemove: 'Remove from Favorites',
play: 'Play',
trackGenre: 'Genre',
mixSettingsHeader: 'Mix Settings',
exclusionsHeader: 'Exclusions',
filterPanelInexactSizeNote: 'Mix size is a target — large requests may return fewer unique tracks if the server\'s random pool runs short.',
mixSize: 'Playlist size',
excludeAudiobooks: 'Exclude audiobooks & radio plays',
excludeAudiobooksDesc: 'Matches keywords against genre, title, album, and artist — e.g. Hörbuch, Audiobook, Spoken Word, …',
genreBlocked: 'Keyword blocked',
genreAddedToBlacklist: 'Added to filter list',
genreAlreadyBlocked: 'Already blocked',
artistBlocked: 'Artist blocked',
artistAddedToBlacklist: 'Artist added to filter list',
artistClickHint: 'Click to block this artist',
blacklistToggle: 'Keyword Filter',
genreMixTitle: 'Genre Mix',
genreMixDesc: 'Top 20 genres by song count — click to load a random mix',
genreMixAll: 'All Songs',
genreMixLoadMore: 'Load 10 more',
genreMixNoGenres: 'No genres found on server.',
shuffleGenres: 'Show different genres',
filterPanelTitle: 'Filters',
filterPanelDesc: 'Click a genre tag or artist name in the tracklist below to block it from future mixes.',
genreClickHint: 'Click a genre tag to add it\nas a filter keyword.\nMatches genre, title, album & artist.',
},
luckyMix: {
done: 'Lucky Mix ready: {{count}} tracks',
failed: 'Could not build Lucky Mix. Try again.',
unavailable: 'Lucky Mix is unavailable for this server.',
cancelTooltip: 'Cancel Lucky Mix build',
},
albums: {
title: 'All Albums',
sortByName: 'AZ (Album)',
sortByArtist: 'AZ (Artist)',
sortNewest: 'Newest first',
sortRandom: 'Random',
yearFrom: 'From',
yearTo: 'To',
yearFilterClear: 'Clear year filter',
yearFilterLabel: 'Year',
compilationLabel: 'Compilations',
compilationOnly: 'Only compilations',
compilationHide: 'Hide compilations',
compilationTooltipAll: 'All albums · click: only compilations',
compilationTooltipOnly: 'Only compilations · click: hide compilations',
compilationTooltipHide: 'Compilations hidden · click: show all',
select: 'Multi-select',
startSelect: 'Enable multi-select',
cancelSelect: 'Cancel',
selectionCount: '{{count}} selected',
downloadZips: 'Download ZIPs',
addOffline: 'Add Offline',
enqueueSelected_one: 'Enqueue ({{count}})',
enqueueSelected_other: 'Enqueue ({{count}})',
enqueueQueued_one: 'Added {{count}} album to queue',
enqueueQueued_other: 'Added {{count}} albums to queue',
downloadingZip: 'Downloading {{current}}/{{total}}: {{name}}',
downloadZipDone: '{{count}} ZIP(s) downloaded',
downloadZipFailed: 'Failed to download {{name}}',
offlineQueuing: 'Queuing {{count}} album(s) for offline…',
offlineFailed: 'Failed to add {{name}} offline',
},
tracks: {
title: 'Tracks',
subtitle: 'Browse. Search. Discover.',
heroEyebrow: 'Track of the moment',
heroReroll: 'Pick another',
playSong: 'Play',
enqueueSong: 'Add to queue',
railRandom: 'Random Pick',
railHighlyRated: 'Highly Rated',
browseTitle: 'Browse all tracks',
browseUnsupported: "This server doesn't list the whole library at once. Use the search above to find specific tracks.",
searchPlaceholder: 'Find a track by title, artist or album…',
count_one: '{{count}} track',
count_other: '{{count}} tracks',
},
artists: {
title: 'Artists',
search: 'Search…',
all: 'All',
gridView: 'Grid view',
listView: 'List view',
imagesOn: 'Artist images on — may increase network and system load',
imagesOff: 'Artist images off — showing initials only',
loadMore: 'Load more',
notFound: 'No artists found.',
albumCount_one: '{{count}} Album',
albumCount_other: '{{count}} Albums',
selectionCount: '{{count}} selected',
select: 'Multi-select',
startSelect: 'Enable multi-select',
cancelSelect: 'Cancel',
addToPlaylist: 'Add to Playlist',
},
composers: {
title: 'Composers',
search: 'Search…',
notFound: 'No composers found.',
unsupported: 'Browse by Composer requires Navidrome 0.55 or newer.',
loadFailed: 'Could not load composers.',
retry: 'Retry',
involvedIn_one: 'Involved in {{count}} album',
involvedIn_other: 'Involved in {{count}} albums',
},
composerDetail: {
back: 'Back',
notFound: 'Composer not found.',
about: 'About this composer',
works: 'Works',
noWorks: 'No works found.',
workCount_one: '{{count}} work',
workCount_other: '{{count}} works',
shareComposer: 'Share composer',
unknownComposer: 'Composer',
},
login: {
subtitle: 'Your Navidrome Desktop Player',
serverName: 'Server Name (optional)',
serverNamePlaceholder: 'My Navidrome',
serverUrl: 'Server URL',
serverUrlPlaceholder: '192.168.1.100:4533 or https://music.example.com',
username: 'Username',
usernamePlaceholder: 'admin',
password: 'Password',
showPassword: 'Show password',
hidePassword: 'Hide password',
connect: 'Connect',
connecting: 'Connecting…',
connected: 'Connected!',
error: 'Connection failed please check your details.',
urlRequired: 'Please enter a server URL.',
savedServers: 'Saved Servers',
addNew: 'Or add a new server',
orMagicString: 'Or magic string',
magicStringPlaceholder: 'Paste a share string (psysonic1-…)',
magicStringInvalid: 'Invalid or unreadable magic string.',
},
connection: {
connected: 'Connected',
connectedTo: 'Connected to {{server}}',
disconnected: 'Disconnected',
disconnectedFrom: 'Cannot reach {{server}} — click to check settings',
checking: 'Connecting…',
extern: 'Extern',
offlineTitle: 'No server connection',
offlineSubtitle: 'Cannot reach {{server}}. Check your network or server.',
offlineModeBanner: 'Offline Mode — playing from local cache',
offlineNoCacheBanner: 'No server connection — cannot reach {{server}}',
offlineLibraryTitle: 'Offline Library',
offlineLibraryEmpty: 'No albums cached yet. Go online, open an album and click "Make available offline".',
offlineAlbumCount: '{{n}} album',
offlineAlbumCount_plural: '{{n}} albums',
offlineFilterAll: 'All',
offlineFilterAlbums: 'Albums',
offlineFilterPlaylists: 'Playlists',
offlineFilterArtists: 'Discographies',
retry: 'Retry',
serverSettings: 'Server Settings',
switchServerTitle: 'Switch server',
switchServerHint: 'Click to choose another saved server.',
manageServers: 'Manage servers…',
switchFailed: 'Could not switch — server unreachable.',
lastfmConnected: 'Last.fm connected as @{{user}}',
lastfmSessionInvalid: 'Session invalid — click to re-connect',
},
common: {
albums: 'Albums',
album: 'Album',
loading: 'Loading…',
loadingMore: 'Loading…',
loadingPlaylists: 'Loading Playlists…',
noAlbums: 'No albums found.',
downloading: 'Downloading…',
downloadZip: 'Download (ZIP)',
back: 'Back',
cancel: 'Cancel',
close: 'Close',
save: 'Save',
delete: 'Delete',
use: 'Use',
add: 'Add',
new: 'New',
active: 'Active',
download: 'Download',
chooseDownloadFolder: 'Choose download folder',
noFolderSelected: 'No folder selected',
rememberDownloadFolder: 'Remember this folder',
filterGenre: 'Genre Filter',
filterSearchGenres: 'Search genres…',
filterNoGenres: 'No genres match',
filterClear: 'Clear',
favorites: 'Favorites',
favoritesTooltipOff: 'Show only favorites',
favoritesTooltipOn: 'Show all',
play: 'Play',
bulkSelected: '{{count}} selected',
clearSelection: 'Clear selection',
bulkAddToPlaylist: 'Add to Playlist',
bulkRemoveFromPlaylist: 'Remove from Playlist',
bulkClear: 'Clear selection',
updaterAvailable: 'Update available',
updaterVersion: 'v{{version}} is available',
updaterWebsite: 'Website',
updaterModalTitle: 'New Version Available',
updaterChangelog: "What's New",
updaterDownloadBtn: 'Download Now',
updaterSkipBtn: 'Skip this Version',
updaterRemindBtn: 'Remind me Later',
updaterDone: 'Download complete',
updaterShowFolder: 'Show in Folder',
updaterInstallHint: 'Close Psysonic and run the installer manually.',
updaterAurHint: 'Install the update via AUR:',
updaterErrorMsg: 'Download failed',
updaterRetryBtn: 'Retry',
updaterInstallNow: 'Install now',
updaterMacReadyTitle: 'Ready to install',
updaterMacReady: 'The update downloads, verifies and applies in place — no DMG needed. The app restarts automatically when done.',
updaterTrustNotarized: 'Notarized by Apple',
updaterTrustSignature: 'Signature verified',
updaterMacDoneTitle: 'Update installed',
updaterRestartingIn: 'Restarting in {{n}}s…',
updaterRestarting: 'Restarting…',
updaterRestartNow: 'Restart now',
durationHoursMinutes: '{{hours}}h {{minutes}}m',
durationMinutesOnly: '{{minutes}}m',
updaterOpenGitHub: 'Open on GitHub',
filters: 'Filters',
more: 'more',
yearRange: 'Year Range',
clearAll: 'Clear all',
},
settings: {
title: 'Settings',
language: 'Language',
languageEn: 'English',
languageDe: 'German',
languageFr: 'French',
languageNl: 'Dutch',
languageZh: 'Chinese',
languageNb: 'Norwegian',
languageRu: 'Russian',
languageEs: 'Spanish',
font: 'Font',
theme: 'Theme',
appearance: 'Appearance',
servers: 'Servers',
serverName: 'Server Name',
serverUrl: 'Server URL',
serverUrlPlaceholder: '192.168.1.100:4533 or https://music.example.com',
serverUsername: 'Username',
serverPassword: 'Password',
addServer: 'Add Server',
addServerTitle: 'Add New Server',
useServer: 'Use',
deleteServer: 'Delete',
noServers: 'No servers saved.',
serverActive: 'Active',
confirmDeleteServer: 'Delete server "{{name}}"?',
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)',
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.',
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',
lfmTitle: 'Last.fm',
lfmConnect: 'Connect with Last.fm',
lfmConnecting: 'Waiting for authorisation…',
lfmConfirm: 'I have authorised the app',
lfmConnected: 'Connected as',
lfmDisconnect: 'Disconnect',
lfmConnectDesc: 'Connect your Last.fm account to enable scrobbling and Now Playing updates directly from Psysonic — no Navidrome configuration required.',
lfmOpenBrowser: 'A browser window will open. Authorise Psysonic on Last.fm, then click the button below.',
lfmScrobbles: '{{n}} scrobbles',
lfmMemberSince: 'Member since {{year}}',
scrobbleEnabled: 'Scrobbling enabled',
scrobbleDesc: 'Send songs to Last.fm after 50% playtime',
behavior: 'App Behavior',
cacheTitle: 'Max. Storage Size',
cacheDesc: 'Cover art and artist images. When full, the oldest entries are removed automatically. Offline albums are not auto-removed, but will be deleted when clearing the cache manually.',
cacheUsedImages: 'Images:',
cacheUsedOffline: 'Offline tracks:',
cacheUsedHot: 'Size on disk:',
hotCacheTrackCount: 'Tracks in cache:',
cacheMaxLabel: 'Max. size',
cacheClearBtn: 'Clear Cache',
waveformCacheClearBtn: 'Clear waveform cache',
cacheClearWarning: 'This will also remove all offline albums from the library.',
cacheClearConfirm: 'Clear Everything',
cacheClearCancel: 'Cancel',
waveformCacheCleared: 'Waveform cache cleared ({{count}} rows).',
waveformCacheClearFailed: 'Failed to clear waveform cache.',
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',
audioOutputDeviceMacNotice: 'On macOS, playback currently always follows the system output device for technical reasons. Change the target via System Settings → Sound or the speaker icon in the menu bar. Background: CoreAudio triggers a microphone-permission prompt when opening a non-default stream — we avoid it by always using the system default.',
hiResTitle: 'Native Hi-Res Playback',
hiResEnabled: 'Enable native hi-res playback',
hiResDesc: "Forces 44.1 kHz output by default for maximum stability. Enable only if your hardware and network reliably support high sample rates (88.2 kHz+).",
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.',
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.',
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.',
linuxWebkitSmoothScroll: 'Smooth wheel (Linux)',
linuxWebkitSmoothScrollDesc: 'On: inertial scroll. Off: line-by-line, GTK-style.',
discordCoverSource: 'Cover art source',
discordCoverSourceDesc: 'Where to fetch album artwork shown on your Discord profile.',
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)',
nowPlayingEnabled: 'Show in Now Playing',
nowPlayingEnabledDesc: 'Broadcast your currently playing track to the server\'s live listener view. Disable to stop sending playback data.',
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',
lyricsModeStandard: 'Standard',
lyricsModeStandardDesc: 'Three sources in a freely orderable list: server tags, LRCLIB, Netease.',
lyricsModeLyricsplus: 'YouLyPlus (Karaoke)',
lyricsModeLyricsplusDesc: 'Word-by-word sync sourced from Apple Music, Spotify, Musixmatch & QQ (community backend). Silently falls back to the standard sources when nothing is found.',
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.",
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',
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: 'Home Page',
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',
sidebarDrag: 'Drag to reorder',
sidebarFixed: 'Always visible',
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?',
backupSuccess: 'Backup saved',
backupImportSuccess: 'Settings restored — reloading…',
backupImportError: 'Invalid or corrupted backup file.',
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',
crossfadeDesc: 'Fade between tracks',
crossfadeSecs: '{{n}} s',
notWithGapless: 'Not available while Gapless is active',
notWithCrossfade: 'Not available while Crossfade is active',
gapless: 'Gapless Playback',
gaplessDesc: 'Pre-buffer next track to eliminate gaps between songs',
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',
preloadMode: 'Preload Next Track',
preloadModeDesc: 'When to start buffering the next track in the queue',
nextTrackBufferingTitle: 'Buffering',
preloadHotCacheMutualExclusive: 'Preload and Hot Cache serve the same purpose — only one can be active at a time. Enabling one will automatically disable the other.',
preloadOff: 'Off',
preloadBalanced: 'Balanced (30 s before end)',
preloadEarly: 'Early (after 5 s of playback)',
preloadCustom: 'Custom',
preloadCustomSeconds: 'Seconds before end: {{n}}',
infiniteQueue: 'Infinite Queue',
infiniteQueueDesc: 'Automatically append random tracks when the queue runs out',
experimental: 'Experimental',
fsPlayerSection: 'Fullscreen Player',
fsLyricsStyle: 'Lyrics style',
fsLyricsStyleRail: 'Rail',
fsLyricsStyleRailDesc: 'Classic 5-line sliding rail.',
fsLyricsStyleApple: 'Scrolling',
fsLyricsStyleAppleDesc: 'Full-screen scrollable list.',
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.',
fsShowArtistPortrait: 'Show artist photo',
fsShowArtistPortraitDesc: 'Display the artist photo (or album art) on the right side of the fullscreen player.',
fsPortraitDim: 'Photo dimming',
seekbarStyle: 'Seekbar Style',
seekbarStyleDesc: 'Choose the look of the player seek bar',
animationMode: 'Animations',
animationModeDesc: 'Trade visual smoothness for lower GPU/CPU usage on slower hardware.',
animationModeFull: 'Full',
animationModeReduced: 'Reduced',
animationModeStatic: 'Static',
animationModeReducedHint: '30 fps cap on the seekbar wave; player title scrolls at half speed.',
animationModeStaticHint: 'No wave animation; player title is truncated instead of scrolling.',
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',
},
changelog: {
modalTitle: "What's New",
dontShowAgain: "Don't show again",
close: 'Got it',
},
whatsNew: {
title: "What's New",
empty: 'No changelog entry for this version yet.',
bannerTitle: 'Changelog',
bannerCollapsed: "What's new in v{{version}}",
dismiss: 'Dismiss',
close: 'Close',
},
help: {
title: 'Help',
searchPlaceholder: 'Search Help…',
noResults: 'No matching topics. Try a different search term.',
// ── Section 1: Getting Started ─────────────────────────────────────────
s1: 'Getting Started',
q1: 'Which servers are compatible?',
a1: 'Psysonic is built first and foremost for Navidrome and is fully Subsonic-compatible — Gonic, Airsonic, LMS and other Subsonic-API servers also work. Some advanced features (ratings, smart playlists, magic-string sharing, user management) require Navidrome.',
q2: 'How do I add a server?',
a2: 'Settings → Servers → Add Server. Enter the URL (e.g. http://192.168.1.100:4533), username, and password. Psysonic tests the connection before saving — nothing is stored if it fails. You can add as many servers as you like and switch between them in the header at any time; only one is active at a time.',
q3: 'Quick tour of the UI?',
a3: 'Sidebar (left) for navigation, Mainstage / pages in the middle, Player Bar at the bottom, and the Queue Panel on the right (toggle from the top-right header next to the Now-Playing indicator). The search bar at the top searches your whole library; "Now Playing" shows what other users on the same Navidrome server are listening to right now.',
// ── Section 2: Playback & Queue ────────────────────────────────────────
s2: 'Playback & Queue',
q4: 'How do I use the queue?',
a4: 'Open the Queue Panel from the top-right header. Drag rows to reorder them, drop them outside to remove, or use the queue toolbar to shuffle, save the queue as a playlist, or jump to a track. Drag rows out of the panel to drop them somewhere else as a transfer.',
q5: 'Gapless vs Crossfade — what is the difference?',
a5: 'Gapless pre-buffers the next track so there is zero silence between songs (best for live albums and DJ mixes). Crossfade fades the current track out while the next fades in over 110 s (best for shuffled mixes). They are mutually exclusive — enabling one disables the other. Configure both in Settings → Audio.',
q6: 'What is Infinite Queue?',
a6: 'When the queue runs out and Repeat is off, Psysonic silently appends similar / random tracks from your library so playback never stops. Auto-added tracks appear below a "— Added automatically —" divider. Toggle it with the infinity icon in the queue header; restrict auto-additions to a genre in Settings → Queue.',
q7: 'Multi-select and Shift-click range selection?',
a7: 'Activate "Select" on Albums / New Releases / Random Albums / Playlists. Click an item to toggle it, then hold Shift and click another item to select everything between them in the visible order. Shift-clicks extend from the most recently clicked anchor. The toolbar above the grid offers bulk actions (queue, download, delete, etc.).',
q8: 'Keyboard shortcuts and global hotkeys?',
a8: 'Default in-app keys: Space = Play / Pause, Esc = close fullscreen / modals, F1 = shortcuts cheat-sheet. Settings → Input lets you rebind every in-app action (also as chords like Ctrl+Shift+P) and set system-wide global shortcuts that work even when Psysonic is in the background or minimized. Media keys (Play/Pause, Next, Previous) work on all platforms.',
// ── Section 3: Audio Tools ─────────────────────────────────────────────
s3: 'Audio Tools',
q9: 'Replay Gain modes?',
a9: 'Settings → Audio → Replay Gain. Track mode normalises every song to a target level. Album mode preserves the loudness curve within an album. Auto mode picks Track or Album based on whether the queue is from a single album. Tracks without Replay Gain tags fall back to a configurable preamp.',
q10: 'What is Smart Loudness Normalization (LUFS)?',
a10: 'A modern alternative to Replay Gain in Settings → Audio. Psysonic analyses each track to LUFS / EBU R128 and stores the result, so volume is consistent across your whole library — including tracks without Replay Gain tags. Cold-cache analysis runs in the background and uses 3540 % CPU for ~1 minute, then drops to 0. Set your target loudness (default 10 LUFS) once.',
q11: 'EQ and AutoEQ?',
a11: 'A 10-band parametric EQ lives in Settings → Audio → Equalizer with manual bands and presets. AutoEQ next to it pulls a measured correction profile for your headphone model from the AutoEQ database and applies it to the bands automatically — pick your headphones and the EQ snaps to the correct curve.',
q12: 'How do I switch the audio output device?',
a12: 'Settings → Audio → Output Device. Psysonic lists every available output and switches instantly when you pick one. The refresh button rescans devices that were plugged in after launch. Linux ALSA names like "sysdefault" are auto-cleaned for readability.',
q13: 'What is Hot Cache?',
a13: 'Hot Cache preloads the current track plus the next several into RAM and onto disk so playback starts with no buffering — especially useful on slow / remote servers. Eviction kicks in when the size limit is reached. Configure size and the debounce delay (so quick skips do not over-fetch) in Settings → Audio.',
// ── Section 4: Library & Discovery ─────────────────────────────────────
s4: 'Library & Discovery',
q14: 'How do ratings and Skip-to-1★ work?',
a14: 'Psysonic supports 15 star ratings via OpenSubsonic (Navidrome ≥ 0.53). Rate songs in track lists, in the right-click menu, or in the player bar below the artist name; albums on the album page; artists on the artist page. Skip-to-1★ (Settings → Library → Ratings) auto-assigns 1 star after a configurable number of consecutive skips. The same panel lets you set a minimum rating filter for Random Mix and other generated mixes.',
q15: 'How do I browse — Folders, Genres, Tracks?',
a15: 'Folder Browser (sidebar) walks your server\'s music directory in a Miller-column layout — click a folder to drill in, click a track or a folder\'s play icon to play / enqueue. Genres uses a tag-cloud view scaled by track count; click a genre to open it. Tracks is a flat library hub with column-sortable virtual list and live search.',
q16: 'Search and Advanced Search?',
a16: 'The header search bar runs a fast live search across artists, albums and tracks as you type. Open Advanced Search (sidebar) for a richer view: filter by year, genre, rating, format, channels, sample rate, BPM, mood, and combine criteria. Right-click any result for the same context menu used elsewhere in the app.',
q17: 'What are the Mixes (Random / Genre / Super Genre / Lucky)?',
a17: 'Random Mix builds a playlist of random tracks from your library; the Keyword Filter excludes audiobooks, comedy, etc. by genre / title / album substrings. Super Genre Mix groups your library into broad styles (Rock, Metal, Electronic, Jazz, Classical…) and builds a focused mix from one. Lucky Mix uses your listening history, ratings and AudioMuse-AI similar tracks to assemble an instant playlist with one click.',
q18: 'Statistics page?',
a18: 'Statistics (sidebar) shows top artists, top albums, top tracks, genre breakdown, listening time over time, and per-library scope when you have more than one music folder configured. Several views are exportable as a sharable card image.',
q19: 'How do I download an album as a ZIP?',
a19: 'Album page → "Download (ZIP)". The server compresses the album first — for large or lossless albums (FLAC / WAV) the progress bar appears only after zipping completes and the transfer starts. This is normal.',
// ── Section 5: Lyrics ──────────────────────────────────────────────────
s5: 'Lyrics',
q20: 'Where do lyrics come from?',
a20: 'Three sources, configurable in Settings → Lyrics: your Navidrome server (embedded / OpenSubsonic lyrics), LRCLIB (community-contributed synced lyrics), and NetEase Cloud Music (large Asian + international catalog). Each source can be enabled / disabled and re-ordered by drag.',
q21: 'How do I view lyrics during playback?',
a21: 'Click the microphone icon in the player bar to open the Lyrics tab inside the queue panel. Synced lyrics auto-scroll and support click-to-seek; plain-text lyrics scroll as a static block. Toggle the Fullscreen Player from the player-bar artwork for an immersive lyrics view with mesh-blob background.',
// ── Section 6: Sharing & Social ────────────────────────────────────────
s6: 'Sharing & Social',
q22: 'What are Magic Strings?',
a22: 'Magic Strings are short copy-paste tokens that share things between Psysonic users without any third-party server. Server-Invite strings let a friend onboard onto your Navidrome with one paste; Album / Artist / Queue Magic Strings open the same album / artist / queue on the recipient\'s installed Psysonic. Generate them from the share menu, paste them into the search bar to consume.',
q23: 'What is Orbit?',
a23: 'Orbit is synchronized listen-together: a host plays a track, every guest hears it in sync, and guests can suggest songs the host can accept into the queue. Start a session from the header Orbit button, share the invite link, and others can join from any Psysonic install. The host owns playback (skip, seek, queue) — guests get a real-time view and a suggestion box. Sessions are ephemeral and end when the host closes them.',
q24: 'What is the Now Playing dropdown?',
a24: 'The broadcast icon in the top-right header shows what other users on your Navidrome server are currently listening to, refreshed every 10 seconds. Your own entry disappears the moment you pause. It is per-server, so users on a different active server are not shown.',
// ── Section 7: Personalization ─────────────────────────────────────────
s7: 'Personalization',
q25: 'Themes and the Theme Scheduler?',
a25: 'Settings → Appearance → Theme picks from 60+ themes across 8 groups (Psysonic, Mediaplayer, Operating Systems, Games, Movies, Series, Social Media, Open Source Classics — Catppuccin / Nord / Gruvbox / Nightfox / Dracula). Auto-Switch Theme lets you set a day theme + night theme with start times so Psysonic flips automatically.',
q26: 'Can I customize the Sidebar, Home and Artist pages?',
a26: 'Yes — Settings → Personalisation. The sidebar customizer lets you drag nav items into the order you want and hide ones you do not use. The Home customizer toggles each Mainstage rail (Hero, Recent, Discover, Recently Played, Starred…). The Artist page customizer reorders the sections (Top Songs, Albums, Singles, Compilations, Similar Artists, etc.) and lets you hide ones you never look at.',
q27: 'What is the Mini Player?',
a27: 'A small floating window with cover art, transport controls and a compact queue. Open it from the player-bar mini-player icon or with its keyboard shortcut. The window remembers its position and size between sessions. On Windows the mini-webview is pre-created at startup so opening is instant; Linux / macOS opt in via Settings → Appearance → Preload mini player.',
q28: 'What is the Floating Player Bar?',
a28: 'An optional player-bar style (Settings → Appearance) that detaches from the bottom edge with a themed background, accent border, rounded album art and a centered volume section. Useful on tall monitors or compact themes.',
q29: 'Track Preview on hover?',
a29: 'Hover over a track row, click the small preview button on the cover, or trigger preview from the right-click menu — Psysonic streams the first ~15 s through the same Rust audio engine so loudness normalisation applies. Useful for skimming an album you have not heard before.',
q30: 'Sleep timer?',
a30: 'Click the moon icon in the player bar to open the sleep timer. Pick a duration (or "end of current track") and Psysonic fades out and pauses at the end. The button shows a circular ring and an in-button countdown while the timer runs.',
q31: 'UI scale, font, seekbar style?',
a31: 'Settings → Appearance — Interface Scale (80125 % without affecting system font size), Font (10 UI fonts including IBM Plex Mono, Fira Code, Geist, Golos Text…), Seekbar Style (Waveform analysed / pseudowave deterministic / Line & Dot / Bar / Thick Bar / Segmented / Neon Glow / Pulse Wave / Particle Trail / Liquid Fill / Retro Tape).',
// ── Section 8: Power User ──────────────────────────────────────────────
s8: 'Power User',
q32: 'CLI player controls?',
a32: 'The Psysonic binary doubles as a remote control. Examples: psysonic --player play / pause / next / prev, --player volume 75, --player seek 15, --player shuffle, --player repeat off|all|one, --player rating 1-5. Switch servers, audio devices, libraries; trigger Instant Mix; search artists / albums / tracks. Run psysonic --player --help for the full list. Shell completions for bash and zsh via psysonic completions.',
q33: 'Smart Playlists (Navidrome)?',
a33: 'Smart Playlists are Navidrome-side dynamic playlists defined by rules (genre = Rock AND year > 2020, etc.). The Playlists page in Psysonic lets you create, edit and delete them with a visual rule editor — Psysonic talks to the Navidrome native API for that. They behave like normal playlists in the rest of the app and update automatically as your library changes.',
q34: 'Backup and restore settings?',
a34: 'Settings → Storage → Backup & Restore exports server profiles, themes, keybindings and other settings to a single JSON file. Import the file on another machine or after a reinstall to restore everything at once. Server passwords are included — keep the file private.',
q35: 'Where do I see all open-source licenses?',
a35: 'Settings → System → Open Source Licenses. The full dependency list (cargo crates and npm packages) ships in the build with bundled license texts. Click an entry for the full text; the curated highlight block at the top calls out the user-recognisable libraries (Tauri, React, rodio, symphonia, etc.).',
// ── Section 9: Offline & Sync ──────────────────────────────────────────
s9: 'Offline & Sync',
q36: 'Offline Mode — caching albums and playlists?',
a36: 'Open any album or playlist and click the download icon in the header — Psysonic caches every track locally so it plays from disk with no network call. The Offline Library page (sidebar) lists everything cached, shows progress in flight, and lets you remove entries with the trash icon (which appears once a download completes).',
q37: 'Offline storage limit?',
a37: 'Settings → Library → Offline lets you set a maximum cache size. When the limit is reached, the album-page download button shows a warning banner. Free space by removing albums or playlists from the Offline Library page.',
q38: 'Device Sync — copy music to USB / SD?',
a38: 'Device Sync (sidebar) copies albums, playlists or whole artists to an external drive for offline listening on other devices. Pick a target folder, choose sources from the browser tabs, click "Transfer to Device". Psysonic writes a manifest (psysonic-sync.json) so it knows which files are already there even if you reopen Device Sync on a different OS with a different filename template. Templates support {artist} / {album} / {title} / {track_number} / {disc_number} / {year} tokens with / for folder separators.',
// ── Section 10: Integrations & Troubleshooting ─────────────────────────
s10: 'Integrations & Troubleshooting',
q39: 'Last.fm scrobbling?',
a39: 'Settings → Integrations → Last.fm. Connect your account once and Psysonic scrobbles directly to Last.fm — no Navidrome configuration needed. A scrobble is sent after you have heard 50 % of a track. Now-playing pings are sent at the start.',
q40: 'Discord Rich Presence?',
a40: 'Settings → Integrations → Discord shows your current track on your Discord profile. Choose between the app icon, your server\'s cover art (via getAlbumInfo2 — needs a publicly reachable server), or Apple Music covers. Customize the display strings (details, state, tooltip) with token templates.',
q41: 'Bandsintown tour dates?',
a41: 'Opt in under Settings → Integrations → Now-Playing Info. The Now Playing tab on the Now Playing page then shows upcoming tour dates for the playing artist via the Bandsintown widget API. Off by default — no requests are made until you enable it.',
q42: 'Internet Radio — playing live streams?',
a42: 'Internet Radio (sidebar) plays any live stream stored on your Navidrome server (add stations via the Navidrome admin UI). Playback runs through the browser HTML5 audio engine — most EQ / Replay Gain / Loudness settings do not apply to radio, but ICY metadata (current song name) does show up. Supports MP3, AAC, OGG Vorbis and most HLS streams; codec support depends on the platform.',
q43: 'The connection test fails — what now?',
a43: 'Double-check the URL including port (e.g. http://192.168.1.100:4533). On a local network http:// often works where https:// does not (no certificate). Make sure no firewall blocks the port and that the username and password are correct. If your server is behind a reverse proxy, try the direct URL first to isolate the cause.',
q44: 'Cover art and artist images load slowly.',
a44: 'Images are fetched from your server on first view and then cached locally for 30 days. If your server\'s storage is slow, the first visit to a page can take a moment; subsequent visits are instant. The Hot Cache also helps for tracks but image cache is independent.',
q45: 'Linux issues — black screen or no audio?',
a45: 'Black screen is usually a GPU / EGL driver issue in WebKitGTK — launch with GDK_BACKEND=x11 WEBKIT_DISABLE_COMPOSITING_MODE=1 WEBKIT_DISABLE_DMABUF_RENDERER=1 (the AUR / .deb / .rpm installers set these automatically). For audio, make sure PipeWire or PulseAudio is running. If audio drops out after sleep / wake, this is now handled automatically by the post-sleep recovery hook.',
},
queue: {
title: 'Queue',
savePlaylist: 'Save Playlist',
updatePlaylist: 'Update Playlist',
filterPlaylists: 'Filter playlists…',
playlistName: 'Playlist Name',
cancel: 'Cancel',
save: 'Save',
loadPlaylist: 'Load Playlist',
loading: 'Loading…',
noPlaylists: 'No playlists found.',
load: 'Replace queue & play',
appendToQueue: 'Append to queue',
delete: 'Delete',
deleteConfirm: 'Delete playlist "{{name}}"?',
clear: 'Clear',
shuffle: 'Shuffle queue',
gapless: 'Gapless',
crossfade: 'Crossfade',
infiniteQueue: 'Infinite Queue',
autoAdded: '— Added automatically —',
radioAdded: '— Radio —',
hide: 'Hide',
hideNowPlaying: 'Hide now playing info',
showNowPlaying: 'Show now playing info',
close: 'Close',
nextTracks: 'Next Tracks',
shareQueue: 'Copy queue share link',
shareQueueEmpty: 'The queue is empty — nothing to share.',
emptyQueue: 'The queue is empty.',
trackSingular: 'track',
trackPlural: 'tracks',
showRemaining: 'Show remaining time',
showTotal: 'Show total time',
showEta: 'Show estimated end time',
replayGain: 'ReplayGain',
rgTrack: 'T {{db}} dB',
rgAlbum: 'A {{db}} dB',
rgPeak: 'Peak {{pk}}',
sourceOffline: 'Playing from offline library',
sourceHot: 'Playing from cache',
sourceStream: 'Playing from network stream',
},
miniPlayer: {
showQueue: 'Show queue',
hideQueue: 'Hide queue',
pinOnTop: 'Pin on top',
pinOff: 'Unpin',
openMainWindow: 'Open main window',
close: 'Close',
emptyQueue: 'Queue is empty',
},
statistics: {
title: 'Statistics',
recentlyPlayed: 'Recently Played',
mostPlayed: 'Most Played Albums',
highestRated: 'Highest Rated Albums',
genreDistribution: 'Genre Distribution (Top 20)',
loadMore: 'Load more',
statArtists: 'Artists',
statArtistsTooltip: 'Album artists only — artists appearing only as a track-level artist (featured, guest, etc.) without their own album are not included.',
statAlbums: 'Albums',
statSongs: 'Songs',
statGenres: 'Genres',
statPlaytime: 'Total Playtime',
genreInsights: 'Genre Insights',
formatDistribution: 'Format Distribution',
formatSample: 'Sample of {{n}} tracks',
computing: 'Computing…',
genreSongs: '{{count}} Songs',
genreAlbums: '{{count}} Albums',
recentlyAdded: 'Recently Added',
decadeDistribution: 'Albums by Decade',
decadeAlbums_one: '{{count}} Album',
decadeAlbums_other: '{{count}} Albums',
decadeUnknown: 'Unknown',
lfmTitle: 'Last.fm Stats',
lfmTopArtists: 'Top Artists',
lfmTopAlbums: 'Top Albums',
lfmTopTracks: 'Top Tracks',
lfmPlays: '{{count}} plays',
lfmPeriodOverall: 'All Time',
lfmPeriod7day: '7 Days',
lfmPeriod1month: '1 Month',
lfmPeriod3month: '3 Months',
lfmPeriod6month: '6 Months',
lfmPeriod12month: '12 Months',
lfmNotConnected: 'Connect Last.fm in Settings to see your stats.',
lfmRecentTracks: 'Recent Scrobbles',
lfmNowPlaying: 'Now Playing',
lfmJustNow: 'just now',
lfmMinutesAgo: '{{n}}m ago',
lfmHoursAgo: '{{n}}h ago',
lfmDaysAgo: '{{n}}d ago',
topRatedSongs: 'Top Rated Songs',
topRatedArtists: 'Top Rated Artists',
noRatedSongs: 'No rated songs yet. Rate songs in album or playlist view.',
noRatedArtists: 'No rated artists yet.',
exportShare: 'Share',
exportTitle: 'Share Top Albums',
exportSubtitle: 'Generate a shareable image of your most-played albums.',
exportFormat: 'Format',
exportFormatStory: 'Story',
exportFormatSquare: 'Square',
exportFormatTwitter: 'Twitter Card',
exportGrid: 'Grid',
exportGridLabel: '{{n}}×{{n}}',
exportPreview: 'Preview',
exportGenerate: 'Generate',
exportSave: 'Save image…',
exportCancel: 'Cancel',
exportFooterLabel: 'Top Albums',
exportNotEnough: 'Need at least {{count}} albums in your library for a {{n}}×{{n}} grid.',
exportSaving: 'Saving…',
exportSaved: 'Saved.',
exportSaveFailed: 'Could not save the image.',
},
player: {
regionLabel: 'Music Player',
openFullscreen: 'Open Fullscreen Player',
fullscreen: 'Fullscreen Player',
closeFullscreen: 'Close Fullscreen',
closeTooltip: 'Close (Esc)',
noTitle: 'No Title',
stop: 'Stop',
prev: 'Previous Track',
play: 'Play',
pause: 'Pause',
previewActive: 'Preview playing',
previewLabel: 'Preview',
delayModalTitle: 'Timer',
delayPauseSection: 'Pause after',
delayStartSection: 'Start after',
delayPreviewPause: 'Pauses at',
delayPreviewStart: 'Starts at',
delaySchedulePause: 'Schedule pause',
delayScheduleStart: 'Schedule start',
delayCancelPause: 'Cancel pause timer',
delayCancelStart: 'Cancel start timer',
delayInactivePause: 'Available only while something is playing.',
delayInactiveStart: 'Available when paused with a track or radio loaded.',
delayCustomMinutes: 'Custom delay (minutes)',
delayCustomPlaceholder: 'e.g. 2.5',
closeDelayModal: 'Close',
delayIn: 'in',
delayFmtSec: '{{n}}s',
delayFmtMin: '{{n}} min',
delayFmtHr: '{{n}} h',
delayCancel: 'Cancel',
delayApply: 'Apply',
next: 'Next Track',
repeat: 'Repeat',
repeatOff: 'Off',
repeatAll: 'All',
repeatOne: 'One',
progress: 'Song Progress',
volume: 'Volume',
toggleQueue: 'Toggle Queue',
collapseQueueResize: 'Collapse queue, resize',
moreOptions: 'More options',
equalizer: 'Equalizer',
miniPlayer: 'Mini Player',
lyrics: 'Lyrics',
fsLyricsToggle: 'Lyrics in fullscreen',
lyricsLoading: 'Loading lyrics…',
lyricsNotFound: 'No lyrics found for this track',
lyricsSourceServer: 'Source: Server',
lyricsSourceLrclib: 'Source: LRCLIB',
lyricsSourceNetease: 'Source: Netease',
lyricsSourceLyricsplus: 'Source: YouLyPlus',
showDuration: 'Show duration',
showRemainingTime: 'Show remaining time',
},
nowPlayingInfo: {
tab: 'Info',
empty: 'Play something to see info',
artist: 'Artist',
songInfo: 'Song info',
onTour: 'On tour',
noTourEvents: 'No upcoming shows',
tourLoading: 'Loading…',
poweredByBandsintown: 'Tour data via Bandsintown',
bioReadMore: 'Read more',
bioReadLess: 'Show less',
showMoreTours_one: 'Show {{count}} more',
showMoreTours_other: 'Show {{count}} more',
showLessTours: 'Show less',
enableBandsintownPrompt: 'See upcoming tour dates?',
enableBandsintownPromptDesc: 'Optional. Loads concerts for the current artist via the public Bandsintown API.',
enableBandsintownPrivacy: 'When enabled, the name of the currently playing artist is sent to the Bandsintown API to fetch tour dates. No account or personal data leaves your device.',
enableBandsintownAction: 'Enable',
role: {
artist: 'Artist',
albumArtist: 'Album artist',
composer: 'Composer',
},
},
songInfo: {
title: 'Song Info',
songTitle: 'Title',
artist: 'Artist',
album: 'Album',
albumArtist: 'Album Artist',
year: 'Year',
genre: 'Genre',
duration: 'Duration',
track: 'Track',
format: 'Format',
bitrate: 'Bitrate',
sampleRate: 'Sample Rate',
bitDepth: 'Bit Depth',
channels: 'Channels',
fileSize: 'File Size',
path: 'Path',
replayGainTrack: 'RG Track Gain',
replayGainAlbum: 'RG Album Gain',
replayGainPeak: 'RG Track Peak',
mono: 'Mono',
stereo: 'Stereo',
},
playlists: {
title: 'Playlists',
newPlaylist: 'New Playlist',
unnamed: 'Unnamed Playlist',
createName: 'Playlist name…',
create: 'Create',
cancel: 'Cancel',
empty: 'No playlists yet.',
emptyPlaylist: 'This playlist is empty.',
addFirstSong: 'Add your first song',
notFound: 'Playlist not found.',
songs: '{{n}} songs',
playAll: 'Play All',
shuffle: 'Shuffle',
addToQueue: 'Add to Queue',
back: 'Back to Playlists',
deletePlaylist: 'Delete',
confirmDelete: 'Click again to confirm',
removeSong: 'Remove from playlist',
addSongs: 'Add Songs',
searchPlaceholder: 'Search your library…',
noResults: 'No results.',
suggestions: 'Suggested Songs',
noSuggestions: 'No suggestions available.',
titleBadge: 'Playlist',
refreshSuggestions: 'New suggestions',
addSong: 'Add to playlist',
preview: 'Preview (30s)',
previewStop: 'Stop preview',
playNextSuggestion: 'Play next',
suggestionsHint: 'Double-click a row to add it to the playlist',
addSelected: 'Add selected',
cacheOffline: 'Cache playlist offline',
offlineCached: 'Playlist cached',
removeOffline: 'Remove from offline cache',
offlineDownloading: 'Caching… ({{done}}/{{total}} albums)',
publicLabel: 'Public',
privateLabel: 'Private',
editMeta: 'Edit playlist',
editNamePlaceholder: 'Playlist name…',
editCommentPlaceholder: 'Add a description…',
editPublic: 'Public playlist',
editSave: 'Save',
editCancel: 'Cancel',
changeCover: 'Change cover image',
changeCoverLabel: 'Change photo',
removeCover: 'Remove photo',
coverUpdated: 'Cover updated',
metaSaved: 'Playlist updated',
downloadZip: 'Download (ZIP)',
// Toast notifications for multi-select
addSuccess: '{{count}} songs added to {{playlist}}',
addPartial: '{{added}} added, {{skipped}} skipped (duplicates)',
addAllSkipped: 'All skipped ({{count}} duplicates)',
addedAsDuplicates: '{{count}} songs added to {{playlist}} (as duplicates)',
duplicateConfirmTitle: 'Already in playlist',
duplicateConfirmMessage: 'All {{count}} songs are already in "{{playlist}}". Add them again as duplicates?',
duplicateConfirmAction: 'Add anyway',
addError: 'Error adding songs',
createAndAddSuccess: 'Playlist "{{playlist}}" created with {{count}} songs',
createError: 'Error creating playlist',
deleteSuccess: '{{count}} playlists deleted',
deleteFailed: 'Error deleting {{name}}',
deleteSelected: 'Delete selected',
deleteSelectedPartial: 'Only {{n}} of {{total}} selected playlists can be deleted (the others belong to someone else).',
mergeSuccess: '{{count}} songs merged into {{playlist}}',
mergeNoNewSongs: 'No new songs to add',
mergeError: 'Error merging playlists',
mergeInto: 'Merge into',
selectionCount: '{{count}} selected',
select: 'Select',
startSelect: 'Enable selection',
cancelSelect: 'Cancel',
loadingAlbums: 'Resolving {{count}} albums…',
loadingArtists: 'Resolving {{count}} artists…',
myPlaylists: 'My Playlists',
noOtherPlaylists: 'No other playlists available',
addToPlaylistSuccess: '{{count}} songs added to {{playlist}}',
addToPlaylistNoNew: 'No new songs to add to {{playlist}}',
addToPlaylistError: 'Error adding to playlist',
removeSuccess: 'Song removed from playlist',
removeError: 'Error removing song from playlist',
importCSV: 'Import CSV',
importCSVTooltip: 'Import from Spotify CSV',
csvImportReport: 'CSV Import Report',
csvImportTotal: 'Total',
csvImportAdded: 'Added',
csvImportDuplicates: 'Duplicates',
csvImportNotFound: 'Not Found',
csvImportNetworkErrors: 'Network Errors',
csvImportDuplicatesTitle: 'Duplicate Tracks (Already in Playlist):',
csvImportNotFoundTitle: 'Tracks Not Found:',
csvImportNetworkErrorsTitle: 'Network Errors (may retry import):',
csvImportDownloadReport: 'Download Report',
csvImportClose: 'Close',
csvImportNoValidTracks: 'No valid tracks found in CSV file',
csvImportFailed: 'Failed to import CSV file',
csvImportToast: '{{added}} added, {{notFound}} not found, {{duplicates}} duplicates',
csvImportDownloadSuccess: 'Report downloaded successfully',
csvImportDownloadError: 'Failed to download report',
},
smartPlaylists: {
sectionBasic: '1. Basic',
sectionGenres: '2. Genres',
sectionYearsAndFilters: '3. Years and filters',
genreMode: 'Genre mode',
genreModeInclude: 'Include genres',
genreModeExclude: 'Exclude genres',
genreSearchPlaceholder: 'Filter genres...',
availableGenres: 'Available',
selectedGenres: 'Selected',
yearMode: 'Year mode',
yearModeInclude: 'Include range',
yearModeExclude: 'Exclude range',
name: 'Name (without prefix)',
limit: 'Limit',
limitHint: 'How many tracks to include in playlist (1-{{max}}, usually 50).',
artistContains: 'Artist contains…',
albumContains: 'Album contains…',
titleContains: 'Title contains…',
minRating: 'Minimum rating',
minRatingAria: 'Minimum rating for smart playlist',
minRatingHint: '0 disables minimum threshold; 1-5 keeps tracks with rating above selected value.',
fromYear: 'From year',
toYear: 'To year',
excludeUnrated: 'Exclude unrated tracks',
compilationOnly: 'Only compilations',
create: 'New Smart Playlist',
save: 'Save Smart Playlist',
created: 'Created {{name}}',
updated: 'Updated {{name}}',
createFailed: 'Could not create smart playlist.',
updateFailed: 'Could not update smart playlist.',
navidromeOnly: 'Smart playlists can only be created on Navidrome servers.',
loadFailed: 'Could not load smart playlists from Navidrome.',
sortRandom: 'Sort: random',
sortTitleAsc: 'Sort: title A-Z',
sortTitleDesc: 'Sort: title Z-A',
sortYearDesc: 'Sort: year desc',
sortYearAsc: 'Sort: year asc',
sortPlayCountDesc: 'Sort: play count desc',
},
mostPlayed: {
title: 'Most Played',
topArtists: 'Top Artists',
topAlbums: 'Top Albums',
plays: '{{n}} plays',
sortMost: 'Most plays first',
sortLeast: 'Fewest plays first',
loadMore: 'Load more albums',
noData: 'No play data yet. Start listening!',
noArtists: 'All artists filtered out.',
filterCompilations: 'Hide compilation artists (Various Artists, Soundtracks, etc.)',
filterCompilationsShort: 'Hide compilations',
},
radio: {
title: 'Internet Radio',
empty: 'No radio stations configured.',
addStation: 'Add Station',
editStation: 'Edit',
deleteStation: 'Delete station',
confirmDelete: 'Click again to confirm',
stationName: 'Station name…',
streamUrl: 'Stream URL…',
homepageUrl: 'Homepage URL (optional)',
save: 'Save',
cancel: 'Cancel',
live: 'LIVE',
liveStream: 'Internet Radio',
openHomepage: 'Open homepage',
changeCoverLabel: 'Change cover',
removeCover: 'Remove cover',
browseDirectory: 'Search Directory',
directoryPlaceholder: 'Search stations…',
noResults: 'No stations found.',
stationAdded: 'Station added',
filterAll: 'All',
filterFavorites: 'Favorites',
sortManual: 'Manual',
sortAZ: 'A → Z',
sortZA: 'Z → A',
sortNewest: 'Newest',
favorite: 'Add to favorites',
unfavorite: 'Remove from favorites',
noFavorites: 'No favorite stations.',
listenerCount_one: '{{count}} listener',
listenerCount_other: '{{count}} listeners',
recentlyPlayed: 'Recently Played',
upNext: 'Up Next',
},
folderBrowser: {
empty: 'Empty folder',
error: 'Failed to load',
},
deviceSync: {
title: 'Device Sync',
targetFolder: 'Target Folder',
noFolderChosen: 'No folder chosen',
selectDrive: 'Select a drive…',
refreshDrives: 'Refresh drives',
noDrivesDetected: 'No removable drives detected',
browseManual: 'Browse manually…',
free: 'free',
notMountedVolume: 'Target is not on a mounted volume. The drive may have been disconnected.',
chooseFolder: 'Choose…',
targetDevice: 'Target Device',
schemaLabel: 'Naming scheme',
schemaHint: 'Fixed scheme for reliable cross-OS sync. Playlists are written as .m3u8 that reference the album tracks — no duplicates on the device.',
migrateButton: 'Reorganize existing files…',
migrateTooltip: 'Rename existing files on the device into the new scheme (from the old filename template).',
migrateTitle: 'Reorganize existing files',
migrateLoading: 'Analyzing existing files…',
migrateNothingToDo: 'All existing files already match the new scheme — nothing to do.',
migrateNoTemplate: 'No legacy filename template found on the device. Migration only applies when the stick was synced with a Psysonic version that supported custom templates.',
migrateFilesToRename: 'files will be renamed',
migrateUnchanged: '{{n}} files are already at the correct path',
migrateCollisions: '{{n}} files cannot be renamed automatically (multiple tracks map to the same target). They will be left untouched — the next sync re-downloads them into the correct location.',
migratePreviewNote: 'Old template: {{tpl}}',
migrateExecuting: 'Renaming files…',
migrateSuccess: '{{n}} files renamed successfully',
migrateFailed: '{{n}} renames failed',
migrateShowErrors: 'Show errors',
migrateStart: 'Start renaming',
onDevice: 'Device Manager',
addSources: 'Add…',
colName: 'Name',
colType: 'Type',
colStatus: 'Status',
syncResult: '{{done}} transferred, {{skipped}} already up to date ({{total}} total)',
deleteFromDevice: 'Mark for deletion ({{count}})',
confirmDelete: 'Delete {{count}} item(s) from the device: {{names}}?',
deleteComplete: '{{count}} item(s) removed from device.',
manifestImported: 'Imported {{count}} source(s) from device manifest.',
selectedSources: 'Selected Sources',
noSourcesSelected: 'No sources selected. Click items in the browser to add them.',
clearAll: 'Clear all',
tabPlaylists: 'Playlists',
tabAlbums: 'Albums',
tabArtists: 'Artists',
searchPlaceholder: 'Search…',
syncButton: 'Sync to Device',
actionTransfer: 'Transfer to Device',
actionDelete: 'Delete from Device',
actionApplyAll: 'Apply All Changes',
cancel: 'Cancel',
noTargetDir: 'Please choose a target folder first.',
noSources: 'Please select at least one source.',
noTracks: 'No tracks found in the selected sources.',
fetchError: 'Failed to fetch tracks from server.',
syncComplete: 'Sync complete.',
dismiss: 'Dismiss',
cancelSync: 'Cancel',
syncCancelled: 'Sync cancelled ({{done}} / {{total}} transferred).',
statusSynced: 'Synced',
statusPending: 'Pending',
statusDeletion: 'Deletion',
markForDeletion: 'Mark for deletion',
undoDeletion: 'Undo deletion',
removeSource: 'Remove',
syncInBackground: 'Sync started in background — you can navigate away.',
syncInProgress: '{{done}} / {{total}} tracks…',
scanningDevice: 'Scanning device…',
syncSummary: 'Sync Summary',
calculating: 'Calculating required payload…',
filesToAdd: 'Files to Add:',
filesToDelete: 'Files to Delete:',
netChange: 'Net Change:',
availableSpace: 'Available Disk Space:',
spaceWarning: 'Warning: Target device does not have enough reported space.',
proceed: 'Proceed with Sync',
notEnoughSpace: 'Not enough physical disk space detected!',
liveSearch: 'Live',
randomAlbumsLabel: 'Random Albums',
},
orbit: {
triggerLabel: 'Orbit',
triggerTooltip: 'Start or join a shared listening session',
launchCreate: 'Create a session',
launchJoin: 'Join a session',
launchHelp: 'How does this work?',
launchHelpSoon: 'Coming soon',
joinModalTitle: 'Join an Orbit session',
joinModalSub: 'Paste the invite link your host sent you.',
joinModalLinkLabel: 'Invite link',
joinModalLinkPlaceholder: 'psysonic2-…',
joinModalLinkHelper: 'Works with any valid Orbit link. Must point to the server you\'re currently signed into.',
joinModalPasteTooltip: 'Paste from clipboard',
joinModalSubmit: 'Join',
joinModalBusy: 'Joining…',
joinErrEmpty: 'Please paste an invite link.',
joinErrInvalid: 'That doesn\'t look like an Orbit invite link.',
closeAria: 'Close',
heroTitlePrefix: 'Listen together with',
heroTitleBrand: 'Orbit',
heroSub: 'Start a session — other users on {{server}} will listen along and can suggest tracks.',
fallbackServer: 'this server',
tipLan: "You're currently connected via a home-network address. Guests outside your network can't join — switch to a public server address in settings before you start.",
tipRemote: 'For guests outside your home network to join, your server address has to be publicly reachable. If your server is internal only, switch before you start.',
labelName: 'Session name',
namePlaceholder: 'Velvet Orbit',
reshuffleTooltip: 'New suggestion',
reshuffleAria: 'Suggest a new name',
helperName: 'How the session shows up to your guests — keep it or type your own.',
labelMax: 'Max guests',
helperMax: "You don't count — this only caps incoming guests.",
labelLink: 'Invite link',
tooltipCopied: 'Copied',
tooltipCopy: 'Copy',
ariaCopyLink: 'Copy invite link',
helperLink: 'Send this link to your guests. They can paste it anywhere in Psysonic with Ctrl+V.',
labelClearQueue: 'Clear my queue first',
helperClearQueue: 'Start the session with a fresh, empty queue. Off: the tracks you already have queued stay and get shared with guests.',
btnCancel: 'Cancel',
btnStarting: 'Starting…',
btnStart: 'Start Orbit',
btnCopyAndStart: 'Copy link & start',
errNameRequired: 'Please give your session a name.',
errStartFailed: "Couldn't start.",
hostLabel: 'Host: {{name}}',
participantsTooltip: 'Participants',
settingsTooltip: 'Session settings',
shareTooltip: 'Share invite link',
shuffleLabel: 'Queue reshuffles in',
catchUpLabel: 'catch up',
catchUpTooltip: "Jump to the host's current position",
hostAway: 'Host offline',
hostOnline: 'Host online',
endTooltip: 'End session',
leaveTooltip: 'Leave session',
helpTooltip: 'How Orbit works',
helpTitle: 'How Orbit works',
helpIntro: 'Orbit turns Psysonic into a shared listening room. A host picks the music; guests listen in sync and can suggest tracks.',
helpHostOnly: '(host)',
helpSec1Title: 'What is Orbit?',
helpSec1Body: "A \"listen together\" mode — the host drives playback, guests tune in. Everything runs through playlists on your own Navidrome server; no external infrastructure.",
helpSec2Title: 'Host and guests',
helpSec2Body: 'Host controls playback (play / pause / skip / seek) and decides when the session ends. Guests follow the host\'s playback, can suggest tracks, and leave or re-join any time. Only the host can kick or permanently ban a participant.',
helpSec2WarnHead: 'Important: separate accounts.',
helpSec2WarnBody: "Every participant needs their own Navidrome account. If host and guest sign in with the same user their submissions collide and the host never sees the guest's suggestions.",
helpSec3Title: 'Starting and inviting',
helpSec3Body: 'Click the "Orbit" button → Create a session → the start modal shows a ready-to-copy invite link and lets you optionally clear your current queue. Share that link however you like. While a session is running, the share button in the session bar at the top copies the link again at any time.',
helpSec4Title: 'Joining',
helpSec4Body: 'Paste the invite link anywhere in Psysonic with Ctrl+V / Cmd+V — the join prompt pops up automatically. Alternatively: "Orbit" → Join a session → paste into the field. If the link points to a server you have an account on, Psysonic switches for you. When you have more than one account on that server, a small picker asks which to use.',
helpSec5Title: 'Suggesting tracks',
helpSec5Body: 'In any song list: double-click a row, or right-click → "Add to Orbit session". A single click shows a hint instead of dumping the whole album into the shared queue — that\'s intentional. Explicit bulk actions like "Play All" or "Play Album" ask for confirmation first and then append (never replace).',
helpSec6Title: 'The shared queue',
helpSec6Body: "The queue shows the host's upcoming tracks — visible to everyone. Incoming bulk additions are always appended, so guest suggestions aren't lost. Auto-shuffle reorders the queue periodically (configurable: 1 / 5 / 10 / 15 / 30 min). If your playback drifts away from the host, the \"Catch up\" button in the session bar jumps you back to the host's live position.",
helpSec7Title: 'Approvals',
helpSec7Body: 'By default, guest suggestions need manual approval — they appear in an "Approvals" strip at the top of the queue panel with accept / decline buttons. Auto-approve can be turned on in the session settings so everything lands straight away.',
helpSec8Title: 'Participants and settings',
helpSec8Body: 'Open the settings icon in the session bar for shuffle cadence, auto-approve, and the "Shuffle now" shortcut. Click the participants count to see who\'s connected — kick (can re-join via the link) or ban (locked out for the session). Guests see the participant list too, but read-only.',
helpSec9Title: 'Ending the session',
helpSec9Body: "Host X → confirm dialog → session closes for everyone and the server playlists are cleaned up automatically. Guest X → the guest leaves, the session keeps running. If the host goes quiet for 5 minutes the guest auto-leaves with a notice; short reconnects within that window are invisible.",
participantsInviteLabel: 'Invite link',
participantsCountLabel: '{{count}} in session',
participantsHost: 'host',
participantsEmpty: 'No guests yet',
participantsKickTooltip: 'Remove from session',
participantsKickAria: 'Remove {{user}}',
participantsRemoveTooltip: 'Remove from session',
participantsRemoveAria: 'Remove {{user}} from this session',
participantsBanTooltip: 'Ban permanently',
participantsBanAria: 'Permanently ban {{user}}',
participantsMuteTooltip: 'Mute suggestions',
participantsUnmuteTooltip: 'Allow suggestions',
participantsMuteAria: 'Mute suggestions from {{user}}',
participantsUnmuteAria: 'Allow suggestions from {{user}}',
confirmRemoveTitle: 'Remove from session?',
confirmRemoveBody: '{{user}} will be dropped from the session, but can re-join via your invite link.',
confirmRemoveConfirm: 'Remove',
confirmBanTitle: 'Permanently ban?',
confirmBanBody: '{{user}} will be removed and blocked from re-joining this session. This can\'t be undone for the lifetime of the session.',
confirmBanConfirm: 'Ban',
confirmCancel: 'Cancel',
confirmJoinTitle: 'Join Orbit session?',
confirmJoinBody: '{{host}} invited you to "{{name}}". Join the session?',
confirmJoinConfirm: 'Join',
confirmLeaveTitle: 'Leave session?',
confirmLeaveBody: 'Leave "{{name}}"? You can re-join via {{host}}\'s invite link any time.',
confirmLeaveConfirm: 'Leave',
confirmEndTitle: 'End session for everyone?',
confirmEndBody: '"{{name}}" will close for all participants. The session\'s playlists are cleaned up automatically.',
confirmEndConfirm: 'End session',
invalidLinkTitle: 'Invite link is no longer valid',
invalidLinkBody: 'This Orbit session doesn\'t exist any more or has already ended. Ask the host for a fresh invite link.',
settingsTitle: 'Session settings',
settingAutoApprove: 'Auto-approve suggestions',
settingAutoApproveHint: "Guest suggestions land in your queue instantly. Off: they stay in the session list for you to review later.",
settingAutoShuffle: 'Automatic reshuffle',
settingAutoShuffleHint: "Periodic FisherYates shuffle of the upcoming queue. Off: order only changes when you rearrange it.",
settingShuffleInterval: 'Reshuffle every',
settingShuffleIntervalHint: 'How often the upcoming queue gets reshuffled while the session runs.',
suggestBlockedMuted: 'The host muted you for this session — suggestions are off.',
settingShuffleIntervalValue_one: '{{count}} min',
settingShuffleIntervalValue_other: '{{count}} min',
settingShuffleNow: 'Shuffle now',
toastSuggested: '{{user}} suggested "{{title}}"',
toastSuggestedMany: '{{count}} new suggestions in the queue',
toastShuffled: 'Queue shuffled',
toastJoined: 'Joined session',
toastLoginFirst: 'Log in before joining a session',
toastSwitchServer: 'Switch to {{url}} first, then paste again',
toastNoAccountForServer: "You don't have access to {{url}}. Ask the host for an invite.",
toastSwitchFailed: "Couldn't switch to {{url}}",
accountPickerTitle: 'Which account?',
accountPickerSub: 'You have more than one account for {{url}}. Pick the one to join the session as.',
toastJoinFail: "Couldn't join session",
joinErrNotFound: 'Session not found',
joinErrEnded: 'Session has ended',
joinErrFull: 'Session is full',
joinErrKicked: "You can't rejoin this session",
joinErrNoUser: 'No active server',
joinErrServerError: "Couldn't join",
ctxAddToSession: 'Add to Orbit session',
ctxSuggestedToast: 'Suggested to the session',
ctxSuggestFailed: "Couldn't suggest — not joined",
ctxAddToSessionHost: 'Add to Orbit session',
ctxAddedHostToast: 'Added to the session',
ctxAddHostFailed: "Couldn't add to session",
bulkConfirmTitle: 'Add all of this to the Orbit queue?',
bulkConfirmBody: "This would drop {{count}} tracks into the shared queue in one go. That's a lot for your guests to notice. Go ahead?",
bulkConfirmYes: 'Add them all',
bulkConfirmNo: 'Cancel',
guestLive: 'Live',
guestPlaying: 'Playing now',
guestPaused: 'Paused',
guestLoading: 'Loading…',
guestSuggestions: 'Suggestions',
guestUpNext: 'Up next',
guestUpNextEmpty: 'Nothing queued. Open a track\'s context menu and pick "Add to Orbit session" to suggest one.',
guestPendingTitle: 'Waiting for host',
guestPendingHint: 'Suggested — will appear in the queue when the host picks it up.',
approvalTitle: 'Pending approvals',
approvalFrom: 'Suggested by {{user}}',
approvalAccept: 'Accept',
approvalDecline: 'Decline',
guestUpNextMore: '+ {{count}} more in the host\'s queue',
guestSubmitter: 'by {{user}}',
queueAddedByHost: 'Added by host',
queueAddedByYou: 'Added by you',
queueAddedByUser: 'Added by {{user}}',
guestEmpty: 'No suggestions yet. Open a track\'s context menu and pick "Add to Orbit session".',
guestFooter: "The host controls playback — you can't change the list.",
exitKickedTitle: 'You were banned from the session',
exitRemovedTitle: 'You were removed from the session',
exitEndedTitle: 'The host ended the session',
exitHostTimeoutTitle: 'Host went silent',
exitHostTimeoutBody: "{{host}} hasn't sent an update for a while, so \"{{name}}\" was closed on your end. You can re-join any time with the invite link.",
exitKickedBody: '{{host}} banned you from "{{name}}". You can\'t re-join.',
exitRemovedBody: '{{host}} removed you from "{{name}}". You can re-join any time via the invite link.',
exitEndedBody: '"{{name}}" has ended. Hope you had fun.',
exitOk: 'OK',
},
tray: {
playPause: 'Play / Pause',
nextTrack: 'Next Track',
previousTrack: 'Previous Track',
showHide: 'Show / Hide',
exitPsysonic: 'Exit Psysonic',
nothingPlaying: 'Nothing playing',
},
licenses: {
title: 'Open Source Licenses',
intro: 'Psysonic is built on open-source software. The list below shows every dependency that ships in the app, with its version, license, and full license text.',
highlights: 'Key dependencies',
searchPlaceholder: 'Search by name, version or license…',
noResults: 'No matches.',
loading: 'Loading licenses…',
loadError: 'Could not load license data.',
noLicenseText: 'No license text bundled for this dependency.',
viewSource: 'View source',
totalLine: '{{total}} dependencies — {{cargo}} cargo, {{npm}} npm',
generatedAt: 'Last generated {{date}}',
},
};