mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
feat(artist): add "add to queue" for the whole discography (#1321)
* feat(artist): enqueue whole discography from artist page * i18n(artist): add enqueue-all button strings * docs(changelog): artist discography enqueue (#1321)
This commit is contained in:
@@ -139,6 +139,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
* The header search field on the Playlists page now filters the list by playlist name (same scoped badge pattern as Artists / Albums), including in folder view.
|
* The header search field on the Playlists page now filters the list by playlist name (same scoped badge pattern as Artists / Albums), including in folder view.
|
||||||
|
|
||||||
|
### Artist page — add the whole discography to the queue
|
||||||
|
|
||||||
|
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1321](https://github.com/Psychotoxical/psysonic/pull/1321)**
|
||||||
|
|
||||||
|
* A new queue button on the artist page appends the artist's entire discography to the current queue in one click, next to Play all and Shuffle — matching what album pages already offer.
|
||||||
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
|
|||||||
@@ -430,6 +430,7 @@ const CONTRIBUTOR_ENTRIES = [
|
|||||||
'Settings → System: community theme authors credited in a Themes sub-section; theme card What\'s new shows the latest version only (PR #1248)',
|
'Settings → System: community theme authors credited in a Themes sub-section; theme card What\'s new shows the latest version only (PR #1248)',
|
||||||
'Fullscreen player style — selectable Minimal or Immersive view, with the artist backdrop, cover-derived accent, and rail/Apple lyrics (PR #1249)',
|
'Fullscreen player style — selectable Minimal or Immersive view, with the artist backdrop, cover-derived accent, and rail/Apple lyrics (PR #1249)',
|
||||||
'Word-by-word lyrics from the server via OpenSubsonic songLyrics v2, no third-party backend needed (PR #1265)',
|
'Word-by-word lyrics from the server via OpenSubsonic songLyrics v2, no third-party backend needed (PR #1265)',
|
||||||
|
'Artist page — add the whole discography to the queue (PR #1321)',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useAlbumDetailBack } from '@/features/album';
|
import { useAlbumDetailBack } from '@/features/album';
|
||||||
import {
|
import {
|
||||||
ArrowLeft, Camera, Check, HardDriveDownload, Heart,
|
ArrowLeft, Camera, Check, HardDriveDownload, Heart,
|
||||||
Loader2, Play, Radio, Share2, Shuffle, Users,
|
ListPlus, Loader2, Play, Radio, Share2, Shuffle, Users,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
|
import type { SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
|
||||||
import { useOfflineStore } from '@/features/offline';
|
import { useOfflineStore } from '@/features/offline';
|
||||||
@@ -35,6 +35,7 @@ interface Props {
|
|||||||
toggleStar: () => Promise<void>;
|
toggleStar: () => Promise<void>;
|
||||||
handlePlayAll: () => void;
|
handlePlayAll: () => void;
|
||||||
handleShuffle: () => void;
|
handleShuffle: () => void;
|
||||||
|
handleEnqueueAll: () => void;
|
||||||
handleStartRadio: () => void;
|
handleStartRadio: () => void;
|
||||||
handleShareArtist: () => void;
|
handleShareArtist: () => void;
|
||||||
handleImageUpload: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
handleImageUpload: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
||||||
@@ -100,7 +101,7 @@ function ArtistHeaderBg({ url, position }: { url: string; position?: string }) {
|
|||||||
|
|
||||||
export default function ArtistDetailHero({
|
export default function ArtistDetailHero({
|
||||||
artist, id, albums, info, isStarred, artistEntityRating, handleArtistEntityRating,
|
artist, id, albums, info, isStarred, artistEntityRating, handleArtistEntityRating,
|
||||||
toggleStar, handlePlayAll, handleShuffle, handleStartRadio, handleShareArtist,
|
toggleStar, handlePlayAll, handleShuffle, handleEnqueueAll, handleStartRadio, handleShareArtist,
|
||||||
handleImageUpload, playAllLoading, radioLoading, uploading,
|
handleImageUpload, playAllLoading, radioLoading, uploading,
|
||||||
openedLink, openLink,
|
openedLink, openLink,
|
||||||
coverId, coverRef, coverRevision, headerCoverFailed, setHeaderCoverFailed,
|
coverId, coverRef, coverRevision, headerCoverFailed, setHeaderCoverFailed,
|
||||||
@@ -302,6 +303,15 @@ export default function ArtistDetailHero({
|
|||||||
{playAllLoading ? <div className="spinner" style={{ width: 16, height: 16, borderTopColor: 'currentColor' }} /> : <Shuffle size={16} />}
|
{playAllLoading ? <div className="spinner" style={{ width: 16, height: 16, borderTopColor: 'currentColor' }} /> : <Shuffle size={16} />}
|
||||||
{!isMobile && <span className="compact-btn-label">{t('artistDetail.shuffle')}</span>}
|
{!isMobile && <span className="compact-btn-label">{t('artistDetail.shuffle')}</span>}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-surface"
|
||||||
|
onClick={handleEnqueueAll}
|
||||||
|
disabled={playAllLoading}
|
||||||
|
aria-label={t('artistDetail.enqueue')}
|
||||||
|
{...tooltipAttrs(t('artistDetail.enqueueTooltip'))}
|
||||||
|
>
|
||||||
|
<ListPlus size={16} />
|
||||||
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import {
|
|||||||
import { useArtistDetailData } from '@/features/artist/hooks/useArtistDetailData';
|
import { useArtistDetailData } from '@/features/artist/hooks/useArtistDetailData';
|
||||||
import { useArtistSimilarArtists } from '@/features/artist/hooks/useArtistSimilarArtists';
|
import { useArtistSimilarArtists } from '@/features/artist/hooks/useArtistSimilarArtists';
|
||||||
import {
|
import {
|
||||||
runArtistDetailPlayAll, runArtistDetailPlayTopSong, runArtistDetailShuffle, runArtistDetailStartRadio,
|
runArtistDetailPlayAll, runArtistDetailPlayTopSong, runArtistDetailShuffle,
|
||||||
|
runArtistDetailStartRadio, runArtistDetailEnqueueAll,
|
||||||
} from '@/features/artist/utils/runArtistDetailPlay';
|
} from '@/features/artist/utils/runArtistDetailPlay';
|
||||||
import { useOfflineBrowseContext } from '@/features/offline';
|
import { useOfflineBrowseContext } from '@/features/offline';
|
||||||
import { offlineActionPolicy } from '@/features/offline';
|
import { offlineActionPolicy } from '@/features/offline';
|
||||||
@@ -113,6 +114,9 @@ export default function ArtistDetail() {
|
|||||||
const handleShuffle = () => runArtistDetailShuffle({
|
const handleShuffle = () => runArtistDetailShuffle({
|
||||||
albums, serverId: activeServerId, setPlayAllLoading, playTrack,
|
albums, serverId: activeServerId, setPlayAllLoading, playTrack,
|
||||||
});
|
});
|
||||||
|
const handleEnqueueAll = () => runArtistDetailEnqueueAll({
|
||||||
|
albums, serverId: activeServerId, setPlayAllLoading, enqueue,
|
||||||
|
});
|
||||||
const handleStartRadio = () => {
|
const handleStartRadio = () => {
|
||||||
if (!artist) return;
|
if (!artist) return;
|
||||||
return runArtistDetailStartRadio({ artist, t, setRadioLoading, playTrack, enqueue });
|
return runArtistDetailStartRadio({ artist, t, setRadioLoading, playTrack, enqueue });
|
||||||
@@ -270,6 +274,7 @@ export default function ArtistDetail() {
|
|||||||
toggleStar={toggleStar}
|
toggleStar={toggleStar}
|
||||||
handlePlayAll={handlePlayAll}
|
handlePlayAll={handlePlayAll}
|
||||||
handleShuffle={handleShuffle}
|
handleShuffle={handleShuffle}
|
||||||
|
handleEnqueueAll={handleEnqueueAll}
|
||||||
handleStartRadio={handleStartRadio}
|
handleStartRadio={handleStartRadio}
|
||||||
handleShareArtist={handleShareArtist}
|
handleShareArtist={handleShareArtist}
|
||||||
handleImageUpload={handleImageUpload}
|
handleImageUpload={handleImageUpload}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||||
import * as offlineMediaResolve from '@/features/offline';
|
import * as offlineMediaResolve from '@/features/offline';
|
||||||
import { fetchArtistDetailTracks, runArtistDetailPlayTopSong } from '@/features/artist/utils/runArtistDetailPlay';
|
import {
|
||||||
|
fetchArtistDetailTracks, runArtistDetailPlayTopSong, runArtistDetailEnqueueAll,
|
||||||
|
} from '@/features/artist/utils/runArtistDetailPlay';
|
||||||
|
|
||||||
vi.mock('@/features/offline', () => ({
|
vi.mock('@/features/offline', () => ({
|
||||||
resolveAlbum: vi.fn(),
|
resolveAlbum: vi.fn(),
|
||||||
@@ -99,3 +101,39 @@ describe('runArtistDetailPlayTopSong', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('runArtistDetailEnqueueAll', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enqueues the whole ordered discography', async () => {
|
||||||
|
resolveAlbumMock
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
album: albums[1],
|
||||||
|
songs: [{ id: 't1', title: 'One', artist: 'A', album: 'A', albumId: 'al-1', duration: 100, track: 1 }],
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
album: albums[0],
|
||||||
|
songs: [{ id: 't2', title: 'Two', artist: 'A', album: 'B', albumId: 'al-2', duration: 100, track: 1 }],
|
||||||
|
});
|
||||||
|
const enqueue = vi.fn();
|
||||||
|
const setPlayAllLoading = vi.fn();
|
||||||
|
|
||||||
|
await runArtistDetailEnqueueAll({ albums, serverId: 'srv-1', setPlayAllLoading, enqueue });
|
||||||
|
|
||||||
|
expect(enqueue).toHaveBeenCalledTimes(1);
|
||||||
|
expect(enqueue.mock.calls[0][0].map((t: { id: string }) => t.id)).toEqual(['t1', 't2']);
|
||||||
|
expect(setPlayAllLoading).toHaveBeenCalledWith(true);
|
||||||
|
expect(setPlayAllLoading).toHaveBeenLastCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is a no-op with no albums', async () => {
|
||||||
|
const enqueue = vi.fn();
|
||||||
|
|
||||||
|
await runArtistDetailEnqueueAll({ albums: [], serverId: 'srv-1', setPlayAllLoading: vi.fn(), enqueue });
|
||||||
|
|
||||||
|
expect(resolveAlbumMock).not.toHaveBeenCalled();
|
||||||
|
expect(enqueue).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
|
|||||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||||
import type { Track } from '@/lib/media/trackTypes';
|
import type { Track } from '@/lib/media/trackTypes';
|
||||||
import { songToTrack } from '@/lib/media/songToTrack';
|
import { songToTrack } from '@/lib/media/songToTrack';
|
||||||
import { runBulkPlayAll, runBulkShuffle } from '@/features/playback/utils/playback/runBulkPlay';
|
import { runBulkPlayAll, runBulkShuffle, runBulkEnqueue } from '@/features/playback/utils/playback/runBulkPlay';
|
||||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||||
|
|
||||||
/** Ordered artist discography tracks for play-all / shuffle (network or local bytes). */
|
/** Ordered artist discography tracks for play-all / shuffle (network or local bytes). */
|
||||||
@@ -82,6 +82,24 @@ export async function runArtistDetailShuffle(deps: RunArtistDetailPlayDeps): Pro
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RunArtistDetailEnqueueDeps {
|
||||||
|
albums: SubsonicAlbum[];
|
||||||
|
serverId?: string | null;
|
||||||
|
setPlayAllLoading: (v: boolean) => void;
|
||||||
|
enqueue: (tracks: Track[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Append the whole artist discography to the play queue. */
|
||||||
|
export async function runArtistDetailEnqueueAll(deps: RunArtistDetailEnqueueDeps): Promise<void> {
|
||||||
|
const { albums, serverId, setPlayAllLoading, enqueue } = deps;
|
||||||
|
if (albums.length === 0) return;
|
||||||
|
await runBulkEnqueue({
|
||||||
|
fetchTracks: () => fetchArtistDetailTracks(albums, serverId),
|
||||||
|
setLoading: setPlayAllLoading,
|
||||||
|
enqueue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export interface RunArtistDetailStartRadioDeps {
|
export interface RunArtistDetailStartRadioDeps {
|
||||||
artist: SubsonicArtist;
|
artist: SubsonicArtist;
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Пусни цялата дискография',
|
playAllTooltip: 'Пусни цялата дискография',
|
||||||
shuffleTooltip: 'Разбъркай цялата дискография',
|
shuffleTooltip: 'Разбъркай цялата дискография',
|
||||||
radioTooltip: 'Стартирай радио на базата на този изпълнител',
|
radioTooltip: 'Стартирай радио на базата на този изпълнител',
|
||||||
|
enqueue: 'Добави в опашката',
|
||||||
|
enqueueTooltip: 'Добави цялата дискография в опашката',
|
||||||
loading: 'Зареждане…',
|
loading: 'Зареждане…',
|
||||||
noRadio: 'Няма намерени подобни песни за този изпълнител.',
|
noRadio: 'Няма намерени подобни песни за този изпълнител.',
|
||||||
notFound: 'Изпълнителят не е намерен.',
|
notFound: 'Изпълнителят не е намерен.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Gesamte Diskografie abspielen',
|
playAllTooltip: 'Gesamte Diskografie abspielen',
|
||||||
shuffleTooltip: 'Gesamte Diskografie zufällig abspielen',
|
shuffleTooltip: 'Gesamte Diskografie zufällig abspielen',
|
||||||
radioTooltip: 'Radio basierend auf diesem Künstler starten',
|
radioTooltip: 'Radio basierend auf diesem Künstler starten',
|
||||||
|
enqueue: 'Einreihen',
|
||||||
|
enqueueTooltip: 'Gesamte Diskografie zur Warteschlange hinzufügen',
|
||||||
loading: 'Lädt…',
|
loading: 'Lädt…',
|
||||||
noRadio: 'Keine ähnlichen Titel für diesen Künstler gefunden.',
|
noRadio: 'Keine ähnlichen Titel für diesen Künstler gefunden.',
|
||||||
notFound: 'Künstler nicht gefunden.',
|
notFound: 'Künstler nicht gefunden.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Play the whole discography',
|
playAllTooltip: 'Play the whole discography',
|
||||||
shuffleTooltip: 'Shuffle the whole discography',
|
shuffleTooltip: 'Shuffle the whole discography',
|
||||||
radioTooltip: 'Start a radio based on this artist',
|
radioTooltip: 'Start a radio based on this artist',
|
||||||
|
enqueue: 'Enqueue',
|
||||||
|
enqueueTooltip: 'Add the whole discography to queue',
|
||||||
loading: 'Loading…',
|
loading: 'Loading…',
|
||||||
noRadio: 'No similar tracks found for this artist.',
|
noRadio: 'No similar tracks found for this artist.',
|
||||||
notFound: 'Artist not found.',
|
notFound: 'Artist not found.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Reproducir toda la discografía',
|
playAllTooltip: 'Reproducir toda la discografía',
|
||||||
shuffleTooltip: 'Reproducir toda la discografía en aleatorio',
|
shuffleTooltip: 'Reproducir toda la discografía en aleatorio',
|
||||||
radioTooltip: 'Iniciar una radio basada en este artista',
|
radioTooltip: 'Iniciar una radio basada en este artista',
|
||||||
|
enqueue: 'Agregar a la Cola',
|
||||||
|
enqueueTooltip: 'Agregar toda la discografía a la cola',
|
||||||
loading: 'Cargando…',
|
loading: 'Cargando…',
|
||||||
noRadio: 'No se encontraron pistas similares para este artista.',
|
noRadio: 'No se encontraron pistas similares para este artista.',
|
||||||
notFound: 'Artista no encontrado.',
|
notFound: 'Artista no encontrado.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Lire toute la discographie',
|
playAllTooltip: 'Lire toute la discographie',
|
||||||
shuffleTooltip: 'Lire toute la discographie en aléatoire',
|
shuffleTooltip: 'Lire toute la discographie en aléatoire',
|
||||||
radioTooltip: 'Démarrer une radio basée sur cet artiste',
|
radioTooltip: 'Démarrer une radio basée sur cet artiste',
|
||||||
|
enqueue: 'Mettre en file',
|
||||||
|
enqueueTooltip: 'Ajouter toute la discographie à la file d\'attente',
|
||||||
loading: 'Chargement…',
|
loading: 'Chargement…',
|
||||||
noRadio: 'Aucun morceau similaire trouvé pour cet artiste.',
|
noRadio: 'Aucun morceau similaire trouvé pour cet artiste.',
|
||||||
notFound: 'Artiste introuvable.',
|
notFound: 'Artiste introuvable.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'A teljes diszkográfia lejátszása',
|
playAllTooltip: 'A teljes diszkográfia lejátszása',
|
||||||
shuffleTooltip: 'A teljes diszkográfia keverése',
|
shuffleTooltip: 'A teljes diszkográfia keverése',
|
||||||
radioTooltip: 'Rádió indítása az előadó alapján',
|
radioTooltip: 'Rádió indítása az előadó alapján',
|
||||||
|
enqueue: 'Sorba',
|
||||||
|
enqueueTooltip: 'A teljes diszkográfia hozzáadása a sorhoz',
|
||||||
loading: 'Betöltés…',
|
loading: 'Betöltés…',
|
||||||
noRadio: 'Nem található hasonló szám ehhez az előadóhoz.',
|
noRadio: 'Nem található hasonló szám ehhez az előadóhoz.',
|
||||||
notFound: 'Az előadó nem található.',
|
notFound: 'Az előadó nem található.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Ascolta tutta la discografia',
|
playAllTooltip: 'Ascolta tutta la discografia',
|
||||||
shuffleTooltip: 'Mescola tutta la discografia',
|
shuffleTooltip: 'Mescola tutta la discografia',
|
||||||
radioTooltip: 'Crea una radio basata su questo artista',
|
radioTooltip: 'Crea una radio basata su questo artista',
|
||||||
|
enqueue: 'Accoda',
|
||||||
|
enqueueTooltip: 'Aggiungi tutta la discografia alla coda',
|
||||||
loading: 'Caricamento…',
|
loading: 'Caricamento…',
|
||||||
noRadio: 'Non sono stati trovati brani simili per questo artista.',
|
noRadio: 'Non sono stati trovati brani simili per questo artista.',
|
||||||
notFound: 'Artista non trovato.',
|
notFound: 'Artista non trovato.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'ディスコグラフィ全体を再生',
|
playAllTooltip: 'ディスコグラフィ全体を再生',
|
||||||
shuffleTooltip: 'ディスコグラフィ全体をシャッフル',
|
shuffleTooltip: 'ディスコグラフィ全体をシャッフル',
|
||||||
radioTooltip: 'このアーティストをもとにラジオを開始',
|
radioTooltip: 'このアーティストをもとにラジオを開始',
|
||||||
|
enqueue: 'キューに追加',
|
||||||
|
enqueueTooltip: 'ディスコグラフィ全体をキューに追加',
|
||||||
loading: '読み込み中…',
|
loading: '読み込み中…',
|
||||||
noRadio: 'このアーティストの類似トラックは見つかりませんでした。',
|
noRadio: 'このアーティストの類似トラックは見つかりませんでした。',
|
||||||
notFound: 'アーティストが見つかりません。',
|
notFound: 'アーティストが見つかりません。',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Spill hele diskografien',
|
playAllTooltip: 'Spill hele diskografien',
|
||||||
shuffleTooltip: 'Spill hele diskografien i tilfeldig rekkefølge',
|
shuffleTooltip: 'Spill hele diskografien i tilfeldig rekkefølge',
|
||||||
radioTooltip: 'Start en radio basert på denne artisten',
|
radioTooltip: 'Start en radio basert på denne artisten',
|
||||||
|
enqueue: 'Legg i kø',
|
||||||
|
enqueueTooltip: 'Legg hele diskografien i kø',
|
||||||
loading: 'Laster…',
|
loading: 'Laster…',
|
||||||
noRadio: 'Ingen lignende spor funnet for denne artisten.',
|
noRadio: 'Ingen lignende spor funnet for denne artisten.',
|
||||||
notFound: 'Artisten ble ikke funnet.',
|
notFound: 'Artisten ble ikke funnet.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Volledige discografie afspelen',
|
playAllTooltip: 'Volledige discografie afspelen',
|
||||||
shuffleTooltip: 'Volledige discografie willekeurig afspelen',
|
shuffleTooltip: 'Volledige discografie willekeurig afspelen',
|
||||||
radioTooltip: 'Radio op basis van deze artiest starten',
|
radioTooltip: 'Radio op basis van deze artiest starten',
|
||||||
|
enqueue: 'In wachtrij',
|
||||||
|
enqueueTooltip: 'Volledige discografie aan wachtrij toevoegen',
|
||||||
loading: 'Laden…',
|
loading: 'Laden…',
|
||||||
noRadio: 'Geen vergelijkbare nummers gevonden voor deze artiest.',
|
noRadio: 'Geen vergelijkbare nummers gevonden voor deze artiest.',
|
||||||
notFound: 'Artiest niet gevonden.',
|
notFound: 'Artiest niet gevonden.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Odtwórz całą dyskografię',
|
playAllTooltip: 'Odtwórz całą dyskografię',
|
||||||
shuffleTooltip: 'Przetasuj całą dyskografię',
|
shuffleTooltip: 'Przetasuj całą dyskografię',
|
||||||
radioTooltip: 'Rozpocznij radio w oparciu o tego wykonawcę',
|
radioTooltip: 'Rozpocznij radio w oparciu o tego wykonawcę',
|
||||||
|
enqueue: 'Dodaj do kolejki',
|
||||||
|
enqueueTooltip: 'Dodaj całą dyskografię do kolejki',
|
||||||
loading: 'Ładowanie…',
|
loading: 'Ładowanie…',
|
||||||
noRadio: 'Nie znaleziono podobnych utworów dla tego wykonawcy.',
|
noRadio: 'Nie znaleziono podobnych utworów dla tego wykonawcy.',
|
||||||
notFound: 'Wykonawca nie znaleziony.',
|
notFound: 'Wykonawca nie znaleziony.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Redă întreaga discografie',
|
playAllTooltip: 'Redă întreaga discografie',
|
||||||
shuffleTooltip: 'Redă întreaga discografie amestecat',
|
shuffleTooltip: 'Redă întreaga discografie amestecat',
|
||||||
radioTooltip: 'Pornește un radio bazat pe acest artist',
|
radioTooltip: 'Pornește un radio bazat pe acest artist',
|
||||||
|
enqueue: 'Adaugă în coadă',
|
||||||
|
enqueueTooltip: 'Adaugă întreaga discografie în coadă',
|
||||||
loading: 'Se încarcă…',
|
loading: 'Se încarcă…',
|
||||||
noRadio: 'Nicio piesă similară găsită pentru acest artist.',
|
noRadio: 'Nicio piesă similară găsită pentru acest artist.',
|
||||||
notFound: 'Artistul nu a fost găsit.',
|
notFound: 'Artistul nu a fost găsit.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: 'Воспроизвести всю дискографию',
|
playAllTooltip: 'Воспроизвести всю дискографию',
|
||||||
shuffleTooltip: 'Воспроизвести всю дискографию вперемешку',
|
shuffleTooltip: 'Воспроизвести всю дискографию вперемешку',
|
||||||
radioTooltip: 'Запустить радио на основе этого исполнителя',
|
radioTooltip: 'Запустить радио на основе этого исполнителя',
|
||||||
|
enqueue: 'В очередь',
|
||||||
|
enqueueTooltip: 'Добавить всю дискографию в очередь',
|
||||||
loading: 'Загрузка…',
|
loading: 'Загрузка…',
|
||||||
noRadio: 'Похожие треки для этого исполнителя не найдены.',
|
noRadio: 'Похожие треки для этого исполнителя не найдены.',
|
||||||
notFound: 'Исполнитель не найден.',
|
notFound: 'Исполнитель не найден.',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const artistDetail = {
|
|||||||
playAllTooltip: '播放全部作品',
|
playAllTooltip: '播放全部作品',
|
||||||
shuffleTooltip: '随机播放全部作品',
|
shuffleTooltip: '随机播放全部作品',
|
||||||
radioTooltip: '基于该艺人开始电台',
|
radioTooltip: '基于该艺人开始电台',
|
||||||
|
enqueue: '加入队列',
|
||||||
|
enqueueTooltip: '将全部作品添加到播放队列',
|
||||||
loading: '加载中…',
|
loading: '加载中…',
|
||||||
noRadio: '未找到该艺术家的相似曲目。',
|
noRadio: '未找到该艺术家的相似曲目。',
|
||||||
notFound: '未找到艺术家。',
|
notFound: '未找到艺术家。',
|
||||||
|
|||||||
Reference in New Issue
Block a user