diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 22d74b30..6302791a 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -290,7 +290,8 @@ }, "albumDetail": { "moreFromArtist": "more from this $t(entity.artist_one)", - "moreFromGeneric": "more from {{item}}" + "moreFromGeneric": "more from {{item}}", + "released": "released" }, "albumList": { "artistAlbums": "albums by {{artist}}", diff --git a/src/renderer/api/jellyfin/jellyfin-normalize.ts b/src/renderer/api/jellyfin/jellyfin-normalize.ts index e8b5d50d..fc7e1470 100644 --- a/src/renderer/api/jellyfin/jellyfin-normalize.ts +++ b/src/renderer/api/jellyfin/jellyfin-normalize.ts @@ -175,8 +175,11 @@ const normalizeSong = ( peak: null, playCount: (item.UserData && item.UserData.PlayCount) || 0, playlistItemId: item.PlaylistItemId, - // releaseDate: (item.ProductionYear && new Date(item.ProductionYear, 0, 1).toISOString()) || null, - releaseDate: null, + releaseDate: item.PremiereDate + ? new Date(item.PremiereDate).toISOString() + : item.ProductionYear + ? new Date(item.ProductionYear, 0, 1).toISOString() + : null, releaseYear: item.ProductionYear ? String(item.ProductionYear) : null, serverId: server?.id || '', serverType: ServerType.JELLYFIN, @@ -237,6 +240,7 @@ const normalizeAlbum = ( lastPlayedAt: null, mbzId: item.ProviderIds?.MusicBrainzAlbum || null, name: item.Name, + originalDate: null, playCount: item.UserData?.PlayCount || 0, releaseDate: item.PremiereDate?.split('T')[0] || null, releaseYear: item.ProductionYear || null, diff --git a/src/renderer/api/navidrome/navidrome-normalize.ts b/src/renderer/api/navidrome/navidrome-normalize.ts index 386c6e34..7bc33b7c 100644 --- a/src/renderer/api/navidrome/navidrome-normalize.ts +++ b/src/renderer/api/navidrome/navidrome-normalize.ts @@ -119,7 +119,10 @@ const normalizeSong = ( : null, playCount: item.playCount, playlistItemId, - releaseDate: new Date(item.year, 0, 1).toISOString(), + releaseDate: (item.releaseDate + ? new Date(item.releaseDate) + : new Date(item.year, 0, 1) + ).toISOString(), releaseYear: String(item.year), serverId: server?.id || 'unknown', serverType: ServerType.NAVIDROME, @@ -173,8 +176,16 @@ const normalizeAlbum = ( lastPlayedAt: normalizePlayDate(item), mbzId: item.mbzAlbumId || null, name: item.name, + originalDate: item.originalDate + ? new Date(item.originalDate).toISOString() + : item.originalYear + ? new Date(item.originalYear, 0, 1).toISOString() + : null, playCount: item.playCount, - releaseDate: new Date(item.minYear, 0, 1).toISOString(), + releaseDate: (item.releaseDate + ? new Date(item.releaseDate) + : new Date(item.minYear, 0, 1) + ).toISOString(), releaseYear: item.minYear, serverId: server?.id || 'unknown', serverType: ServerType.NAVIDROME, diff --git a/src/renderer/api/navidrome/navidrome-types.ts b/src/renderer/api/navidrome/navidrome-types.ts index af7e8846..6061efd8 100644 --- a/src/renderer/api/navidrome/navidrome-types.ts +++ b/src/renderer/api/navidrome/navidrome-types.ts @@ -128,9 +128,12 @@ const album = z.object({ name: z.string(), orderAlbumArtistName: z.string(), orderAlbumName: z.string(), + originalDate: z.string().optional(), + originalYear: z.number().optional(), playCount: z.number(), playDate: z.string().optional(), rating: z.number().optional(), + releaseDate: z.string().optional(), size: z.number(), songCount: z.number(), sortAlbumArtistName: z.string(), @@ -214,6 +217,7 @@ const song = z.object({ playCount: z.number(), playDate: z.string().optional(), rating: z.number().optional(), + releaseDate: z.string().optional(), rgAlbumGain: z.number().optional(), rgAlbumPeak: z.number().optional(), rgTrackGain: z.number().optional(), diff --git a/src/renderer/api/subsonic/subsonic-normalize.ts b/src/renderer/api/subsonic/subsonic-normalize.ts index e6eb41ed..624765b5 100644 --- a/src/renderer/api/subsonic/subsonic-normalize.ts +++ b/src/renderer/api/subsonic/subsonic-normalize.ts @@ -196,6 +196,7 @@ const normalizeAlbum = ( lastPlayedAt: null, mbzId: null, name: item.name, + originalDate: null, playCount: null, releaseDate: item.year ? new Date(item.year, 0, 1).toISOString() : null, releaseYear: item.year ? Number(item.year) : null, diff --git a/src/renderer/api/types.ts b/src/renderer/api/types.ts index 7a16060d..b0dee32d 100644 --- a/src/renderer/api/types.ts +++ b/src/renderer/api/types.ts @@ -164,6 +164,7 @@ export type Album = { lastPlayedAt: string | null; mbzId: string | null; name: string; + originalDate: string | null; playCount: number | null; releaseDate: string | null; releaseYear: number | null; diff --git a/src/renderer/features/albums/components/album-detail-header.tsx b/src/renderer/features/albums/components/album-detail-header.tsx index 157778f2..3047e592 100644 --- a/src/renderer/features/albums/components/album-detail-header.tsx +++ b/src/renderer/features/albums/components/album-detail-header.tsx @@ -1,5 +1,6 @@ -import { Group, Stack } from '@mantine/core'; import { forwardRef, Fragment, Ref } from 'react'; +import { Group, Stack } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; import { generatePath, useParams } from 'react-router'; import { Link } from 'react-router-dom'; import { LibraryItem, ServerType } from '/@/renderer/api/types'; @@ -9,7 +10,7 @@ import { LibraryHeader, useSetRating } from '/@/renderer/features/shared'; import { useContainerQuery } from '/@/renderer/hooks'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer } from '/@/renderer/store'; -import { formatDurationString } from '/@/renderer/utils'; +import { formatDateAbsolute, formatDurationString } from '/@/renderer/utils'; interface AlbumDetailHeaderProps { background: string; @@ -21,26 +22,42 @@ export const AlbumDetailHeader = forwardRef( const server = useCurrentServer(); const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id }); const cq = useContainerQuery(); + const { t } = useTranslation(); + + const originalDifferentFromRelease = + detailQuery.data?.originalDate && + detailQuery.data.originalDate !== detailQuery.data.releaseDate; + + const releasePrefix = originalDifferentFromRelease + ? t('page.albumDetail.released', { postProcess: 'sentenceCase' }) + : '♫'; const metadataItems = [ { - id: 'releaseYear', - secondary: false, - value: detailQuery?.data?.releaseYear, + id: 'releaseDate', + value: + detailQuery?.data?.releaseDate && + `${releasePrefix} ${formatDateAbsolute(detailQuery?.data?.releaseDate)}`, }, { id: 'songCount', - secondary: false, value: `${detailQuery?.data?.songCount} songs`, }, { id: 'duration', - secondary: false, value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration), }, ]; + if (originalDifferentFromRelease) { + const formatted = `♫ ${formatDateAbsolute(detailQuery!.data!.originalDate)}`; + metadataItems.splice(0, 0, { + id: 'originalDate', + value: formatted, + }); + } + const updateRatingMutation = useSetRating({}); const handleUpdateRating = (rating: number) => { @@ -71,7 +88,7 @@ export const AlbumDetailHeader = forwardRef( {metadataItems.map((item, index) => ( {index > 0 && } - {item.value} + {item.value} ))} {showRating && (