[enhancement/localization]: sort navidrome albums by year, add more language keys

This commit is contained in:
Kendall Garner 2024-02-03 15:05:33 -08:00
parent 86a93866d0
commit 24bf7ae31f
No known key found for this signature in database
GPG key ID: 18D2767419676C87
7 changed files with 56 additions and 22 deletions

View file

@ -86,7 +86,7 @@ First thing to do is check that your MPV binary path is correct. Navigate to the
Feishin supports any music server that implements a [Navidrome](https://www.navidrome.org/) or [Jellyfin](https://jellyfin.org/) API. **Subsonic API is not currently supported**. This will likely be added in [later when the new Subsonic API is decided on](https://support.symfonium.app/t/subsonic-servers-participation/1233).
- [Navidrome](https://github.com/navidrome/navidrome)
- [Navidrome](https://github.com/navidrome/navidrome) version 0.48.0 and newer
- [Jellyfin](https://github.com/jellyfin/jellyfin)
- [Funkwhale](https://funkwhale.audio/) - TBD
- Subsonic-compatible servers - TBD

View file

@ -254,6 +254,17 @@
}
},
"page": {
"albumArtistDetail": {
"about": "About {{artist}}",
"appearsOn": "appears on",
"recentReleases": "recent releases",
"viewDiscography": "view discography",
"relatedArtists": "related $t(entity.artist_other)",
"topSongs": "top songs",
"topSongsFrom": "Top songs from {{title}}",
"viewAll": "view all",
"viewAllTracks": "view all $t(entity.track_other)"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
@ -353,6 +364,8 @@
"tracks": "$t(entity.track_other)"
},
"trackList": {
"artistTracks": "Tracks by {{artist}}",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"title": "$t(entity.track_other)"
}
},

View file

@ -421,7 +421,8 @@ export const albumListSortMap: AlbumListSortMap = {
rating: NDAlbumListSort.RATING,
recentlyAdded: NDAlbumListSort.RECENTLY_ADDED,
recentlyPlayed: NDAlbumListSort.PLAY_DATE,
releaseDate: undefined,
// Recent versions of Navidrome support release date, but fallback to year for now
releaseDate: NDAlbumListSort.YEAR,
songCount: NDAlbumListSort.SONG_COUNT,
year: NDAlbumListSort.YEAR,
},

View file

@ -213,7 +213,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
order={2}
weight={700}
>
Recent releases
{t('page.albumArtistDetail.recentReleases', {
postProcess: 'sentenceCase',
})}
</TextTitle>
<Button
compact
@ -222,7 +224,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
to={artistDiscographyLink}
variant="subtle"
>
View discography
{t('page.albumArtistDetail.viewDiscography')}
</Button>
</Group>
),
@ -238,7 +240,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
order={2}
weight={700}
>
Appears on
{t('page.albumArtistDetail.appearsOn', { postProcess: 'sentenceCase' })}
</TextTitle>
),
uniqueId: 'compilationAlbums',
@ -252,7 +254,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
order={2}
weight={700}
>
Related artists
{t('page.albumArtistDetail.relatedArtists', {
postProcess: 'sentenceCase',
})}
</TextTitle>
),
uniqueId: 'similarArtists',
@ -267,6 +271,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
recentAlbumsQuery?.data?.items,
recentAlbumsQuery.isFetching,
recentAlbumsQuery?.isLoading,
t,
]);
const playButtonBehavior = usePlayButtonBehavior();
@ -381,7 +386,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
to={artistDiscographyLink}
variant="subtle"
>
View discography
{t('page.albumArtistDetail.viewDiscography')}
</Button>
<Button
compact
@ -390,7 +395,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
to={artistSongsLink}
variant="subtle"
>
View all songs
{t('page.albumArtistDetail.viewAllTracks')}
</Button>
</Group>
{showGenres ? (
@ -463,7 +468,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
order={2}
weight={700}
>
About {detailQuery?.data?.name}
{t('page.albumArtistDetail.about', {
artist: detailQuery?.data?.name,
})}
</TextTitle>
<Spoiler
dangerouslySetInnerHTML={{ __html: detailQuery?.data?.biography || '' }}
@ -484,7 +491,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
order={2}
weight={700}
>
Top Songs
{t('page.albumArtistDetail.topSongs', {
postProcess: 'sentenceCase',
})}
</TextTitle>
<Button
compact
@ -498,7 +507,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
)}
variant="subtle"
>
View all
{t('page.albumArtistDetail.viewAll', {
postProcess: 'sentenceCase',
})}
</Button>
</Group>
</Group>

