Compare commits

...

7 Commits

Author SHA1 Message Date
Hosted Weblate e49e488b4c Translated using Weblate
Currently translated at 100.0% (1208 of 1208 strings) (Japanese)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/

Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com>
2026-05-08 09:59:37 +02:00
Felix Prillwitz 5d4547080d Always use remote url for discord rpc image (#2009)
* add option to force remote url for api calls

* force remote url for discord rpc image
2026-05-07 21:13:34 -07:00
jeffvli cc8910cfd6 add artistrating smart playlist field (#2015) 2026-05-07 21:08:57 -07:00
jeffvli 9fb241dca2 remove server selector from collapsed sidebar 2026-05-07 20:54:42 -07:00
Maximilian Leith e3a0879301 fix: only show Jellyfin audio playlist (#2000) 2026-05-07 20:48:43 -07:00
Norman 953494e9d0 Fixed bad smart playlist field s (#2011) 2026-05-07 20:46:48 -07:00
jeffvli f190626c8c fix playcounts on jellyfin album detail songs (#2005) 2026-05-07 20:42:51 -07:00
10 changed files with 45 additions and 62 deletions
+14 -6
View File
@@ -85,7 +85,7 @@
"hotkey_zoomIn": "拡大",
"scrobble_description": "再生した音楽をメディアサーバーから Scrobble します",
"hotkey_browserForward": "ブラウザ 進む",
"audioExclusiveMode_description": "排他出力モードを有効にします。このモードでは、システムの他の出力がロックされ、MPV のみがオーディオを出力できるようになります",
"audioExclusiveMode_description": "排他出力モードを有効にします。このモードでは、システムの他の出力がロックされ、MPV のみがオーディオを出力できるようになります。このモードが有効になっている間は、ビジュアライザーやシステムオーディオのキャプチャ機能は動作しません",
"discordUpdateInterval": "{{discord}} Rich Presence の更新間隔",
"themeLight": "テーマ (ライト)",
"fontType_optionBuiltIn": "組み込みフォント",
@@ -425,7 +425,11 @@
"sidePlayQueueLayout_optionHorizontal": "水平",
"sidePlayQueueLayout_optionVertical": "垂直",
"waveformLoadingDelay": "波形読み込みの遅延",
"waveformLoadingDelay_description": "波形を読み込むまでの遅延時間(秒単位)を設定します。Web プレーヤー使用時にカクつきが発生する場合は、この値を増やしてください。"
"waveformLoadingDelay_description": "波形を読み込むまでの遅延時間(秒単位)を設定します。Web プレーヤー使用時にカクつきが発生する場合は、この値を増やしてください。",
"playerbarWaveformStretch": "波形伸縮",
"playerbarWaveformStretch_description": "波形を伸縮させて、利用可能なスペースを埋めます",
"preventSuspendOnPlayback_description": "音楽再生中にアプリケーションが停止しないようにします",
"preventSuspendOnPlayback": "再生の中断を防止する"
},
"action": {
"editPlaylist": "$t(entity.playlist, {\"count\": 1}) を編集",
@@ -808,7 +812,7 @@
"dynamicBackground": "ダイナミック背景",
"synchronized": "同期",
"followCurrentLyric": "歌詞を再生位置に追従",
"opacity": "非透過率",
"opacity": "不透明度",
"lyricSize": "歌詞のサイズ",
"showLyricProvider": "歌詞の提供元を表示",
"unsynchronized": "非同期",
@@ -817,7 +821,9 @@
"lyricGap": "歌詞の間隔",
"dynamicImageBlur": "画像のぼかしサイズ",
"dynamicIsImage": "背景画像を有効にする",
"lyricOffset": "歌詞のオフセット (ms)"
"lyricOffset": "歌詞のオフセット (ms)",
"lyricOpacityNonActive": "非アクティブな歌詞の不透明度",
"lyricScaleNonActive": "非アクティブな歌詞のスケール"
},
"upNext": "次へ",
"lyrics": "歌詞",
@@ -1033,7 +1039,8 @@
"input_skipDuplicates": "重複をスキップ",
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
"create": "$t(entity.playlist, {\"count\": 1}) {{playlist}} を作成する",
"searchOrCreate": "$t(entity.playlist, {\"count\": 2}) を検索するか、入力して新しいプレイリストを作成してください"
"searchOrCreate": "$t(entity.playlist, {\"count\": 2}) を検索するか、入力して新しいプレイリストを作成してください",
"noneAdded": "$t(entity.playlist, {\"count\": 1}) {{playlist}} にトラックが追加されませんでした"
},
"updateServer": {
"title": "サーバーをアップデート",
@@ -1341,6 +1348,7 @@
"systemAudioConsentDecline": "拒否",
"systemAudioConsentTitle": "システムオーディオへのアクセスを許可しますか?",
"systemAudioCaptureFailed": "キャプチャを開始できませんでした: {{message}}",
"systemAudioNoAudioTrack": "音声トラックが返されませんでした。プロンプトが表示されたら、音声キャプチャが有効になっていることを確認してください。"
"systemAudioNoAudioTrack": "音声トラックが返されませんでした。プロンプトが表示されたら、音声キャプチャが有効になっていることを確認してください。",
"systemAudioExclusiveModeNotSupported": "オーディオ排他モードが有効になっている間は、ビジュアライザーは利用できません。MPV の設定でオーディオ排他モードを無効にしてから、もう一度お試しください。"
}
}
+3 -2
View File
@@ -404,11 +404,12 @@ export const createAuthHeader = (): string => {
};
export const jfApiClient = (args: {
forceRemoteUrl?: boolean;
server: null | ServerListItemWithCredential;
signal?: AbortSignal;
url?: string;
}) => {
const { server, signal, url } = args;
const { forceRemoteUrl, server, signal, url } = args;
return initClient(contract, {
api: async ({ body, headers, method, path }) => {
@@ -418,7 +419,7 @@ export const jfApiClient = (args: {
const { params, path: api } = parsePath(path);
if (server) {
const serverUrl = getServerUrl(server);
const serverUrl = getServerUrl(server, forceRemoteUrl);
baseUrl = serverUrl;
token = server?.credential;
} else {
@@ -395,10 +395,15 @@ export const JellyfinController: InternalControllerEndpoint = {
userId: apiClientProps.server.userId,
},
query: {
AlbumIds: query.id,
EnableUserData: true,
Fields: JF_FIELDS.SONG,
IncludeItemTypes: 'Audio',
ParentId: query.id,
Recursive: true,
SortBy: 'ParentIndexNumber,IndexNumber,SortName',
SortOrder: JFSortOrder.ASC,
StartIndex: 0,
UserId: apiClientProps.server.userId,
},
});
@@ -928,6 +933,7 @@ export const JellyfinController: InternalControllerEndpoint = {
Fields: JF_FIELDS.PLAYLIST_LIST,
IncludeItemTypes: 'Playlist',
Limit: query.limit,
MediaTypes: 'Audio',
Recursive: true,
SearchTerm: query.searchTerm,
SortBy: playlistListSortMap.jellyfin[query.sortBy],
+3 -2
View File
@@ -479,11 +479,12 @@ axiosClient.interceptors.response.use(
);
export const ndApiClient = (args: {
forceRemoteUrl?: boolean;
server: null | ServerListItemWithCredential;
signal?: AbortSignal;
url?: string;
}) => {
const { server, signal, url } = args;
const { forceRemoteUrl, server, signal, url } = args;
return initClient(contract, {
api: async ({ body, headers, method, path }) => {
@@ -493,7 +494,7 @@ export const ndApiClient = (args: {
const { params, path: api } = parsePath(path);
if (server) {
const serverUrl = getServerUrl(server);
const serverUrl = getServerUrl(server, forceRemoteUrl);
baseUrl = serverUrl ? `${serverUrl}/api` : undefined;
token = server?.ndCredential;
} else {
+3 -2
View File
@@ -428,12 +428,13 @@ const silentlyTransformResponse = (data: any) => {
};
export const ssApiClient = (args: {
forceRemoteUrl?: boolean;
server: null | ServerListItemWithCredential;
signal?: AbortSignal;
silent?: boolean;
url?: string;
}) => {
const { server, signal, silent, url } = args;
const { forceRemoteUrl, server, signal, silent, url } = args;
return initClient(contract, {
api: async ({ body, headers, method, path, rawQuery }) => {
@@ -443,7 +444,7 @@ export const ssApiClient = (args: {
const { params, path: api } = parsePath(path);
if (server) {
const serverUrl = getServerUrl(server);
const serverUrl = getServerUrl(server, forceRemoteUrl);
baseUrl = serverUrl ? `${serverUrl}/rest` : undefined;
const token = server.credential;
const params = token.split(/&?\w=/gm);
@@ -284,7 +284,10 @@ export const useDiscordRpc = () => {
) {
try {
const info = await api.controller.getAlbumInfo({
apiClientProps: { serverId: song._serverId },
apiClientProps: {
forceRemoteUrl: true,
serverId: song._serverId,
},
query: { id: song.albumId },
});
@@ -6,12 +6,8 @@ import { Link, NavLink, useNavigate } from 'react-router';
import styles from './collapsed-sidebar.module.css';
import JellyfinLogo from '/@/renderer/features/servers/assets/jellyfin.png';
import NavidromeLogo from '/@/renderer/features/servers/assets/navidrome.png';
import OpenSubsonicLogo from '/@/renderer/features/servers/assets/opensubsonic.png';
import { CollapsedSidebarButton } from '/@/renderer/features/sidebar/components/collapsed-sidebar-button';
import { CollapsedSidebarItem } from '/@/renderer/features/sidebar/components/collapsed-sidebar-item';
import { ServerSelectorItems } from '/@/renderer/features/sidebar/components/server-selector-items';
import { getCollectionTo } from '/@/renderer/features/sidebar/components/sidebar-collection-list';
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu';
@@ -19,7 +15,6 @@ import { AppRoute } from '/@/renderer/router/routes';
import {
SidebarItemType,
useCollections,
useCurrentServer,
useSidebarCollapsedNavigation,
useSidebarItems,
useWindowSettings,
@@ -30,7 +25,7 @@ import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Stack } from '/@/shared/components/stack/stack';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
import { LibraryItem } from '/@/shared/types/domain-types';
import { Platform } from '/@/shared/types/types';
export const CollapsedSidebar = () => {
@@ -40,8 +35,6 @@ export const CollapsedSidebar = () => {
const { windowBarStyle } = useWindowSettings();
const sidebarCollapsedNavigation = useSidebarCollapsedNavigation();
const sidebarItems = useSidebarItems();
const currentServer = useCurrentServer();
const translatedSidebarItemMap = useMemo(
() => ({
Albums: t('page.sidebar.albums', { postProcess: 'titleCase' }),
@@ -174,38 +167,6 @@ export const CollapsedSidebar = () => {
/>
),
)}
{currentServer && (
<DropdownMenu offset={0} position="right-end" width={240}>
<DropdownMenu.Target>
<CollapsedSidebarItem
activeIcon={null}
component={Flex}
icon={
<img
className={styles.serverIcon}
src={
currentServer.type === ServerType.NAVIDROME
? NavidromeLogo
: currentServer.type === ServerType.JELLYFIN
? JellyfinLogo
: OpenSubsonicLogo
}
/>
}
label={''}
py="md"
style={{
cursor: 'pointer',
}}
/>
</DropdownMenu.Target>
<DropdownMenu.Dropdown>
<ScrollArea style={{ maxHeight: '95vh' }}>
<ServerSelectorItems />
</ScrollArea>
</DropdownMenu.Dropdown>
</DropdownMenu>
)}
</ScrollArea>
</motion.div>
);
+8 -7
View File
@@ -87,6 +87,7 @@ export const NDSongQueryFields = [
{ label: 'Artist Date Rated', type: 'date', value: 'artistdaterated' },
{ label: 'Artist Is Favorite', type: 'boolean', value: 'artistloved' },
{ label: 'Artist Play Count', type: 'number', value: 'artistplaycount' },
{ label: 'Artist Rating', type: 'number', value: 'artistrating' },
{ label: 'Artists', type: 'string', value: 'artists' },
{ label: 'ASIN', type: 'string', value: 'asin' },
{ label: 'Average Rating', type: 'number', value: 'averagerating' },
@@ -158,7 +159,7 @@ export const NDSongQueryFields = [
{ label: 'MusicBrainz Work Id', type: 'string', value: 'musicbrainz_workid' },
{ label: 'Name', type: 'string', value: 'title' },
{ label: 'Original Date', type: 'date', value: 'originaldate' },
{ label: 'Original Year', type: 'date', value: 'originalyear' },
{ label: 'Original Year', type: 'number', value: 'originalyear' },
{ label: 'Performer', type: 'string', value: 'performer' },
{ label: 'Play Count', type: 'number', value: 'playcount' },
{ label: 'Playlist', type: 'playlist', value: 'id' },
@@ -178,18 +179,18 @@ export const NDSongQueryFields = [
{ label: 'ReplayGain Track Peak', type: 'number', value: 'replaygain_track_peak' },
{ label: 'Remixer', type: 'string', value: 'remixer' },
{ label: 'Script', type: 'string', value: 'script' },
{ label: 'Sample Rate', type: 'number', value: 'sampleRate' },
{ label: 'Sample Rate', type: 'number', value: 'samplerate' },
{ label: 'Size', type: 'number', value: 'size' },
{ label: 'Sort Album', type: 'string', value: 'albumsort' },
{ label: 'Sort Album Artist', type: 'string', value: 'albumartistsort' },
{ label: 'Sort Album', type: 'string', value: 'sortalbum' },
{ label: 'Sort Album Artist', type: 'string', value: 'sortalbumartist' },
{ label: 'Sort Album Artists', type: 'string', value: 'albumartistssort' },
{ label: 'Sort Artist', type: 'string', value: 'artistsort' },
{ label: 'Sort Artist', type: 'string', value: 'sortartist' },
{ label: 'Sort Artists', type: 'string', value: 'artistssort' },
{ label: 'Sort Composer', type: 'string', value: 'composersort' },
{ label: 'Sort Lyricist', type: 'string', value: 'lyricistsort' },
{ label: 'Sort Name', type: 'string', value: 'titlesort' },
{ label: 'Sort Name', type: 'string', value: 'sorttitle' },
{ label: 'Subtitle', type: 'string', value: 'subtitle' },
{ label: 'Track Number', type: 'number', value: 'track' },
{ label: 'Track Number', type: 'number', value: 'tracknumber' },
{ label: 'Track Total', type: 'number', value: 'tracktotal' },
{ label: 'Website', type: 'string', value: 'website' },
{ label: 'Work', type: 'string', value: 'work' },
+1 -1
View File
@@ -318,7 +318,7 @@ export const sortSongList = (songs: Song[], sortBy: SongListSort, sortOrder: Sor
case SongListSort.YEAR:
results = orderBy(
results,
['releaseYear', (v) => v.album?.toLowerCase(), 'discNumber', 'track'],
['releaseYear', (v) => v.album?.toLowerCase(), 'discNumber', 'trackNumber'],
[order, order, order, order],
);
break;
+1
View File
@@ -421,6 +421,7 @@ type ApiContext = {
type BaseEndpointArgs = {
apiClientProps: {
forceRemoteUrl?: boolean;
server?: null | ServerListItemWithCredential;
serverId: string;
signal?: AbortSignal;