diff --git a/src-tauri/crates/psysonic-library/src/dto.rs b/src-tauri/crates/psysonic-library/src/dto.rs index 90bd4b22..9a403bc3 100644 --- a/src-tauri/crates/psysonic-library/src/dto.rs +++ b/src-tauri/crates/psysonic-library/src/dto.rs @@ -791,6 +791,9 @@ pub struct LibraryMainstageAlbumsRequest { pub limit: Option, #[serde(default)] pub offset: Option, + /// OR-matched atomic genres, applied before chronological album grouping. + #[serde(default)] + pub genres: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -798,6 +801,7 @@ pub struct LibraryMainstageAlbumsRequest { pub struct LibraryMainstageAlbumsResponse { pub albums: Vec, pub has_more: bool, + pub genre_counts: Vec, } /// FTS track search over an ordered multi-library scope. diff --git a/src-tauri/crates/psysonic-library/src/mainstage_browse.rs b/src-tauri/crates/psysonic-library/src/mainstage_browse.rs index ca4425cc..bd91c3d7 100644 --- a/src-tauri/crates/psysonic-library/src/mainstage_browse.rs +++ b/src-tauri/crates/psysonic-library/src/mainstage_browse.rs @@ -6,8 +6,8 @@ use rusqlite::params_from_iter; use crate::album_compilation_filter::pick_album_group_artist; use crate::browse_support::overlay_album_starred_at_rows; use crate::dto::{ - LibraryAlbumDto, LibraryMainstageAlbumFeed, LibraryMainstageAlbumsRequest, - LibraryMainstageAlbumsResponse, LibraryScopePair, + GenreAlbumCountDto, LibraryAlbumDto, LibraryMainstageAlbumFeed, + LibraryMainstageAlbumsRequest, LibraryMainstageAlbumsResponse, LibraryScopePair, }; use crate::scope_merge::{ non_empty_scopes, scope_cte_sql, ALBUM_DEDUP_KEY, ALBUM_PICK_KEY, @@ -35,12 +35,22 @@ fn candidate_columns(feed_at: &str, priority: usize) -> String { ) } -fn new_release_candidates_sql(scopes: &[LibraryScopePair]) -> String { +fn new_release_candidates_sql(scopes: &[LibraryScopePair], genre_count: usize) -> String { scopes .iter() .enumerate() .map(|(priority, _)| { let columns = candidate_columns("t.server_created_at", priority); + let genre_predicate = if genre_count == 0 { + String::new() + } else { + let placeholders = (0..genre_count).map(|_| "?").collect::>().join(", "); + format!( + " AND EXISTS (SELECT 1 FROM track_genre tg \ + WHERE tg.server_id = t.server_id AND tg.track_id = t.id \ + AND tg.genre COLLATE NOCASE IN ({placeholders}))" + ) + }; format!( "SELECT * FROM ( \ SELECT {columns} \ @@ -49,7 +59,7 @@ fn new_release_candidates_sql(scopes: &[LibraryScopePair]) -> String { ON ck.server_id = t.server_id AND ck.track_id = t.id \ WHERE t.server_id = ? AND t.library_id = ? \ AND t.deleted = 0 AND t.server_created_at IS NOT NULL \ - AND t.album_id IS NOT NULL AND t.album_id != '' \ + AND t.album_id IS NOT NULL AND t.album_id != '' {genre_predicate} \ ORDER BY t.server_created_at DESC, t.album_id ASC, t.id ASC \ LIMIT ? \ )" @@ -81,6 +91,7 @@ fn recently_played_candidates_sql() -> String { fn build_mainstage_query( scopes: &[LibraryScopePair], feed: LibraryMainstageAlbumFeed, + genres: &[String], bounded_candidates: u32, result_offset: u32, result_limit: u32, @@ -91,9 +102,12 @@ fn build_mainstage_query( for pair in scopes { binds.push(SqlValue::Text(pair.server_id.clone())); binds.push(SqlValue::Text(pair.library_id.clone())); + for genre in genres { + binds.push(SqlValue::Text(genre.clone())); + } binds.push(SqlValue::Integer(i64::from(bounded_candidates))); } - new_release_candidates_sql(scopes) + new_release_candidates_sql(scopes, genres.len()) } LibraryMainstageAlbumFeed::RecentlyPlayed => { binds.push(SqlValue::Integer(i64::from(bounded_candidates))); @@ -151,6 +165,33 @@ fn build_mainstage_query( (sql, binds) } +fn new_release_genre_counts( + conn: &rusqlite::Connection, + scopes: &[LibraryScopePair], +) -> rusqlite::Result> { + let (cte, binds) = scope_cte_sql(scopes); + let sql = format!( + "{cte} \ + SELECT tg.genre, COUNT(DISTINCT t.album_id), COUNT(DISTINCT t.id) \ + FROM scope s CROSS JOIN track t \ + ON t.server_id = s.server_id AND t.library_id = s.library_id \ + INNER JOIN track_genre tg ON tg.server_id = t.server_id AND tg.track_id = t.id \ + WHERE t.deleted = 0 AND t.server_created_at IS NOT NULL \ + AND t.album_id IS NOT NULL AND t.album_id != '' \ + GROUP BY tg.genre COLLATE NOCASE \ + ORDER BY COUNT(DISTINCT t.album_id) DESC, tg.genre COLLATE NOCASE ASC" + ); + let mut stmt = conn.prepare(&sql)?; + let rows = stmt.query_map(params_from_iter(binds.iter()), |row| { + Ok(GenreAlbumCountDto { + value: row.get(0)?, + album_count: row.get::<_, i64>(1)?.max(0) as u32, + song_count: row.get::<_, i64>(2)?.max(0) as u32, + }) + })?.collect::>>(); + rows +} + fn map_mainstage_album( r: &rusqlite::Row<'_>, include_catalog_created_at: bool, @@ -194,11 +235,17 @@ pub fn list_mainstage_albums( let initial_candidates = candidate_limit(offset, fetch_limit); store.with_read_conn(|conn| { + let genre_counts = if request.feed == LibraryMainstageAlbumFeed::NewReleases { + new_release_genre_counts(conn, scopes)? + } else { + Vec::new() + }; let mut bounded_candidates = initial_candidates; loop { let (sql, binds) = build_mainstage_query( scopes, request.feed, + &request.genres, bounded_candidates, 0, requested_results, @@ -233,7 +280,7 @@ pub fn list_mainstage_albums( let has_more = albums.len() > limit as usize; albums.truncate(limit as usize); overlay_album_starred_at_rows(conn, &mut albums); - return Ok(LibraryMainstageAlbumsResponse { albums, has_more }); + return Ok(LibraryMainstageAlbumsResponse { albums, has_more, genre_counts }); } }).map_err(|e| e.to_string()) } @@ -311,6 +358,7 @@ mod tests { feed, limit: Some(30), offset: None, + genres: Vec::new(), } } @@ -391,6 +439,50 @@ mod tests { assert_eq!(response.albums[0].name, "Selected"); } + #[test] + fn genre_filter_and_counts_stay_within_dated_selected_release_scope() { + let store = LibraryStore::open_in_memory(); + TrackRepository::new(&store) + .upsert_batch(&[ + track("s1", "rock", "Rock release", "a-rock", "l1", Some(200)), + track("s2", "jazz", "Jazz release", "a-jazz", "l2", Some(300)), + track("s1", "missing-date", "Undated", "a-undated", "l1", None), + track("s1", "outside", "Outside", "a-outside", "other", Some(400)), + ]) + .unwrap(); + store + .with_conn_mut("test.mainstage_genres", |conn| { + for (server, track_id, genre) in [ + ("s1", "rock", "Rock"), + ("s2", "jazz", "Jazz"), + ("s1", "missing-date", "Ambient"), + ("s1", "outside", "Metal"), + ] { + conn.execute( + "INSERT INTO track_genre (server_id, track_id, genre, album_id, library_id) \ + VALUES (?1, ?2, ?3, (SELECT album_id FROM track WHERE server_id = ?1 AND id = ?2), \ + (SELECT library_id FROM track WHERE server_id = ?1 AND id = ?2))", + rusqlite::params![server, track_id, genre], + )?; + } + Ok(()) + }) + .unwrap(); + + let mut req = request( + vec![scope("s1", "l1"), scope("s2", "l2")], + LibraryMainstageAlbumFeed::NewReleases, + ); + req.genres = vec!["rock".into()]; + let response = list_mainstage_albums(&store, &req).unwrap(); + + assert_eq!(response.albums.iter().map(|album| album.id.as_str()).collect::>(), ["a-rock"]); + assert_eq!( + response.genre_counts.iter().map(|row| (row.value.as_str(), row.album_count)).collect::>(), + [("Jazz", 1), ("Rock", 1)], + ); + } + #[test] fn recently_played_collapses_repeated_sessions_and_uses_latest_global_time() { let store = LibraryStore::open_in_memory(); @@ -549,6 +641,7 @@ mod tests { let response = LibraryMainstageAlbumsResponse { albums: Vec::new(), has_more: true, + genre_counts: Vec::new(), }; assert_eq!(serde_json::to_value(response).unwrap()["hasMore"], true); } @@ -596,6 +689,7 @@ mod tests { let (sql, binds) = build_mainstage_query( scopes, feed, + &[], candidate_limit(0, 31), 0, 31, diff --git a/src/features/album/pages/NewReleases.tsx b/src/features/album/pages/NewReleases.tsx index 0c06afdf..3910fe0e 100644 --- a/src/features/album/pages/NewReleases.tsx +++ b/src/features/album/pages/NewReleases.tsx @@ -1,8 +1,6 @@ import { buildDownloadUrlForServer } from '@/lib/api/subsonicStreamUrl'; -import { getAlbumsByGenre } from '@/lib/api/subsonicGenres'; import { resolveAlbum } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { dedupeById } from '@/lib/util/dedupeById'; import { useEffect, useLayoutEffect, useState, useCallback, useRef, useMemo } from 'react'; import { Download, HardDriveDownload } from 'lucide-react'; import SelectionToggleButton from '@/ui/SelectionToggleButton'; @@ -42,7 +40,6 @@ import { deriveLibraryBrowseScope } from '@/lib/library/libraryBrowseScope'; import { loadLocalNewReleases } from '@/lib/library/newReleasesLocal'; import { mergeHotNewReleases } from '@/features/album/utils/hotNewReleases'; import { useHotNewReleaseOverlay } from '@/features/album/hooks/useHotNewReleaseOverlay'; -import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency'; const PAGE_SIZE = 30; @@ -51,11 +48,6 @@ function sanitizeFilename(name: string): string { return name.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_').trim() || 'download'; } -async function fetchByGenres(genres: string[]): Promise { - const results = await Promise.all(genres.map(g => getAlbumsByGenre(g, 500, 0))); - return dedupeById(results.flat()).sort((a, b) => (b.year ?? 0) - (a.year ?? 0)); -} - export default function NewReleases() { const { t } = useTranslation(); const perfFlags = usePerfProbeFlags(); @@ -108,6 +100,7 @@ export default function NewReleases() { const [albums, setAlbums] = useState(() => initialAlbums ?? []); const [hasMore, setHasMore] = useState(() => initialHasMore ?? true); + const [genreCounts, setGenreCounts] = useState>([]); const { scrollBodyEl, bindScrollBody: bindNewReleasesScrollBody, @@ -115,7 +108,6 @@ export default function NewReleases() { } = useInpageScrollViewport(); const { loading, - setLoading, resetPage, runLoad, requestNextPage, @@ -143,12 +135,6 @@ export default function NewReleases() { ? mergeHotNewReleases(albums, hotAlbums) : albums; }, [textSearchActive, textSearchAlbums, albums, hotAlbums, genreFiltered, selectedGenres, scopedSearchQuery]); - const hotReleaseCount = useMemo( - () => !genreFiltered && !scopedSearchQuery - ? displayAlbums.filter(album => isAlbumRecentlyAdded(album.created)).length - : 0, - [displayAlbums, genreFiltered, scopedSearchQuery], - ); const loadingGrid = textSearchActive ? textSearchLoading : loading; const gridHasMore = textSearchActive ? false : (!genreFiltered && hasMore); @@ -212,42 +198,35 @@ export default function NewReleases() { clearSelection(); }; - const load = useCallback(async (offset: number, append = false) => { + const load = useCallback(async (offset: number, append = false, genres: string[] = []) => { await runLoad(async () => { - const data = await loadLocalNewReleases(anchorServerId ?? '', releaseScopes, PAGE_SIZE, offset); + const data = await loadLocalNewReleases( + anchorServerId ?? '', releaseScopes, PAGE_SIZE, offset, genres, + ); if (append) setAlbums(prev => [...prev, ...data.albums]); else setAlbums(data.albums); setHasMore(data.hasMore); + if (!append) setGenreCounts(data.genreCounts.map(row => ({ genre: row.value, count: row.albumCount }))); }); }, [anchorServerId, releaseScopes, runLoad]); const loadFiltered = useCallback(async (genres: string[]) => { - setLoading(true); - try { - setAlbums(await fetchByGenres(genres)); - setHasMore(false); - } finally { - setLoading(false); - } - // musicLibraryFilterVersion is an intentional re-create trigger (fetchByGenres - // reads the active library filter internally); the setters are stable. The - // loader must refresh when that version bumps even though it is unused here. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [musicLibraryFilterVersion]); + await load(0, false, genres); + }, [load]); useEffect(() => { if (restoringSessionRef.current || scopedSearchQuery) return; if (genreFiltered) loadFiltered(selectedGenres); else { resetPage(); - void load(0); + void load(0, false, selectedGenres); } - }, [genreFiltered, selectedGenres, load, loadFiltered, resetPage, scopedSearchQuery, releaseScopeFingerprint]); + }, [genreFiltered, selectedGenres, load, loadFiltered, resetPage, scopedSearchQuery, releaseScopeFingerprint, musicLibraryFilterVersion]); const loadMore = useCallback(() => { if (!gridHasMore || genreFiltered || textSearchActive || isBlocked()) return; - requestNextPage(offset => load(offset, true)); - }, [gridHasMore, genreFiltered, textSearchActive, isBlocked, requestNextPage, load]); + requestNextPage(offset => load(offset, true, selectedGenres)); + }, [gridHasMore, genreFiltered, textSearchActive, isBlocked, requestNextPage, load, selectedGenres]); const bindLoadMoreSentinel = useInpageScrollSentinel({ active: gridHasMore, @@ -294,11 +273,6 @@ export default function NewReleases() { ? t('albums.selectionCount', { count: selectedIds.size }) : t('sidebar.newReleases')} - {!selectionMode && hotReleaseCount > 0 && ( - - {t('albums.newReleasesHotCount', { count: hotReleaseCount })} - - )}
{selectionMode && selectedIds.size > 0 ? ( <> @@ -312,7 +286,11 @@ export default function NewReleases() { ) : ( - + )} { libraryScopeListMainstageAlbums: vi.fn(() => new Promise<{ albums: LibraryAlbumDto[]; hasMore: boolean; + genreCounts: []; }>(() => {})), }, }); @@ -168,7 +169,7 @@ describe('homeFeedLoader failure isolation', () => { it('distinguishes a successful empty chronological query from an error', async () => { const success = await loadHomeChronologicalFeed({ anchorServerId: 'a', scopes: [], feed: 'recentlyPlayed', - deps: { libraryScopeListMainstageAlbums: vi.fn(async () => ({ albums: [], hasMore: false })) }, + deps: { libraryScopeListMainstageAlbums: vi.fn(async () => ({ albums: [], hasMore: false, genreCounts: [] })) }, }); const error = await loadHomeChronologicalFeed({ anchorServerId: 'a', scopes: [], feed: 'recentlyPlayed', @@ -355,6 +356,7 @@ describe('homeFeedLoader failure isolation', () => { const libraryScopeListMainstageAlbums = vi.fn(async () => ({ albums: [albumDto('b', 'next-2'), albumDto('a', 'next-1')], hasMore: false, + genreCounts: [], })); const result = await loadMoreHomeAlbums({ snapshot: snapshot(), section: 'recent', mixConfig, diff --git a/src/lib/api/library/scopeReads.ts b/src/lib/api/library/scopeReads.ts index 6ff2362f..27c00267 100644 --- a/src/lib/api/library/scopeReads.ts +++ b/src/lib/api/library/scopeReads.ts @@ -12,6 +12,7 @@ import { import type { LibraryAlbumDto, LibraryArtistDto, + GenreAlbumCountRow, LibraryScopePair, LibraryTrackDto, } from './dto'; @@ -36,11 +37,13 @@ export interface LibraryScopeMainstageAlbumsRequest { feed: 'newReleases' | 'recentlyPlayed'; limit: number; offset: number; + genres?: string[]; } export interface LibraryScopeMainstageAlbumsResponse { albums: LibraryAlbumDto[]; hasMore: boolean; + genreCounts: GenreAlbumCountRow[]; } export interface LibraryScopeAlbumDetailRequest { @@ -143,6 +146,7 @@ export function libraryScopeListMainstageAlbums( }).then(response => ({ albums: mapAlbumsServerId(response.albums, serverId), hasMore: response.hasMore, + genreCounts: response.genreCounts, })); } diff --git a/src/lib/library/newReleasesLocal.test.ts b/src/lib/library/newReleasesLocal.test.ts index 2b11b178..4d0a7285 100644 --- a/src/lib/library/newReleasesLocal.test.ts +++ b/src/lib/library/newReleasesLocal.test.ts @@ -29,6 +29,7 @@ describe('loadLocalNewReleases', () => { feed: 'newReleases', limit: 30, offset: 60, + genres: [], }); expect(result.albums.map(album => [album.serverId, album.id])).toEqual([ ['server-b', 'newer'], @@ -38,7 +39,9 @@ describe('loadLocalNewReleases', () => { }); it('skips IPC for an empty selected scope', async () => { - await expect(loadLocalNewReleases('', [], 30)).resolves.toEqual({ albums: [], hasMore: false }); + await expect(loadLocalNewReleases('', [], 30)).resolves.toEqual({ + albums: [], hasMore: false, genreCounts: [], + }); expect(libraryScopeListMainstageAlbums).not.toHaveBeenCalled(); }); }); diff --git a/src/lib/library/newReleasesLocal.ts b/src/lib/library/newReleasesLocal.ts index 92054c0a..d67a4fdc 100644 --- a/src/lib/library/newReleasesLocal.ts +++ b/src/lib/library/newReleasesLocal.ts @@ -1,18 +1,25 @@ import { libraryScopeListMainstageAlbums, type LibraryScopePair } from '@/lib/api/library/scopeReads'; import { albumToAlbum } from '@/lib/library/advancedSearchLocal'; +import type { GenreAlbumCountRow } from '@/lib/api/library/dto'; export async function loadLocalNewReleases( anchorServerId: string, scopes: LibraryScopePair[], limit: number, offset = 0, -) { - if (!anchorServerId || scopes.length === 0) return { albums: [], hasMore: false }; + genres: string[] = [], +): Promise<{ albums: ReturnType[]; hasMore: boolean; genreCounts: GenreAlbumCountRow[] }> { + if (!anchorServerId || scopes.length === 0) return { albums: [], hasMore: false, genreCounts: [] }; const response = await libraryScopeListMainstageAlbums(anchorServerId, { scopes, feed: 'newReleases', limit, offset, + genres, }); - return { albums: response.albums.map(albumToAlbum), hasMore: response.hasMore }; + return { + albums: response.albums.map(albumToAlbum), + hasMore: response.hasMore, + genreCounts: response.genreCounts, + }; } diff --git a/src/locales/bg/albums.ts b/src/locales/bg/albums.ts index 4188767b..cabcc2b3 100644 --- a/src/locales/bg/albums.ts +++ b/src/locales/bg/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Няма любими албуми, съответстващи на текущите филтри.', noCompilations: 'Няма компилации, съответстващи на текущите филтри.', noMatchingFilters: 'Няма албуми, съответстващи на текущите филтри.', - newReleasesHotCount_one: '{{count}} нов албум през последните 48 часа', - newReleasesHotCount_other: '{{count}} нови албума през последните 48 часа', }; diff --git a/src/locales/de/albums.ts b/src/locales/de/albums.ts index 4cd2e734..f4721262 100644 --- a/src/locales/de/albums.ts +++ b/src/locales/de/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Keine Lieblingsalben entsprechen den aktuellen Filtern.', noCompilations: 'Keine Sampler entsprechen den aktuellen Filtern.', noMatchingFilters: 'Keine Alben entsprechen den aktuellen Filtern.', - newReleasesHotCount_one: '{{count}} neu in den letzten 48 Stunden', - newReleasesHotCount_other: '{{count}} neu in den letzten 48 Stunden', }; diff --git a/src/locales/en/albums.ts b/src/locales/en/albums.ts index a197b4e7..f650a3bd 100644 --- a/src/locales/en/albums.ts +++ b/src/locales/en/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'No favorite albums match the current filters.', noCompilations: 'No compilations match the current filters.', noMatchingFilters: 'No albums match the current filters.', - newReleasesHotCount_one: '{{count}} new in the last 48 hours', - newReleasesHotCount_other: '{{count}} new in the last 48 hours', }; diff --git a/src/locales/es/albums.ts b/src/locales/es/albums.ts index 1eafa2ef..5d58c65b 100644 --- a/src/locales/es/albums.ts +++ b/src/locales/es/albums.ts @@ -38,7 +38,5 @@ export const albums = { noFavorites: 'Ningún álbum favorito coincide con los filtros actuales.', noCompilations: 'Ninguna compilación coincide con los filtros actuales.', noMatchingFilters: 'Ningún álbum coincide con los filtros actuales.', - newReleasesHotCount_one: '{{count}} novedad en las últimas 48 horas', - newReleasesHotCount_other: '{{count}} novedades en las últimas 48 horas', addToPlaylist: 'Agregar a Lista de Reproducción', }; diff --git a/src/locales/fr/albums.ts b/src/locales/fr/albums.ts index 67473710..2d45a4e3 100644 --- a/src/locales/fr/albums.ts +++ b/src/locales/fr/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Aucun album favori ne correspond aux filtres actuels.', noCompilations: 'Aucune compilation ne correspond aux filtres actuels.', noMatchingFilters: 'Aucun album ne correspond aux filtres actuels.', - newReleasesHotCount_one: '{{count}} nouveauté au cours des dernières 48 heures', - newReleasesHotCount_other: '{{count}} nouveautés au cours des dernières 48 heures', }; diff --git a/src/locales/hu/albums.ts b/src/locales/hu/albums.ts index 85406729..a9e6d878 100644 --- a/src/locales/hu/albums.ts +++ b/src/locales/hu/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Egyetlen kedvenc album sem felel meg a jelenlegi szűrőknek.', noCompilations: 'Egyetlen válogatás sem felel meg a jelenlegi szűrőknek.', noMatchingFilters: 'Egyetlen album sem felel meg a jelenlegi szűrőknek.', - newReleasesHotCount_one: '{{count}} új album az elmúlt 48 órában', - newReleasesHotCount_other: '{{count}} új album az elmúlt 48 órában', }; diff --git a/src/locales/it/albums.ts b/src/locales/it/albums.ts index ac9ab75a..878d1317 100644 --- a/src/locales/it/albums.ts +++ b/src/locales/it/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Nessun album preferito corrisponde ai filtri attuali.', noCompilations: 'Nessuna compilation corrisponde ai filtri attualmente selezionati.', noMatchingFilters: 'Nessun album corrisponde ai filtri attuali.', - newReleasesHotCount_one: '{{count}} novità nelle ultime 48 ore', - newReleasesHotCount_other: '{{count}} novità nelle ultime 48 ore', }; diff --git a/src/locales/ja/albums.ts b/src/locales/ja/albums.ts index c1903137..7f3b9b49 100644 --- a/src/locales/ja/albums.ts +++ b/src/locales/ja/albums.ts @@ -38,5 +38,4 @@ export const albums = { noFavorites: '現在のフィルターに一致するお気に入りアルバムはありません。', noCompilations: '現在のフィルターに一致するコンピレーションはありません。', noMatchingFilters: '現在のフィルターに一致するアルバムはありません。', - newReleasesHotCount_other: '過去48時間の新着: {{count}} 枚', }; diff --git a/src/locales/nb/albums.ts b/src/locales/nb/albums.ts index 936f6194..2848788c 100644 --- a/src/locales/nb/albums.ts +++ b/src/locales/nb/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Ingen favorittalbum samsvarer med gjeldende filtre.', noCompilations: 'Ingen samleplater samsvarer med gjeldende filtre.', noMatchingFilters: 'Ingen album samsvarer med gjeldende filtre.', - newReleasesHotCount_one: '{{count}} nyhet de siste 48 timene', - newReleasesHotCount_other: '{{count}} nyheter de siste 48 timene', }; diff --git a/src/locales/nl/albums.ts b/src/locales/nl/albums.ts index 42fa9f9b..b09575ae 100644 --- a/src/locales/nl/albums.ts +++ b/src/locales/nl/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Geen favoriete albums komen overeen met de huidige filters.', noCompilations: 'Geen compilaties komen overeen met de huidige filters.', noMatchingFilters: 'Geen albums komen overeen met de huidige filters.', - newReleasesHotCount_one: '{{count}} nieuw album in de afgelopen 48 uur', - newReleasesHotCount_other: '{{count}} nieuwe albums in de afgelopen 48 uur', }; diff --git a/src/locales/pl/albums.ts b/src/locales/pl/albums.ts index 585b5da6..59cfb7a8 100644 --- a/src/locales/pl/albums.ts +++ b/src/locales/pl/albums.ts @@ -38,8 +38,4 @@ export const albums = { noFavorites: 'Brak ulubionych albumów pasujących do bieżących filtrów.', noCompilations: 'Brak kompilacji pasujących do bieżących filtrów.', noMatchingFilters: 'Brak albumów pasujących do bieżących filtrów.', - newReleasesHotCount_one: '{{count}} nowy album w ciągu ostatnich 48 godzin', - newReleasesHotCount_few: '{{count}} nowe albumy w ciągu ostatnich 48 godzin', - newReleasesHotCount_many: '{{count}} nowych albumów w ciągu ostatnich 48 godzin', - newReleasesHotCount_other: '{{count}} nowych albumów w ciągu ostatnich 48 godzin', }; diff --git a/src/locales/ro/albums.ts b/src/locales/ro/albums.ts index b40ad246..41191fe3 100644 --- a/src/locales/ro/albums.ts +++ b/src/locales/ro/albums.ts @@ -38,6 +38,4 @@ export const albums = { noFavorites: 'Niciun album favorit nu corespunde filtrelor curente.', noCompilations: 'Nicio compilație nu corespunde filtrelor curente.', noMatchingFilters: 'Niciun album nu corespunde filtrelor curente.', - newReleasesHotCount_one: '{{count}} noutate în ultimele 48 de ore', - newReleasesHotCount_other: '{{count}} noutăți în ultimele 48 de ore', }; diff --git a/src/locales/ru/albums.ts b/src/locales/ru/albums.ts index d4c6e5a6..01fad5bd 100644 --- a/src/locales/ru/albums.ts +++ b/src/locales/ru/albums.ts @@ -42,8 +42,4 @@ export const albums = { noFavorites: 'Нет избранных альбомов с текущими фильтрами.', noCompilations: 'Нет сборников с текущими фильтрами.', noMatchingFilters: 'Нет альбомов с текущими фильтрами.', - newReleasesHotCount_one: '{{count}} новинка за последние 48 часов', - newReleasesHotCount_few: '{{count}} новинки за последние 48 часов', - newReleasesHotCount_many: '{{count}} новинок за последние 48 часов', - newReleasesHotCount_other: '{{count}} новинки за последние 48 часов', }; diff --git a/src/locales/zh/albums.ts b/src/locales/zh/albums.ts index 2605aacd..ac8bd038 100644 --- a/src/locales/zh/albums.ts +++ b/src/locales/zh/albums.ts @@ -38,5 +38,4 @@ export const albums = { noFavorites: '没有符合当前筛选条件的收藏专辑。', noCompilations: '没有符合当前筛选条件的合辑。', noMatchingFilters: '没有符合当前筛选条件的专辑。', - newReleasesHotCount_other: '过去 48 小时内有 {{count}} 张新专辑', };