View file

@ -33,7 +33,9 @@ export const AlbumArtistDetailTopSongsListHeader = ({
<PageHeader p="1rem">
<LibraryHeaderBar>
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<LibraryHeaderBar.Title>Top songs from {title}</LibraryHeaderBar.Title>
<LibraryHeaderBar.Title>
{t('page.albumArtistDetail.topSongsFrom', { title })}
</LibraryHeaderBar.Title>
<Paper
fw="600"
px="1rem"
@ -57,7 +59,7 @@ export const AlbumArtistDetailTopSongsListHeader = ({
icon={<RiPlayFill />}
onClick={() => handlePlay(Play.NOW)}
>
{t('player.add', { postProcess: 'sentenceCase' })}
{t('player.play', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
<DropdownMenu.Item
icon={<RiAddBoxFill />}

View file

@ -1,7 +1,8 @@
import { ChangeEvent, MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce';
import { ChangeEvent, MutableRefObject } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, Link, useParams, useSearchParams } from 'react-router-dom';
import { useCurrentServer } from '../../../store/auth.store';
import { LibraryItem } from '/@/renderer/api/types';
@ -17,6 +18,7 @@ interface SearchHeaderProps {
}
export const SearchHeader = ({ tableRef, navigationId }: SearchHeaderProps) => {
const { t } = useTranslation();
const { itemType } = useParams() as { itemType: LibraryItem };
const [searchParams, setSearchParams] = useSearchParams();
const cq = useContainerQuery();
@ -70,7 +72,7 @@ export const SearchHeader = ({ tableRef, navigationId }: SearchHeaderProps) => {
}}
variant={itemType === LibraryItem.SONG ? 'filled' : 'subtle'}
>
Tracks
{t('entity.track_other', { postProcess: 'sentenceCase' })}
</Button>
<Button
compact
@ -87,7 +89,7 @@ export const SearchHeader = ({ tableRef, navigationId }: SearchHeaderProps) => {
}}
variant={itemType === LibraryItem.ALBUM ? 'filled' : 'subtle'}
>
Albums
{t('entity.album_other', { postProcess: 'sentenceCase' })}
</Button>
<Button
compact
@ -104,7 +106,7 @@ export const SearchHeader = ({ tableRef, navigationId }: SearchHeaderProps) => {
}}
variant={itemType === LibraryItem.ALBUM_ARTIST ? 'filled' : 'subtle'}
>
Artists
{t('entity.artist_other', { postProcess: 'sentenceCase' })}
</Button>
</Group>
</FilterBar>

View file

@ -1,6 +1,7 @@
import { useCallback, useMemo, useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import isEmpty from 'lodash/isEmpty';
import { useTranslation } from 'react-i18next';
import { useParams, useSearchParams } from 'react-router-dom';
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types';
import { ListContext } from '/@/renderer/context/list-context';
@ -16,6 +17,7 @@ import { titleCase } from '/@/renderer/utils';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
const TrackListRoute = () => {
const { t } = useTranslation();
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
const tableRef = useRef<AgGridReactType | null>(null);
const server = useCurrentServer();
@ -132,6 +134,13 @@ const TrackListRoute = () => {
};
}, [albumArtistId, customFilters, genreId, handlePlay, pageKey]);
const artist = searchParams.get('artistName');
const title = artist
? t('page.trackList.artistTracks', { artist })
: genreId
? t('page.trackList.genreTracks', { genre: titleCase(genreTitle) })
: undefined;
return (
<AnimatedPage>
<ListContext.Provider value={providerValue}>
@ -139,11 +148,7 @@ const TrackListRoute = () => {
gridRef={gridRef}
itemCount={itemCount}
tableRef={tableRef}
title={
searchParams.get('artistName') || genreId
? `"${titleCase(genreTitle)}" Tracks`
: undefined
}
title={title}
/>
<SongListContent
gridRef={gridRef}