Compare commits

...

178 Commits

Author SHA1 Message Date
jeffvli 1f7d510110 update to v0.21.1 2025-10-13 04:48:10 -07:00
Hosted Weblate 44fae06143 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.8% (715 of 716 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: linger <linger0517@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/
Translation: feishin/Translation
2025-10-13 11:33:21 +00:00
Hosted Weblate 1e24e12a55 Translated using Weblate (Catalan)
Currently translated at 100.0% (716 of 716 strings)

Co-authored-by: Ondo <SparkyOndo@proton.me>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/
Translation: feishin/Translation
2025-10-13 11:33:20 +00:00
Hosted Weblate 358bdec2b6 Translated using Weblate (Basque)
Currently translated at 81.9% (587 of 716 strings)

Translated using Weblate (Basque)

Currently translated at 73.1% (524 of 716 strings)

Co-authored-by: Aitor Astorga <a.astorga.sdv@protonmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/eu/
Translation: feishin/Translation
2025-10-13 11:33:20 +00:00
jeffvli 1b8661d566 fix playback controls being called multiple times on media key input 2025-10-13 04:33:11 -07:00
Kendall Garner 68476deb98 fix album song count 2025-10-12 16:44:05 -07:00
jeffvli c88c6cf55e add mediasession playback controls 2025-10-12 16:37:24 -07:00
jeffvli f332d547a3 rename lint job 2025-10-12 16:20:50 -07:00
jeffvli 5803ac04d1 update linux desktop file instructions with disclaimer 2025-10-12 16:08:59 -07:00
jeffvli 58becc5c8e add useMediaSession hook to set metadata and status 2025-10-12 16:07:59 -07:00
jeffvli 309b49b46e refactor playerbar slider to separate component 2025-10-12 16:07:00 -07:00
jeffvli 40e7eda882 hide mediasession setting on desktop non-windows 2025-10-12 15:53:16 -07:00
jeffvli 1081829aa4 add new languages to config 2025-10-12 15:43:34 -07:00
Hosted Weblate 5a411417b0 Translated using Weblate (German)
Currently translated at 86.3% (613 of 710 strings)

Translated using Weblate (German)

Currently translated at 86.3% (613 of 710 strings)

Co-authored-by: Erik Val <Elaktrato@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Trrevvoorr <trevinofficial@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/
Translation: feishin/Translation
2025-10-12 15:29:08 -07:00
Hosted Weblate 8472a4013e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (714 of 714 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (714 of 714 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (706 of 706 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 14bb135116 Translated using Weblate (French)
Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (French)

Currently translated at 100.0% (699 of 699 strings)

Translated using Weblate (French)

Currently translated at 100.0% (699 of 699 strings)

Translated using Weblate (French)

Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Dylan MONTIGAUD <dylanmontigaud17@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 9583c3343b Translated using Weblate (Spanish)
Currently translated at 100.0% (714 of 714 strings)

Translated using Weblate (Spanish)

Currently translated at 99.8% (709 of 710 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (705 of 705 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (701 of 701 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Aitor Astorga <a.astorga.sdv@protonmail.com>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jaime-grj <weblate.4ljj9@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate d28b90afa6 Translated using Weblate (Arabic)
Currently translated at 16.3% (114 of 699 strings)

Added translation using Weblate (Arabic)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: xB <abxb19@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ar/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 479b93410f Translated using Weblate (Italian)
Currently translated at 100.0% (706 of 706 strings)

Co-authored-by: Daivy <reale805@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/it/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 3496e1c938 Translated using Weblate (Polish)
Currently translated at 97.9% (685 of 699 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: skajmer <skajmer@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate be44906b49 Translated using Weblate (Czech)
Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (705 of 705 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (701 of 701 strings)

Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 31488ce973 Translated using Weblate (Catalan)
Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (706 of 706 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ondo <SparkyOndo@proton.me>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 0b0696a42b Translated using Weblate (Japanese)
Currently translated at 72.8% (517 of 710 strings)

Co-authored-by: Erik Val <Elaktrato@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 6d51f66641 Translated using Weblate (Basque)
Currently translated at 73.3% (521 of 710 strings)

Translated using Weblate (Basque)

Currently translated at 57.7% (407 of 705 strings)

Translated using Weblate (Basque)

Currently translated at 44.1% (311 of 705 strings)

Translated using Weblate (Basque)

Currently translated at 41.7% (293 of 701 strings)

Translated using Weblate (Basque)

Currently translated at 15.5% (109 of 699 strings)

Translated using Weblate (Basque)

Currently translated at 11.1% (78 of 699 strings)

Translated using Weblate (Basque)

Currently translated at 7.8% (55 of 699 strings)

Added translation using Weblate (Basque)

Co-authored-by: Aitor Astorga <a.astorga.sdv@protonmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/eu/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 9ec8ec806d Translated using Weblate (German)
Currently translated at 86.3% (613 of 710 strings)

Translated using Weblate (German)

Currently translated at 86.3% (613 of 710 strings)

Co-authored-by: Erik Val <Elaktrato@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Trrevvoorr <trevinofficial@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 1baae08dc7 Translated using Weblate (Turkish)
Currently translated at 99.4% (701 of 705 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Slincess <kisacikdevran0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/tr/
Translation: feishin/Translation
2025-10-12 15:06:25 -07:00
Flame Sage b987324899 Added a Linux Notes section below MacOS for desktop. (#1020)
* Added a Linux Notes section below MacOS for desktop.
2025-10-12 15:01:18 -07:00
Mike Benz f07393c82a enable mediaSession api (#1040)
* enable mediaSession api
2025-10-12 14:59:30 -07:00
Lyall 3636384508 show time remaining instead of duration on click (#1179)
* show time remaining on duration click
2025-10-12 14:22:58 -07:00
jeffvli fc847631d3 move prerelease set to new step 2025-10-12 03:57:08 -07:00
jeffvli 1d9f462959 fix prerelease notes 2025-10-12 03:56:57 -07:00
jeffvli 369b956c66 remove EP_PRE_RELEASE variable on publish 2025-10-12 03:30:36 -07:00
jeffvli 5f14ccf70d set electron-builder publish to draft, convert to prerelease after build 2025-10-12 03:21:09 -07:00
jeffvli f9679f3bda handle prerelease versions in autoupdater beta channel 2025-10-12 02:33:49 -07:00
jeffvli 9f8d9a5b28 move update settings to advanced tab 2025-10-12 02:33:35 -07:00
jeffvli 912aea8174 rename latest channel string to "stable" 2025-10-12 02:31:58 -07:00
jeffvli 9ae4be9336 add missing publish to win beta 2025-10-12 01:05:54 -07:00
jeffvli 1686e7ad0b add separate beta publish scripts 2025-10-12 00:54:07 -07:00
jeffvli bf6047da17 add separate electron builder config for beta channel 2025-10-12 00:53:05 -07:00
jeffvli c3b4a9edf8 attempt fix on beta channel release files 2025-10-11 20:25:33 -07:00
jeffvli 0340cc8a85 update to v0.21.0 2025-10-11 20:12:17 -07:00
jeffvli 3f7a402ce8 add commit notes to beta deploy 2025-10-11 20:05:26 -07:00
jeffvli 20c585aa1c remove unneeded tag deletion 2025-10-11 19:43:08 -07:00
jeffvli 0248997a75 delete old tags in addition to release 2025-10-11 19:41:19 -07:00
jeffvli aaaeea1fa5 split workflow into separate jobs, fix release rename step 2025-10-11 19:39:52 -07:00
jeffvli 22504e9e84 simplify prerelease deletion 2025-10-11 19:28:16 -07:00
jeffvli 5fb2ae839f fix previous release parser 2025-10-11 19:23:05 -07:00
jeffvli 15b00910f3 rework nightly deploy again
- rename to beta
- autodelete previous beta releases
- rename release title to Beta
2025-10-11 19:09:56 -07:00
jeffvli 6cce72a22a rework nightly deploy
- rename to development
- only manual push
- allow input for semantic version number
- set release to github prerelease instead of draft
2025-10-11 19:02:45 -07:00
jeffvli d48fe81d7f re-add build in nightly 2025-10-11 17:14:04 -07:00
jeffvli f0d0f826fb remove duplicate build in nightly 2025-10-11 15:06:52 -07:00
jeffvli 4d12a4d6cb add release channel setting and implementation 2025-10-11 15:05:29 -07:00
jeffvli f14d1f3c5c convert version bump to use pwsh 2025-10-11 14:15:03 -07:00
jeffvli cc466cb0f4 remove exemption for enhancements for stale issues 2025-10-11 13:26:43 -07:00
jeffvli 20941c0405 add initial nightly release workflow 2025-10-11 13:06:51 -07:00
Kendall Garner d52c067dc7 allow customizing windows install 2025-10-11 12:40:27 -07:00
Kendall Garner fccbf83c12 bugfix: handle playlist with no tracks 2025-10-11 12:39:59 -07:00
Kendall Garner 7817059a9e update serve image docs 2025-10-11 08:07:40 -07:00
Luis Canada d3a986e93c Add PWA to web app (#1175)
* add PWA to web app

* Fix sw.js not registering and lint

* Change sw and manifest to live at root

* Revert "Change sw and manifest to live at root"

This reverts commit 4c27d92467.
2025-10-11 14:12:25 +00:00
Kendall Garner 6733047942 improve jellyfin participants 2025-10-10 19:32:11 -07:00
Kendall Garner 452803fc72 support artist art as artist background 2025-10-10 18:26:28 -07:00
jeffvli 4e4a0464d6 pin pnpm/action-setup to v4.1.0 2025-10-10 12:36:08 -07:00
Kendall Garner 4ff317eac9 fix nonexistent filter 2025-10-05 21:25:19 -07:00
Kendall Garner 306167fee3 playlist sort and refactoring 2025-10-05 19:13:35 -07:00
Kendall Garner 1cbb3e56bc add recently released to home page, refactor home route 2025-10-05 07:51:36 -07:00
Kendall Garner 7c24f7cba4 use margin bottom for notifications component to not disable center controls 2025-10-04 07:34:48 -07:00
Evelyn Gravett 1b278cb33a Feature: Add song and artist links to discord RPC (#1160)
* Add song and artist links to discord RPC

* use first artist name for artist link, full artist name for song link

* use first album artist for song link

* add discord rpc links setting

* simplify discord link settings

* fix setting description

* add musicbrainz links

* fix callback missing dependency

* use encodeURIComponent for lastfm links

Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>

* split musicbrainz ids

* combine link settings

---------

Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>
2025-10-04 03:27:59 +00:00
Kendall Garner f1a75d8e81 allow zero warnings on lint 2025-09-30 07:58:57 -07:00
Kendall Garner 4a48598260 add multiple genre support for nd albums/tracks 2025-09-28 19:59:20 -07:00
Kendall Garner 6df270ba34 server add/edit refactor, allow jellyfin prefer instant mix 2025-09-28 19:19:24 -07:00
Kendall Garner eb0ccec0bc Remove cached queries on editing server 2025-09-28 19:10:47 -07:00
Kendall Garner 8caf898172 have default background for artist top songs 2025-09-28 17:12:34 -07:00
Kendall Garner 508013958f ND >= 0.56.0: search songs by artist | album artist id 2025-09-27 20:00:34 -07:00
Kendall Garner c448352ec8 fix linter error 2025-09-26 17:30:20 -07:00
Henry e344adfeed Add autodiscovery for Jellyfin servers (#1146)
* Add autodiscovery for Jellyfin servers

* Remove debugging aids

you didn't see anything

* Fix linter errors

* Send a discovery packet to localhost too
2025-09-26 22:53:19 +00:00
Jeff bca4a14f2e adjust web playback error handler (#1150) 2025-09-24 18:09:30 -07:00
Kendall Garner f4be797f16 Add comment describing jellyfin image tag invalidation 2025-09-24 08:12:00 -07:00
Kendall Garner 2feef206fb add Navidrome/Jellyfin image cache invalidation 2025-09-24 08:05:22 -07:00
dependabot[bot] eea36f720a Bump axios in the npm_and_yarn group across 1 directory (#1145)
Bumps the npm_and_yarn group with 1 update in the / directory: [axios](https://github.com/axios/axios).


Updates `axios` from 1.9.0 to 1.12.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.9.0...v1.12.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.12.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 13:25:52 -07:00
dependabot[bot] 76350ed5af Bump vite in the npm_and_yarn group across 1 directory (#1115)
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.3.5 to 6.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.6
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 12:51:06 -07:00
Gabriele Mancini 4f38e16857 Feature: added playlist duration badge (#1130) 2025-09-23 12:45:08 -07:00
Malachi Soord 8a3edb71df feat: add semantic selectors for now-playing media (#1138)
* feat: add semantic selectors for now-playing media

This change adds unique class names to the elements that display the currently playing media information. This makes it easier for extension developers to parse the DOM and understand what media is playing.

The following classes have been added:
- `media-player`: The main player container.
- `player-cover-art`: The cover art of the playing track.
- `song-title`: The title of the playing track.
- `song-artist`: The artist of the playing track.
- `song-album`: The album of the playing track.
- `player-state-playing`/`player-state-paused`: The state of the player.
- `elapsed-time`: The elapsed time of the playing track.
- `total-duration`: The total duration of the playing track.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-09-23 12:44:22 -07:00
jeffvli 55e35e9b24 set default body background to #000 2025-09-22 18:24:04 -07:00
Gabriele Mancini 6abdbd2f3e Feature: added silent song notification setting (#1129)
* feat: added silent song notification
2025-09-17 21:06:59 -07:00
Kendall Garner 1d46cd5ff9 client-side only sort for all playlists (#1125)
* initial client-side only sort for all playlists

* allow reordering jellyfin (assume it works properly) and navidrome

* on playlist page, add to queue by sort order
2025-09-17 21:06:30 -07:00
Kendall Garner d68165dab5 move title to default layout 2025-09-15 20:10:56 -07:00
Kendall Garner dad80adb8b raise window on mpris raise 2025-09-15 19:31:10 -07:00
jeffvli 4134af0340 update to v0.20.1 2025-09-10 21:28:13 -07:00
Hosted Weblate 29a43ca185 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/
Translation: feishin/Translation
2025-09-11 05:49:32 +02:00
Hosted Weblate e452f86170 Translated using Weblate (Slovak)
Currently translated at 100.0% (699 of 699 strings)

Translated using Weblate (Slovak)

Currently translated at 81.2% (568 of 699 strings)

Translated using Weblate (Slovak)

Currently translated at 78.3% (548 of 699 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: peter cerny <posli.to.semka@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/sk/
Translation: feishin/Translation
2025-09-11 05:49:32 +02:00
Hosted Weblate ec765dca6a Translated using Weblate (French)
Currently translated at 100.0% (699 of 699 strings)

Translated using Weblate (French)

Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Dylan MONTIGAUD <dylanmontigaud17@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translation: feishin/Translation
2025-09-11 05:49:31 +02:00
Hosted Weblate 64a3752b54 Translated using Weblate (Italian)
Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marco Ciotola <github@ciotola.dev>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/it/
Translation: feishin/Translation
2025-09-11 05:49:30 +02:00
Hosted Weblate 24069d285f Translated using Weblate (Czech)
Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translation: feishin/Translation
2025-09-11 05:49:30 +02:00
Hosted Weblate 77fe886da4 Translated using Weblate (Catalan)
Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ondo <SparkyOndo@proton.me>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/
Translation: feishin/Translation
2025-09-11 05:49:29 +02:00
Hosted Weblate ef16e1403d Translated using Weblate (Turkish)
Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Mücahit Kaya <kaya-mucahit@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/tr/
Translation: feishin/Translation
2025-09-11 05:49:28 +02:00
jeffvli ff6dda7b06 prevent width overflow on lyrics container (#1106) 2025-09-10 20:49:15 -07:00
Jeff 479aa2e22d Merge pull request #1122 from jeffvli/properly-handle-context-menu-close
fix(context menu): Properly handle click outside, and show initial rating
2025-09-10 20:39:31 -07:00
jeffvli ab8c3ad0ec handle initial rating for multiple items in context menu 2025-09-10 20:32:34 -07:00
Kendall Garner dc03a432fe add initial rating when a single item is provided in context menu 2025-09-10 20:20:56 -07:00
Kendall Garner 751ad55d02 remove all node selectors in useClickOutside 2025-09-10 17:49:13 -07:00
Kendall Garner 78dc89303d show right control rating for subsonic servers 2025-09-10 17:27:07 -07:00
Jeff 4328d8860e Merge pull request #1113 from maximelafarie/fix/darwin-top-bar
fix: electron menu bar for darwin devices
2025-09-10 00:43:59 -07:00
Maxime LAFARIE 58a36b3bba fix: electron menu bar for darwin 2025-09-10 09:36:19 +02:00
Jeff 618e5d8da8 Merge pull request #1114 from maximelafarie/feature/one-click-context-menu
feat: add context menu on left controls and sidebar image
2025-09-09 19:19:09 -07:00
Jeff be6ec49cfa Merge pull request #1107 from mihawk90/now-playing-reformat
cleanup notification text
2025-09-09 19:00:19 -07:00
Maxime LAFARIE 65ecdc7666 feat: add context menu on left controls and sidebar image 2025-09-09 23:38:39 +02:00
Tarulia da42fd78d2 cleanup notification text 2025-09-08 17:20:03 +02:00
Kendall Garner c36735575f actually allow url('data:) in custom css 2025-09-06 19:28:15 -07:00
jeffvli 854222d2fa use consistent height calculation for sidebar scrollarea 2025-09-06 18:34:13 -07:00
jeffvli cd0a8d0fec revert min-height of library header
- causes issues with the positioning of the image at minimum size
2025-09-06 18:25:34 -07:00
jeffvli ed6376d99b update to 0.20.0 2025-09-06 17:24:12 -07:00
Hosted Weblate a6a51946f1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: 無情天 <kofzhanganguo@126.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/
Translation: feishin/Translation
2025-09-07 02:14:39 +02:00
Hosted Weblate 6e5acfa9da Translated using Weblate (Slovak)
Currently translated at 58.9% (412 of 699 strings)

Translated using Weblate (Slovak)

Currently translated at 57.0% (396 of 694 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: peter cerny <posli.to.semka@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/sk/
Translation: feishin/Translation
2025-09-07 02:14:39 +02:00
Hosted Weblate 2c7fda63d3 Translated using Weblate (French)
Currently translated at 100.0% (694 of 694 strings)

Co-authored-by: Dylan MONTIGAUD <dylanmontigaud17@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translation: feishin/Translation
2025-09-07 02:14:39 +02:00
Hosted Weblate cf72016575 Translated using Weblate (Spanish)
Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Fordas <fordas15@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translation: feishin/Translation
2025-09-07 02:14:39 +02:00
jeffvli 6368137815 move inView ref to ImageContainer component
- the separate outer component is unneeded and affects the positioning of some components which rely on the ImageContainer's styling
2025-09-06 17:14:30 -07:00
jeffvli ea122f5a4f add theme stylesheet loader 2025-09-06 03:02:58 -07:00
jeffvli bb7d561d0f adjust min-height on library header to better match minimum size 2025-09-06 01:21:06 -07:00
jeffvli 6dea9196a4 fix various light theme issues 2025-09-06 01:19:16 -07:00
jeffvli 4a28e7230a add hotkey to navigate to home route (#1074) 2025-09-06 01:01:48 -07:00
jeffvli b00305cc86 add setting to prevent sleep on playback (#1072) 2025-09-06 00:56:06 -07:00
jeffvli 40fb5ba916 add show album / album artist context menu items (#1105) 2025-09-06 00:45:59 -07:00
Kendall Garner 2cf0027419 only show translation when api key and provider are picked, and fix eslint vscode 2025-09-05 19:55:58 -07:00
Kendall Garner c81bd93d04 remove unnecessary console log 2025-09-05 05:51:28 -07:00
jeffvli 229f9e984e fix position of sidebar image when using custom windowbar 2025-09-05 01:17:00 -07:00
jeffvli e1e0670350 remove unused file
- broke on electron v37 due to File.path deprecated
2025-09-05 01:00:51 -07:00
jeffvli 20debb099d remove electron file menu (#1041) 2025-09-05 00:16:49 -07:00
jeffvli 99f9cc5968 bump to electron v37 2025-09-04 21:52:17 -07:00
jeffvli 53b4a2ee8a bump to mantine v8.2.8 2025-09-04 21:42:13 -07:00
jeffvli 0b59a54f04 remove debug tools 2025-09-04 21:18:23 -07:00
jeffvli f85ce9703f add catalan language to config 2025-09-04 21:17:13 -07:00
jeffvli cf3fd53bc1 add portugese language to config 2025-09-04 21:17:13 -07:00
Kendall Garner 871923a977 Merge branch 'development' of github.com:jeffvli/feishin into development 2025-09-04 21:16:08 -07:00
Kendall Garner c21f7df7b2 fix share date setting, notification, lint fix 2025-09-04 21:15:42 -07:00
jeffvli c83f27ce14 add turkish language to config (#1087) 2025-09-04 21:14:54 -07:00
jeffvli 53499e2579 fix clickoutside on main context menu 2025-09-04 20:48:15 -07:00
jeffvli cbbf2db087 remove nested rating menu 2025-09-04 20:37:59 -07:00
Kendall Garner dd60634a40 exclude missing for more fields 2025-09-04 19:53:41 -07:00
jeffvli 54932fee86 fix context menu stuck on rating hover (#1079) 2025-09-04 19:49:25 -07:00
jeffvli 878e0007d9 fix typo 2025-09-04 19:38:17 -07:00
Jeff 2260c0c02b Merge pull request #1002 from jeffvli/react-image-lazy-loaded
Use lazy loading (react-intersection-observer) for image loading
2025-09-03 21:43:55 -07:00
Hosted Weblate dd3285544e Translated using Weblate (Norwegian Bokmål)
Currently translated at 54.8% (376 of 685 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: klodrik <klodrik@zoominn.no>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nb_NO/
Translation: feishin/Translation
2025-09-04 03:29:47 +00:00
Hosted Weblate f3c674fb20 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (694 of 694 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/
Translation: feishin/Translation
2025-09-04 03:29:46 +00:00
Hosted Weblate 6e0ae3ba92 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: York <goog10216922@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/
Translation: feishin/Translation
2025-09-04 03:29:46 +00:00
Hosted Weblate c76cad9727 Translated using Weblate (Slovak)
Currently translated at 53.8% (374 of 694 strings)

Added translation using Weblate (Slovak)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: peter cerny <posli.to.semka@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/sk/
Translation: feishin/Translation
2025-09-04 03:29:45 +00:00
Hosted Weblate 3c29004e28 Translated using Weblate (Finnish)
Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jonoafi <joona@jonottaa.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fi/
Translation: feishin/Translation
2025-09-04 03:29:44 +00:00
Hosted Weblate b08050d9ff Translated using Weblate (French)
Currently translated at 100.0% (694 of 694 strings)

Translated using Weblate (French)

Currently translated at 100.0% (694 of 694 strings)

Translated using Weblate (French)

Currently translated at 100.0% (694 of 694 strings)

Translated using Weblate (French)

Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Dylan MONTIGAUD <dylanmontigaud17@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Co-authored-by: mrchonks <chonkstv@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translation: feishin/Translation
2025-09-04 03:29:44 +00:00
Hosted Weblate 908d5dde0f Translated using Weblate (Spanish)
Currently translated at 100.0% (694 of 694 strings)

Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translation: feishin/Translation
2025-09-04 03:29:43 +00:00
Hosted Weblate 0540b224ec Translated using Weblate (Dutch)
Currently translated at 36.3% (252 of 694 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Joren-vanGoethem <jorenvangoethem@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nl/
Translation: feishin/Translation
2025-09-04 03:29:42 +00:00
Hosted Weblate d0a2583edb Translated using Weblate (Polish)
Currently translated at 98.3% (674 of 685 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Piotr Łoboda <loboda4450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/
Translation: feishin/Translation
2025-09-04 03:29:42 +00:00
Hosted Weblate 97ca234015 Translated using Weblate (Portuguese)
Currently translated at 61.1% (419 of 685 strings)

Added translation using Weblate (Portuguese)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pt/
Translation: feishin/Translation
2025-09-04 03:29:41 +00:00
Hosted Weblate b07395c12b Translated using Weblate (Czech)
Currently translated at 100.0% (694 of 694 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translation: feishin/Translation
2025-09-04 03:29:40 +00:00
Hosted Weblate d8a10c8841 Translated using Weblate (Catalan)
Currently translated at 100.0% (694 of 694 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (685 of 685 strings)

Translated using Weblate (Catalan)

Currently translated at 95.0% (651 of 685 strings)

Translated using Weblate (Catalan)

Currently translated at 88.7% (608 of 685 strings)

Translated using Weblate (Catalan)

Currently translated at 88.7% (608 of 685 strings)

Translated using Weblate (Catalan)

Currently translated at 49.0% (336 of 685 strings)

Translated using Weblate (Catalan)

Currently translated at 49.0% (336 of 685 strings)

Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translated using Weblate (Catalan)

Currently translated at 47.4% (325 of 685 strings)

Added translation using Weblate (Catalan)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Isaac Miró <isaac.miro.garcia@gmail.com>
Co-authored-by: Ondo <SparkyOndo@proton.me>
Co-authored-by: Àlex Bravo <alexbravobosch@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/
Translation: feishin/Translation
2025-09-04 03:29:40 +00:00
Hosted Weblate e112d0acbf Translated using Weblate (German)
Currently translated at 87.7% (601 of 685 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Trrevvoorr <trevinofficial@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/
Translation: feishin/Translation
2025-09-04 03:29:39 +00:00
Hosted Weblate 8ca0a207d4 Translated using Weblate (Tamil)
Currently translated at 100.0% (685 of 685 strings)

Translated using Weblate (Tamil)

Currently translated at 99.8% (684 of 685 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ta/
Translation: feishin/Translation
2025-09-04 03:29:38 +00:00
Hosted Weblate 4950ce1aed Translated using Weblate (Turkish)
Currently translated at 100.0% (685 of 685 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (685 of 685 strings)

Translated using Weblate (Turkish)

Currently translated at 54.8% (376 of 685 strings)

Translated using Weblate (Turkish)

Currently translated at 51.5% (353 of 685 strings)

Translated using Weblate (Turkish)

Currently translated at 31.5% (216 of 685 strings)

Added translation using Weblate (Turkish)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mücahit Kaya <kaya-mucahit@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/tr/
Translation: feishin/Translation
2025-09-04 03:29:38 +00:00
Jeff 471d35dd70 Merge pull request #1055 from jeffvli/dependabot/npm_and_yarn/npm_and_yarn-5a0513363d
Bump tmp from 0.2.3 to 0.2.4 in the npm_and_yarn group across 1 directory
2025-09-03 20:29:31 -07:00
Kendall Garner f5af1c314c add image loader/unloader and only toggle source 2025-09-03 19:56:51 -07:00
Kendall Garner 1aac1a6361 Merge branch 'development' into react-image-lazy-loaded 2025-09-03 19:47:53 -07:00
Jeff 003e71d974 Merge pull request #1101 from ch-rit/fix-hiding-toast
fixed hiding the toast after content is loaded
2025-09-03 18:49:48 -07:00
Jeff 94b2668384 Merge pull request #1095 from johnson-dave/bad_encoding_autoplay
fix: ensure autoplay on metadata duration mismatch causing decoding error
2025-09-03 18:43:54 -07:00
ch-rit 039e553aa0 fixed hiding the toast after content is loaded 2025-09-03 19:04:36 +02:00
Jeff 755a09ceec Merge pull request #1092 from ch-rit/fix-genre-list
fixed the api call to get musicgenres
2025-09-03 01:30:53 -07:00
Jeff 60c94c2299 Merge pull request #1086 from hansyulian/feature/improve-add-to-playlist-ux
Feature: improve add to playlist UX
2025-09-03 01:25:41 -07:00
Dave Johnson 59e01f90e8 fix: ensure autoplay on bad encoding 2025-09-01 09:23:39 +01:00
Jeff 59065d24bc Merge pull request #1034 from Lyall-A/private-mode
add private mode toggle to app menu
2025-08-27 22:02:28 -07:00
Lyall 2271c211cb display private mode status 2025-08-28 05:48:21 +01:00
Jeff 7394d639e2 Merge pull request #1044 from Der-Penz/development
fix song getting added twice to queue when using tab navigation in command palette
2025-08-27 21:05:47 -07:00
Jeff 352a8e71e6 Merge pull request #1037 from vimaexd/discord-displaytype
implement discord status types
2025-08-27 21:00:52 -07:00
Christoph Ritzer 377a3ea8ab fixed the api call to get musicgenres 2025-08-26 17:14:17 +02:00
Hans Yulian 35b869ee7b feature: improve add to playlist UX 2025-08-23 06:00:17 +07:00
dependabot[bot] 618d27ffc1 Bump tmp in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [tmp](https://github.com/raszi/node-tmp).


Updates `tmp` from 0.2.3 to 0.2.4
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-06 17:36:57 +00:00
DerPenz 24b9bf210c fix song getting added twice to queue 2025-08-03 17:19:07 +02:00
mae taylor 76770de7d8 feat: discord presence display type 2025-08-01 16:43:34 +01:00
mae taylor 7ff74b8d5e bump @xhayper/discord-rpc to 1.3.0 2025-08-01 15:50:53 +01:00
Lyall 7423b71a65 change text 2025-07-31 16:26:05 +01:00
Lyall a44ad66d46 add private mode toggle to app menu 2025-07-31 16:12:03 +01:00
Kendall Garner 49bb42a298 Use lazy loading (react-intersection-observer) for image loading 2025-07-07 20:11:32 -07:00
128 changed files with 9821 additions and 1743 deletions
+332
View File
@@ -0,0 +1,332 @@
name: Publish Beta (Manual)
on:
workflow_dispatch:
inputs:
version:
description: 'Semantic version number (e.g., 1.0.0) - beta suffix will be added automatically'
required: false
type: string
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4.1.0
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Validate and set version with beta suffix
id: version
shell: pwsh
run: |
$inputVersion = "${{ github.event.inputs.version }}"
Write-Host "Input version: $inputVersion"
if ($inputVersion -eq "" -or $inputVersion -eq "null") {
# No input version provided, auto-increment patch version
Write-Host "No version provided, auto-incrementing patch version..."
# Get current version from package.json
$currentVersion = (Get-Content package.json | ConvertFrom-Json).version
Write-Host "Current version: $currentVersion"
# Remove any existing suffix (like -beta) to get clean semantic version
$cleanVersion = $currentVersion -replace '-.*$', ''
# Extract major, minor, patch components
$versionParts = $cleanVersion.Split('.')
if ($versionParts.Length -ne 3) {
Write-Error "Current version format is invalid: $cleanVersion"
exit 1
}
$major = [int]$versionParts[0]
$minor = [int]$versionParts[1]
$patch = [int]$versionParts[2]
# Increment patch version
$newPatch = $patch + 1
$inputVersion = "$major.$minor.$newPatch"
Write-Host "Auto-generated version: $inputVersion"
} else {
# Validate semantic version format (major.minor.patch)
$versionPattern = '^\d+\.\d+\.\d+$'
if ($inputVersion -notmatch $versionPattern) {
Write-Error "Invalid version format. Expected semantic version (e.g., 1.0.0), got: $inputVersion"
exit 1
}
}
# Add beta suffix
$versionWithBeta = "$inputVersion-beta"
Write-Host "Setting version to: $versionWithBeta"
# Update package.json
$packageJson = Get-Content package.json | ConvertFrom-Json
$packageJson.version = $versionWithBeta
$packageJson | ConvertTo-Json -Depth 10 | Set-Content package.json
Write-Host "Updated package.json version to: $versionWithBeta"
# Set output for other jobs
echo "version=$versionWithBeta" >> $env:GITHUB_OUTPUT
- name: Delete existing releases and tags
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Get the version that was set in the previous step
$versionWithBeta = "${{ steps.version.outputs.version }}"
Write-Host "Checking for existing releases with tag: $versionWithBeta"
# Find and delete any releases with isPrerelease "true"
Write-Host "Deleting existing prereleases..."
Write-Host "Searching for releases with isPrerelease 'true'..."
$betaReleases = gh release list --limit 100 --json tagName,isPrerelease,name | ConvertFrom-Json | Where-Object { $_.isPrerelease -eq $true }
if ($betaReleases) {
Write-Host "Found $($betaReleases.Count) release(s) with isPrerelease 'true':"
foreach ($release in $betaReleases) {
Write-Host " - Tag: $($release.tagName), Title: $($release.name)"
gh release delete $release.tagName --yes --cleanup-tag
Write-Host " Deleted release with tag: $($release.tagName)"
}
} else {
Write-Host "No releases found with isPrerelease 'true'"
}
publish:
needs: prepare
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
steps:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4.1.0
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Set version from prepare job
shell: pwsh
run: |
$versionWithBeta = "${{ needs.prepare.outputs.version }}"
Write-Host "Setting version from prepare job: $versionWithBeta"
# Update package.json with the version from prepare job
$packageJson = Get-Content package.json | ConvertFrom-Json
$packageJson.version = $versionWithBeta
$packageJson | ConvertTo-Json -Depth 10 | Set-Content package.json
Write-Host "Updated package.json version to: $versionWithBeta"
- name: Build and Publish releases (Windows)
if: matrix.os == 'windows-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run publish:win:beta
on_retry_command: pnpm cache delete
- name: Build and Publish releases (macOS)
if: matrix.os == 'macos-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run publish:mac:beta
on_retry_command: pnpm cache delete
- name: Build and Publish releases (Linux)
if: matrix.os == 'ubuntu-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run publish:linux:beta
on_retry_command: pnpm cache delete
- name: Build and Publish releases (Linux ARM64)
if: matrix.os == 'ubuntu-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run publish:linux-arm64:beta
on_retry_command: pnpm cache delete
edit-release:
needs: [prepare, publish]
runs-on: ubuntu-latest
steps:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Edit release with commits and title
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Get the version from the prepare job
$versionWithBeta = "${{ needs.prepare.outputs.version }}"
$tagVersion = "v" + $versionWithBeta
Write-Host "Editing release for tag: $tagVersion"
# Check if release exists
$releaseExists = gh release view $tagVersion 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Host "Found release with tag $tagVersion"
# Get current release notes
# Find the latest non-prerelease tag
Write-Host "Finding latest non-prerelease tag..."
$latestNonPrerelease = gh release list --limit 100 --json tagName,isPrerelease | ConvertFrom-Json | Where-Object { $_.isPrerelease -eq $false -and $_.tagName -ne $tagVersion } | Select-Object -First 1
if ($latestNonPrerelease) {
$latestTag = $latestNonPrerelease.tagName
Write-Host "Latest non-prerelease tag: $latestTag"
# Get commits between latest non-prerelease and current HEAD
Write-Host "Getting commits between $latestTag and HEAD..."
# Use proper git range syntax and handle PowerShell string interpolation
$gitRange = "$latestTag..HEAD"
Write-Host "Git range: $gitRange"
# Get commits using proper git command with datetime
$commits = git log --oneline --pretty=format:"%ad|%s|%h" --date=short $gitRange
# Check if commits exist
if ($commits -and $commits.Trim() -ne "") {
Write-Host "Found commits:"
Write-Host $commits
# Group commits by date
$groupedCommits = @{}
foreach ($line in $commits) {
if ($line.Trim() -ne "") {
$parts = $line.Split('|')
$date = $parts[0]
$message = $parts[1]
$hash = $parts[2]
if (-not $groupedCommits.ContainsKey($date)) {
$groupedCommits[$date] = @()
}
$groupedCommits[$date] += "- $message ($hash)"
}
}
# Build formatted release notes grouped by date
$commitNotes = "## Changes since $latestTag`n`n"
$sortedDates = $groupedCommits.Keys | Sort-Object -Descending
foreach ($date in $sortedDates) {
$commitNotes += "### $date`n"
foreach ($commit in $groupedCommits[$date]) {
$commitNotes += "$commit`n"
}
$commitNotes += "`n"
}
$releaseNotes = $commitNotes
} else {
Write-Host "No commits found between $latestTag and HEAD"
Write-Host "Trying alternative approach..."
# Alternative: get commits since the tag (not range) with datetime
$commits = git log --oneline --pretty=format:"%ad|%s|%h" --date=short $latestTag.. --not $latestTag
if ($commits -and $commits.Trim() -ne "") {
Write-Host "Found commits with alternative method:"
Write-Host $commits
# Group commits by date
$groupedCommits = @{}
foreach ($line in $commits) {
if ($line.Trim() -ne "") {
$parts = $line.Split('|')
$date = $parts[0]
$message = $parts[1]
$hash = $parts[2]
if (-not $groupedCommits.ContainsKey($date)) {
$groupedCommits[$date] = @()
}
$groupedCommits[$date] += "- $message ($hash)"
}
}
# Build formatted release notes grouped by date
$commitNotes = "## Changes since $latestTag`n`n"
$sortedDates = $groupedCommits.Keys | Sort-Object -Descending
foreach ($date in $sortedDates) {
$commitNotes += "### $date`n"
foreach ($commit in $groupedCommits[$date]) {
$commitNotes += "$commit`n"
}
$commitNotes += "`n"
}
$releaseNotes = $commitNotes
} else {
Write-Host "Still no commits found, using basic release notes"
$releaseNotes = "## Beta Release`n`nThis is a beta release."
}
}
} else {
Write-Host "No non-prerelease tags found, using basic release notes"
$releaseNotes = "## Beta Release`n`nThis is a beta release."
}
# Update the release with new title and notes
Write-Host "Updating release with title 'Beta' and new notes..."
gh release edit $tagVersion --title "Beta" --notes "$releaseNotes"
Write-Host "Successfully updated release title to 'Beta' and added commit notes"
} else {
Write-Host "No release found with tag $tagVersion"
}
- name: Set release as prerelease
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release edit $tagVersion --prerelease
Write-Host "Successfully set release as prerelease"
+1 -3
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.1.0
with:
version: 9
@@ -31,7 +31,6 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux
pnpm run publish:linux
on_retry_command: pnpm cache delete
@@ -44,6 +43,5 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux-arm64
pnpm run publish:linux-arm64
on_retry_command: pnpm cache delete
+1 -2
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.1.0
with:
version: 9
@@ -31,6 +31,5 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:mac
pnpm run publish:mac
on_retry_command: pnpm cache delete
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v3
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.1.0
with:
version: 9
+1 -2
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.1.0
with:
version: 9
@@ -31,6 +31,5 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:win
pnpm run publish:win
on_retry_command: pnpm cache delete
+1 -1
View File
@@ -42,6 +42,6 @@ jobs:
stale-issue-label: 'stale'
exempt-issue-labels: 'enhancement,keep,security'
exempt-issue-labels: 'keep,security'
stale-pr-label: 'stale'
exempt-pr-labels: 'keep,security'
+2 -2
View File
@@ -3,7 +3,7 @@ name: Test
on: [push, pull_request]
jobs:
release:
lint:
runs-on: ${{ matrix.os }}
strategy:
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v1
- name: Install Node.js and PNPM
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.1.0
with:
version: 9
+1 -2
View File
@@ -13,10 +13,9 @@
".prettierrc": "jsonc",
".eslintignore": "ignore"
},
"eslint.validate": ["typescript"],
"eslint.validate": ["typescript", "typescriptreact"],
"eslint.workingDirectories": [
{ "directory": "./", "changeProcessCWD": true },
{ "directory": "./server", "changeProcessCWD": true }
],
"typescript.tsserver.experimental.enableProjectDiagnostics": false,
"editor.codeActionsOnSave": {
+20 -4
View File
@@ -57,6 +57,18 @@ If you're using a device running macOS 12 (Monterey) or higher, [check here](htt
For media keys to work, you will be prompted to allow Feishin to be a Trusted Accessibility Client. After allowing, you will need to restart Feishin for the privacy settings to take effect.
#### Linux Notes
If you're using a Linux device, a `.desktop` file is recommended for easy launching of Feishin.
Download the [latest release (AppImage)](https://github.com/jeffvli/feishin/releases) and [application icon](https://github.com/jeffvli/feishin/blob/development/resources/icon.png?raw=true) to your `~/applications/` folder. This folder may need to be created if it does not already exist.
Rename the icon to `Feishin-linux-x86_64.png`.
Save the [example desktop file](https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/feishin.desktop) as `~/.local/share/applications/feishin.desktop`.
You will now see Feishin show up in your menu. The properties in the example desktop file may need to be modified to match your system.
### Web and Docker
Visit [https://feishin.vercel.app](https://feishin.vercel.app) to use the hosted web version of Feishin. The web client only supports the web player backend.
@@ -157,14 +169,18 @@ This project is built off of [electron-vite](https://github.com/alex8088/electro
- `pnpm run build:remote` - Build the remote app (remote)
- `pnpm run build:web` - Build the standalone web app (renderer)
- `pnpm run package` - Package the project
- `pnpm run package:dev` - Package the project for development
- `pnpm run package:linux` - Package the project for Linux
- `pnpm run package:mac` - Package the project for Mac
- `pnpm run package:win` - Package the project for Windows
- `pnpm run package:dev` - Package the project for development locally
- `pnpm run package:linux` - Package the project for Linux locally
- `pnpm run package:mac` - Package the project for Mac locally
- `pnpm run package:win` - Package the project for Windows locally
- `pnpm run publish:linux` - Publish the project for Linux
- `pnpm run publish:linux:beta` - Publish the project for Linux (beta channel)
- `pnpm run publish:linux-arm64` - Publish the project for Linux ARM64
- `pnpm run publish:linux-arm64:beta` - Publish the project for Linux ARM64 (beta channel)
- `pnpm run publish:mac` - Publish the project for Mac
- `pnpm run publish:mac:beta` - Publish the project for Mac (beta channel)
- `pnpm run publish:win` - Publish the project for Windows
- `pnpm run publish:win:beta` - Publish the project for Windows (beta channel)
- `pnpm run typecheck` - Type check the project
- `pnpm run typecheck:node` - Type check the project with tsconfig.node.json
- `pnpm run typecheck:web` - Type check the project with tsconfig.web.json
+58
View File
@@ -0,0 +1,58 @@
appId: org.jeffvli.feishin
productName: Feishin
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
electronVersion: 35.1.5
directories:
buildResources: assets
files:
- 'out/**/*'
- 'package.json'
extraResources:
- assets/**
asarUnpack:
- resources/**
win:
target:
- zip
- nsis
icon: assets/icons/icon.png
nsis:
allowToChangeInstallationDirectory: true
oneClick: false
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
target:
target: default
arch:
- arm64
- x64
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: true
entitlements: assets/entitlements.mac.plist
entitlementsInherit: assets/entitlements.mac.plist
gatekeeperAssess: false
notarize: false
dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
linux:
target:
- AppImage
- tar.xz
category: AudioVideo;Audio;Player
icon: assets/icons/icon.png
artifactName: ${productName}-${os}-${arch}.${ext}
npmRebuild: false
publish:
provider: github
owner: jeffvli
repo: feishin
channel: beta
releaseType: draft
+9
View File
@@ -16,10 +16,14 @@ win:
- zip
- nsis
icon: assets/icons/icon.png
nsis:
allowToChangeInstallationDirectory: true
oneClick: false
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
target:
target: default
@@ -33,8 +37,10 @@ mac:
entitlementsInherit: assets/entitlements.mac.plist
gatekeeperAssess: false
notarize: false
dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
linux:
target:
- AppImage
@@ -42,8 +48,11 @@ linux:
category: AudioVideo;Audio;Player
icon: assets/icons/icon.png
artifactName: ${productName}-${os}-${arch}.${ext}
npmRebuild: false
publish:
provider: github
owner: jeffvli
repo: feishin
channel: latest
releaseType: draft
+9
View File
@@ -0,0 +1,9 @@
[Desktop Entry]
Name=Feishin
Comment=An Electron-based music streaming app
Exec=/home/username/.applications/Feishin-linux-x86_64.AppImage
Icon=/home/username/.applications/Feishin-linux-x86_64.png
Terminal=false
Type=Application
Categories=AudioVideo;Audio;Music;Player;
StartupNotify=true
+25 -19
View File
@@ -1,6 +1,6 @@
{
"name": "feishin",
"version": "0.19.0",
"version": "0.21.1",
"description": "A modern self-hosted music player.",
"keywords": [
"subsonic",
@@ -30,9 +30,9 @@
"i18next": "i18next -c src/i18n/i18next-parser.config.js",
"postinstall": "electron-builder install-app-deps",
"lint": "pnpm run lint-code && pnpm run lint-styles",
"lint-code": "eslint --cache .",
"lint-code": "eslint --max-warnings=0 --cache .",
"lint-code:fix": "eslint --cache --fix .",
"lint-styles": "stylelint 'src/**/*.{css,scss}'",
"lint-styles": "stylelint --max-warnings=0 'src/**/*.{css,scss}'",
"lint-styles:fix": "stylelint 'src/**/*.{css,scss}' --fix",
"lint:fix": "pnpm run lint-code:fix && pnpm run lint-styles:fix",
"package": "pnpm run build && electron-builder",
@@ -44,10 +44,14 @@
"package:mac:pr": "pnpm run build && electron-builder --mac --publish never",
"package:win": "pnpm run build && electron-builder --win",
"package:win:pr": "pnpm run build && electron-builder --win --publish never",
"publish:linux": "electron-builder --publish always --linux",
"publish:linux-arm64": "electron-builder --publish always --linux --arm64",
"publish:mac": "electron-builder --publish always --mac",
"publish:win": "electron-builder --publish always --win",
"publish:linux": "pnpm run build && electron-builder --publish always --linux",
"publish:linux-arm64": "pnpm run build && electron-builder --publish always --linux --arm64",
"publish:linux-arm64:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux --arm64",
"publish:linux:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux",
"publish:mac": "pnpm run build && electron-builder --publish always --mac",
"publish:mac:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --mac",
"publish:win": "pnpm run build && electron-builder --publish always --win",
"publish:win:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --win",
"start": "electron-vite preview",
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
@@ -64,21 +68,21 @@
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0",
"@mantine/colors-generator": "^8.1.1",
"@mantine/core": "^8.1.1",
"@mantine/dates": "^8.1.1",
"@mantine/form": "^8.1.1",
"@mantine/hooks": "^8.1.1",
"@mantine/modals": "^8.1.1",
"@mantine/notifications": "^8.1.1",
"@mantine/colors-generator": "^8.2.8",
"@mantine/core": "^8.2.8",
"@mantine/dates": "^8.2.8",
"@mantine/form": "^8.2.8",
"@mantine/hooks": "^8.2.8",
"@mantine/modals": "^8.2.8",
"@mantine/notifications": "^8.2.8",
"@tanstack/react-query": "^4.32.1",
"@tanstack/react-query-devtools": "^4.32.1",
"@tanstack/react-query-persist-client": "^4.32.1",
"@ts-rest/core": "^3.23.0",
"@xhayper/discord-rpc": "^1.0.24",
"@xhayper/discord-rpc": "^1.3.0",
"audiomotion-analyzer": "^4.5.0",
"auto-text-size": "^0.2.3",
"axios": "^1.6.0",
"axios": "^1.12.0",
"cheerio": "^1.0.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
@@ -112,6 +116,7 @@
"react-i18next": "^11.18.6",
"react-icons": "^5.5.0",
"react-image": "^4.1.0",
"react-intersection-observer": "^9.16.0",
"react-loading-skeleton": "^3.5.0",
"react-player": "^2.11.0",
"react-router": "^6.16.0",
@@ -143,7 +148,7 @@
"@vitejs/plugin-react": "^4.3.4",
"concurrently": "^7.1.0",
"cross-env": "^7.0.3",
"electron": "^35.1.5",
"electron": "^37.4.0",
"electron-builder": "^26.0.12",
"electron-devtools-installer": "^3.2.0",
"electron-vite": "^3.1.0",
@@ -163,10 +168,11 @@
"stylelint-config-recess-order": "^7.1.0",
"stylelint-config-standard": "^38.0.0",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite": "^6.3.6",
"vite-plugin-conditional-import": "^0.1.7",
"vite-plugin-dynamic-import": "^1.6.0",
"vite-plugin-ejs": "^1.7.0"
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-pwa": "^1.0.3"
},
"pnpm": {
"onlyBuiltDependencies": [
+2363 -288
View File
File diff suppressed because it is too large Load Diff
+32 -2
View File
@@ -2,10 +2,13 @@ import { PostProcessorModule, StringMap, TOptions } from 'i18next';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ar from './locales/ar.json';
import ca from './locales/ca.json';
import cs from './locales/cs.json';
import de from './locales/de.json';
import en from './locales/en.json';
import es from './locales/es.json';
import eu from './locales/eu.json';
import fa from './locales/fa.json';
import fi from './locales/fi.json';
import fr from './locales/fr.json';
@@ -18,19 +21,24 @@ import nbNO from './locales/nb-NO.json';
import nl from './locales/nl.json';
import pl from './locales/pl.json';
import ptBr from './locales/pt-BR.json';
import pt from './locales/pt.json';
import ru from './locales/ru.json';
import sl from './locales/sl.json';
import sr from './locales/sr.json';
import sv from './locales/sv.json';
import ta from './locales/ta.json';
import tr from './locales/tr.json';
import zhHans from './locales/zh-Hans.json';
import zhHant from './locales/zh-Hant.json';
const resources = {
ar: { translation: ar },
ca: { translation: ca },
cs: { translation: cs },
de: { translation: de },
en: { translation: en },
es: { translation: es },
eu: { translation: eu },
fa: { translation: fa },
fi: { translation: fi },
fr: { translation: fr },
@@ -42,12 +50,14 @@ const resources = {
'nb-NO': { translation: nbNO },
nl: { translation: nl },
pl: { translation: pl },
pt: { translation: pt },
'pt-BR': { translation: ptBr },
ru: { translation: ru },
sl: { translation: sl },
sr: { translation: sr },
sv: { translation: sv },
ta: { translation: ta },
tr: { translation: tr },
'zh-Hans': { translation: zhHans },
'zh-Hant': { translation: zhHant },
};
@@ -57,17 +67,29 @@ export const languages = [
label: 'English',
value: 'en',
},
{
label: 'العربية',
value: 'ar',
},
{
label: 'Català',
value: 'ca',
},
{
label: 'Čeština',
value: 'cs',
},
{
label: 'Deutsch',
value: 'de',
},
{
label: 'Español',
value: 'es',
},
{
label: 'Deutsch',
value: 'de',
label: 'Basque',
value: 'eu',
},
{
label: 'Français',
@@ -109,6 +131,10 @@ export const languages = [
label: 'فارسی',
value: 'fa',
},
{
label: 'Português',
value: 'pt',
},
{
label: 'Português (Brasil)',
value: 'pt-BR',
@@ -137,6 +163,10 @@ export const languages = [
label: 'Tamil',
value: 'ta',
},
{
label: 'Türkçe',
value: 'tr',
},
{
label: '简体中文',
value: 'zh-Hans',
+149
View File
@@ -0,0 +1,149 @@
{
"action": {
"addToFavorites": "إضافة الى $t(entity.favorite_other)",
"addToPlaylist": "إضافة الى $t(entity.playlist_one)",
"clearQueue": "مسح قائمة الإنتظار",
"createPlaylist": "إنشاء $t(entity.playlist_one)",
"deletePlaylist": "حذف $t(entity.playlist_one)",
"deselectAll": "إلغاء تحديد الكل",
"editPlaylist": "تعديل $t(entity.playlist_one)",
"goToPage": "اذهب الى صفحة",
"moveToNext": "الذهاب الى التالي",
"moveToBottom": "الذهاب الى الأسفل",
"moveToTop": "الذهاب الى الأعلى",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "حذف من $t(entity.favorite_other)",
"removeFromPlaylist": "حذف من $t(entity.playlist_one)",
"removeFromQueue": "حذف من قائمة الإنتظار",
"setRating": "تحديد التقييم",
"toggleSmartPlaylistEditor": "تشغيل / إطفاء وضع التعديل لـ $t(entity.smartPlaylist)",
"viewPlaylists": "إظهار $t(entity.playlist_other)",
"openIn": {
"lastfm": "فتح في Last.fm",
"musicbrainz": "فتح في MusicBrainz"
}
},
"common": {
"action_zero": "عملية",
"action_one": "عملية",
"action_two": "عمليتين",
"action_few": "عمليات",
"action_many": "عمليات",
"action_other": "عمليات",
"add": "إضافة",
"additionalParticipants": "مشاركين إضافيين",
"newVersion": "تم تثبيت تحديث جديد {{version}}",
"viewReleaseNotes": "عرض معلومات الإصدار",
"albumGain": "مستوى صوت الألبوم",
"albumPeak": "اعلى مستوى للألبوم",
"areYouSure": "هل أنت متأكد؟",
"ascending": "تصاعدي",
"backward": "خلف",
"biography": "سيرة",
"bitDepth": "عمق البت",
"bitrate": "معدل البت (البت ريت)",
"bpm": "نبضة في الدقيقة",
"cancel": "إلغاء",
"center": "منتصف",
"channel_zero": "قناة",
"channel_one": "قناة",
"channel_two": "قناتين",
"channel_few": "قنوات",
"channel_many": "قنوات",
"channel_other": "قنوات",
"clear": "مسح",
"close": "إغلاق",
"codec": "كوديك",
"collapse": "طي",
"comingSoon": "قريبًا…",
"configure": "تعديل",
"confirm": "تأكيد",
"create": "إنشاء",
"currentSong": "$t(entity.track_one) الحالي",
"decrease": "تنقيص",
"delete": "حذف",
"descending": "تنازلي",
"description": "وصف",
"disable": "تعطيل",
"disc": "قرص",
"dismiss": "إخفاء",
"duration": "مدة",
"edit": "تعديل",
"enable": "تفعيل",
"expand": "توسيع",
"favorite": "مفضلة",
"filter_zero": "فلتر",
"filter_one": "فلتر",
"filter_two": "فلاتر",
"filter_few": "فلاتر",
"filter_many": "فلاتر",
"filter_other": "فلاتر",
"filters": "فلاتر",
"forceRestartRequired": "اعد التشغيل لتطبيق التعديلات... اغلق التنبية لإعادة التشغيل",
"forward": "امام",
"gap": "فجوة",
"home": "الرئيسية",
"increase": "زيادة",
"left": "يسار",
"limit": "حد",
"manage": "إدارة",
"maximize": "تكبير",
"menu": "القائمة",
"minimize": "تصغير",
"modified": "تم تعديله",
"mbid": "معرف MusicBrainz",
"name": "إسم",
"no": "لا",
"none": "لا شي",
"noResultsFromQuery": "لا توجد نتائج",
"note": "ملاحظة",
"ok": "نعم",
"owner": "المالك",
"path": "المسار",
"playerMustBePaused": "يجب إيقاف المشغل",
"preview": "معاينة",
"previousSong": "$t(entity.track_one) السابق",
"quit": "خروج",
"random": "عشوائي",
"rating": "التقييم",
"refresh": "تحديث",
"reload": "تحديث",
"reset": "إعادة تعيين",
"resetToDefault": "إعادة تعيين الى الافتراضي",
"restartRequired": "يجب إعادة التشغيل",
"right": "يمين",
"sampleRate": "معدل العينة (sample rate)",
"save": "حفظ",
"saveAndReplace": "حفظ واستبدال",
"saveAs": "حفظ بإسم",
"search": "بحث",
"setting": "إعداد",
"share": "نشر",
"size": "حجم",
"sortOrder": "الترتيب",
"tags": "العلامات",
"title": "العنوان",
"trackNumber": "رقم المسار",
"trackGain": "مستوى صوت المسار",
"trackPeak": "اعلى مستوى للمسار",
"translation": "الترجمة",
"unknown": "غير معروف",
"version": "الإصدار",
"year": "السنة",
"yes": "نعم"
},
"entity": {
"album_zero": "الالبوم",
"album_one": "الالبوم",
"album_two": "الالبومين",
"album_few": "الالبومات",
"album_many": "الالبومات",
"album_other": "الالبومات",
"albumArtist_zero": "فنان الالبوم",
"albumArtist_one": "فنان الالبوم",
"albumArtist_two": "فنان الالبومين",
"albumArtist_few": "فنان الالبومات",
"albumArtist_many": "فنان الالبومات",
"albumArtist_other": "فنان الالبومات"
}
}
+848
View File
@@ -0,0 +1,848 @@
{
"page": {
"sidebar": {
"myLibrary": "La meva llibreria",
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"artists": "$t(entity.artist_other)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"tracks": "$t(entity.track_other)",
"nowPlaying": "ara sona",
"shared": "$t(entity.playlist_other) compartida"
},
"albumArtistDetail": {
"relatedArtists": "$t(entity.artist_other) similars",
"viewAllTracks": "veure totes les $t(entity.track_other)",
"about": "Sobre {{artist}}",
"appearsOn": "apareix a",
"recentReleases": "Llançaments recents",
"viewDiscography": "Mosta la discografia",
"topSongs": "millors cançons",
"topSongsFrom": "les millors cançons de {{title}}",
"viewAll": "mostra-ho tot"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "més d'aquest $t(entity.artist_one)",
"moreFromGeneric": "més de {{item}}",
"released": "publicat"
},
"albumList": {
"title": "$t(entity.album_other)",
"artistAlbums": "àlbums de {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)"
},
"appMenu": {
"quit": "$t(common.quit)",
"settings": "$t(common.setting_other)",
"goBack": "torna enrere",
"goForward": "avança",
"collapseSidebar": "replega la barra lateral",
"expandSidebar": "expandeix la barra lateral",
"manageServers": "gestionar servidors",
"selectServer": "seleccionar servidor",
"version": "versió {{version}}",
"openBrowserDevtools": "obre les eines de desenvolupament del navegador",
"privateModeOff": "desactiva el mode privat",
"privateModeOn": "activa el mode privat"
},
"contextMenu": {
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"play": "$t(player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"download": "descarregar",
"showDetails": "informació",
"numberSelected": "{{count}} seleccionat",
"shareItem": "comparteix l'element",
"goToAlbumArtist": "Ves a $t(entity.albumArtist_one)",
"goToAlbum": "ves a $t(entity.album_one)"
},
"genreList": {
"title": "$t(entity.genre_other)",
"showAlbums": "mostra $t(entity.album_other) $t(entity.genre_one)",
"showTracks": "mostra $t(entity.track_other) $t(entity.genre_one)"
},
"home": {
"title": "$t(common.home)",
"explore": "explora la teva biblioteca",
"newlyAdded": "afegits recentment",
"mostPlayed": "els més reproduïts",
"recentlyPlayed": "reproduït recentment",
"recentlyReleased": "estrenat fa poc"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"trackList": {
"title": "$t(entity.track_other)",
"artistTracks": "pistes de {{artist}}",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)"
},
"manageServers": {
"username": "nom d'usuari",
"title": "gestionar servidors",
"serverDetails": "detalls del servidor",
"editServerDetailsTooltip": "editar els detalls del servidor",
"removeServer": "eliminar el servidor",
"url": "URL"
},
"fullscreenPlayer": {
"config": {
"opacity": "opacitat",
"synchronized": "sincronitzat",
"unsynchronized": "no sincronitzat",
"useImageAspectRatio": "utilitza la relació d'aspecte de la imatge",
"dynamicBackground": "fons dinàmic",
"dynamicIsImage": "activar la imatge de fons",
"followCurrentLyric": "seguir la lletra actual",
"lyricAlignment": "alineació de la lletra",
"lyricSize": "tamany de la lletra",
"dynamicImageBlur": "mida del desenfocament de la imatge",
"lyricOffset": "demora de la lletra (ms)",
"showLyricMatch": "mosta coincidències de lletres",
"showLyricProvider": "mostra el proveïdor de la lletra",
"lyricGap": "espera entre lletres"
},
"lyrics": "lletres",
"visualizer": "visualitzador",
"noLyrics": "no s'ha trobat cap lletra",
"related": "relacionat",
"upNext": "a continuació"
},
"setting": {
"advanced": "avançat",
"generalTab": "general",
"hotkeysTab": "tecles d'accés ràpid",
"playbackTab": "reproducció",
"windowTab": "finestra"
},
"globalSearch": {
"commands": {
"goToPage": "anar a la pàgina",
"searchFor": "cerca {{query}}",
"serverCommands": "ordres del servidor"
},
"title": "ordres"
},
"itemDetail": {
"copyPath": "copia ruta al porta-retalls",
"copiedPath": "ruta copiada correctament",
"openFile": "mostra la pista al gestor d'arxius"
},
"playlist": {
"reorder": "el reordenament només s'activa quan s'ordena per id"
}
},
"common": {
"home": "inici",
"year": "any",
"add": "afegir",
"ascending": "ascendent",
"biography": "biografia",
"bitrate": "taxa de bits",
"bpm": "bpm",
"cancel": "cancel·lar",
"center": "centrar",
"close": "tancar",
"codec": "còdec",
"configure": "configurar",
"confirm": "confirmar",
"create": "crear",
"decrease": "disminuir",
"delete": "eliminar",
"descending": "descendent",
"description": "descripció",
"disable": "desactivar",
"disc": "disc",
"dismiss": "descartar",
"duration": "duració",
"edit": "editar",
"enable": "activar",
"expand": "expandir",
"filters": "filtres",
"increase": "incrementar",
"left": "esquerra",
"maximize": "maximitzar",
"menu": "menú",
"minimize": "minimitzar",
"modified": "modificació",
"name": "nom",
"no": "no",
"none": "cap",
"note": "nota",
"ok": "bé",
"preview": "vista prèvia",
"quit": "sortir",
"random": "aleatori",
"rating": "valoració",
"reload": "torna a carregar",
"reset": "restablir",
"right": "dreta",
"save": "desar",
"search": "cercar",
"share": "compartir",
"size": "mida",
"sortOrder": "ordenar",
"tags": "etiquetes",
"title": "títol",
"translation": "traducció",
"unknown": "desconegut",
"version": "versió",
"yes": "sí",
"additionalParticipants": "participants addicionals",
"channel_one": "canal",
"channel_many": "canals",
"channel_other": "canals",
"filter_one": "filtre",
"filter_many": "filtres",
"filter_other": "filtres",
"saveAs": "desar com",
"action_one": "acció",
"action_many": "accions",
"action_other": "accions",
"newVersion": "s'ha instal·lat una nova versió ({{version}})",
"viewReleaseNotes": "veure les notes de la versió",
"currentSong": "$t(entity.track_one) actual",
"limit": "límit",
"previousSong": "$t(entity.track_one) anterior",
"trackNumber": "pista",
"albumGain": "guany de l'àlbum",
"albumPeak": "pic de l'àlbum",
"areYouSure": "estàs segur?",
"backward": "enrere",
"clear": "neteja",
"collapse": "col·lapsa",
"comingSoon": "aviat disponible…",
"favorite": "preferit",
"forceRestartRequired": "reinicia per aplicar els canvis… tanca la notificació per reiniciar",
"owner": "propietari",
"refresh": "actualitzar",
"resetToDefault": "restablir els valors predeterminats",
"saveAndReplace": "desar i substituir",
"bitDepth": "profunditat de bits",
"forward": "endavant",
"manage": "gestiona",
"mbid": "ID de MusicBrainz",
"noResultsFromQuery": "la petició no ha produït resultats",
"path": "ruta",
"playerMustBePaused": "cal pausar el reproductor",
"restartRequired": "cal reiniciar",
"sampleRate": "freqüència de mostreig",
"setting": "configuració",
"trackGain": "guany de pista",
"trackPeak": "pic de pista",
"gap": "espera"
},
"entity": {
"album_one": "àlbum",
"album_many": "àlbums",
"album_other": "àlbums",
"albumWithCount_one": "{{count}} àlbum",
"albumWithCount_many": "{{count}} àlbums",
"albumWithCount_other": "{{count}} àlbums",
"albumArtist_one": "artista de l'àlbum",
"albumArtist_many": "artistes de l'àlbum",
"albumArtist_other": "artistes de l'àlbum",
"albumArtistCount_one": "{{count}} artista de l'àlbum",
"albumArtistCount_many": "{{count}} artistes de l'àlbum",
"albumArtistCount_other": "{{count}} artistes de l'àlbum",
"artist_one": "artista",
"artist_many": "artistes",
"artist_other": "artistes",
"artistWithCount_one": "{{count}} artista",
"artistWithCount_many": "{{count}} artistes",
"artistWithCount_other": "{{count}} artistes",
"playlist_one": "llista de reproducció",
"playlist_many": "llistes de reproducció",
"playlist_other": "llistes de reproducció",
"playlistWithCount_one": "{{count}} llista de reproducció",
"playlistWithCount_many": "{{count}} llistes de reproducció",
"playlistWithCount_other": "{{count}} llistes de reproducció",
"smartPlaylist": "$t(entity.playlist_one) intel·ligent",
"play_one": "{{count}} reproducció",
"play_many": "{{count}} reproduccions",
"play_other": "{{count}} reproduccions",
"folderWithCount_one": "{{count}} carpeta",
"folderWithCount_many": "{{count}} carpetes",
"folderWithCount_other": "{{count}} carpetes",
"genreWithCount_one": "{{count}} gènere",
"genreWithCount_many": "{{count}} gèneres",
"genreWithCount_other": "{{count}} gèneres",
"track_one": "pista",
"track_many": "pistes",
"track_other": "pistes",
"trackWithCount_one": "{{count}} pista",
"trackWithCount_many": "{{count}} pistes",
"trackWithCount_other": "{{count}} pistes",
"folder_one": "carpeta",
"folder_many": "carpetes",
"folder_other": "carpetes",
"genre_one": "gènere",
"genre_many": "gèneres",
"genre_other": "gèneres",
"song_one": "cançó",
"song_many": "cançons",
"song_other": "cançons",
"favorite_one": "preferit",
"favorite_many": "preferits",
"favorite_other": "preferits"
},
"form": {
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"title": "afegir a una $t(entity.playlist_one)",
"input_skipDuplicates": "salta't els duplicats",
"success": "s'ha afegit $t(entity.trackWithCount, {\"count\": {{message}} }) a $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"success": "$t(entity.playlist_one) s'ha creat amb èxit",
"title": "crear una $t(entity.playlist_one)",
"input_public": "públic"
},
"deletePlaylist": {
"success": "$t(entity.playlist_one) s'ha eliminat amb èxit",
"title": "elimina la $t(entity.playlist_one)",
"input_confirm": "escriviu el nom de la $t(entity.playlist_one) per confirmar"
},
"editPlaylist": {
"success": "$t(entity.playlist_one) s'ha actualitzat amb èxit",
"title": "editar la $t(entity.playlist_one)",
"publicJellyfinNote": "Per algun motiu, Jellyfin no exposa si una llista de reproducció és pública o no. Si voleu que es mantingui pública, seleccioneu la següent entrada"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "cerca de lletres"
},
"addServer": {
"input_password": "contrasenya",
"input_username": "nom d'usuari",
"error_savePassword": "hi ha hagut un error en intentar desar la contrasenya",
"ignoreCors": "ignora el cors ($t(common.restartRequired))",
"ignoreSsl": "ignora l'ssl ($t(common.restartRequired))",
"input_legacyAuthentication": "activa l'autenticació antiga",
"input_name": "nom del servidor",
"input_savePassword": "desa la contrasenya",
"input_url": "url",
"success": "servidor afegit correctament",
"title": "afegeix un servidor",
"input_preferInstantMix": "prefereix el mix instantani",
"input_preferInstantMixDescription": "utilitza només el mix instantani per obtenir cançons similars. útil si teniu complements que modifiquin aquest comportament"
},
"shareItem": {
"description": "descripció",
"allowDownloading": "permetre descàrrega",
"setExpiration": "estableix expiració",
"success": "s'ha copiat l'enllaç de compartició al porta-retalls (o feu clic aquí per obrir-lo)",
"expireInvalid": "la data d'expiració ha de ser al futur",
"createFailed": "no s'ha pogut crear el recurs compartit (està habilitat, l'ús compartit?)"
},
"updateServer": {
"success": "s'ha actualitzat el servidor amb èxit",
"title": "actualitzar el servidor"
},
"queryEditor": {
"title": "editor de consultes",
"input_optionMatchAll": "coincidències totals",
"input_optionMatchAny": "coincidències parcials"
},
"privateMode": {
"enabled": "mode privat actiu; l'estat de reproducció ara està ocult d'integracions externes",
"disabled": "mode privat inactiu; l'estat de reproducció ara és visible per les integracions externes",
"title": "mode privat"
}
},
"action": {
"addToFavorites": "afegir als $t(entity.favorite_other)",
"addToPlaylist": "afegir a $t(entity.playlist_one)",
"createPlaylist": "crear $t(entity.playlist_one)",
"deletePlaylist": "elimina la $t(entity.playlist_one)",
"editPlaylist": "edita la $t(entity.playlist_one)",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "elimina dels $t(entity.favorite_other)",
"removeFromPlaylist": "elimina de $t(entity.playlist_one)",
"clearQueue": "buidar la cua",
"removeFromQueue": "treure de la cua",
"goToPage": "anar a la pàgina",
"openIn": {
"lastfm": "Obrir a Last.fm",
"musicbrainz": "Obrir a MusicBrainz"
},
"deselectAll": "deselecciona-ho tot",
"viewPlaylists": "veure$t(entity.playlist_other)",
"moveToNext": "passar al següent",
"moveToBottom": "anar al final",
"moveToTop": "anar al principi",
"setRating": "Qualifica",
"toggleSmartPlaylistEditor": "canvia l'editor $t(entity.smartPlaylist)"
},
"setting": {
"language_description": "estableix l'idioma de l'aplicació ($t(common.restartRequired))",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"playButtonBehavior_optionPlay": "$t(player.play)",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"font": "tipus de lletra",
"fontType": "selecció de tipus de lletra",
"fontType_optionBuiltIn": "tipus de lletra integrats",
"fontType_optionCustom": "tipus de lletra personalitzats",
"fontType_optionSystem": "tipus de lletra del sistema",
"disableAutomaticUpdates": "desactivar les actualitzacions automàtiques",
"disableLibraryUpdateOnStartup": "desactiva la comprovació de noves versions a l'inici",
"homeConfiguration": "configuració de la pàgina d'inici",
"sidebarConfiguration": "configuració de la barra lateral",
"contextMenu": "configuració del menú contextual (clic amb el botó dret)",
"hotkey_playbackNext": "pista següent",
"hotkey_playbackPrevious": "pista anterior",
"sidePlayQueueStyle_optionAttached": "unida",
"sidePlayQueueStyle_optionDetached": "separada",
"audioDevice": "dispositiu d'àudio",
"audioDevice_description": "seleccioneu el dispositiu d'àudio que voleu utilitzar per a la reproducció (només pel reproductor web)",
"audioPlayer": "reproductor d'àudio",
"audioPlayer_description": "seleccioneu el reproductor d'àudio que voleu utilitzar per a la reproducció",
"sidebarConfiguration_description": "selecciona els elements i l'ordre en què apareixen a la barra lateral",
"sidebarPlaylistList_description": "mostra o amaga les llistes de reproducció a la barra lateral",
"accentColor": "color de ressaltat",
"accentColor_description": "estableix el color de ressaltat de l'aplicació",
"useSystemTheme_description": "seguir la preferència de d'aspecte clar o fosc definida pel sistema",
"themeDark": "aspecte fosc",
"theme": "aspecte",
"themeLight": "aspecte clar",
"useSystemTheme": "utilitzar l'aspecte del sistema",
"discordUpdateInterval_description": "el temps en segons entre cada actualització (mínim 15 segons)",
"enableRemote": "activar el servidor de control remot",
"enableRemote_description": "el servidor de control remot permet que altres dispositius controlin l'aplicació",
"notify": "activa les notificacions de cançons",
"transcode": "activa la transcodificació",
"transcode_description": "permet la transcodificació a diferents formats",
"albumBackground": "imatge de fons de l'àlbum",
"albumBackground_description": "afegeix una imatge de fons per les pàgines d'àlbum amb caràtula",
"albumBackgroundBlur": "mida del desenfocament de la imatge de fons de l'àlbum",
"albumBackgroundBlur_description": "ajusa la quantitat de desenfocament que s'aplica a la imatge de fons de l'àlbum",
"applicationHotkeys": "tecles de drecera de l'aplicació",
"applicationHotkeys_description": "configura les tecles de drecera de l'aplicació. marca la casella per configurar-les com a derecres globals (només per ordinador)",
"artistConfiguration": "configuració de la pàgina de l'artista de l'àlbum",
"artistConfiguration_description": "configura quins elements es mostren i el seu ordre de la pàgina de l'artista de l'àlbum",
"audioExclusiveMode": "mode d'àudio exclusiu",
"audioExclusiveMode_description": "activa el mode d'àudio exclusiu. En aquest mode, el sistema normalment estarà bloquejat i només mpv podrà emetre àudio",
"buttonSize": "mida dels botons de la barra de reproducció",
"buttonSize_description": "la mida dels botons de la barra de reproducció",
"clearCache": "neteja la memòria del navegador",
"clearCache_description": "una \"neteja profunda\" del feishin. a més de netejar la memòria del feishin, buida la memòria del navegador (com les imatges desades i altres recursos). la configuració i les credencials del servidor es mantenen",
"clearQueryCache": "buida la memòria de feishin",
"clearQueryCache_description": "una neteja superficial de feishin. això refrescarà les llistes de reproducció, les metadades de les pistes i reestablirà les lletres desades. la configuració, les credencials del servidor i les imatges desades es mantindran",
"clearCacheSuccess": "memòria netejada correctament",
"contextMenu_description": "us permet amagar els elements que es mostren al menú quan fas clic dret sobre un element. els elements no seleccionats estaran amagats",
"crossfadeDuration": "duracció de la fosa encadenada",
"crossfadeDuration_description": "estableix la duració de l'efecte de fosa encadenada",
"crossfadeStyle": "estil de fosa encadenada",
"crossfadeStyle_description": "selecciona l'estil de fosa encadenada que s'utilitzarà pel reproductor d'àudio",
"customCssEnable": "activa el css personalitzat",
"customCssEnable_description": "permet escriure CSS personalitzat.",
"customCssNotice": "Atenció: tot i que hi ha un filtre (no es permet ni url() ni content:), l'ús de CSS personalitzat pot presentar riscs si canvieu la interfície.",
"customCss": "css personalitzat",
"customCss_description": "contingut del css personalitzat. Nota: la propietat \"content\" i els urls remots no es permeten. A sota hi teniu una previsualització. Els camps addicionals que no establiu hi apareixin pel filtre.",
"customFontPath": "ruta de font personalitzada",
"customFontPath_description": "estableix la ruta a una font personalitzada per utilitzar-la a l'aplicació",
"discordApplicationId": "id d'aplicació de {{discord}}",
"discordApplicationId_description": "l'id d'aplicació per l'estat d'activitat de {{discord}} (per defecte, {{defaultId}})",
"discordPausedStatus": "mosta l'estat d'activitat quan està en pausa",
"discordPausedStatus_description": "si està activat, l'estat es mostrarà quan el reproductor estigui pausat",
"discordIdleStatus": "mosta l'estat d'activitat en inactivitat",
"discordIdleStatus_description": "si està activat, s'actualitzarà l'estat mentre el reproductor estigui inactiu",
"discordListening": "mosta l'estat com escoltant",
"discordListening_description": "mosta l'estat com escoltant en comptes de jugant",
"discordRichPresence": "estat d'activitat de {{discord}}",
"discordRichPresence_description": "activa l'estat de reproducció a l'activitat de {{discord}}. Les tecles d'imatge són: {{icon}}, {{playing}} i {{paused}}",
"discordServeImage": "serveix imatges de {{discord}} des del servidor",
"discordServeImage_description": "comparteix la caràtula per l'estat d'activitat de {{discord}} des del servidor; només disponible per jellyfin i navidrome. {{discord}} fa ser un bot per trobar les imatges, de manera que el vostre servidor ha de ser visible per l'internet públic.",
"discordUpdateInterval": "interval d'actualització de l'estat d'activitat de {{discord}}",
"doubleClickBehavior": "posa en cua totes les pistes cercades en fer doble clic",
"doubleClickBehavior_description": "si està actiu, totes les pistes coincidents en una cerca de pistes es posaran a la cua. altrament, només la que seleccioneu s'afegirà a la cua",
"externalLinks": "mostra enllaços externs",
"externalLinks_description": "permet mostrar enllaços externs (Last.fm, MusicBrainz) a les pàgines d'artista/àlbum",
"exitToTray": "surt a la safata",
"exitToTray_description": "en sortir de l'aplicació, minimitza-la a la safa del sistema",
"floatingQueueArea": "mostra la zona flotant de la cua",
"floatingQueueArea_description": "mostra una icona flotant al costat dret de la pantalla per veure la cua de reproducció",
"followLyric": "segueix la lletra actual",
"followLyric_description": "desplaça la lletra a la posició de reproducció actual",
"preferLocalLyrics": "prefereix les lletres locals",
"preferLocalLyrics_description": "prefereix les lletres locals per sobre de les remotes, si estan disponibles",
"font_description": "estableix la font utilitzada a l'aplicació",
"fontType_description": "\"font incorporada\" selecciona una de les fonts proporcionades per Feishin. \"font del sistema\" us permet seleccionar qualsevol font proporcionada pel sistema operatiu. \"personalitzada\" us permet proporcionar la vostra pròpia font",
"gaplessAudio": "àudio sense pauses",
"gaplessAudio_description": "estableix la configuració d'àudio sense pauses per mpv",
"gaplessAudio_optionWeak": "feble (recomanat)",
"genreBehavior": "comportament predeterminat per les pàgines de gènere",
"genreBehavior_description": "determina si clicar sobre un gènere obre per defecte la llista de pistes o d'àlbums",
"globalMediaHotkeys": "tecles de drecera globals",
"globalMediaHotkeys_description": "activa o desactiva l'ús de les tecles multimèdia del sistema per controlar la reproducció",
"homeConfiguration_description": "configura quins objectes es mostren, i en quin ordre, a la pàgina d'inici",
"homeFeature": "carrusel de destacats d'inici",
"homeFeature_description": "controla si es mostra el gran carrusel d'elements destacats a la pàgina d'inici",
"hotkey_browserBack": "anar enrere",
"hotkey_browserForward": "anar endavant",
"hotkey_favoriteCurrentSong": "marca $t(common.currentSong) com a preferida",
"hotkey_favoritePreviousSong": "marca $t(common.previousSong) com a preferida",
"hotkey_globalSearch": "cerca global",
"hotkey_localSearch": "cerca a la pàgina",
"hotkey_playbackPause": "pausa",
"hotkey_playbackPlay": "reprodueix",
"hotkey_playbackPlayPause": "reprodueix / pausa",
"hotkey_playbackStop": "atura",
"hotkey_rate0": "neteja la qualificació",
"hotkey_rate1": "qualifica amb 1 estrella",
"hotkey_rate2": "qualifica amb 2 estrelles",
"hotkey_rate3": "qualifica amb 3 estrelles",
"hotkey_rate4": "qualifica amb 4 estrelles",
"hotkey_rate5": "qualifica amb 5 estrelles",
"hotkey_skipBackward": "salta enrere",
"hotkey_skipForward": "salta endavant",
"hotkey_toggleCurrentSongFavorite": "canvia si $t(common.currentSong) és preferida",
"hotkey_toggleFullScreenPlayer": "activa o desactiva el reproductor a pantalla completa",
"hotkey_togglePreviousSongFavorite": "canvia si $t(common.previousSong) és preferida",
"hotkey_toggleQueue": "activa o desactiva la cua",
"hotkey_toggleRepeat": "activa o desactiva la repetició",
"hotkey_toggleShuffle": "activa o desactiva la reproducció a l'atzar",
"hotkey_unfavoriteCurrentSong": "elimina $t(common.currentSong) dels preferits",
"hotkey_unfavoritePreviousSong": "elimina $t(common.previousSong) dels preferits",
"hotkey_volumeDown": "redueix el volum",
"hotkey_volumeMute": "silencia el volum",
"hotkey_volumeUp": "augmenta el volum",
"hotkey_zoomIn": "amplia",
"hotkey_zoomOut": "redueix",
"imageAspectRatio": "utilitza la relació d'aspecte predeterminada de la caràtula",
"imageAspectRatio_description": "si està activat, la caràtula es mostrarà amb la relació d'aspecte predeterminada. per caràtules que no siguin 1:1, l'espai restant estarà buit",
"language": "llengua",
"lastfm": "mostra els enllaços last.fm",
"lastfm_description": "mosta enllaços a last.fm a les pàgines d'artista/àlbum",
"lastfmApiKey": "clau d'API per {{lastfm}}",
"lastfmApiKey_description": "la clau d'API per {{lastfm}}. necessària per la caràtula",
"lyricFetch": "extreu la lletra d'internet",
"lyricFetch_description": "extreu la lletra de diverses fonts d'internet",
"lyricFetchProvider": "proveïdors de lletres",
"lyricFetchProvider_description": "selecciona els proveïdors de lletres. l'ordre en què apareixen és l'ordre en què es consultaran",
"lyricOffset": "desfasament de la lletra (ms)",
"lyricOffset_description": "desplaça la lletra els mil·lisegons especificats",
"notify_description": "mostra notificacions en canvia la cançó actual",
"minimizeToTray": "minimitza a la safata",
"minimizeToTray_description": "minimitza l'aplicació a la safata del sistema",
"minimumScrobblePercentage": "duració mínima de l'scrobble (percentatge)",
"minimumScrobblePercentage_description": "el percentatge mínim de la cançó que cal reproduir abans d'activar l'scrobble",
"minimumScrobbleSeconds": "scrobble mínim (segons)",
"minimumScrobbleSeconds_description": "la duració mínima en segons durant la qual cal reproduir la cançó abans d'activar l'scrobble",
"mpvExecutablePath": "ruta de l'executable de l'mpv",
"mpvExecutablePath_description": "estableix la ruta de l'executable de l'mpv. si el deixeu buit, s'utilitzarà la ruta predeterminada",
"mpvExtraParameters": "paràmetres de l'mpv",
"mpvExtraParameters_help": "un per línia",
"musicbrainz": "mostra els enllaços de musicbrainz",
"musicbrainz_description": "mostra enllaços a les pàgines d'artista/àlbum a musicbrainz si hi ha mbid",
"neteaseTranslation": "activeu les traduccions NetEase",
"neteaseTranslation_description": "Si ho activeu, cerca i mostra lletres traduïdes de NetEase si estan disponibles.",
"passwordStore": "contrasenyes/emmagatzematge secret",
"passwordStore_description": "quina contrasenya/emmagatzematge secret s'utilitza. canvieu-ho si teniu problemes per desar contrasenyes.",
"playbackStyle": "estil de reproducció",
"playbackStyle_description": "selecciona l'estil de reproducció a utilitzar pel reproductor d'àudio",
"playbackStyle_optionCrossFade": "fosa encadenada",
"playbackStyle_optionNormal": "normal",
"playButtonBehavior": "comportament del botó de reproducció",
"playButtonBehavior_description": "estableix el comportament predeterminat del botó de reproducció quan s'afegeixen cançons a la cua",
"playerAlbumArtResolution": "resolució de la caràtula de l'àlbum al reproductor",
"playerAlbumArtResolution_description": "la resolució de la previsualització gran de la caràtula al reproductor. si és més alta, serà més nítida, però es carregarà més lent. el valor predeterminat 0 vol dir automàtic",
"playerbarOpenDrawer": "activa el reproductor en pantalla completa",
"playerbarOpenDrawer_description": "permet fer clic a la barra de reproducció per obrir el reproductor de pantalla completa",
"remotePassword": "contrasenya del servidor de control remot",
"remotePassword_description": "estableix la contrasenya pel servidor de control remot. Aquestes credencials es transfereixen de forma no segura per defecte, de manera que hauríeu d'utilitzar una contrasenya única no relacionada amb res més",
"remotePort": "port del servidor de control remot",
"remotePort_description": "estableix el port pel servidor de control remot",
"remoteUsername": "nom d'usuari pel servidor de control remot",
"remoteUsername_description": "estableix el nom d'usuari pel servidor de control remot. si tant el nom d'usuari com la contrasenya són buits, l'autenticació estarà desactivada",
"replayGainPreamp_description": "ajusta el guany del preamplificador aplicat als valors de {{ReplayGain}}",
"sampleRate": "ràtio de mostratge",
"sampleRate_description": "selecciona el ràtio de mostratge de sortida que s'ha d'utilitzar si la freqüència de mostratge seleccionada és diferent a la del mitjà actual. un valor inferior a 8000 utilitzarà la freqüència predeterminada",
"savePlayQueue": "desa la cua de reproducció",
"savePlayQueue_description": "desa la cua de reproducció quan l'aplicació es tanca i la restaura quan s'obre",
"scrobble": "scrobble",
"scrobble_description": "fa scrobble de les reproduccions al vostre servidor multimèdia",
"showSkipButton": "mostra els botons de saltar",
"showSkipButton_description": "mostra o amaga els botons de saltar a la barra de reproducció",
"showSkipButtons": "mostra els botons de saltar",
"showSkipButtons_description": "mostra o amaga els botons de saltar a la barra de reproducció",
"sidebarCollapsedNavigation": "navegació de la barra lateral (plegada)",
"sidebarCollapsedNavigation_description": "mostra o amaga la navegació a la barra lateral plegada",
"sidebarPlaylistList": "llista de reproducció lateral",
"sidePlayQueueStyle": "estil de la cua de reproducció lateral",
"sidePlayQueueStyle_description": "estableix l'estil de la cua de reproducció lateral",
"skipDuration": "interval de salt",
"skipDuration_description": "estableix l'interval de temps que se saltarà en fer servir els botons de saltar a la barra de reproducció",
"skipPlaylistPage": "salta la pàgina de la llista de reproducció",
"skipPlaylistPage_description": "en navegar a una llista de reproducció, obre la pàgina de cançons de la llista de reproducció en comptes de la pàgina predeterminada",
"startMinimized": "obre minimitzada",
"startMinimized_description": "obre l'aplicació a la safata del sistema",
"theme_description": "estableix el tema visual per l'aplicació",
"themeDark_description": "estableix el tema fosc per l'aplicació",
"themeLight_description": "estableix el tema clar per l'aplicació",
"transcodeNote": "tindrà efecte després d'1 (web) o 2 (mpv) cançons",
"transcodeBitrate": "taxa de bits per transcodificar",
"transcodeBitrate_description": "selecciona la taxa de bits per transcodificar. 0 significa deixar que el servidor triï",
"transcodeFormat": "format per transcodificar",
"transcodeFormat_description": "selecciona el format per transcodificar. deixeu-ho buit per deixar que el servidor decideixi",
"translationApiProvider": "proveïdor d'api de traducció",
"translationApiProvider_description": "proveïdor de l'api de traducció",
"translationApiKey": "clau de l'api de traducció",
"translationTargetLanguage": "llengua meta de traducció",
"translationTargetLanguage_description": "llengua meta per la traducció",
"trayEnabled": "mostra a la safata",
"trayEnabled_description": "mostra/oculta la icona/menú de la safata. si està desactivat, també desactiva la funcionalitat de minimitzar/sortir a la safata",
"volumeWheelStep": "increment de volum de la roda",
"volumeWheelStep_description": "la quantitat de volum a canviar quan utilitzeu la roda del ratolí sobre el controlador de volum",
"volumeWidth": "amplada del controlador de volum",
"volumeWidth_description": "l'amplada del controlador de volum",
"webAudio": "utilitza l'àudio web",
"webAudio_description": "utilitza l'àudio web. això habilita funcions avançades com Replaygain. desactiveu-ho si teniu una experiència diferent",
"replayGainClipping": "saturació de {{ReplayGain}}",
"replayGainClipping_description": "rebaixa automàticament el guany per evitar la saturació causada pel {{ReplayGain}}",
"replayGainFallback": "alternativa per {{ReplayGain}}",
"replayGainFallback_description": "guany en db que s'ha d'aplicar si el fitxer no té etiquetes de {{ReplayGain}}",
"replayGainMode": "mode de {{ReplayGain}}",
"replayGainMode_description": "ajuda el volum del guany segons els vlors de {{ReplayGain}} desats a les metadades del fitxer",
"replayGainPreamp": "preamplificador de {{ReplayGain}} (dB)",
"translationApiKey_description": "clau api per la traducció (només per serveis globals)",
"preservePitch": "mantén el to",
"preservePitch_description": "manté el to quan s'altera la velocitat de reproducció",
"windowBarStyle": "estil de la barra de la finestra",
"windowBarStyle_description": "selecciona l'estil de la barra de la finestra",
"zoom": "percentatge de zoom",
"zoom_description": "estableix el percentatge de zoom de l'aplicació",
"discordDisplayType": "tipus de pantalla d'activitat de {{discord}}",
"discordDisplayType_description": "canvia què escolteu al vostre estat",
"discordDisplayType_songname": "nom de la cançó",
"discordDisplayType_artistname": "nom de l'artista",
"hotkey_navigateHome": "ves a l'inici",
"preventSleepOnPlayback": "evitar entrar en repòs durant la reproducció",
"preventSleepOnPlayback_description": "evita que la pantalla s'adormi mentre la música es reprodueix",
"discordLinkType": "enllaços d'estat de {{discord}}",
"discordLinkType_description": "afegeix enllaços externs a {{lastfm}} o {{musicbrainz}} als camps de cançó i artista a l'estat d'activitat de {{discord}}. {{musicbrainz}} és el més precís, però requereix etiquetes i no proporciona enllaços d'artista, mentre que {{lastfm}} hauria de propocionar un enllaç sempre. no fa sol·licituds de xarxa addicionals",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} amb {{lastfm}} com a alternativa",
"artistBackground": "imatge de fons de l'artista",
"artistBackground_description": "afegeix una imatge de fons per les pàgines d'artista amb l'art de l'artista",
"artistBackgroundBlur": "mida del desenfocament de la imatge de fons de l'artista",
"artistBackgroundBlur_description": "ajusta la quantitat de desenfocament aplicat a la imatge de fons de l'artista",
"releaseChannel_optionLatest": "estable",
"releaseChannel_optionBeta": "beta",
"releaseChannel": "canal de versions",
"releaseChannel_description": "tria entre versions estables i versions beta per les actualitzacions automàtiques",
"mediaSession": "activa Media Session",
"mediaSession_description": "Activa la integració amb Windows Media Session per mostrar els controls multimèdia i les metadades a l'indicador de volum del sistema i la pantalla de bloqueig (només per Windows)"
},
"table": {
"column": {
"albumCount": "$t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"genre": "$t(entity.genre_one)",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"releaseYear": "any",
"playCount": "reproduccions",
"releaseDate": "data de llançament",
"album": "àlbum",
"albumArtist": "artista de l'àlbum",
"biography": "biografia",
"bitrate": "taxa de bits",
"bpm": "bpm",
"dateAdded": "data d'addició",
"discNumber": "disc",
"trackNumber": "pista",
"comment": "comentari",
"favorite": "preferit",
"lastPlayed": "última reproducció",
"path": "ruta",
"rating": "qualificació",
"title": "títol"
},
"config": {
"general": {
"gap": "$t(common.gap)",
"size": "$t(common.size)",
"autoFitColumns": "ajusta les columnes automàticament",
"followCurrentSong": "segueix la cançó actual",
"displayType": "tipus de visualització",
"itemGap": "espai entre elements (px)",
"itemSize": "mida dels elements (px)",
"tableColumns": "columnes de la taula"
},
"label": {
"actions": "$t(common.action_other)",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"rating": "$t(common.rating)",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"year": "$t(common.year)",
"playCount": "compte de reproduccions",
"releaseDate": "data de llançament",
"dateAdded": "data d'addició",
"trackNumber": "número de pista",
"discNumber": "número de disc",
"lastPlayed": "última reproducció",
"rowIndex": "índex de files",
"titleCombined": "$t(common.title) (combinat)"
},
"view": {
"table": "taula",
"card": "targeta",
"grid": "quadrícula",
"list": "llista",
"poster": "pòster"
}
}
},
"filter": {
"fromYear": "des de l'any",
"releaseYear": "any de llançament",
"toYear": "fins a l'any",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "biografia",
"bitrate": "taxa de bits",
"bpm": "bpm",
"channels": "$t(common.channel_other)",
"comment": "comentari",
"disc": "disc",
"duration": "durada",
"genre": "$t(entity.genre_one)",
"id": "identificador",
"name": "nom",
"note": "nota",
"owner": "$t(common.owner)",
"random": "aleatori",
"rating": "valoració",
"search": "cercar",
"title": "títol",
"playCount": "compte de reproduccions",
"releaseDate": "data de llançament",
"mostPlayed": "els més reproduïts",
"dateAdded": "data d'addició",
"trackNumber": "pista",
"communityRating": "valoració de la comunitat",
"criticRating": "valoració dels crítics",
"recentlyAdded": "afegit recentment",
"recentlyPlayed": "reproduït recentment",
"recentlyUpdated": "actualitzat recentment",
"albumCount": "nombre de $t(entity.album_other)",
"favorited": "preferits",
"isCompilation": "és una compilació",
"isFavorited": "és un preferit",
"isPublic": "és públic",
"isRated": "està qualificat",
"isRecentlyPlayed": "s'ha reproduït fa poc",
"lastPlayed": "última reproducció",
"path": "ruta",
"songCount": "nombre de cançons"
},
"player": {
"muted": "silenciat",
"repeat": "repetició d'una pista",
"skip": "saltar",
"stop": "parar",
"queue_clear": "buidar la cua",
"viewQueue": "veure la cua",
"playbackFetchInProgress": "carregant cançons…",
"playbackFetchNoResults": "no s'han trobat cançons",
"playbackSpeed": "velocitat de reproducció",
"playSimilarSongs": "reproduir cançons similars",
"repeat_off": "repetició desactivada",
"repeat_all": "repetició",
"shuffle": "reproducció aleatòria",
"shuffle_off": "reproducció aleatòria desactivada",
"addLast": "afegeix al final",
"addNext": "afegeix a continuació",
"favorite": "marcar com a preferida",
"mute": "silencia",
"next": "següent",
"play": "reprodueix",
"playbackFetchCancel": "està trigant bastant... tanqueu la notificació per cancel·lar",
"playRandom": "reproducció a l'atzar",
"previous": "anterior",
"queue_moveToBottom": "mou la selecció a l'inici",
"queue_moveToTop": "mou la selecció al final",
"queue_remove": "elimina la selecció",
"skip_back": "salta enrere",
"skip_forward": "salta endavant",
"toggleFullscreenPlayer": "activa el reproductor de pantalla completa",
"unfavorite": "elimina de preferits",
"pause": "pausa"
},
"error": {
"credentialsRequired": "credencials requerides",
"genericError": "s'ha produït un error",
"invalidServer": "servidor no vàlid",
"localFontAccessDenied": "accés denegat als tipus de lletra locals",
"networkError": "s'ha produït un error de xarxa",
"openError": "no s'ha pogut obrir el fitxer",
"remotePortError": "s'ha produït un error en intentar configurar el port del servidor remot",
"serverNotSelectedError": "no s'ha seleccionat cap servidor",
"sessionExpiredError": "la sessió ha caducat",
"systemFontError": "s'ha produït un error en intentar obtenir els tipus de lletra del sistema",
"remoteEnableError": "s'ha produït un error en intentar $t(common.enable) el servidor remot",
"remotePortWarning": "reiniciar el servidor per aplicar el nou port",
"serverRequired": "servidor requerit",
"apiRouteError": "no es pot encaminar la sol·licitud",
"audioDeviceFetchError": "hi ha hagut un error en obtenir els dispositius d'àudio",
"authenticationFailed": "autenticació fallida",
"badAlbum": "esteu veient aquesta pàgina perquè aquesta cançó no és part de cap àlbum. aquest problema pot passar si teniu una cançó al nivell superior de la vostra carpeta de música. jellyfin només agrupa pistes si són en una carpeta.",
"badValue": "l'opció \"{{value}}\"és invàlida. aquest valor ja no existeix",
"loginRateError": "massa intents d'inici de sessió, intenteu-ho de nou d'aquí uns segons",
"mpvRequired": "Cal l'MPV",
"notificationDenied": "s'han negat els permisos per enviar notificacions. aquesta opció no té cap efecte",
"playbackError": "hi ha hagut un error en intentar reproduir el mitjà",
"remoteDisableError": "hi ha hagut un error en intentar $t(common.disable) el servidor remot",
"endpointNotImplementedError": "el punt final {{endpoint}} no està implementat per {{serverType}}"
}
}
+33 -6
View File
@@ -4,7 +4,7 @@
"stop": "zastavit",
"repeat": "opakovat",
"queue_remove": "odebrat vybrané",
"playRandom": "přehrát náhodné",
"playRandom": "přehrát náhodně",
"skip": "přeskočit",
"previous": "předchozí",
"toggleFullscreenPlayer": "přepnout celoobrazovkový přehrávač",
@@ -273,7 +273,22 @@
"preservePitch": "zachovat výšku",
"preservePitch_description": "zachová výšku při úpravě rychlosti přehrávání",
"notify": "povolit oznámení o skladbách",
"notify_description": "zobrazit oznámení při změně aktuální skladby"
"notify_description": "zobrazit oznámení při změně aktuální skladby",
"discordDisplayType": "typ zobrazení stavu {{discord}}",
"discordDisplayType_description": "změní, co posloucháte, ve vašem stavu",
"discordDisplayType_songname": "název skladby",
"discordDisplayType_artistname": "jména umělců",
"hotkey_navigateHome": "přejít domů",
"preventSleepOnPlayback": "zabránit uspání při přehrávání",
"preventSleepOnPlayback_description": "zabránit uspání displeje během přehrávání hudby",
"discordLinkType": "odkazy ve stavu na službě {{discord}}",
"discordLinkType_description": "přidá externí odkazy na {{lastfm}} nebo {{musicbrainz}} do polí skladby a umělce ve stavu na službě {{discord}}. {{musicbrainz}} je nejpřesnější, ale vyžaduje značky a neposkytuje odkazy na umělce, zatímco {{lastfm}} by mělo vždy poskytnout odkaz. neprovádí žádné další síťové požadavky",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} se zálohou na {{lastfm}}",
"artistBackground": "obrázek umělce na pozadí",
"artistBackground_description": "přidá obrázek na pozadí u stránek umělců",
"artistBackgroundBlur": "velikost rozostření obrázku umělce na pozadí",
"artistBackgroundBlur_description": "upraví velikost rozostření použitého na obrázek umělce na pozadí"
},
"action": {
"editPlaylist": "upravit $t(entity.playlist_one)",
@@ -595,7 +610,9 @@
"openBrowserDevtools": "otevřít vývojářské nástroje",
"quit": "$t(common.quit)",
"goBack": "přejít zpět",
"goForward": "přejít vpřed"
"goForward": "přejít vpřed",
"privateModeOff": "vypnout soukromý režim",
"privateModeOn": "zapnout soukromý režim"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
@@ -619,14 +636,17 @@
"playSimilarSongs": "$t(player.playSimilarSongs)",
"download": "stáhnout",
"playShuffled": "$t(player.shuffle)",
"moveToNext": "$t(action.moveToNext)"
"moveToNext": "$t(action.moveToNext)",
"goToAlbum": "přejít na $t(entity.album_one)",
"goToAlbumArtist": "přejít na $t(entity.albumArtist_one)"
},
"home": {
"mostPlayed": "nejpřehrávanější",
"newlyAdded": "nově přidáno",
"title": "$t(common.home)",
"explore": "procházet z vaší knihovny",
"recentlyPlayed": "nedávno přehráno"
"recentlyPlayed": "nedávno přehráno",
"recentlyReleased": "nedávno vydáno"
},
"albumDetail": {
"moreFromArtist": "více od tohoto umělce",
@@ -722,7 +742,9 @@
"input_savePassword": "uložit heslo",
"ignoreSsl": "ignorovat SSL $t(common.restartRequired)",
"ignoreCors": "ignorovat CORS $t(common.restartRequired)",
"error_savePassword": "při ukládání hesla se vyskytla chyba"
"error_savePassword": "při ukládání hesla se vyskytla chyba",
"input_preferInstantMix": "preferovat instantní mix",
"input_preferInstantMixDescription": "pro získání podobných skladeb použít pouze instantní mix. užitečné, pokud máte doplňky, které upravují toto chování"
},
"addToPlaylist": {
"success": "přidáno $t(entity.trackWithCount, {\"count\": {{message}} }) do $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -756,6 +778,11 @@
"expireInvalid": "čas vypršení musí být v budoucnosti",
"setExpiration": "nastavit vypršení",
"createFailed": "nepodařilo se vytvořit sdílení (je sdílení povoleno?)"
},
"privateMode": {
"enabled": "soukromý režim povolen, stav přehrávání je nyní skryt před externími integracemi",
"disabled": "soukromý režim povolen, stav přehrávání je nyní viditelný pro externími integrace",
"title": "soukromý režim"
}
},
"entity": {
+45 -13
View File
@@ -20,7 +20,8 @@
"openIn": {
"lastfm": "In Last.fm öffnen",
"musicbrainz": "In MusicBrainz öffnen"
}
},
"moveToNext": "nach unten verschieben"
},
"common": {
"backward": "rückwärts",
@@ -115,7 +116,10 @@
"albumPeak": "Album-Spitzenpegel",
"albumGain": "Album-Pegelverstärkung",
"tags": "tags",
"viewReleaseNotes": "Release Notes anzeigen"
"viewReleaseNotes": "Release Notes anzeigen",
"newVersion": "eine neue Version wurde installiert ({{version}})",
"bitDepth": "Bittiefe",
"sampleRate": "Abtastrate"
},
"error": {
"remotePortWarning": "Starten Sie den Server neu, um den neuen Port anzuwenden",
@@ -139,7 +143,9 @@
"loginRateError": "Zu viele Anmeldeversuche, bitte versuche es in einigen Sekunden erneut",
"badAlbum": "Sie sehen diese Seite, weil dieses Lied nicht Teil eines Albums ist. Wahrscheinlich sehen Sie dieses Problem, wenn Sie einen Song in Ihrem Musikordner auf oberster Ebene haben. Jellyfin gruppiert nur Songs, wenn sie sich in einem Ordner befinden.",
"networkError": "ein Netzwerkfehler ist aufgetreten",
"openError": "datei kann nicht geöffnet werden"
"openError": "datei kann nicht geöffnet werden",
"badValue": "ungültige option \"{{value}}\". Dieser Wert existiert nicht mehr",
"notificationDenied": "Berechtigungen über Benachrichtigungen wurden verweigert. Diese Einstellung hat keinen Effekt"
},
"filter": {
"mostPlayed": "Meistgespielt",
@@ -228,7 +234,8 @@
},
"editPlaylist": {
"title": "Bearbeite $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) erfolgreich aktualisiert"
"success": "$t(entity.playlist_one) erfolgreich aktualisiert",
"publicJellyfinNote": "Jellyfin legt aus irgendwelchen Gründen nicht offen ob eine Playlist öffentlich ist oder nicht. Wenn du möchtest, dass sie öffentlich bleibt, wähle bitte diese Option aus"
},
"lyricSearch": {
"title": "Songtext Suche",
@@ -240,7 +247,13 @@
"setExpiration": "Ablaufdatum setzen",
"expireInvalid": "Ablaufdatum muss in der Zukunft liegen",
"allowDownloading": "Herunterladen zulassen",
"success": "Link in die Zwischenablage kopiert (oder hier klicken um zu öffnen)"
"success": "Link in die Zwischenablage kopiert (oder hier klicken um zu öffnen)",
"createFailed": "Fehler beim Teilen (Ist Teilen aktiviert?)"
},
"privateMode": {
"enabled": "Privatmodus aktiviert, Wiedergabe-Status wird externen Quellen nicht preisgegeben",
"disabled": "Privatmodus deaktiviert, Wiedergabe-Status wird externen Quellen preisgegeben",
"title": "Privatmodus"
}
},
"entity": {
@@ -360,12 +373,15 @@
"lyricAlignment": "Songtext-Ausrichtung",
"useImageAspectRatio": "Bildseitenverhältnis verwenden",
"lyricGap": "Songtext-Lücke",
"dynamicIsImage": "Hintergrundbild aktivieren"
"dynamicIsImage": "Hintergrundbild aktivieren",
"dynamicImageBlur": "Größe der Bildunschärfe",
"lyricOffset": "Zeitversetzung des Liedtexts (ms)"
},
"upNext": "als nächstes",
"lyrics": "Songtexte",
"related": "Ähnliche",
"noLyrics": "Keine Liedtexte gefunden"
"noLyrics": "Keine Liedtexte gefunden",
"visualizer": "visualizer"
},
"appMenu": {
"selectServer": "Server auswählen",
@@ -377,14 +393,17 @@
"goBack": "Gehe zurück",
"goForward": "Gehe vorwärts",
"settings": "$t(common.setting_other)",
"quit": "$t(common.quit)"
"quit": "$t(common.quit)",
"privateModeOff": "Privatmodus deaktivieren",
"privateModeOn": "Privatmodus aktivieren"
},
"home": {
"mostPlayed": "Meistgespielt",
"newlyAdded": "Neu hinzugefügte Veröffentlichungen",
"explore": "Entdecke deine Bibliothek",
"recentlyPlayed": "Kürzlich gespielt",
"title": "$t(common.home)"
"title": "$t(common.home)",
"recentlyReleased": "kürzlich veröffentlicht"
},
"albumDetail": {
"moreFromArtist": "Mehr von diesem $t(entity.artist_one)",
@@ -418,7 +437,10 @@
"removeFromQueue": "$t(action.removeFromQueue)",
"playShuffled": "$t(player.shuffle)",
"download": "Download",
"playSimilarSongs": "$t(player.playSimilarSongs)"
"playSimilarSongs": "$t(player.playSimilarSongs)",
"moveToNext": "$t(action.moveToNext)",
"shareItem": "teilen",
"showDetails": "Informationen"
},
"sidebar": {
"nowPlaying": "läuft gerade",
@@ -471,7 +493,8 @@
"viewAllTracks": "Alle $t(entity.track_other) ansehen",
"topSongsFrom": "Toplieder von {{title}}",
"viewAll": "Alles ansehen",
"topSongs": "Toplieder"
"topSongs": "Toplieder",
"relatedArtists": "ähnliche $t(entity.artist_other)"
},
"manageServers": {
"title": "Servers verwalten",
@@ -517,7 +540,8 @@
"unfavorite": "Aus Favoriten entfernen",
"skip_forward": "Vorspulen",
"skip": "Überspringen",
"playSimilarSongs": "Ähnliche Lieder abspielen"
"playSimilarSongs": "Ähnliche Lieder abspielen",
"viewQueue": "Warteschlange anzeigen"
},
"setting": {
"audioDevice_description": "Wählen Sie das Audiogerät aus, das für die Wiedergabe verwendet werden soll (nur Webplayer)",
@@ -691,6 +715,14 @@
"lastfm": "zeige last.fm links",
"lastfm_description": "zeige links zu last.fm auf dem Künstler/Album-Seiten",
"musicbrainz": "Zeig musicbrainz links",
"customCssEnable": "aktiviere Benutzerdefinierte css"
"customCssEnable": "aktiviere Benutzerdefinierte css",
"albumBackground_description": "fügt ein Hintergrundbild für die Albumseiten hinzu, welche das Albumcover zeigen",
"albumBackgroundBlur": "Größe der Album-Bildunschärfe",
"albumBackgroundBlur_description": "passt die Stärke der Unschärfe an, welche auf das Hintergrundbild des Albums angewandt wird",
"clearCacheSuccess": "Cache erfolgreich geleert",
"contextMenu": "Kontextmenü-Einstellungen (Rechtsklick)",
"customCssEnable_description": "ermöglicht das Schreiben benutzerdefinierten CSS.",
"doubleClickBehavior": "bei Doppelklick alle gesuchten Tracks zur Warteschlange hinzufügen",
"artistBackground": "Künstler Hintergrundbild"
}
}
+34 -1
View File
@@ -237,6 +237,8 @@
"input_legacyAuthentication": "enable legacy authentication",
"input_name": "server name",
"input_password": "password",
"input_preferInstantMix": "prefer instant mix",
"input_preferInstantMixDescription": "only use instant mix to get similar songs. useful if you have plugins that modify this behavior",
"input_savePassword": "save password",
"input_url": "url",
"input_username": "username",
@@ -288,6 +290,11 @@
"updateServer": {
"success": "server updated successfully",
"title": "update server"
},
"privateMode": {
"enabled": "private mode enabled, playback status is now hidden from external integrations",
"disabled": "private mode disabled, playback status is now visible to enabled external integrations",
"title": "private mode"
}
},
"page": {
@@ -321,6 +328,8 @@
"goBack": "go back",
"goForward": "go forward",
"manageServers": "manage servers",
"privateModeOff": "turn off private mode",
"privateModeOn": "turn on private mode",
"openBrowserDevtools": "open browser devtools",
"quit": "$t(common.quit)",
"selectServer": "select server",
@@ -357,6 +366,8 @@
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "share item",
"goToAlbum": "go to $t(entity.album_one)",
"goToAlbumArtist": "go to $t(entity.albumArtist_one)",
"showDetails": "get info"
},
"fullscreenPlayer": {
@@ -400,6 +411,7 @@
"mostPlayed": "most played",
"newlyAdded": "newly added releases",
"recentlyPlayed": "recently played",
"recentlyReleased": "recently released",
"title": "$t(common.home)"
},
"itemDetail": {
@@ -485,6 +497,10 @@
"albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image",
"applicationHotkeys": "application hotkeys",
"applicationHotkeys_description": "configure application hotkeys. toggle the checkbox to set as a global hotkey (desktop only)",
"artistBackground": "artist background image",
"artistBackground_description": "adds a background image for artist pages containing the artist art",
"artistBackgroundBlur": "artist background image blur size",
"artistBackgroundBlur_description": "adjusts the amount of blur applied to the artist background image",
"artistConfiguration": "album artist page configuration",
"artistConfiguration_description": "configure what items are shown, and in what order, on the album artist page",
"audioDevice": "audio device",
@@ -514,6 +530,10 @@
"customFontPath": "custom font path",
"customFontPath_description": "sets the path to the custom font to use for the application",
"disableAutomaticUpdates": "disable automatic updates",
"releaseChannel_optionLatest": "stable",
"releaseChannel_optionBeta": "beta",
"releaseChannel": "release channel",
"releaseChannel_description": "choose between stable releases or beta releases for automatic updates",
"disableLibraryUpdateOnStartup": "disable checking for new versions on startup",
"discordApplicationId": "{{discord}} application id",
"discordApplicationId_description": "the application id for {{discord}} rich presence (defaults to {{defaultId}})",
@@ -526,9 +546,17 @@
"discordRichPresence": "{{discord}} rich presence",
"discordRichPresence_description": "enable playback status in {{discord}} rich presence. Image keys are: {{icon}}, {{playing}}, and {{paused}}",
"discordServeImage": "serve {{discord}} images from server",
"discordServeImage_description": "share cover art for {{discord}} rich presence from server itself, only available for jellyfin and navidrome",
"discordServeImage_description": "share cover art for {{discord}} rich presence from server itself, only available for jellyfin and navidrome. {{discord}} uses a bot to fetch images, so your server must be reachable from the public internet.",
"discordUpdateInterval": "{{discord}} rich presence update interval",
"discordUpdateInterval_description": "the time in seconds between each update (minimum 15 seconds)",
"discordDisplayType": "{{discord}} presence display type",
"discordDisplayType_description": "changes what you are listening to in your status",
"discordDisplayType_songname": "song name",
"discordDisplayType_artistname": "artist name(s)",
"discordLinkType": "{{discord}} presence links",
"discordLinkType_description": "adds external links to {{lastfm}} or {{musicbrainz}} to the song and artist fields in {{discord}} rich presence. {{musicbrainz}} is the most accurate but requires tags and doesn't provide artist links while {{lastfm}} should always provide a link. makes no extra network requests",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} with {{lastfm}} fallback",
"doubleClickBehavior": "queue all searched tracks when double clicking",
"doubleClickBehavior_description": "if true, all matching tracks in a track search will be queued. otherwise, only the clicked one will be queued",
"enableRemote": "enable remote control server",
@@ -567,6 +595,7 @@
"hotkey_favoritePreviousSong": "favorite $t(common.previousSong)",
"hotkey_globalSearch": "global search",
"hotkey_localSearch": "in-page search",
"hotkey_navigateHome": "navigate to home",
"hotkey_playbackNext": "next track",
"hotkey_playbackPause": "pause",
"hotkey_playbackPlay": "play",
@@ -683,6 +712,8 @@
"skipPlaylistPage_description": "when navigating to a playlist, go to the playlist song list page instead of the default page",
"startMinimized": "start minimized",
"startMinimized_description": "start the application in system tray",
"preventSleepOnPlayback": "prevent sleep on playback",
"preventSleepOnPlayback_description": "prevent the display from sleeping while music is playing",
"theme": "theme",
"theme_description": "sets the theme to use for the application",
"themeDark": "theme (dark)",
@@ -696,6 +727,8 @@
"transcodeBitrate_description": "selects the bitrate to transcode. 0 means let the server pick",
"transcodeFormat": "format to transcode",
"transcodeFormat_description": "selects the format to transcode. leave empty to let the server decide",
"mediaSession": "enable media session",
"mediaSession_description": "Enables Windows Media Session integration, displaying media controls and metadata in the system volume overlay and lock screen (Windows only)",
"translationApiProvider": "translation api provider",
"translationApiProvider_description": "api provider for translation",
"translationApiKey": "translation api key",
+39 -8
View File
@@ -259,7 +259,7 @@
"lastfmApiKey_description": "la clave API para {{lastfm}}. Requerida para la portada",
"lastfmApiKey": "Clave API para {{lastfm}}",
"discordServeImage": "Servir imágenes de {{discord}} desde el servidor",
"discordServeImage_description": "Comparte el arte de la portada para el estado de actividad de {{discord}} desde el propio servidor, solo disponible para Jellyfin y Navidrome",
"discordServeImage_description": "Comparte el arte de la portada para el estado de actividad de {{discord}} desde el propio servidor, solo disponible para Jellyfin y Navidrome. {{discord}} usa un bot para obtener las imágenes, por lo que tu servidor debe ser alcanzable desde el Internet público.",
"lastfm": "Mostrar enlaces de last.fm",
"lastfm_description": "Muestra enlaces a last.fm en las páginas de artistas/álbumes",
"musicbrainz": "Mostrar enlaces de MusicBrainz",
@@ -273,7 +273,26 @@
"preservePitch": "Mantener el tono",
"preservePitch_description": "Mantiene el tono cuando se modifica la velocidad de reproducción",
"notify": "Activar notificaciones de canciones",
"notify_description": "Muestra notificaciones cuando se cambia la canción actual"
"notify_description": "Muestra notificaciones cuando se cambia la canción actual",
"discordDisplayType_songname": "Nombre de la canción",
"discordDisplayType_artistname": "Nombre(s) del artista(s)",
"discordDisplayType_description": "Cambia qué estás escuchando en tu estado",
"discordDisplayType": "Tipo de pantalla de actividad de {{discord}}",
"hotkey_navigateHome": "Navegar a inicio",
"preventSleepOnPlayback": "Evitar entrar en reposo durante la reproducción",
"preventSleepOnPlayback_description": "Evita que la pantalla entre en reposo mientras se está reproduciendo música",
"discordLinkType": "Enlaces de estado de {{discord}}",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} con {{lastfm}} como alternativa",
"discordLinkType_description": "Añade enlaces externos a {{lastfm}} o {{musicbrainz}} a la canción y campos del artista en el estado de actividad de {{discord}} . {{musicbrainz}} es el más preciso pero requiere etiquetas y no proporciona enlaces del artista mientras que {{lastfm}} debería siempre proporcionar un enlace. No realiza peticiones de red adicionales",
"artistBackground": "imagen de fondo del artista",
"artistBackgroundBlur": "tamaño de desenfoque de imagen de fondo del artista",
"artistBackgroundBlur_description": "ajusta la cantidad de desenfoque aplicado a la imagen de fondo del artista",
"releaseChannel_optionLatest": "Estable",
"releaseChannel_optionBeta": "Beta",
"releaseChannel": "Canal de lanzamiento",
"releaseChannel_description": "Elige entre lanzamientos estables o beta para las actualizaciones automáticas",
"artistBackground_description": "Añade una imagen de fondo para las páginas de artista que contienen el arte del artista"
},
"action": {
"editPlaylist": "editar $t(entity.playlist_one)",
@@ -306,7 +325,7 @@
"bpm": "lpm",
"refresh": "actualizar",
"unknown": "desconocido",
"areYouSure": "estás seguro?",
"areYouSure": "seguro?",
"edit": "editar",
"favorite": "favorito",
"left": "izquierda",
@@ -471,7 +490,7 @@
},
"page": {
"sidebar": {
"nowPlaying": "en reproducción",
"nowPlaying": "reproduciendo",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"tracks": "$t(entity.track_other)",
@@ -495,7 +514,9 @@
"openBrowserDevtools": "abrir herramientas de desarrollador del navegador",
"quit": "$t(common.quit)",
"goBack": "retroceder",
"goForward": "avanzar"
"goForward": "avanzar",
"privateModeOff": "Desactivar modo privado",
"privateModeOn": "Activar modo privado"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
@@ -519,14 +540,17 @@
"playSimilarSongs": "$t(player.playSimilarSongs)",
"download": "descargar",
"playShuffled": "$t(player.shuffle)",
"moveToNext": "$t(action.moveToNext)"
"moveToNext": "$t(action.moveToNext)",
"goToAlbum": "Ir a $t(entity.album_one)",
"goToAlbumArtist": "Ir a $t(entity.albumArtist_one)"
},
"home": {
"mostPlayed": "más reproducidos",
"newlyAdded": "nuevos lanzamientos añadidos",
"title": "$t(common.home)",
"explore": "explora desde tu biblioteca",
"recentlyPlayed": "reproducidos recientemente"
"recentlyPlayed": "reproducidos recientemente",
"recentlyReleased": "Lanzado recientemente"
},
"fullscreenPlayer": {
"upNext": "siguiente",
@@ -645,7 +669,9 @@
"input_savePassword": "guardar contraseña",
"ignoreSsl": "ignorar ssl ($t(common.restartRequired))",
"ignoreCors": "ignorar cors ($t(common.restartRequired))",
"error_savePassword": "un error ocurrió cuando se intentó guardar la contraseña"
"error_savePassword": "un error ocurrió cuando se intentó guardar la contraseña",
"input_preferInstantMix": "Preferir mix instantáneo",
"input_preferInstantMixDescription": "Usa solo el mix instantáneo para obtener canciones similares. Útil si tienes complementos que modifican este comportamiento"
},
"addToPlaylist": {
"success": "añadido $t(entity.trackWithCount, {\"count\": {{message}} }) a $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -679,6 +705,11 @@
"setExpiration": "Establecer expiración",
"success": "Enlace de compartición copiado al portapapeles (o pulsa aquí para abrir)",
"expireInvalid": "La expiración debe ser en el futuro"
},
"privateMode": {
"enabled": "Modo privado activado, el estado de reproducción ahora está oculto de integraciones externas",
"disabled": "Modo privado desactivado, el estado de reproducción ahora es visible a las integraciones externas habilitadas",
"title": "Modo privado"
}
},
"table": {
+699
View File
@@ -0,0 +1,699 @@
{
"action": {
"deselectAll": "deshautatu dena",
"editPlaylist": "editatu $t(entity.playlist_one)",
"goToPage": "joan orrira",
"moveToNext": "mugitu hurrengora",
"moveToBottom": "mugitu behera",
"moveToTop": "mugitu gora",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "kendu $t(entity.favorite_other)-(e)tik",
"removeFromPlaylist": "kendu $t(entity.playlist_one)-(e)tik",
"removeFromQueue": "kendu ilaratik",
"setRating": "ezarri balorazioa",
"toggleSmartPlaylistEditor": "txandakatu $t(entity.smartPlaylist) editorea",
"viewPlaylists": "ikusi $t(entity.playlist_other)",
"openIn": {
"lastfm": "Ireki Last.fm-n",
"musicbrainz": "Ireki MusicBrainz-en"
},
"clearQueue": "garbitu ilara",
"createPlaylist": "sortu $t(entity.playlist_one)",
"deletePlaylist": "ezabatu $t(entity.playlist_one)",
"addToFavorites": "gehitu $t(entity.favorite_other)-(e)ra",
"addToPlaylist": "gehitu $t(entity.playlist_one)-(e)ra"
},
"common": {
"add": "gehitu",
"additionalParticipants": "partaide gehigarriak",
"newVersion": "bertsio berri bat instalatu da ({{version}})",
"viewReleaseNotes": "ikusi argitalpen oharrak",
"areYouSure": "ziur zaude?",
"ascending": "goranzkoa",
"backward": "atzeraka",
"biography": "biografia",
"close": "itxi",
"codec": "kodeka",
"collapse": "tolestu",
"configure": "konfiguratu",
"confirm": "berretsi",
"create": "sortu",
"currentSong": "uneko $t(entity.track_one)",
"decrease": "gutxitu",
"delete": "ezabatu",
"descending": "beheranzkoa",
"description": "deskripzioa",
"disable": "desgaitu",
"disc": "diskoa",
"dismiss": "baztertu",
"duration": "iraupena",
"edit": "editatu",
"enable": "gaitu",
"expand": "zabaldu",
"favorite": "gogokoa",
"filter_one": "iragazkia",
"filter_other": "iragazkiak",
"filters": "iragazkiak",
"forceRestartRequired": "berreabiarazi aldaketak aplikatzeko... itxi notifikazioa berreabiarazteko",
"setting": "ezarpena",
"share": "partekatu",
"action_one": "ekintza",
"action_other": "ekintzak",
"unknown": "ezezaguna",
"version": "bertsioa",
"year": "urtea",
"yes": "bai",
"bitrate": "bit-emaria",
"bpm": "bpm",
"cancel": "utzi",
"center": "lerrokatu",
"channel_one": "kanala",
"channel_other": "kanalak",
"clear": "garbitu",
"forward": "aurrerantz",
"home": "etxea",
"increase": "handitu",
"left": "ezkerra",
"limit": "mugatu",
"manage": "kudeatu",
"maximize": "maximizatu",
"menu": "menua",
"minimize": "minimizatu",
"modified": "aldatuta",
"mbid": "MusicBrainz IDa",
"name": "izena",
"no": "ez",
"none": "bat ere ez",
"noResultsFromQuery": "kontsultak ez du emaitzik itzuli",
"note": "oharra",
"ok": "ados",
"owner": "jabea",
"path": "bidea",
"playerMustBePaused": "erreproduzitzailea pausatuta egon behar da",
"preview": "aurrebista",
"previousSong": "aurreko $t(entity.track_one)",
"quit": "irten",
"random": "ausazkoa",
"rating": "balorazioa",
"refresh": "freskatu",
"reload": "birkargatu",
"reset": "berrerazi",
"right": "eskuina",
"save": "gorde",
"search": "bilatu",
"size": "tamaina",
"sortOrder": "ordena",
"tags": "etiketak",
"title": "tituloa",
"trackNumber": "pista",
"translation": "itzulpena",
"albumGain": "album irabazpena",
"bitDepth": "bit-sakonera",
"resetToDefault": "lehenetsitako egoerara berrezarri",
"restartRequired": "berrabiarazi behar da",
"sampleRate": "laginketa-tasa",
"saveAndReplace": "gorde eta ordezkatu",
"saveAs": "gorde honela",
"trackGain": "pista irabazpena",
"comingSoon": "laster…",
"trackPeak": "pistaren gailurra",
"albumPeak": "albumaren gailurra"
},
"player": {
"repeat": "errepikatu",
"play": "erreproduzitu",
"previous": "aurrekoa",
"pause": "pausatu",
"favorite": "gogokoa",
"mute": "isilarazi",
"muted": "isilduta",
"next": "hurrengoa",
"skip": "saltatu",
"stop": "gelditu",
"unfavorite": "kendu gogokoetatik",
"addLast": "gehitu azkena",
"addNext": "gehitu hurrengoa",
"playbackFetchInProgress": "abestiak kargatzen…",
"playbackSpeed": "erreprodukzio-abiadura",
"playRandom": "erreproduzitu auzaz",
"playbackFetchNoResults": "ez da abestirik aurkitu",
"playSimilarSongs": "erreproduzitu antzeko abestiak",
"queue_clear": "garbitu ilara",
"queue_moveToBottom": "gora eraman hautatutakoak",
"queue_moveToTop": "behera eraman hautatutakoak",
"queue_remove": "kendu hautatutakoak",
"repeat_all": "errepikatu dena",
"repeat_off": "errepikapena desgaituta",
"shuffle": "erreproduzitu ausaz",
"shuffle_off": "auza desgaituta",
"skip_back": "saltatu atzeraka",
"skip_forward": "saltatu aurreraka",
"toggleFullscreenPlayer": "txandakatu pantaila osoko erreproduzitzailea",
"viewQueue": "ikusi ilara"
},
"table": {
"config": {
"view": {
"table": "taula",
"list": "zerrenda",
"card": "txartela",
"grid": "sareta",
"poster": "kartela"
},
"general": {
"gap": "$t(common.gap)",
"size": "$t(common.size)",
"tableColumns": "taula zutabeak",
"itemSize": "elementuaren tamaina (px)",
"followCurrentSong": "jarraitu uneko abestia"
},
"label": {
"actions": "$t(common.action_other)",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"rating": "$t(common.rating)",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"year": "$t(common.year)",
"titleCombined": "$t(common.title) (batuta)",
"releaseDate": "argitalpen data",
"playCount": "erreprodukzio kopurua",
"lastPlayed": "azken aldiz entzundakoa",
"discNumber": "disko zenbakia",
"dateAdded": "gehitze data"
}
},
"column": {
"album": "albuma",
"albumCount": "$t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "biografia",
"bitrate": "bit-emaria",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"discNumber": "diskoa",
"favorite": "gogokoa",
"genre": "$t(entity.genre_one)",
"path": "bidea",
"rating": "balorazioa",
"releaseYear": "urtea",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "tituloa",
"trackNumber": "pista",
"bpm": "bpm",
"comment": "iruzkina",
"playCount": "erreprodukzioak",
"releaseDate": "argitalpen data",
"lastPlayed": "azken aldiz entzundakoa",
"dateAdded": "gehitutako data",
"albumArtist": "albumeko artista"
}
},
"entity": {
"album_one": "albuma",
"album_other": "albumak",
"albumArtist_one": "albumaren artista",
"albumArtist_other": "albumaren artistak",
"albumArtistCount_one": "album artista {{count}}",
"albumArtistCount_other": "{{count}} album artista",
"albumWithCount_one": "album {{count}}",
"albumWithCount_other": "{{count}} album",
"artist_one": "artista",
"artist_other": "artistak",
"artistWithCount_one": "artista {{count}}",
"artistWithCount_other": "{{count}} artista",
"favorite_one": "gogokoa",
"favorite_other": "gogokoak",
"folder_one": "karpeta",
"folder_other": "karpetak",
"folderWithCount_one": "karpeta {{count}}",
"folderWithCount_other": "{{count}} karpeta",
"genre_one": "generoa",
"genre_other": "generoak",
"genreWithCount_one": "genero {{count}}generoa",
"genreWithCount_other": "{{count}} genero",
"playlist_one": "erreprodukzio-zerrenda",
"playlist_other": "erreprodukzio-zerrendak",
"play_one": "erreprodukzio {{count}}",
"play_other": "{{count}} erreprodukzio",
"playlistWithCount_one": "erreprodukzio-zerrenda {{count}}",
"playlistWithCount_other": "{{count}} erreprodukzio-zerrenda",
"smartPlaylist": "$t(entity.playlist_one) adimentsua",
"track_one": "pista",
"track_other": "pistak",
"song_one": "abestia",
"song_other": "abestiak",
"trackWithCount_one": "pista {{count}}",
"trackWithCount_other": "{{count}} pista"
},
"error": {
"apiRouteError": "ezin izan da eskaera bideratu",
"audioDeviceFetchError": "errore bat gertatu da audio gailuak lortzen saiatzean",
"authenticationFailed": "autentifikazioa huts egin du",
"badValue": "\"{{value}}\" aukera baliogabea. Balio hau ez da gehiago existitzen.",
"credentialsRequired": "kredentzialak beharrezkoak dira",
"endpointNotImplementedError": "{{endpoint}} amaiera-puntua ez dago {{serverType}}-(e)rako inplementatuta",
"genericError": "errore bat gertatu da",
"invalidServer": "zerbitzari baliogabea",
"localFontAccessDenied": "tokiko letra-tipoetarako sarbidea ukatuta",
"mpvRequired": "MPV beharrezkoa da",
"networkError": "sareko errore bat gertatu da",
"openError": "ezin izan da fitxategia ireki",
"playbackError": "errore bat gertatu da multimedia erreproduzitzen saiatzean",
"remoteDisableError": "errore bat gertatu da urruneko zerbitzaria $t(common.disable) desgaitzen saiatzean",
"remoteEnableError": "errore bat gertatu da urruneko zerbitzaria $t(common.enable) gaitzen saiatzean",
"remotePortError": "errore bat gertatu da urruneko zerbitzariaren ataka ezartzen saiatzean",
"remotePortWarning": "Berrabiarazi zerbitzaria portu berria aplikatzeko",
"serverNotSelectedError": "ez da zerbitzaririk hautatu",
"serverRequired": "zerbitzaria beharrezkoa da",
"sessionExpiredError": "zure saioa iraungi da",
"badAlbum": "Orrialde hau ikusten ari zara abesti hau album batekoa ez delako. Ziurrenik arazo hau ikusten ari zara zure musika karpetaren goiko mailan abesti bat baduzu. Jellyfinek abestiak karpeta batean badaude taldekatzen ditu bakarrik.",
"loginRateError": "Saioa hasteko saiakera gehiegi egin dira, saiatu berriro segundo batzuk barru",
"notificationDenied": "Jakinarazpenetarako baimenak ukatu dira. Ezarpen honek ez du eraginik.",
"systemFontError": "errore bat gertatu da sistemaren letra-tipoak lortzen saiatzean"
},
"filter": {
"disc": "diskoa",
"duration": "iraupena",
"id": "id-a",
"isPublic": "publikoa da",
"name": "izena",
"note": "oharra",
"owner": "$t(common.owner)",
"path": "bidea",
"random": "ausazkoa",
"rating": "balorazioa",
"trackNumber": "pista",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "biografia",
"bitrate": "bit-emaria",
"bpm": "bpm-ak",
"channels": "$t(common.channel_other)",
"comment": "iruzkina",
"favorited": "gogoko gisa markatua",
"genre": "$t(entity.genre_one)",
"search": "bilatu",
"title": "tituloa",
"albumCount": "$t(entity.album_other) kopurua",
"communityRating": "komunitatearen balorazioa",
"criticRating": "kritikarien balorazioa",
"dateAdded": "gehitutako data",
"isCompilation": "konpilazioa da",
"isFavorited": "gogokoetan dago",
"isRated": "baloratua dago",
"isRecentlyPlayed": "duela gutxi entzundakoa",
"lastPlayed": "azken aldiz entzundakoa",
"mostPlayed": "gehien entzundakoa",
"playCount": "erreprodukzio kopurua",
"recentlyAdded": "duela gutxi gehitutakoa",
"recentlyPlayed": "duela gutxi entzundakoa",
"recentlyUpdated": "duela gutxi eguneratua",
"songCount": "abesti kopurua",
"releaseDate": "argitalpen data",
"releaseYear": "argitalpen urtea",
"toYear": "urtera arte",
"fromYear": "urtetik aurrera"
},
"setting": {
"hotkey_playbackPause": "pausatu",
"hotkey_playbackPlay": "erreproduzitu",
"language": "hizkuntza",
"playbackStyle_optionNormal": "normala",
"playButtonBehavior_optionPlay": "$t(player.play)",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"font": "letra-tipoa",
"hotkey_playbackStop": "gelditu",
"buttonSize_description": "erreproduzitzailearen barrako botoien tamaina",
"clearCache": "garbitu nabigatzailearen katxea",
"clearQueryCache": "garbitu feishinen katxea",
"clearCacheSuccess": "katxea behar bezala garbitu da",
"contextMenu": "testuinguru-menuaren konfigurazioa (klik eskuineko botoiarekin)",
"customCssEnable": "gaitu css pertsonalizatua",
"customCssEnable_description": "css pertsonalizatua idazteko aukera eman.",
"customCss": "css pertsonalizatua",
"customFontPath": "letra-tipo pertsonalizatuaren bidea",
"customFontPath_description": "aplikazioan erabiliko den letra-tipo pertsonalizatuaren bidea ezartzen du",
"disableAutomaticUpdates": "desgaitu eguneratze automatikoak",
"discordApplicationId": "{{discord}} aplikazioaren IDa",
"followLyric": "jarraitu uneko letra",
"font_description": "aplikazioan erabiliko den letra-tipoa ezartzen du",
"fontType": "letra-tipo mota",
"fontType_optionCustom": "letra-tipo pertsonalizatua",
"fontType_optionSystem": "sistemaren letra-tipoa",
"gaplessAudio_optionWeak": "ahula (gomendatua)",
"homeConfiguration": "hasierako orriaren konfigurazioa",
"hotkey_favoriteCurrentSong": "$t(common.currentSong) gogokoa",
"hotkey_favoritePreviousSong": "$t(common.previousSong) gogokoa",
"hotkey_navigateHome": "nabigatu etxera",
"hotkey_playbackNext": "hurrengo pista",
"hotkey_playbackPlayPause": "erreproduzitu / pausatu",
"hotkey_playbackPrevious": "aurreko pista",
"hotkey_skipBackward": "saltatu atzeraka",
"hotkey_skipForward": "saltatu aurrerantz",
"hotkey_toggleCurrentSongFavorite": "txandakatu $t(common.currentSong) gogokoa",
"hotkey_toggleFullScreenPlayer": "txandakatu pantaila osoko erreproduzitzailea",
"hotkey_togglePreviousSongFavorite": "txandakatu $t(common.previousSong) gogokoa",
"hotkey_toggleQueue": "txandakatu ilara",
"hotkey_toggleRepeat": "txandakatu errepikapena",
"hotkey_toggleShuffle": "txandakatu auzazkoa",
"hotkey_unfavoriteCurrentSong": "kendu $t(common.currentSong) gogokoetatik",
"hotkey_unfavoritePreviousSong": "kendu $t(common.previousSong) gogokoetatik",
"hotkey_volumeDown": "bolumena jaitsi",
"hotkey_volumeMute": "isilarazi bolumena",
"hotkey_volumeUp": "bolumena igo",
"hotkey_zoomIn": "hurbildu",
"hotkey_zoomOut": "txikiagotu",
"language_description": "aplikazioaren hizkuntza ezartzen du ($t(common.restartRequired))",
"lastfm": "erakutsi last.fm estekak",
"lastfm_description": "erakutsi last.fm-rako estekak artista/album orrialdeetan",
"lastfmApiKey": "{{lastfm}} API gakoa",
"lastfmApiKey_description": "{{lastfm}}-ren API gakoa. Azaleko arterako beharrezkoa.",
"lyricFetch": "eskuratu letrak internetetik",
"lyricFetch_description": "Eskuratu letrak hainbat internet iturrietatik",
"notify": "gaitu abesti japinarazpenak",
"notify_description": "erakutsi jakinarazpenak uneko abestia aldatzean",
"audioExclusiveMode_description": "gaitu irteera esklusiboko modua. Modu honetan, sistema normalean blokeatuta egoten da, eta mpv-k bakarrik atera ahal izango du audioa",
"audioDevice_description": "aukeratu erreproduzitzeko erabiliko den audio gailua (web erreproduzitzailea bakarrik)",
"audioPlayer": "audio erreproduzitzailea",
"audioPlayer_description": "aukeratu erabiliko den audio erreproduzitzailea",
"buttonSize": "erreproduzitzaile barrako botoien tamaina",
"crossfadeDuration": "crossfade iraupena",
"crossfadeDuration_description": "crossfade efektuaren iraupena ezartzen du",
"crossfadeStyle": "crossfade estiloa",
"crossfadeStyle_description": "aukeratu audio erreproduzitzailearentzat erabiliko den crossfade estiloa",
"disableLibraryUpdateOnStartup": "desgaitu bertsio berrien egiaztapena abiaraztean",
"discordApplicationId_description": "{{discord}} jarduera-egoeraren aplikazioaren IDa (lehenetsia {{defaultId}} da)",
"discordPausedStatus": "erakutsi jarduera-egoera pausatuta dagoenean",
"discordPausedStatus_description": "gaituta dagoenean, egoera agertuko da erreproduzitzailea pausatuta dagoenean",
"discordIdleStatus": "erakutsi inaktibo jarduera-egoeran",
"discordIdleStatus_description": "gaituta dagoenean, eguneratu egoera erreproduzitzailea inaktibo dagoen bitartean",
"discordListening_description": "erakutsi egoera entzuten bezala erreproduzitzen ordez",
"discordListening": "erakutsi egoera entzuten bezala",
"discordRichPresence": "{{discord}} jarduera-egoera",
"discordRichPresence_description": "gaitu erreprodukzioa egoera {{discord}}-en jarduera-egoeran. Irudi gakoak hauek dira: {{icon}}, {{playing}}, eta {{paused}}",
"discordServeImage": "zerbitzatu {{discord}} irudiak zerbitzaritik",
"discordServeImage_description": "partekatu {{discord}} jarduera-egoerarentzako azala artea zerbitzaritik bertatik, Jellyfin eta Navidrome-rentzat bakarrik eskuragarri. {{discord}}-(e)k bot bat erabiltzen du irudiak eskuratzeko, beraz, zure zerbitzaria internet publikotik eskuragarri egon behar da.",
"discordUpdateInterval": "{{discord}} jarduera-egoera eguneraketa tartea",
"discordLinkType_none": "$t(common.none)",
"albumBackground": "albumaren atzeko planoaren irudia",
"albumBackground_description": "albumaren azala artea duten album orrietarako atzeko plano irudi bat gehitzen du",
"albumBackgroundBlur": "albumaren atzeko planoaren irudiaren lausotze tamaina",
"discordLinkType_description": "{{lastfm}} edo {{musicbrainz}}-(e)rako kanpoko estekak gehitzen ditu abesti eta artista eremuetan {{discord}} jarduera-egoeran. {{musicbrainz}} da zehatzena, baina etiketak behar ditu eta ez ditu artistaren estekak ematen, {{lastfm}}-k beti esteka bat eman beharko lukeen bitartean. ez du sareko eskaera gehigarririk egiten",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"scrobble": "scrobble",
"sidePlayQueueStyle_optionAttached": "erantsita",
"sidePlayQueueStyle_optionDetached": "bereizita",
"theme": "gaia",
"audioDevice": "audio gailua",
"discordDisplayType_songname": "abesti izena",
"discordDisplayType_artistname": "artista izena(k)",
"fontType_optionBuiltIn": "barneko letra-tipoa",
"hotkey_globalSearch": "bilaketa globala",
"albumBackgroundBlur_description": "albumaren atzeko planoaren irudiari aplikatzen zaion lausotze-kopurua doitzen du",
"artistBackground": "artistaren atzeko planoaren irudia",
"artistBackgroundBlur": "artistaren atzeko planoko irudiaren lausotze-tamaina",
"artistBackgroundBlur_description": "artistaren atzeko planoaren irudiari aplikatzen zaion lausotze-kopurua doitzen du",
"artistConfiguration": "albumaren artistaren konfigurazio orria",
"artistConfiguration_description": "konfiguratu zein elementu erakusten diren eta zein ordenatan albumaren artistaren orrian",
"audioExclusiveMode": "audio esklusiboko modua",
"releaseChannel_optionLatest": "egonkorra",
"releaseChannel_optionBeta": "beta",
"releaseChannel": "argitalpen kanala",
"releaseChannel_description": "aukeratu argitalpen egonkorren edo beta artean eguneratze automatikoak lortzeko",
"discordUpdateInterval_description": "eguneratze bakoitzaren arteko denbora segundotan (gutxienez 15 segundo)",
"discordDisplayType": "{{discord}} jarduera-pantailaren mota",
"discordDisplayType_description": "zure egoeran entzuten ari zarena aldatzen du",
"discordLinkType": "{{discord}} egoera estekak",
"fontType_description": "barneko letra-tipoa Feishinek eskaintzen dituen letra-tipoetako bat aukeratzen du. sistemaren letra-tipoa zure sistema eragileak eskaintzen duen edozein letra-tipo hautatzeko aukera ematen dizu. pertsonalizatua zure letra-tipoa eskaintzeko aukera ematen dizu",
"genreBehavior": "genero orriaren portaera lehenetsia",
"homeConfiguration_description": "konfiguratu zein elementu erakusten diren hasierako orrian eta zein ordenatan",
"homeFeature": "etxeko karrusela nabarmendua",
"homeFeature_description": "hasierako orrian karrusel nabarmen handia erakutsi behar den ala ez kontrolatzen du",
"hotkey_localSearch": "orrian bilatu",
"hotkey_rate0": "garbitu balorazioa",
"hotkey_rate1": "1 izarretako balorazioa",
"hotkey_rate2": "2 izarretako balorazioa",
"hotkey_rate3": "3 izarretako balorazioa",
"hotkey_rate4": "4 izarretako balorazioa",
"hotkey_rate5": "5 izarretako balorazioa",
"zoom_description": "aplikazioaren zoom ehunekoa ezartzen du",
"zoom": "zoom ehunekoa",
"windowBarStyle_description": "aukeratu leiho-barraren estiloa",
"windowBarStyle": "leiho-barra estiloa",
"webAudio": "erabili web audioa",
"useSystemTheme_description": "jarraitu sistemak definitutako argi edo iluntasun lehentasuna",
"useSystemTheme": "erabili sistemaren gaia",
"translationTargetLanguage_description": "itzulpenerako helburu-hizkuntza",
"translationTargetLanguage": "itzulpenerako helburu-hizkuntza",
"translationApiKey": "itzulpen api gakoa",
"translationApiProvider_description": "itzulpenerako api hornitzailea",
"translationApiProvider": "itzulpen api hornitzailea",
"mediaSession": "gaitu multimedia saioa",
"themeLight_description": "aplikaziorako erabiliko den gaia argia ezartzen du",
"themeLight": "gaia (argia)",
"themeDark_description": "aplikaziorako erabiliko den gai iluna ezartzen du",
"themeDark": "gaia (iluna)",
"theme_description": "aplikaziorako erabiliko den gaia ezartzen du",
"externalLinks": "kanpoko estekak erakutsi",
"externalLinks_description": "kanpoko estekak (Last.fm, MusicBrainz) artista/album orrietan erakustea gaitzen du",
"exitToTray": "irten erretilura"
},
"form": {
"addServer": {
"input_password": "pasahitza",
"input_url": "url-a",
"input_username": "erabiltzaile-izena",
"error_savePassword": "errore bat gertatu da pasahitza gordetzen saiatzean",
"input_name": "zerbitzari izena",
"input_savePassword": "pasahitza gorde",
"title": "zerbitzaria gehitu",
"ignoreCors": "alde batera utzi cors $t(common.restartRequired)",
"ignoreSsl": "alde batera utzi ssl $t(common.restartRequired)",
"input_legacyAuthentication": "gaitu zaharkitutako autentifikazioa",
"success": "zerbitzaria behar bezala gehitu da"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) gehitu da $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })-ra",
"input_skipDuplicates": "saltatu bikoiztuak",
"title": "gehitu $t(entity.playlist_one)-(a)ri"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "publikoa",
"title": "$t(entity.playlist_one) sortu",
"success": "$t(entity.playlist_one) behar bezala sortu da"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "letra bilatu"
},
"shareItem": {
"description": "deskripzioa",
"setExpiration": "iraungitze-data ezarri",
"success": "partekatzeko esteka arbelera kopiatu da (edo egin klik hemen irekitzeko)",
"expireInvalid": "iraungitze-data etorkizunean izan behar da",
"allowDownloading": "baimendu deskargatzea",
"createFailed": "partekatzea sortzeak huts egin du (partekatzea gaituta al dago?)"
},
"deletePlaylist": {
"success": "$t(entity.playlist_one) behar bezala ezabatu da",
"title": "$t(entity.playlist_one) ezabatu",
"input_confirm": "idatzi $t(entity.playlist_one)-(a)ren izena berresteko"
},
"editPlaylist": {
"success": "$t(entity.playlist_one) behar bezala eguneratu da",
"title": "$t(entity.playlist_one) editatu",
"publicJellyfinNote": "Arrazoiren batengatik, Jellyfin ez du erakusten erreprodukzio-zerrendak publikoak diren edo ez. Hau publiko izaten jarraitzea nahi baduzu, hautatu sarrera hau"
},
"queryEditor": {
"title": "kontsulta editorea",
"input_optionMatchAll": "guztiak bat etorri",
"input_optionMatchAny": "edozeinekin bat etorri"
},
"updateServer": {
"success": "zerbitzaria behar bezala eguneratu da",
"title": "zerbitzaria eguneratu"
},
"privateMode": {
"title": "modu pribatua",
"enabled": "modu pribatua gaituta, erreprodukzio egoera kanpoko integrazioetatik ezkutatuta dago orain",
"disabled": "modu pribatua desgaituta, erreprodukzio egoera ikusgai dago orain gaitutako kanpoko integrazioentzat"
}
},
"page": {
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"released": "argitaratuta",
"moreFromArtist": "$t(entity.artist_one) honetatik gehiago",
"moreFromGeneric": "{{item}}-(e)tik gehiago"
},
"albumList": {
"title": "$t(entity.album_other)",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"artistAlbums": "{{artist}}-(a)ren albumak"
},
"appMenu": {
"quit": "$t(common.quit)",
"settings": "$t(common.setting_other)",
"collapseSidebar": "tolestu alboko barra",
"expandSidebar": "zabaldu alboko barra",
"goBack": "atzera",
"goForward": "aurrera",
"manageServers": "kudeatu zerbitzariak",
"privateModeOff": "itzali modu pribatua",
"privateModeOn": "aktibatu modu pribatua",
"selectServer": "aukeratu zerbitzaria",
"version": "bertsioa {{version}}",
"openBrowserDevtools": "ireki nabigatzailearen garapen tresnak"
},
"manageServers": {
"url": "URLa",
"username": "erabiltzaile-izena",
"title": "kudeatu zerbitzariak",
"serverDetails": "zerbitzariaren xehetasunak",
"editServerDetailsTooltip": "editatu zerbitzariaren xehetasunak",
"removeServer": "kendu zerbitzaria"
},
"contextMenu": {
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"download": "deskargatu",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"play": "$t(player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"numberSelected": "{{count}} hautatuta",
"shareItem": "partekatu elementua",
"goToAlbum": "joan $t(entity.album_one)-(e)ra",
"goToAlbumArtist": "joan $t(entity.albumArtist_one)-(e)ra",
"showDetails": "informazioa lortu"
},
"fullscreenPlayer": {
"config": {
"opacity": "opakotasuna",
"synchronized": "sinkronizatuta",
"unsynchronized": "sinkronizatu gabe",
"dynamicIsImage": "gaitu atzeko planoaren irudia",
"followCurrentLyric": "jarraitu uneko letra",
"lyricSize": "letraren tamaina",
"dynamicBackground": "atzeko plano dinamikoa",
"dynamicImageBlur": "irudiaren lausotze tamaina",
"lyricAlignment": "letraren lerrokatzea",
"showLyricMatch": "erakutsi letren bat-etortzea",
"showLyricProvider": "erakutsi letra hornitzailea",
"lyricOffset": "letra-desplazamendua (ms)"
},
"lyrics": "letrak",
"related": "erlazionatuta",
"upNext": "hurrengoa",
"visualizer": "bistaratzailea",
"noLyrics": "ez da letrarik aurkitu"
},
"genreList": {
"title": "$t(entity.genre_other)",
"showAlbums": "erakutsi $t(entity.album_other) $t(entity.album_other)",
"showTracks": "erakutsi $t(entity.genre_one) $t(entity.track_other)"
},
"globalSearch": {
"title": "komandoak",
"commands": {
"goToPage": "joan orrira",
"searchFor": "bilatu {{query}}",
"serverCommands": "zerbitzariaren komandoak"
}
},
"home": {
"title": "$t(common.home)",
"mostPlayed": "gehien entzundakoak",
"newlyAdded": "azken aldian gehitutako argitalpenak",
"recentlyPlayed": "azken aldian entzundakoak",
"recentlyReleased": "azken aldian argitaratutak",
"explore": "arakatu zure liburutegitik"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"setting": {
"advanced": "aurreratua",
"generalTab": "orokorra",
"playbackTab": "erreprodukzioa",
"windowTab": "leihoa"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"artists": "$t(entity.artist_other)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"tracks": "$t(entity.track_other)",
"myLibrary": "nire liburutegia",
"nowPlaying": "orain erreproduzitzen",
"shared": "partekatutako $t(entity.playlist_other)"
},
"trackList": {
"title": "$t(entity.track_other)",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"artistTracks": "{{artist}}-(r)en abestiak"
},
"albumArtistDetail": {
"about": "{{artist}}-(r)i buruz",
"relatedArtists": "erlazionatutako $t(entity.artist_other)",
"topSongs": "abesti nagusiak",
"topSongsFrom": "{{title}}-(a)ren abesti nagusiak",
"viewAll": "ikusi guztiak",
"viewAllTracks": "ikusi $t(entity.track_other) guztiak",
"appearsOn": "agertzen da hemen",
"recentReleases": "azken argitalpenak",
"viewDiscography": "ikusi diskografia"
},
"itemDetail": {
"copyPath": "kopiatu bidea arbelean",
"openFile": "erakutsi pista fitxategi-kudeatzailean",
"copiedPath": "bidea behar bezala kopiatu da"
},
"playlist": {
"reorder": "berrantolaketa IDaren arabera ordenatzean bakarrik gaituta dago"
}
}
}
+8 -3
View File
@@ -92,7 +92,9 @@
"additionalParticipants": "muut osallistujat",
"tags": "tägit",
"newVersion": "uusi versio on asennettu ({{version}})",
"viewReleaseNotes": "katsele julkaisutietoja"
"viewReleaseNotes": "katsele julkaisutietoja",
"bitDepth": "bittisyvyys",
"sampleRate": "näytteenottotaajuus"
},
"entity": {
"album_one": "albumi",
@@ -178,7 +180,8 @@
"playbackError": "mediaa toistaessa tapahtui virhe",
"remotePortWarning": "käynnistä palvelin uudestaan ottaaksesi uuden portin käyttöön",
"endpointNotImplementedError": "päätepiste {{endpoint}} ei ole toteutettu {{serverType}} varten",
"badValue": "kelpaamaton optio \"{{value}}\". tätä arvoa ei ole enää olemassa"
"badValue": "kelpaamaton optio \"{{value}}\". tätä arvoa ei ole enää olemassa",
"notificationDenied": "luvat ilmouilmoituksia varten evättiin. tällä asetuksella ei ole vaikutusta"
},
"filter": {
"album": "$t(entity.album_one)",
@@ -524,7 +527,9 @@
"discordPausedStatus": "näytä rich presence tauotettuna",
"discordPausedStatus_description": "ollessak käytössä, status näyttää milloin soitin on tautotettuna",
"preservePitch": "säilytä sävelkorkeus",
"preservePitch_description": "säilytä sävelkorkeus toistonopeutta muokatessa"
"preservePitch_description": "säilytä sävelkorkeus toistonopeutta muokatessa",
"notify": "käytä kappaleen ilmoituksia",
"notify_description": "näytä limoituksia, kun vaihdetaan nykyistä kappaletta"
},
"page": {
"itemDetail": {
+100 -77
View File
@@ -12,8 +12,8 @@
"favorite": "favori",
"next": "suivant",
"shuffle": "lecture aléatoire",
"playbackFetchNoResults": "aucune chansons trouvées",
"playbackFetchInProgress": "chargement des chansons…",
"playbackFetchNoResults": "aucun titre trouvé",
"playbackFetchInProgress": "chargement des titres…",
"addNext": "ajouter ensuite",
"playbackSpeed": "vitesse de lecture",
"playbackFetchCancel": "cela prend du temps… fermez la notification pour annuler",
@@ -29,7 +29,7 @@
"skip_forward": "avancer",
"pause": "pause",
"unfavorite": "retirer des favoris",
"playSimilarSongs": "jouer des chansons similaires",
"playSimilarSongs": "jouer des titres similaires",
"viewQueue": "voir la file d'attente"
},
"action": {
@@ -60,12 +60,12 @@
"backward": "en arrière",
"increase": "augmenter",
"rating": "note",
"bpm": "bpm",
"bpm": "BPM",
"refresh": "rafraichir",
"unknown": "inconnu",
"areYouSure": "êtes-vous sûr?",
"edit": "éditer",
"favorite": "favoris",
"favorite": "favori",
"left": "gauche",
"save": "enregistrer",
"right": "droite",
@@ -81,11 +81,11 @@
"manage": "gérer",
"limit": "limite",
"minimize": "minimiser",
"modified": "modifier",
"modified": "modifié",
"duration": "durée",
"name": "nom",
"maximize": "agrandir",
"decrease": "baisser",
"decrease": "diminuer",
"ok": "ok",
"description": "description",
"configure": "configurer",
@@ -110,7 +110,7 @@
"filter_other": "filtres",
"filters": "filtres",
"create": "créer",
"bitrate": "bitrate",
"bitrate": "bit binaire",
"saveAndReplace": "enregistrer et remplacer",
"action_one": "action",
"action_many": "actions",
@@ -144,7 +144,7 @@
"albumGain": "gain de l'album",
"albumPeak": "crête de l'album",
"close": "fermer",
"mbid": "Identifiants MusicBrainz",
"mbid": "Identifiant MusicBrainz",
"preview": "aperçu",
"share": "partager",
"reload": "recharger",
@@ -154,10 +154,10 @@
"translation": "traduction",
"additionalParticipants": "participants additionnels",
"tags": "tags",
"newVersion": "une nouvelle version vient d'être installé ({{version}})",
"newVersion": "une nouvelle version vient d'être installée ({{version}})",
"viewReleaseNotes": "voir la note de version",
"sampleRate": "taux d'échantillonnage",
"bitDepth": "bit par échantillon"
"bitDepth": "format d'échantillonnage"
},
"error": {
"remotePortWarning": "redémarrer le serveur pour appliquer le nouveau port",
@@ -187,7 +187,7 @@
},
"filter": {
"mostPlayed": "plus joués",
"playCount": "nombre d'écoute",
"playCount": "nombre d'écoutes",
"isCompilation": "est une compilation",
"recentlyPlayed": "récemment joué",
"isRated": "est noté",
@@ -202,17 +202,17 @@
"releaseDate": "date de sortie",
"communityRating": "note de la communauté",
"path": "chemin",
"favorited": "favoris",
"favorited": "favori",
"isRecentlyPlayed": "est récemment joué",
"isFavorited": "est favori",
"bpm": "bpm",
"bpm": "BPM",
"releaseYear": "année de sortie",
"disc": "disque",
"biography": "biographie",
"songCount": "nombre de chansons",
"duration": "durée",
"random": "aléatoire",
"lastPlayed": "dernier joué",
"lastPlayed": "écouté récemment",
"toYear": "à l'année",
"fromYear": "depuis l'année",
"criticRating": "note des critiques",
@@ -243,7 +243,7 @@
"artists": "$t(entity.artist_other)",
"albumArtists": "$t(entity.albumArtist_other)",
"shared": "partagé $t(entity.playlist_other)",
"myLibrary": "ma bibliothèque"
"myLibrary": "Bibliothèque"
},
"fullscreenPlayer": {
"config": {
@@ -256,10 +256,10 @@
"lyricAlignment": "alignement des paroles",
"useImageAspectRatio": "utiliser le ratio de l'image",
"opacity": "opacité",
"lyricSize": "Taille des paroles",
"lyricSize": "taille des paroles",
"lyricGap": "espacement des lettres",
"dynamicIsImage": "activer l'image d'arrière-plan",
"dynamicImageBlur": "intensité de flou sur image d'arrière-plan",
"dynamicImageBlur": "intensité du flou sur l'image d'arrière-plan",
"lyricOffset": "paroles décalées (ms)"
},
"upNext": "à suivre",
@@ -278,14 +278,17 @@
"goForward": "avancer",
"version": "version {{version}}",
"settings": "$t(common.setting_other)",
"quit": "$t(common.quit)"
"quit": "$t(common.quit)",
"privateModeOff": "désactiver le mode privé",
"privateModeOn": "activer le mode privé"
},
"home": {
"mostPlayed": "plus joués",
"newlyAdded": "versions récemment ajoutés",
"explore": "explorer depuis votre bibliothèque",
"recentlyPlayed": "récemment joué",
"title": "$t(common.home)"
"mostPlayed": "Les plus joués",
"newlyAdded": "Ajoutés récemment",
"explore": "Explorer depuis la bibliothèque",
"recentlyPlayed": "Joués récemment",
"title": "$t(common.home)",
"recentlyReleased": "Sortis récemment"
},
"albumDetail": {
"moreFromArtist": "plus de $t(entity.artist_one)",
@@ -329,7 +332,9 @@
"showDetails": "obtenir des informations",
"download": "télécharger",
"playShuffled": "$t(player.shuffle)",
"moveToNext": "$t(action.moveToNext)"
"moveToNext": "$t(action.moveToNext)",
"goToAlbumArtist": "aller à l'$t(entity.albumArtist_one)",
"goToAlbum": "aller à l'$t(entity.album_one)"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
@@ -355,13 +360,13 @@
"albumArtistDetail": {
"about": "À propos de {{artist}}",
"appearsOn": "apparaît sur",
"topSongsFrom": "meilleures chansons de {{title}}",
"topSongsFrom": "meilleurs titres de {{title}}",
"viewAll": "voir tout",
"viewAllTracks": "voir tout $t(entity.track_other)",
"recentReleases": "sorties récentes",
"viewDiscography": "voir la discographie",
"relatedArtists": "en rapport avec $t(entity.artist_other)",
"topSongs": "meilleures chansons"
"relatedArtists": "$t(entity.artist_other) similaires",
"topSongs": "meilleurs titres"
},
"itemDetail": {
"copyPath": "copier le chemin dans le presse-papiers",
@@ -369,11 +374,11 @@
"copiedPath": "chemin copié avec succès"
},
"playlist": {
"reorder": "le tri n'est possible que lorsque l'on trie par identifiant"
"reorder": "le tri n'est possible que lors du tri par identifiant"
},
"manageServers": {
"serverDetails": "détails du serveur",
"removeServer": "supprimer le serveur",
"removeServer": "retirer le serveur",
"url": "URL du serveur",
"title": "gérer les serveurs",
"username": "nom d'utilisateur",
@@ -390,12 +395,12 @@
"accentColor": "couleur d'accentuation",
"accentColor_description": "définit la couleur d'accentuation de l'application",
"applicationHotkeys": "raccourcis clavier d'application",
"crossfadeDuration": "durée de fondue enchaînée",
"crossfadeDuration": "durée de fondu enchaîné",
"audioPlayer": "lecteur audio",
"applicationHotkeys_description": "configurer les raccourcis clavier dapplication. activer la case à cocher pour définir comme raccourci clavier global (bureau uniquement)",
"crossfadeStyle_description": "sélectionnez le style du fondu enchaîné à utiliser pour le lecteur audio",
"customFontPath": "chemin de police personnalisé",
"disableAutomaticUpdates": "désactiver les mises à jour automatique",
"disableAutomaticUpdates": "désactiver les mises à jour automatiques",
"customFontPath_description": "définit le chemin de police personnalisé pour l'application",
"remotePort_description": "définit le port du serveur de contrôle à distance",
"hotkey_skipBackward": "reculer",
@@ -413,9 +418,9 @@
"sampleRate": "taux d'échantillonnage",
"sampleRate_description": "sélectionne le taux d'échantillonnage de sortie utilisé si la fréquence d'échantillonnage sélectionnée est différente de celle du média actuel. une valeur inférieure à 8000 utilisera la fréquence par défaut",
"hotkey_zoomIn": "zoom avant",
"scrobble_description": "scrobble les lectures à votre serveur multimédia",
"scrobble_description": "scrobbler les lectures à votre serveur multimédia",
"hotkey_browserForward": "avancer",
"discordUpdateInterval": "interval de mise à jour de {{discord}} rich presence",
"discordUpdateInterval": "intervalle de mise à jour de {{discord}} Rich Presence",
"fontType_optionBuiltIn": "police intégrée",
"hotkey_playbackPlayPause": "lecture / pause",
"hotkey_rate1": "noter 1 étoile",
@@ -458,14 +463,14 @@
"discordRichPresence_description": "active l'état de lecteur dans le status d'activité {{discord}}. Les images clés sont : {{icon}}, {{playing}}, et {{paused}}",
"mpvExecutablePath": "chemin de l'exécutable mpv",
"hotkey_rate2": "noter 2 étoiles",
"playButtonBehavior_description": "définit le comportement par défaut du bouton play, lors de l'ajout de chanson à la file d'attente",
"minimumScrobblePercentage_description": "le pourcentage minimum de la chanson qui doit être joué avant qu'elle ne soit scrobbleée",
"playButtonBehavior_description": "définit le comportement par défaut du bouton Jouer/Pause, lors de l'ajout de titres à la file d'attente",
"minimumScrobblePercentage_description": "le pourcentage minimum de la chanson qui doit être joué avant qu'elle ne soit scrobblée",
"exitToTray": "quitter vers la barre des tâches",
"hotkey_rate4": "noter 4 étoiles",
"enableRemote": "activer le serveur de contrôle à distance",
"showSkipButton_description": "affiche ou cache les boutons suivants et précédents de la barre de lecture",
"savePlayQueue": "sauvegarder la liste de lecture",
"minimumScrobbleSeconds_description": "la durée minimale en secondes de la chanson qui doit être jouée avant qu'elle ne soit scrobbleée",
"minimumScrobbleSeconds_description": "la durée minimale en secondes de la chanson qui doit être jouée avant qu'elle ne soit scrobblée",
"fontType_description": "police intégré vous permet de sélectionner une des polices fourni par Feishin. Police système vous permet de sélectionner une des polices fourni par votre système d'éxploitation. personnalisé vous permet de fournir votre propre police",
"playButtonBehavior": "comportement du bouton play",
"playbackStyle_optionNormal": "normale",
@@ -497,12 +502,12 @@
"sidebarCollapsedNavigation_description": "affiche ou cache la navigation dans la barre latérale réduite",
"sidebarConfiguration": "configuration de la barre latérale",
"sidebarConfiguration_description": "sélectionnez les éléments et l'ordre dans lequel ils seront affichés dans la barre latérale",
"sidebarPlaylistList": "liste de playlist de la barre latérale",
"sidebarCollapsedNavigation": "navigation de la barre latéral (réduite)",
"sidebarPlaylistList": "liste des listes de lecture de la barre latérale",
"sidebarCollapsedNavigation": "navigation de la barre latérale (réduite)",
"skipDuration": "durée de l'avance rapide",
"sidePlayQueueStyle_optionAttached": "attaché",
"sidePlayQueueStyle": "style de la liste de lecture latérale",
"sidebarPlaylistList_description": "affiche ou cache la liste de playlist de la barre latérale",
"sidebarPlaylistList_description": "affiche ou cache la liste des listes de lecture de la barre latérale",
"sidePlayQueueStyle_description": "définit le style de la liste de lecture latérale",
"sidePlayQueueStyle_optionDetached": "détaché",
"volumeWheelStep_description": "la valeur de volume à modifier lors du défilement de la molette de la souris sur le curseur de volume",
@@ -514,11 +519,11 @@
"themeLight_description": "définit le thème clair à utiliser pour l'application",
"zoom_description": "définit le pourcentage de zoom de l'application",
"theme": "thème",
"skipPlaylistPage_description": "lors de la navigation dans une playlist, aller directement vers la liste des morceaux, au lieu de la page par défaut",
"skipPlaylistPage_description": "lors de la navigation dans une liste de lecture, aller directement vers la liste des titres, au lieu de la page par défaut",
"volumeWheelStep": "valeur du pas de volume",
"windowBarStyle": "style de la barre de la fenêtre",
"useSystemTheme_description": "suivre les préférences du système (mode clair ou sombre)",
"skipPlaylistPage": "sauter la page de playlist",
"skipPlaylistPage": "sauter la page de listes de lecture",
"themeDark": "thème (sombre)",
"windowBarStyle_description": "ajuster le style de la barre de la fenêtre",
"useSystemTheme": "utiliser le thème du système",
@@ -534,17 +539,17 @@
"replayGainMode_optionTrack": "$t(entity.track_one)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"replayGainMode_description": "ajuste le gain de volume accordement à la valeur de {{ReplayGain}} sauvegardé dans les métadonnées du fichier",
"replayGainFallback": "{{ReplayGain}} fallback",
"replayGainFallback": "valeur de repli {{ReplayGain}}",
"replayGainClipping_description": "Prévient le clipping causé par {{ReplayGain}} en baissant automatiquement le gain",
"replayGainPreamp": "préamplificateur (dB) de {{ReplayGain}}",
"replayGainClipping": "{{ReplayGain}} clipping",
"replayGainClipping": "écrêtage {{ReplayGain}}",
"replayGainMode": "mode de {{ReplayGain}}",
"replayGainFallback_description": "gain en dB à appliquer si le fichier n'a pas de tag {{ReplayGain}}",
"replayGainPreamp_description": "ajuste le gain de préampli appliqué a la valeur de {{ReplayGain}}",
"clearQueryCache": "vide le cache de feishin",
"clearCache": "vider le cache navigateur",
"buttonSize_description": "la taille des boutons de la barre de lecture",
"clearQueryCache_description": "un 'soft clear' de feishin. cela actualisera les playlists, les métadonnées des pistes, et réinitialisera les paroles enregistrées. les paramètres, identifiants serveurs et les images mises en cache sont conservés",
"clearQueryCache_description": "un 'soft clear' de Feishin. cela actualisera les liste de lecture, les métadonnées des titres, et réinitialisera les paroles enregistrées. les paramètres, identifiants du serveur et images mises en cache seront conservés",
"clearCache_description": "un 'hard clear' de feishin. en plus de vider le cache de feishin, vide le cache du navigateur (images sauvegardées et autres ressources). les identifiants serveurs et paramètres sont conservés",
"buttonSize": "taille des boutons du lecteur",
"clearCacheSuccess": "le cache a été vidé",
@@ -555,13 +560,13 @@
"homeConfiguration": "configuration de la page d'accueil",
"homeFeature": "carrousel de la page d'accueil",
"homeFeature_description": "active ou désactive le carrousel sur la page d'accueil",
"imageAspectRatio": "utiliser le rapport hauteur/largeur natif de la pochette",
"imageAspectRatio_description": "si cette option est activée, les pochettes seront affichées en utilisant leur rapport hauteur/largeur natif. pour les pochettes qui n'ont pas un rapport 1:1 (carré), l'espace restant sera vide",
"imageAspectRatio": "utiliser le rapport hauteur/largeur natif de la pochette d'album",
"imageAspectRatio_description": "si cette option est activée, les pochettes d'album seront affichées en utilisant leur rapport hauteur/largeur natif. pour les pochettes qui n'ont pas un rapport 1:1 (carré), l'espace restant sera vide",
"mpvExtraParameters_help": "un par ligne",
"passwordStore_description": "quel mot de passe utiliser. changez cela si vous rencontrez des problèmes pour stocker les mots de passe.",
"playerAlbumArtResolution": "résolution de la pochette de l'album du lecteur",
"playerAlbumArtResolution": "résolution de la pochette d'album du lecteur",
"passwordStore": "mots de passe",
"playerAlbumArtResolution_description": "la résolution pour l'aperçu de la pochette d'album agrandie du lecteur. plus grand le rend plus net, mais peut ralentir le chargement. la valeur par défaut est 0 (automatique)",
"playerAlbumArtResolution_description": "résolution pour l'aperçu de la pochette d'album agrandie du lecteur. plus grand le rend plus net, mais peut ralentir le chargement. la valeur par défaut est 0 (automatique)",
"homeConfiguration_description": "configurer quels éléments sont affichés sur la page d'accueil, et dans quel ordre",
"startMinimized": "démarrer l'application en mode réduit",
"genreBehavior_description": "détermine si cliquer sur un genre ouvre par défaut la liste des pistes ou des albums",
@@ -585,7 +590,7 @@
"contextMenu": "configuration du menu contextuel (clic droit)",
"contextMenu_description": "permet de masquer les éléments qui s'affichent dans le menu lorsque vous cliquez avec le bouton droit de la souris sur un élément. les éléments qui ne sont pas cochés seront masqués",
"albumBackground": "image d'arrière-plan de l'album",
"albumBackground_description": "ajoute une image d'arrière-plan pour les pages de l'album contenant les illustrations de l'album",
"albumBackground_description": "ajoute une image d'arrière-plan pour les pages de l'album contenant une pochette d'album",
"albumBackgroundBlur_description": "ajuste le niveau de flou appliqué à l'image d'arrière-plan de l'album",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playerbarOpenDrawer": "basculement plein écran de la barre de lecteur",
@@ -600,18 +605,18 @@
"customCss_description": "contenu css personnalisé. Remarque : le contenu et les URL distantes sont des propriétés non autorisées. Un aperçu de votre contenu est affiché ci-dessous. Des champs supplémentaires que vous n'avez pas définis sont présents en raison de la vérification.",
"translationApiKey": "clé api de traduction",
"translationTargetLanguage_description": "langue cible pour la traduction des paroles",
"transcodeNote": "prend effet après 1 (web) - 2 (mpv) chansons",
"transcodeNote": "prend effet après 1 (web) - 2 (mpv) titres",
"trayEnabled_description": "afficher ou masquer l'icône et le menu de la barre d'état système. si désactivé, désactive également la réduction et la sortie vers la barre d'état système",
"doubleClickBehavior_description": "si vrai, toutes les pistes correspondantes dans une recherche de piste seront mises en file d'attente. sinon, seule celle sur laquelle vous avez cliqué sera mise en file d'attente",
"albumBackgroundBlur": "taille du flou de l'image d'arrière-plan de l'album",
"albumBackgroundBlur": "intensité du flou de l'image d'arrière-plan de l'album",
"lastfmApiKey": "clé API {{lastfm}}",
"lastfmApiKey_description": "la clé API pour {{lastfm}} . requise pour la pochette d'album",
"lastfmApiKey_description": "la clé API pour {{lastfm}}. requise pour la pochette d'album",
"discordServeImage": "servir l'image {{discord}} depuis le serveur",
"discordServeImage_description": "partage pochette du status d'activité {{discord}} depuis le serveur lui même, disponible uniquement pour jellyfin et navidrome",
"discordServeImage_description": "partage de la pochette d'album de Rich Presence {{discord}} depuis le serveur directement (disponible uniquement pour Jellyfin et Navidrome)",
"lastfm": "affiche les liens de last.fm",
"musicbrainz_description": "affiches les liens vers musicbrainz sur les pages des artistes/albums, quand mbid existes",
"musicbrainz_description": "affiche les liens vers MusicBrainz sur les pages des artistes/albums, quand l'identifiant MusicBrainz existe",
"lastfm_description": "affiche les liens vers last.fm sur les pages des artistes/albums",
"musicbrainz": "affiches les liens musicbrainz",
"musicbrainz": "affiche les liens MusicBrainz",
"neteaseTranslation": "Activer les traductions NetEase",
"neteaseTranslation_description": "Lorsque cette option est activée, récupère et affiche les paroles traduites de NetEase si elles sont disponibles.",
"preferLocalLyrics_description": "privilégier les paroles locales aux paroles distantes lorsqu'elles sont disponibles",
@@ -621,7 +626,18 @@
"preservePitch": "préserver la hauteur",
"preservePitch_description": "préserver la hauteur lors du changement de la vitesse de lecture",
"notify": "activer les notifications des chansons",
"notify_description": "affiche une notification lors du changement de chanson"
"notify_description": "affiche une notification lors du changement de chanson",
"discordDisplayType": "type d'affichage du status {{discord}}",
"discordDisplayType_description": "modifie ce que vous écoutez dans votre statut",
"discordDisplayType_songname": "nom du morceau",
"discordDisplayType_artistname": "nom(s) dartiste",
"hotkey_navigateHome": "aller à l'accueil",
"preventSleepOnPlayback_description": "Empêche la mise en veille du lecteur lorsque la musique est en cours de lecture",
"preventSleepOnPlayback": "Empêche la mise en veille lors de la lecture",
"discordLinkType": "lien de Rich Presence {{discord}}",
"discordLinkType_description": "Ajoute des liens externes vers {{lastfm}} ou {{musicbrainz}} aux champs piste et artiste de la Rich Presence de {{discord}}. {{musicbrainz}} est la méthode la plus précise, mais nécessite des balises et ne fournit pas de liens vers les artistes, tandis que {{lastfm}} doit toujours fournir un lien. Aucune requête réseau supplémentaire n'est effectuée",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} avec {{lastfm}} si le premier n'est pas disponible"
},
"form": {
"deletePlaylist": {
@@ -640,7 +656,9 @@
"input_savePassword": "enregister le mot de passe",
"ignoreSsl": "ignorer ssl $t(common.restartRequired)",
"ignoreCors": "ignorer cors $t(common.restartRequired)",
"error_savePassword": "une erreur sest produite lors de la tentative de sauvegarde du mot de passe"
"error_savePassword": "une erreur sest produite lors de la tentative de sauvegarde du mot de passe",
"input_preferInstantMix": "Préférer le mix instantané",
"input_preferInstantMixDescription": "Utiliser uniquement le mix instantané pour jouer des pistes similaires. Activez cette option si vous avez des plugins qui modifient ce comportement"
},
"addToPlaylist": {
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) ajouté à $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -649,7 +667,7 @@
"input_playlists": "$t(entity.playlist_other)"
},
"createPlaylist": {
"title": "créer $t(entity.playlist_one)",
"title": "créer une $t(entity.playlist_one)",
"input_public": "publique",
"success": "$t(entity.playlist_one) créée avec succès",
"input_description": "$t(common.description)",
@@ -667,11 +685,11 @@
},
"editPlaylist": {
"title": "modifier $t(entity.playlist_one)",
"publicJellyfinNote": "Jellyfin n'indique pas si une playlist est publique ou non. Si vous souhaitez que cette playlist reste publique, veuillez sélectionner l'entrée suivante",
"publicJellyfinNote": "Jellyfin n'indique pas si une liste de lecture est publique ou non. Si vous souhaitez que cette liste de lecture reste publique, veuillez sélectionner l'entrée suivante",
"success": "$t(entity.playlist_one) mis à jour avec succès"
},
"lyricSearch": {
"title": "rechercher parole",
"title": "recherche de paroles",
"input_name": "$t(common.name)",
"input_artist": "$t(entity.artist_one)"
},
@@ -682,18 +700,23 @@
"success": "lien de partage copié dans le presse-papier (ou cliquez ici pour ouvrir)",
"expireInvalid": "l'expiration doit être définie à une date ultérieure",
"createFailed": "échec de la création du lien de partage (le partage est-il activé ?)"
},
"privateMode": {
"enabled": "le mode privé est activé, le statut de lecture est maintenant caché des intégrations externes",
"disabled": "le mode privé est désactivé, le statut de lecture est maintenant visible des intégrations externes",
"title": "mode privé"
}
},
"entity": {
"genre_one": "genre",
"genre_many": "genres",
"genre_other": "genres",
"playlistWithCount_one": "{{count}} playlist",
"playlistWithCount_many": "{{count}} playlists",
"playlistWithCount_other": "{{count}} playlists",
"playlist_one": "playlist",
"playlist_many": "playlists",
"playlist_other": "playlists",
"playlistWithCount_one": "{{count}} liste de lecture",
"playlistWithCount_many": "{{count}} listes de lecture",
"playlistWithCount_other": "{{count}} listes de lecture",
"playlist_one": "liste de lecture",
"playlist_many": "listes de lecture",
"playlist_other": "listes de lecture",
"artist_one": "artiste",
"artist_many": "artistes",
"artist_other": "artistes",
@@ -731,12 +754,12 @@
"trackWithCount_one": "{{count}} piste",
"trackWithCount_many": "{{count}} pistes",
"trackWithCount_other": "{{count}} pistes",
"play_one": "{{count}} écouter",
"play_many": "{{count}} écoute",
"play_other": "{{count}} écoute",
"song_one": "chanson",
"song_many": "chansons",
"song_other": "chansons"
"play_one": "{{count}} écoute",
"play_many": "{{count}} écoutes",
"play_other": "{{count}} écoutes",
"song_one": "titre",
"song_many": "titres",
"song_other": "titres"
},
"table": {
"config": {
@@ -752,7 +775,7 @@
},
"view": {
"table": "liste",
"poster": "poster",
"poster": "affiche",
"card": "Carte",
"grid": "grille",
"list": "liste"
@@ -761,7 +784,7 @@
"releaseDate": "date de sortie",
"titleCombined": "$t(common.title) (combiné)",
"dateAdded": "date d'ajout",
"lastPlayed": "dernière écoute",
"lastPlayed": "écouté récemment",
"trackNumber": "numéro de piste",
"rowIndex": "index de ligne",
"playCount": "nombre de lecture",
@@ -793,7 +816,7 @@
"album": "album",
"rating": "note",
"favorite": "favori",
"playCount": "lectures",
"playCount": "écoutes",
"releaseYear": "année",
"biography": "biographie",
"releaseDate": "date de sortie",
@@ -806,7 +829,7 @@
"path": "chemin",
"discNumber": "disque",
"albumCount": "$t(entity.album_other)",
"lastPlayed": "dernière lecture",
"lastPlayed": "écouté récemment",
"artist": "$t(entity.artist_one)",
"genre": "$t(entity.genre_one)",
"songCount": "$t(entity.track_other)",
+40 -12
View File
@@ -114,12 +114,14 @@
"codec": "codec",
"mbid": "MusicBrainz ID",
"preview": "anteprima",
"reload": "ricarica",
"reload": "aggiorna",
"share": "condividi",
"tags": "tags",
"trackGain": "normalizzazione (gain) del brano",
"trackPeak": "picco di volume del brano",
"translation": "traduzione"
"translation": "traduzione",
"bitDepth": "bit depth (profondità di bit)",
"sampleRate": "sample rate (frequenza di campionamento)"
},
"player": {
"repeat_all": "ripeti coda",
@@ -231,7 +233,7 @@
"hotkey_toggleShuffle": "attiva/disattiva mescolamento",
"theme": "tema",
"playbackStyle_description": "selezione lo stile di riproduzione da usare per il player audio",
"discordRichPresence_description": "abilita lo status del playback nello stato attività di {{discord}}. Le chiavi immagine sono: {{icon}}, {{playing}} e {{paused}}",
"discordRichPresence_description": "abilita lo stato di riproduzione nello stato attività di {{discord}}. Le chiavi immagine sono: {{icon}}, {{playing}} e {{paused}}",
"mpvExecutablePath": "percorso eseguibile mpv",
"audioDevice": "device audio",
"hotkey_rate2": "voto 2 stelle",
@@ -266,7 +268,7 @@
"customFontPath": "percorso font personalizzato",
"followLyric": "segui testo corrente",
"crossfadeDuration": "durata dissolvenza",
"discordIdleStatus": "visualizza lo stato attività in stato inattivo",
"discordIdleStatus": "mostra lo stato attività di Discord quando non stai riproducendo",
"audioPlayer": "player audio",
"hotkey_zoomOut": "rimpicciolisci layout",
"hotkey_rate0": "rimuovi voto",
@@ -331,12 +333,12 @@
"customCssNotice": "Attenzione: sebbene ci sia una certa sanitizzazione (vengono bloccati url() e content:), luso di CSS personalizzati può comunque comportare dei rischi modificando linterfaccia.",
"customCss": "css personalizzato",
"customCss_description": "contenuto CSS personalizzato. Nota: le proprietà content e gli URL remoti non sono consentiti. Di seguito è mostrata unanteprima del tuo contenuto. Sono presenti anche altri campi non impostati da te a causa della sanitizzazione.",
"discordPausedStatus": "mostra rich presence di Discord quando la riproduzione è in pausa",
"discordPausedStatus": "mostra lo stato attività di Discord quando la riproduzione è in pausa",
"discordPausedStatus_description": "quando abilitato, verrà mostrato lo stato del lettore in standby/pausa (nessun brano in riproduzione)",
"discordListening": "mostra stato come in ascolto",
"discordListening_description": "mostra lo stato come in ascolto invece che in riproduzione",
"discordServeImage": "recupera le immagini di {{discord}} dal server",
"discordServeImage_description": "condividi la copertina per la rich presence di {{discord}} direttamente dal server, disponibile solo per Jellyfin e Navidrome",
"discordServeImage_description": "condividi la copertina per lo stato attività di {{discord}} direttamente dal server, disponibile solo per Jellyfin e Navidrome",
"doubleClickBehavior": "aggiungi alla coda tutte le tracce cercate, con un doppio clic",
"doubleClickBehavior_description": "se attivato, tutte le tracce corrispondenti alla ricerca verranno aggiunte alla coda. altrimenti, verrà aggiunta alla coda solo la traccia selezionata",
"externalLinks": "mostra link esterni",
@@ -393,7 +395,20 @@
"webAudio_description": "usa audio web. abilita funzionalità avanzate come ReplayGain. disabilita se riscontri problemi",
"preservePitch": "mantieni tono (pitch)",
"preservePitch_description": "mantiene il tono (pitch) durante la modifica della velocità di riproduzione",
"volumeWidth_description": "larghezza del cursore del volume"
"volumeWidth_description": "larghezza del cursore del volume",
"discordDisplayType_description": "modifica cosa stai ascoltando nel tuo stato",
"discordDisplayType_songname": "titolo traccia",
"discordDisplayType_artistname": "nome artisti",
"hotkey_navigateHome": "vai alla schermata iniziale",
"notify": "abilita notifiche delle tracce",
"notify_description": "mostra una notifica quando cambia la traccia riprodotta",
"preventSleepOnPlayback": "non sospendere in riproduzione",
"preventSleepOnPlayback_description": "non sospendere il sistema quando la riproduzione è attiva",
"discordDisplayType": "stile dello stato su {{discord}}",
"discordLinkType": "link di attività {{discord}}",
"discordLinkType_description": "aggiunge collegamenti esterni a {{lastfm}} o {{musicbrainz}} ai campi del brano e dell'artista nell'attività {{discord}}. {{musicbrainz}} è il più accurato, ma richiede tag e non fornisce collegamenti dell'artista mentre {{lastfm}} dovrebbe sempre fornire un link. non rende richieste di rete extra",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} con {{lastfm}} fallback"
},
"error": {
"remotePortWarning": "riavvia il server per applicare la nuova porta",
@@ -418,7 +433,8 @@
"badAlbum": "stai visualizzando questa pagina perché questa canzone non fa parte di un album. probabilmente vedi questo messaggio perché hai una canzone posizionata direttamente nella cartella principale della tua libreria musicale. jellyfin raggruppa le tracce solo se si trovano allinterno di una cartella.",
"badValue": "opzione non valida \"{{value}}\". valore inesistente",
"networkError": "si è verificato un errore di rete",
"openError": "impossibile aprire il file"
"openError": "impossibile aprire il file",
"notificationDenied": "i permessi per le notifiche non sono stati concessi. questa configurazione non ha effetto"
},
"filter": {
"mostPlayed": "più riprodotti",
@@ -513,7 +529,9 @@
"openBrowserDevtools": "apri devtools browser",
"quit": "$t(common.quit)",
"goBack": "torna indietro",
"goForward": "vai avanti"
"goForward": "vai avanti",
"privateModeOff": "disabilita modalità privata",
"privateModeOn": "abilita modalità privata"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
@@ -537,14 +555,17 @@
"playSimilarSongs": "$t(player.playSimilarSongs)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "condividi elemento",
"showDetails": "mostra info"
"showDetails": "mostra info",
"goToAlbum": "vai a $t(entity.album_one)",
"goToAlbumArtist": "vai a $t(entity.albumArtist_one)"
},
"home": {
"mostPlayed": "più riprodotti",
"newlyAdded": "nuovi rilasci aggiunti",
"title": "$t(common.home)",
"explore": "esplora dalla tua libreria",
"recentlyPlayed": "riprodotti recentemente"
"recentlyPlayed": "riprodotti recentemente",
"recentlyReleased": "appena pubblicato"
},
"albumDetail": {
"moreFromArtist": "di più da questo $t(entity.artist_one)",
@@ -640,7 +661,9 @@
"input_savePassword": "salva password",
"ignoreSsl": "ignora ssl ($t(common.restartRequired))",
"ignoreCors": "ignora cors ($t(common.restartRequired))",
"error_savePassword": "si è verificato un errore quando si è provato a salvare la password"
"error_savePassword": "si è verificato un errore quando si è provato a salvare la password",
"input_preferInstantMix": "preferisci mix istantaneo",
"input_preferInstantMixDescription": "usa solo mix istantaneo per ottenere canzoni simili. utile se si dispone di plugin che modificano questo comportamento"
},
"addToPlaylist": {
"success": "aggiunto $t(entity.trackWithCount, {\"count\": {{message}} }) a $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -674,6 +697,11 @@
"success": "link di condivisione copiato negli appunti (o clicca qui per aprirlo)",
"expireInvalid": "la scadenza deve essere nel futuro",
"createFailed": "condivisione fallita (è abilitata la condivisione?)"
},
"privateMode": {
"enabled": "la modalità privata è abilitata: lo stato di riproduzione viene ora nascosto alle integrazioni esterne",
"disabled": "la modalità privata è disabilitata: lo stato di riproduzione è ora visibile alle integrazioni esterne abilitate",
"title": "modalità privata"
}
},
"table": {
+5 -1
View File
@@ -209,7 +209,11 @@
"moveToBottom": "末尾に移動",
"setRating": "評価",
"toggleSmartPlaylistEditor": "$t(entity.smartPlaylist) エディタの切り替え",
"removeFromFavorites": "$t(entity.favorite_other) から削除"
"removeFromFavorites": "$t(entity.favorite_other) から削除",
"openIn": {
"lastfm": "Last.fmで開く",
"musicbrainz": "MusicBrainzで開く"
}
},
"common": {
"backward": "戻る",
+16 -5
View File
@@ -111,7 +111,14 @@
"maximize": "maksimer",
"right": "høyre",
"sortOrder": "rekkefølge",
"tags": "tagger"
"tags": "tagger",
"newVersion": "en ny versjon har blitt installert ({{version}})",
"viewReleaseNotes": "se utgivelsesnotater",
"additionalParticipants": "ytterligere deltakere",
"albumGain": "gjennomsnittlig lydnivå for album",
"albumPeak": "høyeste lydnivå for album",
"bitDepth": "bitdybde",
"sampleRate": "samplingsfrekvens"
},
"entity": {
"smartPlaylist": "smart $t(entity.playlist_one)",
@@ -154,7 +161,7 @@
"apiRouteError": "kan ikke behandle forespørselen",
"mpvRequired": "MPV er påkrevd",
"authenticationFailed": "autentisering feilet",
"badAlbum": "du ser denne siden fordi sangen ikke er med i et album. Mest sannsynlig opplever du dette problemet fordi du har en sang helt øverst i musikkmappen. jellyfin gruperer kun spor som ligger i en mappe.",
"badAlbum": "du ser denne siden fordi sangen ikke er med i et album. Mest sannsynlig opplever du dette problemet fordi du har en sang helt øverst i musikkmappen. Jellyfin grupperer kun spor som ligger i en mappe.",
"endpointNotImplementedError": "endepunkt {{endpoint}} er ikke implementert for {{serverType}}",
"credentialsRequired": "innloggingsdetaljer er påkrevd",
"genericError": "en feil har oppstått",
@@ -172,7 +179,9 @@
"sessionExpiredError": "sesjonen din har utløpt",
"remotePortWarning": "ta omstart av serveren for å aktivere ny port",
"remoteDisableError": "en problem oppstod ved å $t(common.disable) serveren",
"remoteEnableError": "et problem oppstod ved å $t(common.enable) serveren"
"remoteEnableError": "et problem oppstod ved å $t(common.enable) serveren",
"notificationDenied": "tillatelser for varsler ble avvist. Denne innstillingen har ingen effekt",
"badValue": "ugyldig alternativ \"{{value}}\". Denne verdien eksisterer ikke lenger"
},
"filter": {
"bpm": "bpm",
@@ -215,7 +224,8 @@
"recentlyAdded": "nylig lagt til",
"channels": "$t(common.channel_other)",
"genre": "$t(entity.genre_one)",
"trackNumber": "spor"
"trackNumber": "spor",
"albumCount": "$t(entity.album_other) opptelling"
},
"form": {
"createPlaylist": {
@@ -273,7 +283,8 @@
},
"queryEditor": {
"input_optionMatchAll": "match alle",
"input_optionMatchAny": "matche hvilken som helst"
"input_optionMatchAny": "matche hvilken som helst",
"title": "redigeringsverktøy for spørringer"
}
},
"page": {
+11 -3
View File
@@ -20,7 +20,8 @@
"openIn": {
"lastfm": "Open in Last.fm",
"musicbrainz": "Open in MusicBrainz"
}
},
"moveToNext": "ga naar volgende"
},
"common": {
"backward": "achteruit",
@@ -102,7 +103,12 @@
"size": "grootte",
"reload": "herlaad",
"setting": "instelling",
"close": "sluiten"
"close": "sluiten",
"additionalParticipants": "andere deelnemers",
"newVersion": "een nieuwe versie is geinstalleerd ({{version}})",
"viewReleaseNotes": "zie release notes",
"albumGain": "album gain",
"translation": "vertaling"
},
"filter": {
"rating": "rating",
@@ -252,7 +258,9 @@
"genreWithCount_one": "{{count}} genre",
"genreWithCount_other": "{{count}} genres",
"trackWithCount_one": "{{count}} track",
"trackWithCount_other": "{{count}} tracks"
"trackWithCount_other": "{{count}} tracks",
"song_one": "lied",
"song_other": "liedjes"
},
"table": {
"column": {
+37 -7
View File
@@ -115,7 +115,13 @@
"codec": "kodek",
"preview": "podgląd",
"close": "zamknij",
"translation": "tłumaczenie"
"translation": "tłumaczenie",
"additionalParticipants": "dodatkowi uczestnicy",
"newVersion": "nowa wersja została zaintalowana ({{version}})",
"viewReleaseNotes": "zobacz notatki dotyczące wydania",
"bitDepth": "głębia bitowa",
"sampleRate": "częstotliwość próbkowania",
"tags": "tagi"
},
"entity": {
"genre_one": "gatunek",
@@ -193,7 +199,9 @@
"loginRateError": "zbyt dużo prób logowania, poczekaj chwilę i spróbuj ponownie",
"badAlbum": "ta strona jest wyświetlana, ponieważ ten utwór nie jest częścią albumu. najprawdopodobniej ten problem występuje, jeśli utwór znajduje się w nadrzędnym folderze plików z muzyką. jellyfin grupuje utwory tylko wtedy, gdy znajdują się one w folderze.",
"networkError": "wystąpił błąd sieciowy",
"openError": "nie można otworzyć pliku"
"openError": "nie można otworzyć pliku",
"badValue": "niewłaściwa opcja \"{{value}}\". ta wartość już nie istnieje",
"notificationDenied": "odmówiono uprawnień dla powiadomień. to ustawienie nie będzie miało efektu"
},
"filter": {
"mostPlayed": "najczęściej odtwarzane",
@@ -278,7 +286,8 @@
},
"queryEditor": {
"input_optionMatchAll": "dopasuj wszystkie",
"input_optionMatchAny": "dopasuj dowolne"
"input_optionMatchAny": "dopasuj dowolne",
"title": "edytor zapytań"
},
"lyricSearch": {
"input_name": "$t(common.name)",
@@ -297,6 +306,11 @@
"success": "link do udostępniania skopiowany do schowka (lub kliknij tutaj, aby otworzyć)",
"createFailed": "nie udało się utworzyć linku do udostępniania (czy udostępnianie jest włączone?)",
"expireInvalid": "ustawiony czas wygaśnięcia musi być w przyszłości"
},
"privateMode": {
"enabled": "tryb prywatny włączony, status odtwarzania jest ukryty przed usługami zewnętrznymi",
"disabled": "tryb prywatny wyłączony, status odtwarzania jest widoczny dla usług zewnętrznych",
"title": "tryb prywatny"
}
},
"page": {
@@ -333,7 +347,9 @@
"openBrowserDevtools": "otwórz narzędzia deweloperskie przeglądarki",
"quit": "$t(common.quit)",
"goBack": "do tyłu",
"goForward": "do przodu"
"goForward": "do przodu",
"privateModeOff": "wyłącz tryb prywatny",
"privateModeOn": "włącz tryb prywatny"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
@@ -357,7 +373,9 @@
"download": "pobierz",
"playShuffled": "$t(player.shuffle)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"moveToNext": "$t(action.moveToNext)"
"moveToNext": "$t(action.moveToNext)",
"goToAlbum": "przejdź do $t(entity.album_one)",
"goToAlbumArtist": "przejdź do $t(entity.albumArtist_one)"
},
"albumDetail": {
"moreFromArtist": "więcej od $t(entity.artist_one)",
@@ -389,7 +407,8 @@
"home": "$t(common.home)",
"artists": "$t(entity.artist_other)",
"albumArtists": "$t(entity.albumArtist_other)",
"shared": "udostępnione $t(entity.playlist_other)"
"shared": "udostępnione $t(entity.playlist_other)",
"myLibrary": "Moja biblioteka"
},
"home": {
"mostPlayed": "najczęściej odtwarzane",
@@ -707,7 +726,18 @@
"doubleClickBehavior_description": "jeżeli włączone, wszystkie pasujące utwory w wyszukiwaniu zostaną zakolejkowane. w przeciwnym wypadku, tylko kliknięty będzie zakolejkowany",
"lastfmApiKey": "klucz API {{lastfm}}",
"lastfmApiKey_description": "klucz API dla {{lastfm}}. wymagany dla okładek",
"translationTargetLanguage": "docelowy język tłumaczenia"
"translationTargetLanguage": "docelowy język tłumaczenia",
"discordPausedStatus_description": "jeżeli włączone, status będzie pokazywany kiedy odtwarzanie jest wstrzymane",
"preferLocalLyrics": "preferuj lokalne teksty",
"preferLocalLyrics_description": "jeśli to możliwe, preferuj lokalne teksty zamiast tekstów zdalnych",
"lastfm": "pokazuj linki do last.fm",
"lastfm_description": "pokazuj linki do last.fm na stronach artystów/albumów",
"notify": "włącz powiadomienia o piosenkach",
"musicbrainz": "pokazuj linki do musicbrainz",
"musicbrainz_description": "pokazuj linki do musicbrainz na stronach artystów/albumów, gdzie istnieje mbid",
"discordPausedStatus": "pokaż status podczas pauzy",
"discordServeImage": "wysyłaj obrazy dla {{discord}} z serwera",
"discordServeImage_description": "pokazuj okładki w statusie {{discord}} prosto z serwera, dostępne tylko dla jellyfin i navidrome"
},
"table": {
"config": {
+545
View File
@@ -0,0 +1,545 @@
{
"action": {
"addToFavorites": "adicionar a $t(entity.favorite_other)",
"addToPlaylist": "adicionar a $t(entity.playlist_one)",
"clearQueue": "limpar fila",
"createPlaylist": "criar $t(entity.playlist_one)",
"deletePlaylist": "apagar $t(entity.playlist_one)",
"deselectAll": "desmarcar todos",
"editPlaylist": "editar $t(entity.playlist_one)",
"goToPage": "vá para página",
"moveToNext": "mover para o próximo",
"moveToBottom": "mover para baixo",
"moveToTop": "mover para o topo",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "remover de $t(entity.favorite_other)",
"removeFromPlaylist": "remover da $t(entity.playlist_one)",
"removeFromQueue": "remover da fila",
"setRating": "definir classificação",
"toggleSmartPlaylistEditor": "alternar editor $t(entity.smartPlaylist)",
"viewPlaylists": "ver $t(entity.playlist_other)",
"openIn": {
"lastfm": "Abrir em Last.fm",
"musicbrainz": "Abrir em MusicBrainz"
}
},
"common": {
"action_one": "ação",
"action_many": "ações",
"action_other": "ações",
"add": "adicionar",
"additionalParticipants": "participantes adicionais",
"newVersion": "uma nova versão foi instalada ({{version}})",
"viewReleaseNotes": "ver notas de lançamento",
"albumGain": "ganho do álbum",
"albumPeak": "pico do álbum",
"areYouSure": "tem certeza?",
"ascending": "ascendente",
"backward": "para trás",
"biography": "biografia",
"bitrate": "taxa de bits",
"bpm": "bpm",
"cancel": "cancelar",
"center": "centro",
"channel_one": "canal",
"channel_many": "canais",
"channel_other": "canais",
"clear": "limpar",
"close": "fechar",
"codec": "codec",
"collapse": "minimizar",
"comingSoon": "em breve…",
"configure": "configurar",
"confirm": "confirmar",
"create": "criar",
"currentSong": "$t(entity.track_one) atual",
"decrease": "diminuir",
"delete": "apagar",
"descending": "abaixar",
"description": "descrição",
"disable": "desativar",
"disc": "disco",
"dismiss": "liberar",
"duration": "duração",
"edit": "editar",
"enable": "ativar",
"expand": "expandir",
"favorite": "favorito",
"filter_one": "filtro",
"filter_many": "filtros",
"filter_other": "filtros",
"filters": "filtros",
"forceRestartRequired": "reinicie para aplicar as alterações… feche a notificação para reiniciar",
"forward": "para frente",
"gap": "intervalo",
"home": "início",
"increase": "incrementar",
"left": "esquerda",
"limit": "limite",
"manage": "gerir",
"maximize": "maximizar",
"menu": "menu",
"minimize": "minimizar",
"modified": "modificado",
"mbid": "ID no MusicBrainz",
"name": "nome",
"no": "não",
"none": "nenhum",
"noResultsFromQuery": "a consulta não retornou resultados",
"note": "observação",
"ok": "ok",
"owner": "dono",
"path": "caminho",
"playerMustBePaused": "o player deve estar pausado",
"preview": "pré-visualizar",
"previousSong": "anterior $t(entity.track_one)",
"quit": "sair",
"random": "aleatório",
"rating": "classificação",
"refresh": "atualizar",
"reload": "recarregar",
"reset": "reiniciar",
"resetToDefault": "restaurar ao padrão",
"restartRequired": "é necessário reiniciar",
"right": "direita",
"save": "gravar",
"saveAndReplace": "gravar e substituir",
"saveAs": "gravar como",
"search": "procurar",
"setting": "configuração",
"share": "partilhar",
"size": "tamanho",
"sortOrder": "ordem",
"tags": "tags",
"title": "titulo",
"trackNumber": "faixa",
"trackGain": "ganho da faixa",
"trackPeak": "pico da faixa",
"translation": "tradução",
"unknown": "desconhecido",
"version": "versão",
"year": "ano",
"yes": "sim"
},
"entity": {
"album_one": "álbum",
"album_many": "álbuns",
"album_other": "álbuns",
"albumArtist_one": "artista do álbum",
"albumArtist_many": "artistas do álbum",
"albumArtist_other": "artistas do álbum",
"albumArtistCount_one": "{{count}} artista do álbum",
"albumArtistCount_many": "{{count}} artistas do álbum",
"albumArtistCount_other": "{{count}} artistas do álbum",
"albumWithCount_one": "{{count}} álbum",
"albumWithCount_many": "{{count}} álbuns",
"albumWithCount_other": "{{count}} álbuns",
"artist_one": "artista",
"artist_many": "artistas",
"artist_other": "artistas",
"artistWithCount_one": "{{count}} artista",
"artistWithCount_many": "{{count}} artistas",
"artistWithCount_other": "{{count}} artistas",
"favorite_one": "favorito",
"favorite_many": "favoritos",
"favorite_other": "favoritos",
"folder_one": "pasta",
"folder_many": "pastas",
"folder_other": "pastas",
"folderWithCount_one": "{{count}} pasta",
"folderWithCount_many": "{{count}} pastas",
"folderWithCount_other": "{{count}} pastas",
"genre_one": "gênero",
"genre_many": "gêneros",
"genre_other": "gêneros",
"genreWithCount_one": "{{count}} gênero",
"genreWithCount_many": "{{count}} gêneros",
"genreWithCount_other": "{{count}} gêneros",
"playlist_one": "playlist",
"playlist_many": "playlists",
"playlist_other": "playlists",
"play_one": "{{count}} reprodução",
"play_many": "{{count}} reproduções",
"play_other": "{{count}} reproduções",
"playlistWithCount_one": "{{count}} playlist",
"playlistWithCount_many": "{{count}} playlists",
"playlistWithCount_other": "{{count}} playlists",
"smartPlaylist": "$t(entity.playlist_one) inteligente",
"track_one": "faixa",
"track_many": "faixas",
"track_other": "faixas",
"song_one": "música",
"song_many": "músicas",
"song_other": "músicas",
"trackWithCount_one": "{{count}} faixa",
"trackWithCount_many": "{{count}} faixas",
"trackWithCount_other": "{{count}} faixas"
},
"error": {
"apiRouteError": "não é possível encaminhar a solicitação",
"audioDeviceFetchError": "ocorreu um erro ao tentar obter dispositivos de áudio",
"authenticationFailed": "falha na autenticação",
"badAlbum": "está a ver este erro por que está música não é parte de algum album. um motivo comum para si estar a ver este erro é se a sua música estiver na raiz da sua pasta de músicas. o jellyfin apenas agrupa as músicas se elas estiveram na mesma pasta.",
"badValue": "opção inválida \"{{value}}\". este valor não existe no momento",
"credentialsRequired": "credenciais necessárias",
"endpointNotImplementedError": "endpoint {{endpoint}} não está implementado para {{serverType}}",
"genericError": "um erro ocorreu",
"invalidServer": "servidor inválido",
"localFontAccessDenied": "acesso a fontes locais rejeitado",
"loginRateError": "muitas tentativas de login, tente novamente em alguns segundos",
"mpvRequired": "MPV necessário",
"networkError": "ocorreu um erro na internet",
"openError": "não foi possível abrir o ficheiro",
"playbackError": "ocorreu um erro ao tentar reproduzir a média",
"remoteDisableError": "ocorreu um erro ao tentar $t(common.disable) o servidor remoto",
"remoteEnableError": "ocorreu um erro ao tentar $t(common.enable) o servidor remoto",
"remotePortError": "ocorreu um erro ao tentar definir a porta do servidor remoto",
"remotePortWarning": "reinicie o servidor para aplicar a nova porta",
"serverNotSelectedError": "nenhum servidor selecionado",
"serverRequired": "servidor necessário",
"sessionExpiredError": "a sua sessão expirou",
"systemFontError": "ocorreu um erro ao tentar obter fontes do sistema"
},
"filter": {
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"albumCount": "número de $t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "bibliografia",
"bitrate": "bitrate",
"bpm": "bpm",
"channels": "$t(common.channel_other)",
"comment": "comentário",
"communityRating": "Nota da comunidade",
"criticRating": "avaliação da crítica",
"dateAdded": "data de adição",
"disc": "disco",
"duration": "duração",
"favorited": "favoritado",
"fromYear": "a partir do ano",
"genre": "$t(entity.genre_one)",
"id": "id",
"isCompilation": "é compilação",
"isFavorited": "é favoritado",
"isPublic": "é público",
"isRated": "possui avaliação",
"isRecentlyPlayed": "foi tocado recentemente",
"lastPlayed": "última tocada",
"mostPlayed": "mais tocado",
"name": "nome",
"note": "nota",
"owner": "$t(common.owner)",
"path": "caminho",
"playCount": "contador de reproduções",
"random": "aleatório",
"rating": "avaliação",
"recentlyAdded": "adicionado recentemente",
"recentlyPlayed": "tocado recentemente",
"recentlyUpdated": "atualizado recentemente",
"releaseDate": "data de lançamento",
"releaseYear": "ano de lançamento",
"search": "buscar",
"songCount": "contador de músicas",
"title": "titulo",
"toYear": "até o ano",
"trackNumber": "faixa"
},
"form": {
"addServer": {
"error_savePassword": "um erro ocorreu ao tentar gravar a palavra-passe",
"ignoreCors": "ignorar CORS ($t(common.restartRequired))",
"ignoreSsl": "ignorar ssl ($t(common.restartRequired))",
"input_legacyAuthentication": "ativar autenticação legada",
"input_name": "nome do servidor",
"input_password": "palavra-passe",
"input_savePassword": "gravar palavra-passe",
"input_url": "url",
"input_username": "nome de utilizador",
"success": "servidor adicionado com sucesso",
"title": "adicionar servidor"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"input_skipDuplicates": "pular duplicadas",
"success": "adicionado $t(entity.trackWithCount, {\"count\": {{message}} }) para $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "adicionar à $t(entity.playlist_one)"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "público",
"success": "$t(entity.playlist_one) criada com sucesso",
"title": "criar $t(entity.playlist_one)"
},
"deletePlaylist": {
"input_confirm": "escreva o nome da $t(entity.playlist_one) para confirmar",
"success": "$t(entity.playlist_one) apagada com sucesso",
"title": "apagar $t(entity.playlist_one)"
},
"editPlaylist": {
"publicJellyfinNote": "O Jellyfin por algum motivo não expõe se uma playlist é pública ou não. Se deseja que ela permaneça pública, por favor selecione a seguinte entrada",
"success": "$t(entity.playlist_one) atualizada com sucesso",
"title": "editar $t(entity.playlist_one)"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "pesquisa de letras"
},
"queryEditor": {
"input_optionMatchAll": "corresponder todos",
"input_optionMatchAny": "corresponder qualquer um"
},
"shareItem": {
"allowDownloading": "permitir descargas",
"description": "descrição",
"setExpiration": "definir expiração",
"success": "ligação de compartilhamento copiado para a área de transferência (ou clique aqui para abrir)",
"expireInvalid": "a expiração deve ser uma data futura",
"createFailed": "falha ao criar compartilhamento (o compartilhamento está ativado?)"
},
"updateServer": {
"success": "servidor atualizado com sucesso",
"title": "atualizar servidor"
}
},
"page": {
"albumArtistDetail": {
"about": "Sobre {{artist}}",
"appearsOn": "aparece em",
"recentReleases": "lançamentos recentes",
"viewDiscography": "ver discografia",
"relatedArtists": "$t(entity.artist_other) relacionados",
"topSongs": "músicas mais tocadas",
"topSongsFrom": "músicas mais tocadas de {{title}}",
"viewAll": "ver tudo",
"viewAllTracks": "ver todas as $t(entity.track_other)"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "mais deste $t(entity.artist_one)",
"moreFromGeneric": "mais que {{elemento}}",
"released": "lançado"
},
"albumList": {
"artistAlbums": "álbuns de {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"title": "$t(entity.album_other)"
},
"appMenu": {
"collapseSidebar": "recolher barra lateral",
"expandSidebar": "expandir barra lateral",
"goBack": "voltar",
"goForward": "avançar",
"manageServers": "gerir servidores",
"openBrowserDevtools": "abrir ferramentas do programador",
"quit": "$t(common.quit)",
"selectServer": "selecionar servidor",
"settings": "$t(common.setting_other)",
"version": "versão {{version}}"
},
"manageServers": {
"title": "gerir servidores",
"serverDetails": "pormenores do servidor",
"url": "URL",
"username": "nome de utilizador",
"editServerDetailsTooltip": "editar pormenores do servidor",
"removeServer": "remover servidor"
},
"contextMenu": {
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"download": "descarregar",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"numberSelected": "{{count}} selecionado",
"play": "$t(player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "partilhar elemento",
"showDetails": "obter informações"
},
"fullscreenPlayer": {
"config": {
"dynamicBackground": "fundo dinâmico",
"dynamicImageBlur": "tamanho do desfoque da imagem",
"dynamicIsImage": "ativar imagem de fundo",
"followCurrentLyric": "acompanhar letra",
"lyricAlignment": "alinhamento da letra",
"lyricOffset": "deslocamento da letra (ms)",
"lyricGap": "espaçamento da letra",
"lyricSize": "tamanho da letra",
"opacity": "opacidade",
"showLyricMatch": "exibir correspondência da letra",
"showLyricProvider": "exibir origem da letra",
"synchronized": "sincronizado",
"unsynchronized": "não sincronizado",
"useImageAspectRatio": "usar proporção da imagem"
},
"lyrics": "letra",
"related": "relacionado",
"upNext": "a seguir",
"visualizer": "visualizador",
"noLyrics": "nenhuma letra encontrada"
},
"genreList": {
"showAlbums": "mostrar $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "mostrar $t(entity.genre_one) $t(entity.track_other)",
"title": "$t(entity.genre_other)"
},
"globalSearch": {
"commands": {
"goToPage": "ir à página",
"searchFor": "procurar {{query}}",
"serverCommands": "comandos do servidor"
},
"title": "comandos"
},
"home": {
"explore": "explore a sua biblioteca",
"mostPlayed": "mais tocado",
"newlyAdded": "lançamentos recém-adicionados",
"recentlyPlayed": "tocado recentemente",
"title": "$t(common.home)"
},
"itemDetail": {
"copyPath": "copiar caminho para a área de transferência",
"copiedPath": "caminho copiado com sucesso",
"openFile": "mostrar faixa no gestor de ficheiros"
},
"playlist": {
"reorder": "reordenar apenas disponível quando ordenado pelo id"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"setting": {
"advanced": "avançado",
"generalTab": "geral",
"hotkeysTab": "teclas de atalho",
"playbackTab": "reprodução",
"windowTab": "janela"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"artists": "$t(entity.artist_other)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"myLibrary": "a minha biblioteca",
"nowPlaying": "agora a tocar",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"shared": "$t(entity.playlist_other) partilhada",
"tracks": "$t(entity.track_other)"
},
"trackList": {
"artistTracks": "faixas de {{artist}}",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"title": "$t(entity.track_other)"
}
},
"player": {
"addLast": "adicionar no final",
"addNext": "adicionar a seguir",
"favorite": "favorito",
"mute": "mudo",
"muted": "mudo",
"next": "próximo",
"play": "tocar",
"playbackFetchCancel": "isto demora um pouco... feche a notificação para cancelar",
"playbackFetchInProgress": "a carregar músicas…",
"playbackFetchNoResults": "nenhuma música encontrada",
"playbackSpeed": "velocidade de reprodução",
"playRandom": "tocar aleatório",
"playSimilarSongs": "tocar músicas similares",
"previous": "anterior",
"queue_clear": "limpar fila",
"queue_moveToBottom": "mover selecionados para o topo",
"queue_moveToTop": "mover selecionados para o fim",
"queue_remove": "remover selecionados",
"repeat": "repetir",
"repeat_all": "repetir tudo",
"repeat_off": "repetição desativada",
"shuffle": "tocar aleatório",
"shuffle_off": "aleatório desativado",
"skip": "pular",
"skip_back": "retroceder",
"skip_forward": "avançar",
"stop": "parar",
"toggleFullscreenPlayer": "alternar player de ecrã cheio",
"unfavorite": "remover favorito",
"pause": "pausar",
"viewQueue": "ver fila"
},
"setting": {
"accentColor": "cor de realce",
"accentColor_description": "define a cor de realce para a aplicação",
"albumBackground": "imagem de fundo do álbum",
"albumBackground_description": "adiciona uma imagem de fundo contendo a arte do álbum para a página de álbum",
"albumBackgroundBlur": "tamanho de desfoque da imagem de fundo do álbum",
"albumBackgroundBlur_description": "ajusta a quantidade de desfoque aplicada para a imagem de fundo do álbum",
"applicationHotkeys": "teclas de atalho da aplicação",
"applicationHotkeys_description": "configure as teclas de atalho da aplicação. clique na caixa de seleção para definir como tecla de atalho global (somente desktop)",
"artistConfiguration": "configuração da página de artista de álbum",
"artistConfiguration_description": "configure quais elementos serão mostrados, e em qual ordem, na página de artista de álbum",
"audioDevice": "dispositivo de áudio",
"audioDevice_description": "selecione o dispositivo de áudio usado para reprodução (somente player web)",
"audioExclusiveMode": "modo de áudio exclusivo",
"audioExclusiveMode_description": "ativar modo de saída exclusiva. Neste modo, o sistema é geralmente bloqueado, e apenas mpv terá saída de áudio",
"audioPlayer": "player de áudio",
"audioPlayer_description": "selecione o player de áudio usado para reprodução",
"buttonSize": "tamanho do botão da barra de reprodução",
"buttonSize_description": "o tamanho dos botões da barra de reprodução",
"clearCache": "limpar cache do navegador",
"clearCache_description": "uma 'limpeza geral' do feishin. em adição a limpar o cache do feishin, limpa o cache do navegador (imagens gravadas e outros recursos). as credenciais de servidor e as configurações serão mantidas",
"clearQueryCache": "limpar cache do feishin",
"clearQueryCache_description": "uma 'limpeza leve' do feishin. isto irá renovar playlists, metadados de faixas, e resetar letras gravadas. as configurações, as credenciais de servidor e o cache de imagens serão mantidos",
"clearCacheSuccess": "cache limpo com sucesso",
"contextMenu": "configuração do menu de contexto (clique do botão direito do rato)",
"contextMenu_description": "permite esconder elementos exibidos no menu quando clica num elemento com o botão direito. elementos não selecionados serão escondidos",
"crossfadeDuration": "duraçao de crossfade",
"crossfadeDuration_description": "define a duração do efeito crossfade",
"crossfadeStyle": "estilo do crossfade",
"crossfadeStyle_description": "seleciona qual estilo de crossfade usado no player de áudio",
"customCssEnable": "ativar css customizado",
"customCssEnable_description": "permite escrever css customizado.",
"customCssNotice": "Aviso: apesar de existir alguma higienização (url() e content: não são permitidas), o uso de CSS personalizado ainda pode representar riscos ao alterar a interface.",
"customCss": "css customizado",
"disableAutomaticUpdates": "desativar atualizações automáticas",
"disableLibraryUpdateOnStartup": "desativar a verificação de novas versões na inicialização",
"discordApplicationId": "{{discord}} ID da aplicação",
"discordIdleStatus_description": "quando ativado, atualiza o estado enquanto o player está ocioso",
"discordUpdateInterval_description": "o tempo em segundos entre cada atualização (mínimo 15 segundos)",
"playButtonBehavior_description": "define o comportamento padrão do botão play ao adicionar músicas à fila"
},
"table": {
"column": {
"discNumber": "disco",
"size": "$t(common.size)",
"title": "titulo"
},
"config": {
"label": {
"discNumber": "numero do disco",
"titleCombined": "$t(common.title) (combinado)"
}
}
}
}
+831
View File
@@ -0,0 +1,831 @@
{
"action": {
"addToFavorites": "pridať do $t(entity.favorite_other)",
"addToPlaylist": "pridať do $t(entity.playlist_one)",
"clearQueue": "vymazať frontu",
"createPlaylist": "vytvoriť $t(entity.playlist_one)",
"deletePlaylist": "odstrániť $t(entity.playlist_one)",
"deselectAll": "odznačiť všetko",
"editPlaylist": "upraviť $t(entity.playlist_one)",
"goToPage": "ísť na stránku",
"moveToNext": "prejsť na ďalší",
"moveToBottom": "presunúť sa na spodok",
"moveToTop": "presunúť sa navrch",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "odstrániť z $t(entity.favorite_other)",
"removeFromPlaylist": "odstrániť z $t(entity.playlist_one)",
"removeFromQueue": "odstrániť z fronty",
"setRating": "ohodnotiť",
"toggleSmartPlaylistEditor": "prepnúť $t(entity.smartPlaylist) editor",
"viewPlaylists": "zobraziť $t(entity.playlist_other)",
"openIn": {
"lastfm": "Otvoriť v Last.fm",
"musicbrainz": "Otvoriť v MusicBrainz"
}
},
"common": {
"action_one": "akcia",
"action_few": "akcie",
"action_other": "akcií",
"add": "pridať",
"additionalParticipants": "ďalší účastníci",
"newVersion": "bola nainštalovaná nová verzia ({{version}})",
"viewReleaseNotes": "zobraziť poznámky k vydaniu",
"albumGain": "hranosť albumu",
"albumPeak": "vrchol albumu",
"areYouSure": "ste si istý?",
"ascending": "vzostupne",
"backward": "dozadu",
"biography": "životopis",
"bitDepth": "bitová hĺbka",
"bitrate": "bitrate",
"bpm": "bpm",
"cancel": "zrušiť",
"center": "uprostred",
"channel_one": "kanál",
"channel_few": "kanály",
"channel_other": "kanálov",
"clear": "vyčistiť",
"close": "zavrieť",
"codec": "kodek",
"collapse": "zbaliť",
"comingSoon": "čoskoro…",
"configure": "nastaviť",
"confirm": "potvrdiť",
"create": "vytvoriť",
"currentSong": "aktuálne $t(entity.track_one)",
"decrease": "znížiť",
"delete": "zmazať",
"descending": "zostupne",
"description": "popis",
"disable": "zakázať",
"disc": "disk",
"dismiss": "zamietnuť",
"duration": "dĺžka",
"edit": "zmeniť",
"enable": "povoliť",
"expand": "rozbaliť",
"favorite": "obľúbené",
"filter_one": "filter",
"filter_few": "filtre",
"filter_other": "filtrov",
"filters": "filtre",
"forceRestartRequired": "zmeny vyžadujú reštart... zavretím upozornenia sa aplikácia reštartuje",
"forward": "dopredu",
"gap": "medzera",
"home": "domov",
"increase": "zvýšiť",
"left": "vľavo",
"limit": "limit",
"manage": "spravovať",
"maximize": "maximalizovať",
"menu": "ponuka",
"minimize": "minimalizovať",
"modified": "zmenené",
"mbid": "MusicBrainz ID",
"name": "meno",
"no": "nie",
"none": "žiadny",
"noResultsFromQuery": "dopyt nevrátil žiadne výsledky",
"note": "poznámka",
"ok": "ok",
"owner": "majiteľ",
"path": "cesta",
"playerMustBePaused": "prehrávač musí byť pozastavený",
"preview": "náhľad",
"previousSong": "predchádzajúca $t(entity.track_one)",
"quit": "ukončiť",
"random": "náhodne",
"rating": "hodnotenie",
"refresh": "obnoviť",
"reload": "znovu načítať",
"reset": "resetovať",
"resetToDefault": "resetovať na predvolené",
"restartRequired": "vyžaduje sa reštart",
"right": "vpravo",
"sampleRate": "vzorkovacia frekvencia",
"save": "uložiť",
"saveAndReplace": "uložiť a nahradiť",
"saveAs": "uložiť ako",
"search": "vyhľadať",
"setting": "nastavenie",
"share": "zdieľať",
"size": "veľkosť",
"sortOrder": "poradie",
"tags": "štítky",
"title": "názov",
"trackNumber": "skladba",
"trackGain": "hranosť skladby",
"trackPeak": "vrchol skladby",
"translation": "preklad",
"unknown": "neznámy",
"version": "verzia",
"year": "rok",
"yes": "áno"
},
"filter": {
"name": "meno",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"albumCount": "$t(entity.album_other) počet",
"artist": "$t(entity.artist_one)",
"biography": "životopis",
"bitrate": "bitrate",
"bpm": "bpm",
"channels": "$t(common.channel_other)",
"comment": "komentár",
"communityRating": "hodnotenie komunity",
"criticRating": "hodnotenie kritiky",
"dateAdded": "dátum pridania",
"disc": "disk",
"duration": "dĺžka",
"favorited": "obľúbené",
"fromYear": "od roku",
"genre": "$t(entity.genre_one)",
"id": "id",
"isCompilation": "je kompilácia",
"isFavorited": "je obľúbený",
"isPublic": "je verejný",
"isRated": "je hodnotený",
"isRecentlyPlayed": "je nedávno hraný",
"lastPlayed": "posledne hraný",
"mostPlayed": "najhranejší",
"note": "poznámka",
"owner": "$t(common.owner)",
"path": "cesta",
"playCount": "počet prehraní",
"random": "náhodne",
"rating": "hodnotenie",
"recentlyAdded": "nedávno pridané",
"recentlyPlayed": "nedávno hrané",
"recentlyUpdated": "nedávno aktualizované",
"releaseDate": "dátum vydania",
"releaseYear": "rok vydania",
"search": "vyhľadať",
"songCount": "počet skladieb",
"title": "názov",
"toYear": "do roku",
"trackNumber": "stopa"
},
"form": {
"addServer": {
"input_name": "názov servera",
"input_username": "užívateľské meno",
"error_savePassword": "pri pokuse o uloženie hesla sa vyskytla chyba",
"ignoreCors": "ignorovať cors ($t(common.restartRequired)",
"ignoreSsl": "ignorovať ssl ($t(common.restartRequired))",
"input_legacyAuthentication": "povoliť zastarané overenie",
"input_password": "heslo",
"input_savePassword": "uložiť heslo",
"input_url": "url",
"success": "server úspešne pridaný",
"title": "pridať server"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"input_skipDuplicates": "preskočiť duplicity",
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) pridané do $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "pridať do $t(entity.playlist_one)"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "verejný",
"success": "$t(entity.playlist_one) úspešne vytvorený",
"title": "vytvoriť $t(entity.playlist_one)"
},
"deletePlaylist": {
"input_confirm": "pre potvrdenie zadajte názov $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) bol úspešne odstránený",
"title": "odstrániť $t(entity.playlist_one)"
},
"editPlaylist": {
"publicJellyfinNote": "Jellyfin z nejakého dôvodu neinformuje, či je playlist verejný alebo nie. Ak si ho želáte ponechať ako verejný, ponechajte nasledujúci vstup ako povolený",
"success": "$t(entity.playlist_one) úspešne aktualizovaný",
"title": "upraviť $t(entity.playlist_one)"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "vyhľadať text skladby"
},
"queryEditor": {
"title": "editor dopytov",
"input_optionMatchAll": "zhoda na všetkých",
"input_optionMatchAny": "zhoda na ľubovoľnom"
},
"shareItem": {
"allowDownloading": "povoliť sťahovanie",
"description": "popis",
"setExpiration": "nastaviť vypršanie platnosti",
"success": "zdieľať link skopírovaný do schránky (alebo klinutím otvoriť tu)",
"expireInvalid": "vypršanie platnosti musí byť v budúcnosti",
"createFailed": "nepodarilo sa nazdielať (je zdieľanie povolené)"
},
"updateServer": {
"success": "server bol úspešne aktualizovaný",
"title": "aktualizovať server"
},
"privateMode": {
"enabled": "je zapnutý súkromný režim, status prehrávania je pre vonkajšie integrácie skrytý",
"disabled": "súkromný režim je vypnutý, status prehrávania je teraz pre vonkajšie integrácie povolený",
"title": "súkromný režim"
}
},
"entity": {
"album_one": "album",
"album_few": "albumy",
"album_other": "albumov",
"albumArtist_one": "interpret albumu",
"albumArtist_few": "interpreti albumu",
"albumArtist_other": "interpretov albumu",
"albumArtistCount_one": "{{count}} interpret albumu",
"albumArtistCount_few": "{{count}} interpreti albumu",
"albumArtistCount_other": "{{count}} interpretov albumu",
"albumWithCount_one": "{{count}} album",
"albumWithCount_few": "{{count}} albumy",
"albumWithCount_other": "{{count}} albumov",
"artist_one": "interpret",
"artist_few": "interpreti",
"artist_other": "interpretov",
"artistWithCount_one": "{{count}} interpret",
"artistWithCount_few": "{{count}} interpreti",
"artistWithCount_other": "{{count}} interpretov",
"favorite_one": "obľúbený",
"favorite_few": "obľúbení",
"favorite_other": "obľúbených",
"folder_one": "priečinok",
"folder_few": "priečinky",
"folder_other": "priečinkov",
"folderWithCount_one": "{{count}} priečinok",
"folderWithCount_few": "{{count}} priečinky",
"folderWithCount_other": "{{count}} priečinkov",
"genre_one": "žáner",
"genre_few": "žánre",
"genre_other": "žánrov",
"genreWithCount_one": "{{count}} žáner",
"genreWithCount_few": "{{count}} žánre",
"genreWithCount_other": "{{count}} žánrov",
"playlist_one": "playlist",
"playlist_few": "playlisty",
"playlist_other": "playlistov",
"play_one": "{{count}} prehranie",
"play_few": "{{count}} prehrania",
"play_other": "{{count}} prehraní",
"playlistWithCount_one": "{{count}} playlist",
"playlistWithCount_few": "{{count}} playlisty",
"playlistWithCount_other": "{{count}} playlistov",
"smartPlaylist": "smart $t(entity.playlist_one)",
"track_one": "stopa",
"track_few": "stopy",
"track_other": "stôp",
"song_one": "skladba",
"song_few": "skladby",
"song_other": "skladieb",
"trackWithCount_one": "{{count}} stopa",
"trackWithCount_few": "{{count}} stopy",
"trackWithCount_other": "{{count}} stôp"
},
"error": {
"apiRouteError": "nie je možné zaslať požiadavku",
"audioDeviceFetchError": "pri vyhľadávaní zvukových zariadení sa vyskytla chyba",
"authenticationFailed": "overenie zlyhalo",
"badAlbum": "túto stránku vidíte, pretože táto skladba nie je súčasťou albumu. najčastejšou príčinou tohto problému býva umiestnenie skladby priamo v priečinku hudobnej knižnice. jellyfin navzájom prepája iba skladby, ktoré sú spolu v jednom priečinku.",
"badValue": "neplatná možnosť \"{{value}}\". táto hodnota už neexistuje",
"credentialsRequired": "vyžadujú sa prihlasovacie údaje",
"endpointNotImplementedError": "koncový bod {{endpoint}} nie je implementovaný v {{serverType}}",
"genericError": "vyskyla sa chyba",
"invalidServer": "neplatný server",
"localFontAccessDenied": "prístup k lokálnym fontom bol odmietnutý",
"loginRateError": "príliš veľa pokusov o prihlásenie, skúste to znova o pár sekúnd",
"mpvRequired": "vyžaduje sa MPV",
"networkError": "vyskytla sa chyba siete",
"notificationDenied": "povolenia na oznámenia boli odmietnuté. toto nastavenie nemá žiadny účinok",
"openError": "súbor nebolo možné otvoriť",
"playbackError": "pri prehrávaní média sa vyskytla chyba",
"remoteDisableError": "pri pokuse o $t(common.disable) vzdialeného severa sa vyskytla chyba",
"remoteEnableError": "pri pokuse o $t(common.enable) vzdialeného servera sa vyskytla chyba",
"remotePortError": "pri nastavovaní portu vzdialeného servera sa vyskytla chyba",
"remotePortWarning": "pre použitie nového portu reštartujte server",
"serverNotSelectedError": "nebol vybraný žiadny server",
"serverRequired": "vyžaduje sa server",
"sessionExpiredError": "vaša relácia vypršala",
"systemFontError": "pri pokuse o získanie systémových fontov sa vyskytla chyba"
},
"page": {
"albumArtistDetail": {
"about": "O {{artist}}",
"appearsOn": "vyskytuje sa na",
"recentReleases": "posledné vydania",
"viewDiscography": "zobraziť diskografiu",
"relatedArtists": "súvisiaci s $t(entity.artist_other)",
"topSongs": "top skladby",
"topSongsFrom": "top skladby z {{title}}",
"viewAll": "zobraziť všetko",
"viewAllTracks": "zobraziť všetky $t(entity.track_other)"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "viac od $t(entity.artist_one)",
"moreFromGeneric": "viac z {{item}}",
"released": "vydané"
},
"albumList": {
"artistAlbums": "albumy {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"title": "$t(entity.album_other)"
},
"appMenu": {
"collapseSidebar": "zbaliť bočnú lištu",
"expandSidebar": "rozbaliť bočnú lištu",
"goBack": "ísť naspäť",
"goForward": "ísť dopredu",
"manageServers": "spravovať servery",
"privateModeOff": "vypnúť súkromný režim",
"privateModeOn": "zapnúť súkromný režim",
"openBrowserDevtools": "otvoriť vývojárske nástroje prehliadača",
"quit": "$t(common.quit)",
"selectServer": "vybrať server",
"settings": "$t(common.setting_other)",
"version": "verzia {{version}}"
},
"manageServers": {
"title": "spravovať servery",
"serverDetails": "podrobnosti servera",
"url": "URL",
"username": "užívateľské meno",
"editServerDetailsTooltip": "zmeniť podrobnosti servera",
"removeServer": "odstrániť server"
},
"contextMenu": {
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"download": "stiahnuť",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"numberSelected": "{{count}} vybrané",
"play": "$t(player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "zdieľať položku",
"showDetails": "získať informácie",
"goToAlbum": "choď na $t(entity.album_one)",
"goToAlbumArtist": "choď na $t(entity.albumArtist_one)"
},
"fullscreenPlayer": {
"config": {
"dynamicBackground": "dynamické pozadie",
"dynamicImageBlur": "veľkosť rozostrenia obrázku",
"dynamicIsImage": "povoliť obrázok pozadia",
"followCurrentLyric": "nasleduj aktuálny text skladby",
"lyricAlignment": "zarovnanie textov skladieb",
"lyricOffset": "posunutie textov skladieb (ms)",
"lyricGap": "medzera textov skladieb",
"lyricSize": "veľkosť textov skladieb",
"opacity": "opacita",
"showLyricMatch": "zobraziť zhodu textu skladby",
"showLyricProvider": "zobraziť poskytovateľa textov skladieb",
"synchronized": "synchronizované",
"unsynchronized": "nesynchronizované",
"useImageAspectRatio": "použiť pomer strán obrázka"
},
"lyrics": "texty skladieb",
"related": "súvisiace",
"upNext": "ďalšia nahor",
"visualizer": "vizualizátor",
"noLyrics": "nenašli sa žiadne texty"
},
"genreList": {
"showAlbums": "zobraziť $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "zobraziť $t(entity.genre_one) $t(entity.track_other)",
"title": "$t(entity.genre_other)"
},
"globalSearch": {
"commands": {
"goToPage": "ísť na stránku",
"searchFor": "hľadať {{query}}",
"serverCommands": "príkazy servera"
},
"title": "príkazy"
},
"home": {
"explore": "preskúmať tvoju knižnicu",
"mostPlayed": "najhranejší",
"newlyAdded": "novopridané vydania",
"recentlyPlayed": "nedávno hrané",
"title": "$t(common.home)"
},
"itemDetail": {
"copyPath": "skopírovať cestu do schránky",
"copiedPath": "cesta úspešne skopírovaná",
"openFile": "zobraziť stopu v správcovi súborov"
},
"playlist": {
"reorder": "zmena poradia povolená len pri zoradení podľa id"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"setting": {
"advanced": "pokročilé",
"generalTab": "všeobecné",
"hotkeysTab": "klávesové skratky",
"playbackTab": "prehrávanie",
"windowTab": "okno"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"artists": "$t(entity.artist_other)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"myLibrary": "moja knižnica",
"nowPlaying": "teraz hrá",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"shared": "zdieľaný $t(entity.playlist_other)",
"tracks": "$t(entity.track_other)"
},
"trackList": {
"artistTracks": "skladby {{artist}}",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"title": "$t(entity.track_other)"
}
},
"player": {
"addLast": "pridať posledné",
"addNext": "pridať nasledujúce",
"favorite": "obľúbené",
"mute": "stíšiť",
"muted": "stíšené",
"next": "nasledujúci",
"play": "prehrať",
"playbackFetchCancel": "toto chvíľu trvá... ak to chcete zrušiť, zavrite notifikáciu",
"playbackFetchInProgress": "načítanie skladieb…",
"playbackFetchNoResults": "neboli nájdené žiadne skladby",
"playbackSpeed": "rýchlosť prehrávania",
"playRandom": "prehrávať náhodne",
"playSimilarSongs": "prehrávať podobné skladby",
"previous": "predchádzajúce",
"queue_clear": "vymazať frontu",
"queue_moveToBottom": "presunúť vybrané navrch",
"queue_moveToTop": "presunúť vybrané naspodok",
"queue_remove": "odstrániť vybrané",
"repeat": "opakovať",
"repeat_all": "opakovať všetky",
"repeat_off": "opakovanie vypnuté",
"shuffle": "prehrávať náhodne",
"shuffle_off": "náhodné prehrávanie vypnuté",
"skip": "preskočiť",
"skip_back": "preskočiť dozadu",
"skip_forward": "preskočiť dopredu",
"stop": "zastaviť",
"toggleFullscreenPlayer": "prepnúť prehrávač na celú obrazovku",
"unfavorite": "odstrániť z obľúbených",
"pause": "pozastaviť",
"viewQueue": "zobraziť frontu"
},
"setting": {
"accentColor": "odtieň farby",
"accentColor_description": "nastaviť odtieň farby aplikácie",
"albumBackground": "obrázok pozadia albumu",
"albumBackground_description": "pridáva obrázky albumov na pozadia na jednotlivých stránok albumov",
"albumBackgroundBlur": "veľkosť rozmazania obrázku pozadia albumu",
"albumBackgroundBlur_description": "upravuje mieru rozmazania obrázku pozadia albumu",
"applicationHotkeys": "klávesové skratky aplikácie",
"applicationHotkeys_description": "nastaviť klávesové skratky aplukácie. zaškrtni políčko, ak chceš nastaviť skratku ako globálnu (len desktop)",
"artistConfiguration": "nastavenie stránky interpreta albumu",
"artistConfiguration_description": "konfigurovať aké položky sa zobrazujú, a v akom poradí, na stránke interpreta albumu",
"audioDevice": "zvukové zariadenie",
"audioDevice_description": "vybrať zvukové zariadenie na prehrávanie (len webový prehrávač)",
"audioExclusiveMode": "exkluzívny zvukový režim",
"audioExclusiveMode_description": "povoliť exkluzívny režim výstupu. V tomto režime je systém zvyčajne zamknutý a len mpv je schopný poskytovať zvukový výstup",
"audioPlayer": "audioprehrávač",
"audioPlayer_description": "vybrať zvukové zariadenie, ktoré bude použité na prehrávanie",
"buttonSize": "veľkosť tlačidiel na paneli prehrávania",
"buttonSize_description": "veľkosť tlačidiel na paneli prehrávania",
"clearCache": "vyčistiť dočasnú pamäť prehliadača",
"imageAspectRatio_description": "ak je povolené, obrázok albumu sa zobrazí s originálnym pomerom strán. pre obrázky s pomerom iným ako 1:1 zostane nevyplnený priestor prázdny",
"clearCache_description": "'hard' vyčistenie feishin-u. okrem vyrovnávacej pamäte feishin-u sa vymaže aj vyrovnávacia pamäť prehliadača (uložené obrázky a iné súbory). prihlasovacie údaje k serveru a iné nastavenia zostávajú zachované",
"clearQueryCache": "vymazať vyrovnávaciu pamäť feishin-u",
"clearQueryCache_description": "'soft' vyčistenie feishin-u. obnovia sa playlisty, metadáta skladieb a resetujú sa uložené texty skladieb. nastavenia, prihlasovacie údaje k serveru a obrázky vo vyrovnávacej pamäti zostávajú zachované",
"clearCacheSuccess": "vyrovnávacia pamäť úspešne vymazaná",
"contextMenu": "konfigurácia kontextovej ponuky (pravé tlačítko myši)",
"contextMenu_description": "umožňuje vám skryť položky nachádzajúce v menu, ktoré sa zobrazí po kliknutí pravým tlačítkom myši. nezakliknuté položky budú skryté",
"crossfadeDuration": "dĺžka crossfade",
"crossfadeDuration_description": "nastavuje dĺžku trvania crossfade efektu",
"crossfadeStyle": "štýl crossfade",
"crossfadeStyle_description": "vybrať štýl crossfade efektu pre prehrávač",
"customCssEnable": "povoliť vlastné css",
"customCssEnable_description": "umožňuje písať vlastné css.",
"customCssNotice": "Varovanie: hoci sa využíva istá miera sanitizaácie (deaktivácia url() a obsahu:), používanie vlastných CSS pri zmene rozhrania stále predstavuje riziko.",
"customCss": "vlastné css",
"customCss_description": "vlastný css obsah. Poznámka: obsah a vzdialené url linky sú defaultne deaktivované.Náhľad vášho obsahu je zobrazený nižšie. Pridané polia, ktoré ste nenastavovali boli pridané pri sanitizácii.",
"customFontPath": "cesta k vlastným fontom",
"customFontPath_description": "Nastaví cestu k vlastným fontom na použitie aplikáciou",
"disableAutomaticUpdates": "vypnúť automatické aktualizácie",
"disableLibraryUpdateOnStartup": "vypnúť kontrolu nových verzií pri štarte",
"discordApplicationId": "id aplikácie {{discord}}",
"discordApplicationId_description": "aplikačné id pre plnohodnotné prepojenie s {{discord}} (predvolená hodnota {{defaultId}})",
"discordPausedStatus": "pri pozastavení prehrávania zobrazuje 'rich presence'",
"discordPausedStatus_description": "pri povolení bude status zobrazovaný aj pri pozastavenom prehrávaní",
"discordIdleStatus": "zobraziť 'rich presence idle status'",
"discordIdleStatus_description": "pri povolení bude 'rich presense' status zobrazený aj pri nečinnosti",
"discordListening": "zobraziť status počúvanie",
"discordListening_description": "zobraziť status počúvanie namiesto prehrávanie",
"discordRichPresence": "{{discord}} rich resence",
"discordRichPresence_description": "povoliť status prehrávania v {{discord}} rich presence. Obrázky kláves sú: {{icon}}, {{playing}} a {{paused}}",
"discordServeImage": "poskytuje {{discord}} obrázky zo servera",
"discordServeImage_description": "zdieľať obrázok albumu na {{discord}} rich presence priamo zo servera, dostupné iba pre jellyfin a navidrome",
"discordUpdateInterval": "interval aktualizácií {{discord}} rich presence",
"discordUpdateInterval_description": "čas v sekundách medzi dvomi nasledujúcimi aktualizáciami (minimálne 15 sekúnd)",
"discordDisplayType": "typ zobrazenia {{discord}} presence",
"discordDisplayType_description": "mení vo vašom statuse info, čo počúvate",
"discordDisplayType_songname": "názov skladby",
"discordDisplayType_artistname": "názov interpreta(-ov)",
"doubleClickBehavior": "po dvojkliku zaradí do fronty všetky vyhľadané skladby",
"doubleClickBehavior_description": "ak je povolené, všetky nájdené skladby budú zaradené do fronty. inak budú skladby zaradené iba po kliknutí",
"enableRemote": "povoliť vzdialené ovládanie servera",
"enableRemote_description": "pomocou vzdialeného servera umožňuje ovládanie aplikácie prostredníctvom iných zariadení",
"externalLinks": "zobraziť externé odkazy",
"externalLinks_description": "umožňuje zobrazovať externé odkazy (Last.fm, MusicBrainz) na stránkach umelca/albumu",
"exitToTray": "ukončiť do lišty",
"exitToTray_description": "po zavretí sa aplikácia minimalizuje do lišty a beží ďalej",
"floatingQueueArea": "zobraziť ikonu výsuvnej fronty prehrávania",
"floatingQueueArea_description": "zobraziť ikonu výsuvnej fronty prehrávania na pravej strane obrazovky",
"followLyric": "nasleduj aktuálny text skladby",
"followLyric_description": "posunúť sa v texte skladby na aktuálne prehrávanú pozíciu",
"preferLocalLyrics": "uprednostniť lokálne texty skladieb",
"preferLocalLyrics_description": "uprednostniť miestne skladby textov, ak sú dostupné, pred vzdialenými",
"font": "font",
"font_description": "nastaví font písma pre aplikáciu",
"fontType": "typ písma",
"fontType_description": "vstavané písmo vyberie jeden z fontov poskytovaných Feishin-om. systémové písmo umožňuje vybrať ľubovoľný font poskytovaný vaším operačným systémom. vlastné umožňuje poskytnúť váš vlastný font",
"fontType_optionBuiltIn": "vstavané písmo",
"fontType_optionCustom": "vlastné písmo",
"fontType_optionSystem": "systémové písmo",
"gaplessAudio": "prehrávanie bez prerušení",
"gaplessAudio_description": "nastaví prehrávanie bez prerušení pre mpv",
"gaplessAudio_optionWeak": "slabo (odporúčané)",
"genreBehavior": "predvolené správanie stránky žánru",
"genreBehavior_description": "určuje, či kliknutie na žáner otvorí zoznam skladieb alebo zoznam albumov",
"globalMediaHotkeys": "globálne klávesové skratky médií",
"globalMediaHotkeys_description": "povoliť alebo zakázať použitie vašich klávesových skratiek médií na ovládanie prehrávania",
"homeConfiguration": "konfigurácia domovskej stránky",
"homeConfiguration_description": "konfigurovať, aké položky sú zobrazené a v akom poradí na domovskej stránke",
"homeFeature": "carousel odporúčania na domovskej stránke",
"homeFeature_description": "povoľuje zobrazenie veľkoformátového odporúčaného carouselu na domovskej stránke",
"hotkey_browserBack": "naspäť v prehliadači",
"hotkey_browserForward": "dopredu v prehliadači",
"hotkey_favoriteCurrentSong": "obľúbené $t(common.currentSong)",
"hotkey_favoritePreviousSong": "obľúbené $t(common.previousSong)",
"hotkey_globalSearch": "globálne vyhľadávanie",
"hotkey_localSearch": "vyhľadávanie na stránke",
"hotkey_navigateHome": "navigovať domov",
"hotkey_playbackNext": "nasledujúca skladba",
"hotkey_playbackPause": "pozastaviť",
"hotkey_playbackPlay": "prehrať",
"hotkey_playbackPlayPause": "hrať / pozastaviť",
"hotkey_playbackPrevious": "predchádzajúca skladba",
"hotkey_playbackStop": "zastaviť",
"hotkey_rate0": "bez hodnotenia",
"hotkey_rate1": "hodnotené 1 hviezdou",
"hotkey_rate2": "hodnotené 2 hviezdami",
"hotkey_rate3": "hodnotené 3 hviezdami",
"hotkey_rate4": "hodotené 4 hviezdami",
"hotkey_rate5": "hodnotené 5 hviezdami",
"hotkey_skipBackward": "preskočiť dozadu",
"hotkey_skipForward": "preskočiť dopredu",
"hotkey_toggleCurrentSongFavorite": "prepnúť $t(common.currentSong) obľúbené",
"hotkey_toggleFullScreenPlayer": "prepnúť prehrávač na celú obrazovku",
"hotkey_togglePreviousSongFavorite": "prepnúť $t(common.previousSong) obľúbené",
"hotkey_toggleQueue": "prepnúť frontu",
"hotkey_toggleRepeat": "prepnúť opakovanie",
"hotkey_toggleShuffle": "prepnúť náhodné prehrávanie",
"hotkey_unfavoriteCurrentSong": "odobrať z obľúbených $t(common.currentSong)",
"hotkey_unfavoritePreviousSong": "odobrať z obľúbených $t(common.previousSong)",
"hotkey_volumeDown": "znížiť hlasitosť",
"hotkey_volumeMute": "stíšiť hlasitosť",
"hotkey_volumeUp": "zvýšiť hlasitosť",
"hotkey_zoomIn": "priblížiť",
"hotkey_zoomOut": "vzdialiť",
"imageAspectRatio": "použiť pôvodný pomer strán obalu albumu",
"language": "jazyk",
"language_description": "nastaví jazyk aplikácie ($t(common.restartRequired))",
"lastfm": "zobraziť last.fm odkazy",
"lastfm_description": "zobraziť last.fm odkazy na stránky interpreta/albumu",
"lastfmApiKey": "{{lastfm}} API kľúč",
"lastfmApiKey_description": "API kľúč pre {{lastfm}}. vyžaduje sa obálky albumov",
"lyricFetch": "stiahnuť texty skladieb z internetu",
"lyricFetch_description": "stiahnuť texty skladieb z rôznych internetových zdrojov",
"lyricFetchProvider": "poskytovatelia pre sťahovanie textov skladieb",
"lyricFetchProvider_description": "vybrať poskytovateľov pre sťahovanie textov skladieb. poradie poskytovateľov určuje poradie, v ktorom sa budú používať",
"lyricOffset": "posunutie textu skladieb (ms)",
"lyricOffset_description": "posunutie textu voči skladbe vyjadrené v milisekundách",
"notify": "povoliť notifikácie o skladbách",
"notify_description": "zobraziť notifikácie pri zmene aktuálnej skladby",
"minimizeToTray": "minimalizovať do lišty",
"minimizeToTray_description": "minimalizovať aplikáciu do systémovej lišty",
"minimumScrobblePercentage": "minimálna dĺžka pre skroblovanie (percentá)",
"minimumScrobblePercentage_description": "minimálna časť skladby v percentách, ktorá musí byť prehraná pred tým, než je skroblovaná",
"minimumScrobbleSeconds": "minimálna dĺžka skroblovania (sekundy)",
"minimumScrobbleSeconds_description": "minimálna dĺžka časti skladby, ktorá musí byť prehraná pred tým, než je skladba skroblovaná",
"mpvExecutablePath": "cesta k spustiteľnému súboru mpv",
"mpvExecutablePath_description": "nastavuje cestu k spustiteľnému súboru mpv. ak je prázdna, použije sa predvolená cesta",
"mpvExtraParameters": "parametre mpv",
"mpvExtraParameters_help": "jeden na riadok",
"musicbrainz": "zobraziť linky na musicbrainz",
"musicbrainz_description": "zobrazí linky na stránky interpreta/albumu na musicbrainz, ak je vyplnené mbid",
"neteaseTranslation": "Povoliť NetEasy preklady",
"neteaseTranslation_description": "Ak sú povolené, aplikácia stiahne a zobrazí preložené texty skladieb z NetEasy, ak sú dostupné.",
"passwordStore": "ukladanie hesiel/utajených údajov",
"passwordStore_description": "aký spôsob ukladania hesiel/utajených údajov použiť. ak máte problém s ukladaním hesiel, skúste zmeniť nastavenie.",
"playbackStyle": "štýl prehrávania",
"playbackStyle_description": "vyberte štýl prehrávania pre prehrávač skladieb",
"playbackStyle_optionCrossFade": "crossfade",
"playbackStyle_optionNormal": "normálny",
"playButtonBehavior": "správanie sa tlačidla prehrávania",
"playButtonBehavior_description": "nastaví predvolené správanie sa tlačidla prehrávania pri pridávaní skladieb do fronty",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"playButtonBehavior_optionPlay": "$t(player.play)",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playerAlbumArtResolution": "rozlíšenie obrázka albumu",
"playerAlbumArtResolution_description": "rozlíšenie zobrazenia náhľadu veľkých obrázkov albumov. pri väčšom rozlíšení budú krajšie, ale môže sa spomaliť ich načítavanie. predvolené je 0, čo znamená automatické",
"playerbarOpenDrawer": "zobrazenie na celú obrazovku panelom prehrávača",
"playerbarOpenDrawer_description": "umožní kliknutím na panel prehrávača prepnúť zobrazenie prehrávača na celú obrazovku",
"remotePassword": "heslo servera vzdialeného ovládania",
"remotePassword_description": "nastaví heslo pre server diaľkového ovládania. Jeho obsah je odosielaný bez zabezpečenia, preto by ste si mali zvoliť jedinečné heslo, ktoré pre vás nie je dôležité",
"remotePort": "port servera diaľkového ovládania",
"remotePort_description": "nastaví port servera diaľkového ovládania",
"remoteUsername": "používateľské meno servera diaľkového ovládania",
"remoteUsername_description": "nasstaví používateľské meno servera diaľkového ovládania. v prípade, ak sú používateľské meno aj heslo prázdne, je overovanie pri prihlásení vypnuté",
"replayGainClipping": "clipping {{ReplayGain}}",
"replayGainClipping_description": "Zabraňuje clipping-u spôsobenému {{ReplayGain}} automatickým znížením zosilenia",
"replayGainFallback": "fallback {{ReplayGain}}",
"replayGainFallback_description": "zosilenie v db, ktoré sa aplikuje, ak súbor nemá {{ReplayGain}} štítky",
"replayGainMode": "{{ReplayGain}} režim",
"replayGainMode_description": "pozmení zosilenie hlasitosti podľa hodnôt {{ReplayGain}} uložených v metadátach súboru",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"replayGainPreamp": "predzosilenie {{ReplayGain}} dB",
"replayGainPreamp_description": "pozmení predzosilenie použité na hodnoty {{ReplayGain}}",
"sampleRate": "vzorkovacia frekvencia",
"sidePlayQueueStyle_optionAttached": "pripojené",
"sidePlayQueueStyle_optionDetached": "odpojené",
"skipDuration": "dĺžka preskočenia",
"skipDuration_description": "určuje časovú dĺžku posunu pri stlačení tlačidla preskočiť na lište prehrávača",
"skipPlaylistPage": "preskočiť stránku playlistu",
"skipPlaylistPage_description": "pri navigácii v playliste, idete na výber stránky playlistu namiesto predvolenej stránky",
"startMinimized": "spistiť mnimalizované",
"startMinimized_description": "spustí aplikáciu minimalizovanú do systémovej lišty",
"preventSleepOnPlayback": "zabrániť spánku pri prehrávaní",
"preventSleepOnPlayback_description": "pri prehávaní hudby zabráni obrazovke v prechode do spánku",
"theme": "téma",
"theme_description": "nastaví tému aplikácie",
"themeDark": "téma (tmavá)",
"themeDark_description": "nastaví tmavú tému aplikácie",
"themeLight": "téma (svetlá)",
"themeLight_description": "nastaví svetlú tému aplikácie",
"transcodeNote": "zmena sa prejaví po 1 (web) - 2 (mpv) skladbách",
"transcode": "povoliť prekódovanie",
"transcode_description": "umožňuje prekódovanie do rôznych formátov",
"transcodeBitrate": "bitová frekvencia prekódovania",
"transcodeBitrate_description": "určuje bitovú frekvenciu, pri ktorej sa použije prekódovanie. 0 znamená ponechať rozhodnutie na server",
"transcodeFormat": "formát prekódovania",
"transcodeFormat_description": "učuje výstupný formát prekódovania. ak chcete ponechať rozhodnutie na server, nechajte políčko prázdne",
"translationApiProvider": "api poskytovateľa prekladu",
"translationApiProvider_description": "api poskytovateľa prekladu",
"translationApiKey": "api kľúč prekladu",
"translationApiKey_description": "api kľúč pre preklad (Podporuje iba koncové body globálnych služieb)",
"translationTargetLanguage": "cieľový jazyk prekladu",
"translationTargetLanguage_description": "cieľový jazyk, do ktorého sa prekladá",
"trayEnabled": "zobraziť lištu",
"trayEnabled_description": "zobraziť/skryť ikonu/ponuku lišty. ak nie je povolené, taktiež vypne minimalizovanie/zavretie do lišty",
"useSystemTheme": "použiť systémovú tému",
"useSystemTheme_description": "prispôsobiť výber svetlej, či tmavej témy aktuálnej systémovej téme",
"volumeWheelStep": "krok zmeny hlasitosti",
"volumeWheelStep_description": "veľkosť zmeny hlasitosti pri otočení kolieskom myši o jeden krok na ovládači hlasitosti",
"volumeWidth": "šírka posuvného ovládača hlasitosti",
"volumeWidth_description": "šírka ovládača hlasitosti",
"webAudio": "používať webový výstup",
"webAudio_description": "bude sa používať webový výstup, čím povolíte pokročilé funkcie ako replaygain. v prípade problémov voľbu vypnite",
"preservePitch": "zachovať výšku",
"preservePitch_description": "pri zmene rýchlosti prehrávania zostane výška zachovaná",
"windowBarStyle": "štýl okna",
"windowBarStyle_description": "vyberte štýl okna",
"zoom": "percento priblíženia",
"zoom_description": "nastaví percento priblíženia pre aplikáciu",
"sampleRate_description": "vyberte výstupnú vzorkovaciu frekvenciu, ktorá sa použije v prípade, ak je vybraná vzorkovacia frekvencia iná ako je u aktuálnej skladby. pri hodnote menšej ako 8000 sa použije predvolená frekvencia",
"savePlayQueue": "uložiť frontu prehrávania",
"savePlayQueue_description": "uloží frontu prehrávania pri ukončení aplikácie a obnoví ju opäť po jej otvorení",
"scrobble": "skroblovať",
"scrobble_description": "scroblovať vaše prehrávanie na medálny server",
"showSkipButton": "zobraziť tlačítka preskočenia",
"showSkipButton_description": "zobrazí alebo skryje tlačítka preskočenia na lište prehrávača",
"showSkipButtons": "zobraziť tlačítka preskočenia",
"showSkipButtons_description": "zobraziť alebo skryť tlačítka preskočenia na lište prehrávača",
"sidebarCollapsedNavigation": "navigácia bočnej lišty (zasunutá)",
"sidebarCollapsedNavigation_description": "zobraziť alebo skryť navigovanie na zasunutej bočnej lište",
"sidebarConfiguration": "nastavenie bočnej lišty",
"sidebarConfiguration_description": "zvoľte položky a ich poradie, v akom sa zabrazia na bočnej lište",
"sidebarPlaylistList": "playlist bočnej lišty",
"sidebarPlaylistList_description": "zobraziť alebo skryť playlist na bočnej lište",
"sidePlayQueueStyle": "štýl bočnej fronty prehrávania",
"sidePlayQueueStyle_description": "nastaví štýl bočnej fronty prehrávania"
},
"table": {
"column": {
"album": "album",
"albumArtist": "interpret albumu",
"albumCount": "$t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "životopis",
"bitrate": "bitrate",
"bpm": "bpm",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"comment": "komentár",
"dateAdded": "dátum pridania",
"discNumber": "disk",
"favorite": "obľúbené",
"genre": "$t(entity.genre_one)",
"lastPlayed": "posledne hraný",
"path": "cesta",
"playCount": "prehratí",
"rating": "hodnotenie",
"releaseDate": "dátum vydania",
"releaseYear": "rok",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "názov",
"trackNumber": "skladba"
},
"config": {
"general": {
"autoFitColumns": "automatická šírka stĺpcov",
"followCurrentSong": "nasledovať aktuálnu skladbu",
"displayType": "typ zobrazenia",
"gap": "$t(common.gap)",
"itemGap": "medzera položky (px)",
"itemSize": "veľkosť položky (px)",
"size": "$t(common.size)",
"tableColumns": "stĺpce tabuľky"
},
"label": {
"actions": "$t(common.action_other)",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"dateAdded": "dátum pridania",
"discNumber": "číslo disku",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
"lastPlayed": "posledne prehraté",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"playCount": "počet prehraní",
"rating": "$t(common.rating)",
"releaseDate": "dátum vydania",
"rowIndex": "číslo riadku",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"titleCombined": "$t(common.title) (kombinovaný)",
"trackNumber": "číslo skladby",
"year": "$t(common.year)"
},
"view": {
"card": "karta",
"grid": "mriežka",
"list": "zoznam",
"poster": "plagát",
"table": "tabuľka"
}
}
}
}
+171 -143
View File
@@ -1,22 +1,22 @@
{
"action": {
"addToFavorites": "$ t இல் சேர்க்கவும் (entity.foavorite_other)",
"addToFavorites": "$t(entity.favorite_other) இல் சேர்க்கவும்",
"clearQueue": "தெளிவான வரிசை",
"goToPage": "பக்கத்திற்குச் செல்லுங்கள்",
"moveToBottom": "கீழே செல்லுங்கள்",
"moveToTop": "மேலே செல்லுங்கள்",
"refresh": "$ t (காமன்.ரெஃப்ரெச்)",
"removeFromFavorites": "$ t இலிருந்து அகற்று (entity.foavorite_other)",
"removeFromPlaylist": "$ t இலிருந்து அகற்று (entity.playlist_one)",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "$t(entity.favorite_other)இலிருந்து அகற்று",
"removeFromPlaylist": "$t(entity.playlist_one) இலிருந்து அகற்று",
"removeFromQueue": "வரிசையிலிருந்து அகற்று",
"setRating": "மதிப்பீட்டை அமைக்கவும்",
"toggleSmartPlaylistEditor": "மாற்று $ t (entity.smartplaylist) ஆசிரியர்",
"viewPlaylists": "$ t (entity.playlist_other) காண்க",
"addToPlaylist": "$ t இல் சேர்க்கவும் (entity.playlist_one)",
"createPlaylist": "$ t ஐ உருவாக்கவும் (entity.playlist_one)",
"deletePlaylist": "$ t (entity.playlist_one) ஐ நீக்கு",
"toggleSmartPlaylistEditor": "மாற்று $t(entity.smartPlaylist) ஆசிரியர்",
"viewPlaylists": "$t(entity.playlist_other) காண்க",
"addToPlaylist": "$t(entity.playlist_one)இல் சேர்க்கவும்",
"createPlaylist": "$t(entity.playlist_one)ஐ உருவாக்கவும்",
"deletePlaylist": "$t(entity.playlist_one)ஐ நீக்கு",
"deselectAll": "அனைத்தையும் தேர்வு செய்யுங்கள்",
"editPlaylist": "திருத்து $ t (entity.playlist_one)",
"editPlaylist": "திருத்து $t(entity.playlist_one)",
"moveToNext": "அடுத்து செல்லுங்கள்",
"openIn": {
"lastfm": "Last.fm இல் திறந்திருக்கும்",
@@ -33,7 +33,7 @@
"configure": "உள்ளமைக்கவும்",
"confirm": "உறுதிப்படுத்தவும்",
"create": "உருவாக்கு",
"currentSong": "தற்போதைய $ t (entity.track_one)",
"currentSong": "தற்போதைய $t(entity.track_one)",
"decrease": "குறைவு",
"action_one": "செயல்",
"action_other": "செயல்கள்",
@@ -69,7 +69,7 @@
"filter_one": "வடிப்பி",
"filter_other": "வடிப்பான்கள்",
"filters": "வடிப்பான்கள்",
"forceRestartRequired": "மாற்றங்களைப் பயன்படுத்த மறுதொடக்கம்… மறுதொடக்கம் செய்ய அறிவிப்பை மூடு",
"forceRestartRequired": "மாற்றங்களைப் பயன்படுத்த மறுதொடக்கம் செய்… மறுதொடக்கம் செய்ய அறிவிப்பை மூடு",
"forward": "முன்னோக்கி",
"gap": "இடைவெளி",
"home": "வீடு",
@@ -87,7 +87,7 @@
"path": "பாதை",
"playerMustBePaused": "வீரர் இடைநிறுத்தப்பட வேண்டும்",
"preview": "முன்னோட்டம்",
"previousSong": "முந்தைய $ t (entity.track_one)",
"previousSong": "முந்தைய $t(entity.track_one)",
"quit": "வெளியேறு",
"random": "சீரற்ற",
"rating": "செயல்வரம்பு",
@@ -112,7 +112,13 @@
"trackNumber": "மின்தடம்",
"trackGain": "தடமறிதல்",
"trackPeak": "ட்ராக் பீக்",
"translation": "மொழிபெயர்ப்பு"
"translation": "மொழிபெயர்ப்பு",
"additionalParticipants": "கூடுதல் பங்கேற்பாளர்கள்",
"newVersion": "புதிய பதிப்பு நிறுவப்பட்டுள்ளது ({{version}})",
"viewReleaseNotes": "வெளியீட்டு குறிப்புகளைக் காண்க",
"bitDepth": "பிட் ஆழம்",
"sampleRate": "மாதிரி வீதம்",
"tags": "குறிச்சொற்கள்"
},
"entity": {
"folderWithCount_one": "{{count}} கோப்புறை",
@@ -121,8 +127,8 @@
"genre_other": "வகைகள்",
"genreWithCount_one": "{{count}} வகை",
"genreWithCount_other": "{{count}} வகைகள்",
"album_one": "ஆல்பம்",
"album_other": "ஆல்பம்",
"album_one": "செருகேடு",
"album_other": "செருகேடுகள்",
"albumArtist_one": "ஆல்பம் கலைஞர்",
"albumArtist_other": "ஆல்பம் கலைஞர்கள்",
"albumArtistCount_one": "{{count}} ஆல்பம் கலைஞர்",
@@ -139,11 +145,11 @@
"folder_other": "கோப்புறைகள்",
"playlist_one": "பிளேலிச்ட்",
"playlist_other": "பிளேலிச்ட்கள்",
"play_one": "{{count}} play",
"play_one": "{{count}} நாடகம்",
"play_other": "{{count}} நாடகங்கள்",
"playlistWithCount_one": "{{count}} பிளேலிச்ட்",
"playlistWithCount_other": "{{count}} பிளேலிச்ட்கள்",
"smartPlaylist": "அறிவுள்ள $ t (entity.playlist_one)",
"smartPlaylist": "அறிவுள்ள $t(entity.playlist_one)",
"track_one": "மின்தடம்",
"track_other": "தடங்கள்",
"song_one": "பாடல்",
@@ -157,13 +163,13 @@
"remotePortWarning": "புதிய துறைமுகத்தைப் பயன்படுத்த சேவையகத்தை மறுதொடக்கம் செய்யுங்கள்",
"serverNotSelectedError": "சேவையகம் எதுவும் தேர்ந்தெடுக்கப்படவில்லை",
"serverRequired": "சேவையகம் தேவை",
"remoteEnableError": "தொலைநிலை சேவையகத்தை $ t (பொதுவானது) முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"remoteEnableError": "தொலைநிலை சேவையகத்தை $t(common.enable) முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"apiRouteError": "பாதை கோரிக்கை செய்ய முடியவில்லை",
"audioDeviceFetchError": "ஆடியோ சாதனங்களைப் பெற முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"authenticationFailed": "ஏற்பு தோல்வியடைந்தது",
"badAlbum": "இந்த பாடல் ஆல்பத்தின் பகுதியாக இல்லாததால் இந்தப் பக்கத்தைப் பார்க்கிறீர்கள். உங்கள் இசை கோப்புறையின் மேல் மட்டத்தில் ஒரு பாடல் இருந்தால் இந்த சிக்கலைப் பார்க்கிறீர்கள். செல்லிஃபின் ஒரு கோப்புறையில் இருந்தால் தடங்களை மட்டுமே குழுக்கள்.",
"credentialsRequired": "நற்சான்றிதழ்கள் தேவை",
"endpointNotImplementedError": "Endpoint {{endpoint}} {{serverType} க்கு க்கு செயல்படுத்தப்படவில்லை",
"endpointNotImplementedError": "Endpoint {{endpoint}} {{serverType}} க்கு செயல்படுத்தப்படவில்லை",
"genericError": "பிழை ஏற்பட்டது",
"invalidServer": "தவறான சேவையகம்",
"localFontAccessDenied": "உள்ளக எழுத்துருக்களுக்கு மறுக்கப்பட்டது",
@@ -171,32 +177,34 @@
"networkError": "பிணைய பிழை ஏற்பட்டது",
"openError": "கோப்பைத் திறக்க முடியவில்லை",
"playbackError": "ஊடகங்களை விளையாட முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"remoteDisableError": "தொலைநிலை சேவையகத்தை $ t (பொதுவானது. குறைக்கக்கூடிய) முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"remoteDisableError": "தொலைநிலை சேவையகத்தை $t(common.disable) முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"sessionExpiredError": "உங்கள் அமர்வு காலாவதியானது",
"systemFontError": "கணினி எழுத்துருக்களைப் பெற முயற்சிக்கும்போது பிழை ஏற்பட்டது"
"systemFontError": "கணினி எழுத்துருக்களைப் பெற முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"badValue": "தவறான விருப்பம் \"{{value}}\". இந்த மதிப்பு இனி இல்லை",
"notificationDenied": "அறிவிப்புகளுக்கான அனுமதிகள் மறுக்கப்பட்டன. இந்த அமைப்பு எந்த விளைவையும் ஏற்படுத்தாது"
},
"filter": {
"albumArtist": "$ t (entity.albumartist_one)",
"albumCount": "$ t (entity.album_other) எண்ணிக்கை",
"artist": "$ t (entity.artist_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"albumCount": "$t(entity.album_other) எண்ணிக்கை",
"artist": "$t(entity.artist_one)",
"biography": "சுயசரிதை",
"bitrate": "பிட்ரேட்",
"bpm": "பிபிஎம்",
"channels": "$ t (common.channel_other)",
"channels": "$t(common.channel_other)",
"comment": "கருத்து",
"communityRating": "சமூக மதிப்பீடு",
"path": "பாதை",
"playCount": "விளையாட்டு எண்ணிக்கை",
"random": "சீரற்ற",
"rating": "செயல்வரம்பு",
"album": "$ t (entity.album_one)",
"album": "$t(entity.album_one)",
"criticRating": "விமர்சகர் மதிப்பீடு",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
"disc": "வட்டு",
"duration": "காலம்",
"favorited": "பிடித்தது",
"fromYear": "ஆண்டு முதல்",
"genre": "$ t (entity.genre_one)",
"genre": "$t(entity.genre_one)",
"id": "ஐடி",
"isCompilation": "தொகுப்பு",
"isFavorited": "பிடித்தது",
@@ -207,7 +215,7 @@
"mostPlayed": "அதிகம் விளையாடியது",
"name": "பெயர்",
"note": "குறிப்பு",
"owner": "$ t (பொதுவானவர்)",
"owner": "$t(common.owner)",
"recentlyAdded": "அண்மைக் காலத்தில் சேர்க்கப்பட்டது",
"recentlyPlayed": "அண்மைக் காலத்தில் விளையாடியது",
"recentlyUpdated": "அண்மைக் காலத்தில் புதுப்பிக்கப்பட்டது",
@@ -223,8 +231,8 @@
"addServer": {
"input_password": "கடவுச்சொல்",
"error_savePassword": "கடவுச்சொல்லை சேமிக்க முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"ignoreCors": "CORS ஐ புறக்கணிக்கவும் ($ t (Common.RestartRequired))",
"ignoreSsl": "SSL ஐ புறக்கணிக்கவும் ($ t (பொதுவானது",
"ignoreCors": "CORS ஐ புறக்கணிக்கவும் ($t(common.restartRequired))",
"ignoreSsl": "SSL ஐ புறக்கணிக்கவும் ($t(common.restartRequired))",
"input_legacyAuthentication": "மரபு அங்கீகாரத்தை இயக்கவும்",
"input_name": "சேவையக பெயர்",
"input_savePassword": "கடவுச்சொல்லைச் சேமிக்கவும்",
@@ -234,23 +242,24 @@
"title": "சேவையகத்தைச் சேர்க்கவும்"
},
"deletePlaylist": {
"input_confirm": "உறுதிப்படுத்த $ t (entity.playlist_one) பெயரைத் தட்டச்சு செய்க",
"success": "$ t (entity.playlist_one) வெற்றிகரமாக நீக்கப்பட்டது",
"title": "$ t (entity.playlist_one) ஐ நீக்கு"
"input_confirm": "உறுதிப்படுத்த $t(entity.playlist_one) பெயரைத் தட்டச்சு செய்க",
"success": "$t(entity.playlist_one) வெற்றிகரமாக நீக்கப்பட்டது",
"title": "$t(entity.playlist_one)ஐ நீக்கு"
},
"editPlaylist": {
"title": "திருத்து $ t (entity.playlist_one)",
"title": "திருத்து $t(entity.playlist_one)",
"publicJellyfinNote": "சில காரணங்களால் செல்லிஃபின் ஒரு பிளேலிச்ட் பொதுவில் இல்லையா என்பதை அம்பலப்படுத்தவில்லை. இது பொதுவில் இருக்க விரும்பினால், தயவுசெய்து பின்வரும் உள்ளீட்டைத் தேர்ந்தெடுக்கவும்",
"success": "$ t (entity.playlist_one) வெற்றிகரமாக புதுப்பிக்கப்பட்டது"
"success": "$t(entity.playlist_one) வெற்றிகரமாகப் புதுப்பிக்கப்பட்டது"
},
"lyricSearch": {
"input_artist": "$ t (entity.artist_one)",
"input_name": "$ t (common.name)",
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "பாடல் தேடல்"
},
"queryEditor": {
"input_optionMatchAll": "அனைத்தையும் பொருத்துங்கள்",
"input_optionMatchAny": "எந்த பொருத்தவும்"
"input_optionMatchAny": "எந்த பொருத்தவும்",
"title": "வினவல் ஆசிரியர்"
},
"shareItem": {
"description": "விவரம்",
@@ -261,18 +270,18 @@
"createFailed": "பங்கை உருவாக்கத் தவறிவிட்டது (பகிர்வு இயக்கப்பட்டதா?)"
},
"createPlaylist": {
"success": "$ t (entity.playlist_one) வெற்றிகரமாக உருவாக்கப்பட்டது",
"title": "$ t (entity.playlist_one) ஐ உருவாக்கவும்",
"input_description": "$ t (common.description)",
"input_name": "$ t (common.name)",
"input_owner": "$ t (பொதுவானவர்)",
"success": "$t(entity.playlist_one) வெற்றிகரமாக உருவாக்கப்பட்டது",
"title": "$t(entity.playlist_one) ஐ உருவாக்கவும்",
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "பொது"
},
"addToPlaylist": {
"input_playlists": "$ t (entity.playlist_other)",
"input_playlists": "$t(entity.playlist_other)",
"input_skipDuplicates": "நகல்களைத் தவிர்க்கவும்",
"success": "$ t (entity.trackwithCount, {\"count\": {{message}}}) $ t (entity.playlistwithCount, {\"count\": {{numOfPlaylists}}})",
"title": "$ t இல் சேர்க்கவும் (entity.playlist_one)"
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) இதற்கு $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} }) சேர்க்கப்பட்டது",
"title": "$t(entity.playlist_one) இல் சேர்"
},
"updateServer": {
"success": "சேவையகம் வெற்றிகரமாக புதுப்பிக்கப்பட்டது",
@@ -286,8 +295,8 @@
"recentReleases": "அண்மைக் கால வெளியீடுகள்",
"viewDiscography": "டிச்கோகிராஃபி காண்க",
"topSongs": "சிறந்த பாடல்கள்",
"viewAllTracks": "அனைத்தையும் காண்க (entity.track_other)",
"relatedArtists": "தொடர்புடைய $ t (entity.artist_other)",
"viewAllTracks": "அனைத்தையும் காண்க $t(entity.track_other)",
"relatedArtists": "தொடர்புடைய $t(entity.artist_other)",
"topSongsFrom": "{{title}} இலிருந்து சிறந்த பாடல்கள்",
"viewAll": "அனைத்தையும் காண்க"
},
@@ -298,9 +307,9 @@
"goForward": "முன்னோக்கிச் செல்லுங்கள்",
"manageServers": "சேவையகங்களை நிர்வகிக்கவும்",
"openBrowserDevtools": "திறந்த உலாவி தேவ்டூல்கள்",
"quit": "$ t (common.quit)",
"quit": "$t(common.quit)",
"selectServer": "சேவையகத்தைத் தேர்ந்தெடுக்கவும்",
"settings": "$ t (common.setting_other)",
"settings": "$t(common.setting_other)",
"version": "பதிப்பு {{version}}"
},
"manageServers": {
@@ -312,28 +321,28 @@
"removeServer": "சேவையகத்தை அகற்று"
},
"contextMenu": {
"addNext": "$ t (player.addnext)",
"deletePlaylist": "$ t (action.deleteplaylist)",
"deselectAll": "$ t (action.deselectall)",
"addNext": "$t(player.addNext)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"download": "பதிவிறக்கம்",
"moveToBottom": "$ t (action.movetobottom)",
"moveToTop": "$ t (action.movetotop)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"numberSelected": "{{count}} தேர்ந்தெடுக்கப்பட்டது",
"playSimilarSongs": "$ t (player.playsimilarsongs)",
"removeFromFavorites": "$ t (action.removefromfoavites)",
"removeFromPlaylist": "$ t (action.RemoveFrollayList)",
"removeFromQueue": "$ t (action.RemoveFromQueue)",
"setRating": "$ t (action.setrating)",
"playShuffled": "$ t (player.shuffle)",
"addFavorite": "$ t (action.addtofoaverites)",
"addLast": "$ t (player.addlast)",
"moveToNext": "$ t (action.movetonext)",
"play": "$ t (player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"moveToNext": "$t(action.moveToNext)",
"play": "$t(player.play)",
"shareItem": "உருப்படியைப் பகிரவும்",
"showDetails": "தகவலைப் பெறுங்கள்",
"addToFavorites": "$ t (action.addtofoaverites)",
"addToPlaylist": "$ t (action.addtoplailist)",
"createPlaylist": "$ t (action.createplaylist)"
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)"
},
"fullscreenPlayer": {
"config": {
@@ -359,9 +368,9 @@
"related": "தொடர்புடைய"
},
"genreList": {
"showAlbums": "$ t (entity.genre_one) $ t (entity.album_other)",
"showTracks": "$ t (entity.genre_one) $ t (entity.track_other)",
"title": "$ t (entity.genre_other)"
"showAlbums": "காட்டு $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "காட்டு $t(entity.genre_one) $t(entity.track_other)",
"title": "$t(entity.genre_other)"
},
"globalSearch": {
"commands": {
@@ -376,7 +385,7 @@
"mostPlayed": "அதிகம் விளையாடியது",
"newlyAdded": "புதிதாக சேர்க்கப்பட்ட வெளியீடுகள்",
"recentlyPlayed": "அண்மைக் காலத்தில் விளையாடியது",
"title": "$ t (காமன்.ஓம்)"
"title": "$t(common.home)"
},
"itemDetail": {
"copyPath": "இடைநிலைப்பலகைக்கு பாதையை நகலெடுக்கவும்",
@@ -387,7 +396,7 @@
"reorder": "ஐடியால் வரிசைப்படுத்தும்போது மட்டுமே மறுசீரமைப்பு இயக்கப்பட்டது"
},
"playlistList": {
"title": "$ t (entity.playlist_other)"
"title": "$t(entity.playlist_other)"
},
"setting": {
"advanced": "மேம்பட்ட",
@@ -397,43 +406,44 @@
"windowTab": "சாளரம்"
},
"sidebar": {
"folders": "$ t (entity.folder_other)",
"genres": "$ t (entity.genre_other)",
"home": "$ t (காமன்.ஓம்)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"nowPlaying": "இப்போது விளையாடுகிறது",
"playlists": "$ t (entity.playlist_other)",
"search": "$ t (பொதுவானது. தேடல்)",
"settings": "$ t (common.setting_other)",
"albumArtists": "$ t (entity.albumartist_other)",
"albums": "$ t (entity.album_other)",
"artists": "$ t (entity.artist_other)",
"shared": "பகிரப்பட்ட $ t (entity.playlist_other)",
"tracks": "$ t (entity.track_other)"
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"artists": "$t(entity.artist_other)",
"shared": "$t(entity.playlist_other) பகிரப்பட்டது",
"tracks": "$t(entity.track_other)",
"myLibrary": "எனது நூலகம்"
},
"trackList": {
"title": "$ t (entity.track_other)",
"genreTracks": "\"{{genre}}\" $ t (entity.track_other)",
"title": "$t(entity.track_other)",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"artistTracks": "{{artist}}"
},
"albumArtistList": {
"title": "$ t (entity.albumartist_other)"
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "இந்த $ t (entity.artist_one) இலிருந்து மேலும்",
"moreFromGeneric": "{{item} இருந்து இலிருந்து மேலும்",
"moreFromArtist": "இந்த $t(entity.artist_one) இலிருந்து மேலும்",
"moreFromGeneric": "{{item}} இலிருந்து மேலும்",
"released": "வெளியிடப்பட்டது"
},
"albumList": {
"artistAlbums": "ஆல்பங்கள் {{artist}}",
"genreAlbums": "\"{{genre}}\" $ t (entity.album_other)",
"title": "$ t (entity.album_other)"
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"title": "$t(entity.album_other)"
}
},
"player": {
"addLast": "கடைசியாக சேர்க்கவும்",
"addNext": "அடுத்து சேர்க்கவும்",
"favorite": "பிடித்த",
"mute": "முடக்கு",
"mute": "ஒலிமுடக்கு",
"muted": "முடக்கிய",
"next": "அடுத்தது",
"play": "விளையாடுங்கள்",
@@ -500,7 +510,7 @@
"globalMediaHotkeys_description": "பிளேபேக்கைக் கட்டுப்படுத்த உங்கள் கணினி மீடியா ஆட்கீசின் பயன்பாட்டை இயக்கவும் அல்லது முடக்கவும்",
"homeConfiguration": "முகப்பு பக்க உள்ளமைவு",
"homeFeature": "வீட்டில் கொணர்வி இடம்பெற்றது",
"hotkey_favoriteCurrentSong": "பிடித்த $ t (common.curressong)",
"hotkey_favoriteCurrentSong": "பிடித்த $t(common.currentSong)",
"hotkey_globalSearch": "உலக தேடல்",
"hotkey_playbackPrevious": "முந்தைய பாடல்",
"hotkey_playbackStop": "நிறுத்து",
@@ -511,19 +521,19 @@
"hotkey_rate4": "மதிப்பீடு 4 நட்சத்திரங்கள்",
"hotkey_rate5": "மதிப்பீடு 5 நட்சத்திரங்கள்",
"hotkey_toggleFullScreenPlayer": "முழு திரை பிளேயரை மாற்றவும்",
"hotkey_togglePreviousSongFavorite": "மாற்றவும் (பொதுவானது. ப்ரீவியச்ங்) பிடித்தது",
"hotkey_togglePreviousSongFavorite": "மாற்றவும் $t(common.previousSong) பிடித்தது",
"hotkey_toggleQueue": "வரிசையை மாற்றவும்",
"hotkey_toggleRepeat": "மீண்டும் மீண்டும்",
"hotkey_toggleRepeat": "மாற்று மறுநிகழ்வு",
"hotkey_toggleShuffle": "கலக்கு மாற்று",
"hotkey_unfavoriteCurrentSong": "சாதகமற்ற $ t (common.curressong)",
"hotkey_unfavoritePreviousSong": "சாதகமற்ற $ t (காமன்.பிரெவியச்ங்)",
"hotkey_unfavoriteCurrentSong": "பிடிக்காத $t(common.currentSong)",
"hotkey_unfavoritePreviousSong": "பிடிக்காத $t(common.previousSong)",
"hotkey_volumeDown": "தொகுதி கீழே",
"hotkey_volumeMute": "தொகுதி முடக்கு",
"hotkey_volumeUp": "தொகுதி",
"language": "மொழி",
"language_description": "பயன்பாட்டிற்கான மொழியை அமைக்கிறது ($ t (பொதுவானது",
"language_description": "பயன்பாட்டிற்கான மொழியை அமைக்கிறது ($t(common.restartRequired))",
"lastfmApiKey": "{{lastfm}} பநிஇ key",
"lastfmApiKey_description": "{{lastfm} க்கு க்கான பநிஇ விசை. கவர் கலைக்கு தேவை",
"lastfmApiKey_description": "{{lastfm}} க்கான பநிஇ விசை. கவர் கலைக்குத் தேவை",
"lyricFetch": "இணையத்திலிருந்து வரிகளை பெறுங்கள்",
"lyricFetchProvider_description": "பாடல் பெற வழங்குநர்களைத் தேர்ந்தெடுக்கவும். வழங்குநர்களின் வரிசை அவர்கள் வினவப்படும் ஒழுங்கு",
"lyricOffset": "பாடல் ஆஃப்செட் (எம்.எச்)",
@@ -544,8 +554,8 @@
"playbackStyle_optionNormal": "சாதாரண",
"playButtonBehavior": "பொத்தான் நடத்தை விளையாடுங்கள்",
"playButtonBehavior_description": "வரிசையில் பாடல்களைச் சேர்க்கும்போது ப்ளே பொத்தானின் இயல்புநிலை நடத்தை அமைக்கிறது",
"playButtonBehavior_optionAddLast": "$ t (player.addlast)",
"playButtonBehavior_optionAddNext": "$ t (player.addnext)",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"playerAlbumArtResolution": "பிளேயர் ஆல்பம் கலைத் தீர்மானம்",
"playerAlbumArtResolution_description": "பெரிய வீரரின் ஆல்பம் கலை முன்னோட்டத்திற்கான தீர்மானம். பெரியது இது மிகவும் மிருதுவானதாக தோற்றமளிக்கிறது, ஆனால் மெதுவாக ஏற்றுவதை மெதுவாகக் கொண்டிருக்கலாம். இயல்புநிலை 0 க்கு, அதாவது ஆட்டோ",
"playerbarOpenDrawer": "பிளேயர்பார் முழுத்திரை மாற்று",
@@ -557,14 +567,14 @@
"remoteUsername": "ரிமோட் கண்ட்ரோல் சர்வர் பயனர்பெயர்",
"remoteUsername_description": "ரிமோட் கண்ட்ரோல் சேவையகத்திற்கான பயனர்பெயரை அமைக்கிறது. பயனர்பெயர் மற்றும் கடவுச்சொல் இரண்டும் காலியாக இருந்தால், ஏற்பு முடக்கப்படும்",
"replayGainClipping": "{{ReplayGain}} கிளிப்பிங்",
"replayGainClipping_description": "ஆதாயத்தை தானாகவே குறைப்பதன் மூலம் {{ReplayGain} by காரணமாக ஏற்படும் கிளிப்பிங்கைத் தடுக்கவும்",
"replayGainFallback": "{{{ReplayGain}}} falback",
"replayGainClipping_description": "ஆதாயத்தைத் தானாகவே குறைப்பதன் மூலம் {{ReplayGain}} காரணமாக ஏற்படும் கிளிப்பிங்கைத் தடுக்கவும்",
"replayGainFallback": "{{ReplayGain}} பின்னடைவு",
"replayGainFallback_description": "கோப்பில் {{ReplayGain}} குறிச்சொற்கள் இல்லையென்றால் விண்ணப்பிக்க DB இல் ஆதாயம்",
"replayGainMode": "{{ReplayGain}} பயன்முறை",
"replayGainMode_description": "{{ReplayGain}}} மதிப்புகளின் படி தொகுதி ஆதாயத்தை சரிசெய்யவும் மேனிலை தரவு கோப்பு",
"replayGainMode_optionAlbum": "$ t (entity.album_one)",
"replayGainMode_optionNone": "$ t (common.none)",
"replayGainMode_optionTrack": "$ t (entity.track_one)",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"replayGainPreamp": "{{ReplayGain}} preamp (db)",
"replayGainPreamp_description": "{{ReplayGain}}} மதிப்புகளுக்கு பயன்படுத்தப்படும் Preamp ஆதாயத்தை சரிசெய்யவும்",
"sampleRate": "மாதிரி வீதம்",
@@ -631,7 +641,7 @@
"homeFeature_description": "முகப்பு பக்கத்தில் பெரிய பிரத்யேக கொணர்வி காட்ட வேண்டுமா என்பதைக் கட்டுப்படுத்துகிறது",
"hotkey_browserBack": "உலாவி மீண்டும்",
"hotkey_browserForward": "முன்னோக்கி உலாவி",
"hotkey_favoritePreviousSong": "பிடித்த $ t (காமன்.பிரெவியச்ங்)",
"hotkey_favoritePreviousSong": "பிடித்த $t(common.previousSong)",
"hotkey_localSearch": "பக்க தேடல்",
"hotkey_playbackNext": "அடுத்த பாடல்",
"hotkey_playbackPause": "இடைநிறுத்தம்",
@@ -646,10 +656,10 @@
"lyricFetchProvider": "பாடல் பெற வழங்குநர்கள்",
"lyricOffset_description": "குறிப்பிட்ட அளவு மில்லி விநாடிகளால் பாடலை ஈடுசெய்யவும்",
"hotkey_skipForward": "முன்னோக்கி செல்லுங்கள்",
"hotkey_toggleCurrentSongFavorite": "மாற்று $ t (common.curressong) பிடித்தது",
"hotkey_toggleCurrentSongFavorite": "மாற்று $t(common.currentSong) பிடித்தது",
"minimizeToTray_description": "கணினி தட்டில் பயன்பாட்டைக் குறைக்கவும்",
"playButtonBehavior_optionPlay": "$ t (player.play)",
"playButtonBehavior_optionPlayShuffled": "$ t (player.shuffle)",
"playButtonBehavior_optionPlay": "$t(player.play)",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"savePlayQueue": "விளையாட்டு வரிசையை சேமிக்கவும்",
"savePlayQueue_description": "பயன்பாடு மூடப்படும் போது ப்ளே வரிசையை சேமித்து, பயன்பாடு திறக்கப்படும் போது அதை மீட்டெடுக்கவும்",
"scrobble": "ச்க்ரோபில்",
@@ -687,79 +697,97 @@
"useSystemTheme": "கணினி கருப்பொருளைப் பயன்படுத்தவும்",
"useSystemTheme_description": "கணினி வரையறுக்கப்பட்ட ஒளி அல்லது இருண்ட விருப்பத்தைப் பின்பற்றவும்",
"zoom": "சூம் விழுக்காடு",
"zoom_description": "பயன்பாட்டிற்கான சூம் சதவீதத்தை அமைக்கிறது"
"zoom_description": "பயன்பாட்டிற்கான சூம் சதவீதத்தை அமைக்கிறது",
"discordPausedStatus": "இடைநிறுத்தப்படும்போது பணக்கார இருப்பைக் காட்டுங்கள்",
"discordPausedStatus_description": "இயக்கப்பட்டால், பிளேயர் இடைநிறுத்தப்படும்போது நிலை காண்பிக்கப்படும்",
"discordServeImage": "சேவையகத்திலிருந்து {{discord}} படங்களை பரிமாறவும்",
"discordServeImage_description": "{{discord}} சேவையகத்திலிருந்தே பணக்கார இருப்புக்கான கவர் கலையைப் பகிரவும், செலிஃபின் மற்றும் நுடிட்ரோம் மட்டுமே கிடைக்கும்",
"preferLocalLyrics": "உள்ளக பாடல்களை விரும்புங்கள்",
"preferLocalLyrics_description": "கிடைக்கும்போது தொலைநிலை பாடல்களை விட உள்ளக பாடல்களை விரும்புங்கள்",
"lastfm": "last.fm இணைப்புகளைக் காட்டு",
"lastfm_description": "கலைஞர்/ஆல்பம் பக்கங்களில் Last.fm க்கான இணைப்புகளைக் காட்டு",
"notify": "பாடல் அறிவிப்புகளை இயக்கவும்",
"notify_description": "தற்போதைய பாடலை மாற்றும்போது அறிவிப்புகளைக் காட்டு",
"musicbrainz": "மியூசிக் பிரேன்ச் இணைப்புகளைக் காட்டு",
"musicbrainz_description": "கலைஞர்/ஆல்பம் பக்கங்களில் மியூசிக் பிரைன்ச் இணைப்புகளைக் காட்டு, அங்கு MBID உள்ளது",
"neteaseTranslation": "நெட்ச் மொழிபெயர்ப்புகளை இயக்கவும்",
"neteaseTranslation_description": "இயக்கப்பட்டால், கிடைத்தால் நெட்சிலிருந்து மொழிபெயர்க்கப்பட்ட பாடல்களைப் பெறுகிறது மற்றும் காட்சிப்படுத்துகிறது.",
"preservePitch": "சுருதியைப் பாதுகாக்கவும்",
"preservePitch_description": "பின்னணி வேகத்தை மாற்றும்போது சுருதியைப் பாதுகாக்கிறது"
},
"table": {
"config": {
"label": {
"album": "$ t (entity.album_one)",
"artist": "$ t (entity.artist_one)",
"biography": "$ t (காமன். -புவியியல்)",
"bitrate": "$ t (common.bitrate)",
"bpm": "$ t (common.bpm)",
"channels": "$ t (common.channel_other)",
"codec": "$ t (common.codec)",
"album": "$t(entity.album_one)",
"artist": "$t(entity.artist_one)",
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
"rating": "$ t (பொதுவானது. ரேட்டிங்)",
"rating": "$t(common.rating)",
"releaseDate": "வெளியீட்டு தேதி",
"rowIndex": "வரிசை அட்டவணை",
"size": "$ t (common.size)",
"size": "$t(common.size)",
"trackNumber": "ட்ராக் எண்",
"year": "$ t (பொதுவானது",
"year": "$t(common.year)",
"lastPlayed": "கடைசியாக விளையாடியது",
"note": "$ t (பொதுவானது. குறிப்பு)",
"owner": "$ t (பொதுவானவர்)",
"actions": "$ t (common.action_other)",
"albumArtist": "$ t (entity.albumartist_one)",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"actions": "$t(common.action_other)",
"albumArtist": "$t(entity.albumArtist_one)",
"discNumber": "வட்டு எண்",
"duration": "$ t (பொதுவானது",
"favorite": "$ t (common.foavorite)",
"genre": "$ t (entity.genre_one)",
"path": "$ t (common.path)",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
"path": "$t(common.path)",
"playCount": "விளையாட்டு எண்ணிக்கை",
"songCount": "$ t (entity.track_other)",
"title": "$ t (common.title)",
"titleCombined": "$ t (common.title) (ஒருங்கிணைந்த)"
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"titleCombined": "$t(common.title) (ணைந்தது)"
},
"view": {
"card": "அட்டை",
"table": "அட்டவணை",
"poster": "சுவரொட்டி"
"poster": "சுவரொட்டி",
"grid": "வலைவாய்",
"list": "பட்டியல்"
},
"general": {
"autoFitColumns": "ஆட்டோ பொருத்தம் நெடுவரிசைகள்",
"followCurrentSong": "தற்போதைய பாடலைப் பின்தொடரவும்",
"displayType": "காட்சி வகை",
"gap": "$ t (comman.gap)",
"gap": "$t(common.gap)",
"itemGap": "உருப்படி இடைவெளி (பிஎக்ச்)",
"itemSize": "உருப்படி அளவு (பிஎக்ச்)",
"size": "$ t (common.size)",
"size": "$t(common.size)",
"tableColumns": "அட்டவணை நெடுவரிசைகள்"
}
},
"column": {
"album": "ஆல்பம்",
"albumArtist": "ஆல்பம் கலைஞர்",
"albumCount": "$ t (entity.album_other)",
"artist": "$ t (entity.artist_one)",
"albumCount": "$t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "சுயசரிதை",
"bitrate": "பிட்ரேட்",
"bpm": "பிபிஎம்",
"channels": "$ t (common.channel_other)",
"codec": "$ t (common.codec)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"comment": "கருத்து",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
"discNumber": "வட்டு",
"favorite": "பிடித்த",
"genre": "$ t (entity.genre_one)",
"genre": "$t(entity.genre_one)",
"lastPlayed": "கடைசியாக விளையாடியது",
"path": "பாதை",
"playCount": "நாடகங்கள்",
"rating": "செயல்வரம்பு",
"releaseDate": "வெளியீட்டு தேதி",
"releaseYear": "ஆண்டு",
"size": "$ t (common.size)",
"songCount": "$ t (entity.track_other)",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "தலைப்பு",
"trackNumber": "மின்தடம்"
}
+813
View File
@@ -0,0 +1,813 @@
{
"action": {
"moveToBottom": "alttakine geç",
"moveToTop": "başa dön",
"removeFromFavorites": "$t(entity.favorite_other)lerden kaldır",
"removeFromPlaylist": "$t(entity.playlist_one) listesinden kaldır",
"removeFromQueue": "sıradan kaldır",
"setRating": "oyla",
"viewPlaylists": "$t(entity.playlist_other) listesini görüntüle",
"openIn": {
"lastfm": "Last.fm'de aç",
"musicbrainz": "MusicBrainz'da aç"
},
"addToFavorites": "$t(entity.favorite_other) listesine ekle",
"addToPlaylist": "$t(entity.playlist_one) listesine ekle",
"clearQueue": "sırayı temizle",
"createPlaylist": "$t(entity.playlist_one) listesini oluştur",
"deletePlaylist": "$t(entity.playlist_one) listesini sil",
"deselectAll": "seçimleri kaldır",
"editPlaylist": "$t(entity.playlist_one) listesini düzenle",
"goToPage": "sayfaya git",
"moveToNext": "sonrakine geç",
"refresh": "$t(common.refresh)",
"toggleSmartPlaylistEditor": "$t(entity.smartPlaylist) düzenleyiciye geç"
},
"common": {
"action_one": "eylem",
"action_other": "eylemler",
"add": "ekle",
"additionalParticipants": "ek katılımcılar",
"newVersion": "yeni bir sürüm ({{version}}) yüklendi",
"viewReleaseNotes": "sürüm notlarını görüntüle",
"areYouSure": "emin misin?",
"backward": "geri",
"biography": "biyografi",
"bitDepth": "bit derinliği",
"bitrate": "bit hızı",
"bpm": "bpm",
"cancel": "iptal et",
"center": "merkez",
"channel_one": "kanal",
"channel_other": "kanallar",
"clear": "temizle",
"close": "kapat",
"codec": "codec",
"comingSoon": "çok yakında…",
"configure": "yapılandır",
"confirm": "onayla",
"create": "oluştur",
"currentSong": "şu anki parça $t(entity.track_one)",
"decrease": "azalt",
"delete": "sil",
"descending": "azalan",
"description": "açıklama",
"disable": "devre dışı",
"disc": "disk",
"duration": "süre",
"edit": "düzenle",
"enable": "etkinleştir",
"expand": "genişlet",
"favorite": "favori",
"filter_one": "filtre",
"filter_other": "filtreler",
"filters": "filtreler",
"forceRestartRequired": "değişiklikleri uygulamak için yeniden başlatın... yeniden başlatmak için bildirimi kapatın",
"forward": "ileri",
"gap": "boşluk",
"home": "ana sayfa",
"left": "sol",
"manage": "yönet",
"increase": "arttır",
"limit": "sınır",
"maximize": "ekranı kapla",
"menu": "menü",
"minimize": "simge durumuna küçült",
"modified": "değiştirilmiş",
"mbid": "MusicBrainz ID",
"name": "isim",
"no": "hayır",
"none": "hiçbiri",
"noResultsFromQuery": "arama sorguları için sonuç bulunamadı",
"note": "not",
"ok": "tamam",
"owner": "sahip",
"path": "yol",
"playerMustBePaused": "oynatıcı duraklatılmalı",
"preview": "önizleme",
"previousSong": "önceki $t(entity.track_one)",
"quit": "çık",
"random": "rastgele",
"rating": "oylama",
"refresh": "yenile",
"reload": "yeniden yükle",
"reset": "sıfırla",
"resetToDefault": "varsayılana sıfırla",
"restartRequired": "yeniden başlatma gerekli",
"right": "sağ",
"sampleRate": "örnekleme hızı",
"save": "kaydet",
"saveAndReplace": "kaydet ve değiştir",
"saveAs": "farklı kaydet",
"search": "arama",
"setting": "ayarlar",
"share": "paylaş",
"size": "boyut",
"sortOrder": "sıralama düzeni",
"tags": "etiketler",
"title": "başlık",
"trackNumber": "parça",
"albumGain": "albüm kazancı",
"albumPeak": "albüm zirvesi",
"ascending": "artan",
"collapse": "daralt",
"dismiss": "kapat",
"translation": "çeviri",
"unknown": "bilinmeyen",
"version": "sürüm",
"year": "yıl",
"yes": "evet",
"trackGain": "parça kazancı",
"trackPeak": "parça zirvesi"
},
"entity": {
"album_one": "albüm",
"album_other": "albümler",
"albumArtist_one": "albüm sanatçısı",
"albumArtist_other": "albüm sanatçıları",
"albumArtistCount_one": "{{count}} albüm sanatçısı",
"albumArtistCount_other": "{{count}} albüm sanatçıları",
"albumWithCount_one": "{{count}} albüm",
"albumWithCount_other": "{{count}} albüm",
"artist_one": "sanatçı",
"artist_other": "sanatçılar",
"artistWithCount_one": "{{count}} sanatçı",
"artistWithCount_other": "{{count}} sanatçı",
"favorite_one": "favori",
"favorite_other": "favoriler",
"folder_one": "klasör",
"folder_other": "klasörler",
"folderWithCount_one": "{{count}} klasör",
"folderWithCount_other": "{{count}} klasör",
"genre_one": "tür",
"genre_other": "türler",
"genreWithCount_one": "{{count}} tür",
"genreWithCount_other": "{{count}} türler",
"playlist_one": "çalma listesi",
"playlist_other": "çalma listeleri",
"play_one": "{{count}} oynat",
"play_other": "{{count}} oynatma",
"playlistWithCount_one": "{{count}} oynatma listesi",
"playlistWithCount_other": "{{count}} oynatma listesi",
"smartPlaylist": "akıllı $t(entity.playlist_one)",
"track_one": "parça",
"track_other": "parçalar",
"song_one": "şarkı",
"song_other": "şarkılar",
"trackWithCount_one": "{{count}} parça",
"trackWithCount_other": "{{count}} parça"
},
"error": {
"apiRouteError": "istek yönlendirilemiyor",
"audioDeviceFetchError": "ses aygıtları alınmaya çalışılırken bir hata oluştu",
"authenticationFailed": "kimlik doğrulaması başarısız",
"badAlbum": "bu sayfayı görüyorsunuz çünkü bu şarkı bir albümün parçası değil. büyük olasılıkla müzik klasörünüzün en üst seviyesinde bir şarkınız varsa bu sorunu görüyorsunuz. jellyfin yalnızca bir klasör içindeyse parçaları gruplandırır.",
"badValue": "geçersiz seçenek \"{{value}}\". bu değer artık mevcut değil",
"remotePortError": "uzak sunucu bağlantı noktası ayarlanmaya çalışılırken bir hata oluştu",
"remotePortWarning": "yeni bağlantı noktasını uygulamak için sunucuyu yeniden başlatın",
"serverNotSelectedError": "sunucu seçili değil",
"serverRequired": "sunucu gerekli",
"sessionExpiredError": "oturumunuzun süresi doldu",
"systemFontError": "sistem fontlarını almaya çalışırken bir hata oluştu",
"endpointNotImplementedError": "{{endpoint}} uç noktası bu {{serverType}} için uygulanamaz",
"genericError": "bir hata oluştu",
"invalidServer": "geçersiz sunucu",
"localFontAccessDenied": "yerel fontlara erişim reddedildi",
"loginRateError": "çok fazla giriş denemesi, lütfen birkaç saniye içinde tekrar deneyin",
"mpvRequired": "MPV gerekli",
"networkError": "bir ağ hatası meydana geldi",
"notificationDenied": "bildirimler için izinler reddedildi. bu ayarın hiçbir etkisi yoktur",
"openError": "dosya açılamadı",
"playbackError": "medya oynatmayı çalışırken bir hata meydana geldi",
"credentialsRequired": "ki̇mli̇k bi̇lgi̇leri̇ gerekli",
"remoteDisableError": "uzak sunucuyu $t(common.disable) yapmaya çalışırken bir hata oluştu",
"remoteEnableError": "uzak sunucuyu $t(common.enable) yapmaya çalışırken bir hata oluştu"
},
"filter": {
"albumCount": "$t(entity.album_other) sayısı",
"biography": "biyografi",
"bitrate": "bit hızı",
"bpm": "bpm",
"comment": "yorum",
"communityRating": "topluluk derecelendirmesi",
"criticRating": "eleştirmen derecelendirmesi",
"dateAdded": "tarih eklendi",
"disc": "disk",
"duration": "süre",
"favorited": "favorilendi",
"fromYear": "yılından itibaren",
"id": "kimlik",
"isCompilation": "derleme",
"isFavorited": "favorilendi",
"isPublic": "herkese açık",
"isRated": "oylandı",
"isRecentlyPlayed": "yakın zamanda çalındı",
"lastPlayed": "son çalınan",
"mostPlayed": "en çok çalınan",
"name": "isim",
"note": "not",
"owner": "$t(common.owner)",
"path": "yol",
"playCount": "çalma sayısı",
"random": "rastgele",
"rating": "oylama",
"recentlyAdded": "yakın zamanda eklendi",
"recentlyPlayed": "yakın zamanda oynadı",
"recentlyUpdated": "yakın zamanda güncellendi",
"releaseDate": "çıkış tarihi",
"releaseYear": "çıkış yılı",
"search": "arama",
"songCount": "şarkı sayısı",
"title": "başlık",
"toYear": "yılına kadar",
"trackNumber": "parça",
"genre": "$t(entity.genre_one)",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"channels": "$t(common.channel_other)"
},
"form": {
"addServer": {
"error_savePassword": "şifreyi kaydetmeye çalışırken bir hata oluştu",
"ignoreCors": "cors'u $t(common.restartRequired) görmezden gel",
"ignoreSsl": "ssl bağlantısını görmezden gel $t(common.restartRequired)",
"input_legacyAuthentication": "eski kimlik doğrulamayı etkinleştir",
"input_name": "sunucu ismi",
"input_password": "şifre",
"input_savePassword": "şifreyi kaydet",
"input_url": "URL",
"input_username": "kullanıcı ismi",
"success": "sunucu başarıyla eklendi",
"title": "sunucu ekle",
"input_preferInstantMix": "anında mix tercih et",
"input_preferInstantMixDescription": "sadece benzer şarkılari bulmak icin anında mix kullan. Bu davranışı değiştiren eklentilere sahipseniz faydalı"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"input_skipDuplicates": "kopyaları atla",
"title": "$t(entity.playlist_one) listesine ekle",
"success": "$t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} }) $t(entity.trackWithCount, {\"count\": {{message}} }) eklendi"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "herkese açık",
"success": "$t(entity.playlist_one) listesi başarıyla oluşturuldu",
"title": "$t(entity.playlist_one) listesini oluştur"
},
"deletePlaylist": {
"input_confirm": "onaylamak için $t(entity.playlist_one) listesinin adını yazın",
"success": "$t(entity.playlist_one) listesi başarıyla silindi",
"title": "$t(entity.playlist_one) listesini sil"
},
"editPlaylist": {
"publicJellyfinNote": "Jellyfin bazı nedenlerden dolayı bir çalma listesinin herkese açık olup olmadığını göstermez. Bunun herkese açık kalmasını istiyorsanız, lütfen aşağıdaki girdiyi seçin",
"success": "$t(entity.playlist_one) listesi başarıyla güncellendi",
"title": "$t(entity.playlist_one) listesini düzenle"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "şarkı sözü arama"
},
"queryEditor": {
"title": "sorgu düzenleyici",
"input_optionMatchAll": "hepsini eşleştir",
"input_optionMatchAny": "herhangi biriyle eşleştir"
},
"shareItem": {
"allowDownloading": "indirmeye izin ver",
"description": "açıklama",
"setExpiration": "sona erme tarihi ayarla",
"success": "paylaşma bağlantısı panoya kopyalandı (veya açmak için buraya tıklayın)",
"expireInvalid": "son kullanma tarihi gelecekte olmalı",
"createFailed": "paylaşım oluşturulamadı (paylaşım etkin mi?)"
},
"updateServer": {
"success": "sunucu başarıyla güncellendi",
"title": "sunucuyu güncelle"
},
"privateMode": {
"enabled": "gizli mod etkinleştirildi, oynatma durumu artık harici eklentilerden gizlendi",
"disabled": "gizli mod devre dışı bırakıldı, oynatma durumu artık etkinleştirilmiş harici eklentiler tarafından görülebilir",
"title": "gizli mod"
}
},
"page": {
"albumArtistDetail": {
"about": "{{artist}} hakkında",
"appearsOn": "üzerinde görünür",
"recentReleases": "son sürümler",
"viewDiscography": "diskografiyi görüntüle",
"relatedArtists": "$t(entity.artist_other) ile benzer",
"topSongs": "en iyi şarkılar",
"viewAll": "tümünü görüntüle",
"viewAllTracks": "tüm $t(entity.track_other) görüntüle",
"topSongsFrom": "{{title}} tarafından en iyi şarkılar"
},
"contextMenu": {
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"download": "indir",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"numberSelected": "{{count}} seçildi",
"play": "$t(player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"addFavorite": "$t(action.addToFavorites)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "öğeyi paylaş",
"showDetails": "bilgi al",
"goToAlbum": "$t(entity.album_one) sayfasına git",
"goToAlbumArtist": "$t(entity.albumArtist_one) sayfasına git"
},
"manageServers": {
"url": "URL",
"username": "kullanıcıadı",
"editServerDetailsTooltip": "sunucu ayrıntılarını düzenle",
"removeServer": "sunucuyu kaldır",
"title": "sunucuları yönet",
"serverDetails": "sunucu detayları"
},
"fullscreenPlayer": {
"config": {
"dynamicBackground": "dinamik arka plan",
"dynamicImageBlur": "görüntü bulanıklık boyutu",
"dynamicIsImage": "arka plan resmini etkinleştir",
"followCurrentLyric": "şu anki şarkı sözlerini takip et",
"lyricAlignment": "şarkı sözü hizalama",
"lyricOffset": "şarkı sözü ofseti (ms)",
"lyricGap": "şarkı sözü boşluğu",
"lyricSize": "şarkı sözü boyutu",
"opacity": "opaklık",
"showLyricMatch": "şarkı sözü eşleşmesini göster",
"showLyricProvider": "şarkı sözü sağlayıcısını göster",
"synchronized": "eşitlenmiş",
"unsynchronized": "eşitlenmemiş",
"useImageAspectRatio": "görüntü en boy oranını kullanın"
},
"lyrics": "şarkı sözleri",
"related": "i̇lgili",
"upNext": "sıradaki",
"visualizer": "görselleştirici",
"noLyrics": "şarkı sözü bulunamadı"
},
"genreList": {
"showAlbums": "$t(entity.genre_one) $t(entity.album_other) göster",
"showTracks": "$t(entity.genre_one)$t(entity.track_other) göster",
"title": "$t(entity.genre_other)"
},
"globalSearch": {
"commands": {
"goToPage": "sayfaya git",
"searchFor": "{{query}} için ara",
"serverCommands": "sunucu komutları"
},
"title": "komutlar"
},
"home": {
"explore": "kütüphanenizden keşfedin",
"mostPlayed": "en çok çalınan",
"newlyAdded": "yeni eklenenler",
"recentlyPlayed": "yakınlarda çalınanlar",
"title": "$t(common.home)"
},
"itemDetail": {
"copyPath": "yolu panoya kopyala",
"copiedPath": "yol başarıyla kopyalandı",
"openFile": "dosya yöneticisinde parçayı göster"
},
"playlist": {
"reorder": "yeniden sıralama yalnızca kimliğe göre sıralama yapıldığında etkinleştirilir"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"setting": {
"advanced": "gelişmiş",
"generalTab": "genel",
"hotkeysTab": "kısayol tuşları",
"playbackTab": "oynatma",
"windowTab": "pencere"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"artists": "$t(entity.artist_other)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"myLibrary": "kütüphanem",
"nowPlaying": "şimdi oynatılıyor",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"shared": "paylaşılan $t(entity.playlist_other)",
"tracks": "$t(entity.track_other)"
},
"trackList": {
"artistTracks": "{{artist}} parçaları",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"title": "$t(entity.track_other)"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "$t(entity.artist_one) sanatçısından daha fazla",
"moreFromGeneric": "{{item}} tarafından daha fazla",
"released": "yayınlandı"
},
"albumList": {
"title": "$t(entity.album_other)",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"artistAlbums": "{{artist}} albümleri"
},
"appMenu": {
"collapseSidebar": "kenar çubuğunu daralt",
"expandSidebar": "kenar çubuğunu genişlet",
"goBack": "geri dön",
"goForward": "i̇leriye git",
"manageServers": "sunucuları yönet",
"openBrowserDevtools": "tarayıcı geliştirici araçlarını aç",
"quit": "$t(common.quit)",
"selectServer": "sunucu seç",
"settings": "$t(common.setting_other)",
"version": "{{version}} sürümü",
"privateModeOff": "gizli modu kapat",
"privateModeOn": "gizli modu aç"
}
},
"player": {
"addLast": "sona ekle",
"addNext": "sonrakine ekle",
"favorite": "favori",
"mute": "sessiz",
"muted": "sessiz",
"next": "sonraki",
"play": "çal",
"playbackFetchCancel": "bu biraz zaman alıyor... iptal etmek için bildirimi kapatın",
"playbackFetchInProgress": "şarkılar yükleniyor…",
"playbackFetchNoResults": "hiçbir şarkı bulunamadı",
"playbackSpeed": "oynatma hızı",
"playRandom": "rastgele çal",
"playSimilarSongs": "benzer şarkılar çal",
"previous": "önceki",
"queue_clear": "sırayı temizle",
"queue_moveToBottom": "seçileni üste taşı",
"queue_moveToTop": "seçileni alta taşı",
"queue_remove": "seçileni kaldır",
"repeat": "birini tekrarla",
"repeat_all": "tümünü tekrarla",
"repeat_off": "tekrarlama devre dışı",
"shuffle": "karışık çal",
"shuffle_off": "karışık çalmayı devre dışı bırak",
"skip": "atla",
"skip_back": "geriye atla",
"skip_forward": "ileri atla",
"stop": "durdur",
"toggleFullscreenPlayer": "tam ekran oynatıcıya geç",
"unfavorite": "favoriden kaldır",
"pause": "durdur",
"viewQueue": "kuyruğu görüntüle"
},
"setting": {
"accentColor": "vurgu rengi",
"accentColor_description": "uygulama için vurgu rengini ayarlar",
"albumBackground": "albüm arka plan resmi",
"albumBackground_description": "albüm resmini içeren albüm sayfaları için bir arka plan resmi ekler",
"albumBackgroundBlur": "albüm arka plan resmi bulanıklaştırma boyutu",
"albumBackgroundBlur_description": "albüm arka plan görüntüsüne uygulanan bulanıklık miktarını ayarlar",
"applicationHotkeys": "uygulama kısayol tuşları",
"applicationHotkeys_description": "uygulama kısayol tuşlarını yapılandırın. genel kısayol tuşu olarak ayarlamak için onay kutusunu değiştirin (yalnızca masaüstü)",
"artistConfiguration": "albüm sanatçı sayfası yapılandırması",
"artistConfiguration_description": "albüm sanatçısı sayfasında hangi öğelerin ve hangi sırayla gösterileceğini yapılandır",
"audioDevice": "ses aygıtı",
"audioDevice_description": "oynatma için kullanılacak ses cihazını seçin (yalnızca web oynatıcı)",
"audioExclusiveMode": "ses özel modu",
"audioExclusiveMode_description": "özel çıkış modunu etkinleştirin. Bu modda, sistem genellikle kilitlenir ve yalnızca mpv ses çıkışı yapabilir",
"audioPlayer": "ses oynatıcı",
"audioPlayer_description": "oynatma için kullanılacak ses oynatıcısını seçin",
"buttonSize": "oynatma çubuğu düğme boyutu",
"buttonSize_description": "oynatma çubuğu düğmelerinin boyutu",
"clearCache": "tarayıcı önbelleğini temizle",
"clearCache_description": "feishin'in 'zor temizliği'. feishin'in önbelleğini temizlemeye ek olarak, tarayıcı önbelleğini de boşaltın (kayıtlı resimler ve diğer varlıklar). sunucu kimlik bilgileri ve ayarları korunur",
"clearQueryCache": "feishin önbelleğini temizle",
"clearQueryCache_description": "feishin'in 'yumuşak temizliği'. bu işlem çalma listelerini, parça meta verilerini yeniler ve kayıtlı şarkı sözlerini sıfırlar. ayarlar, sunucu kimlik bilgileri ve önbelleğe alınmış görüntüler korunur",
"clearCacheSuccess": "önbellek başarıyla temizlendi",
"contextMenu": "içerik menüsü (sağ tıklama) yapılandırması",
"contextMenu_description": "bir öğeye sağ tıkladığınızda menüde gösterilen öğeleri gizlemenizi sağlar. işaretli olmayan öğeler gizlenecektir",
"crossfadeDuration": "çapraz geçiş süresi",
"crossfadeDuration_description": "çapraz geçiş efektinin süresini ayarlar",
"crossfadeStyle": "çapraz geçiş stili",
"crossfadeStyle_description": "ses oynatıcı için kullanılacak çapraz geçiş stilini seçin",
"customCssEnable": "özel css etkinleştir",
"customCssEnable_description": "özel css yazmaya izin verir.",
"customCssNotice": "Uyarı: bazı sterillemeler (url() ve içeriğe izin verilmemesi) olsa da, özel CSS kullanmak arayüzü değiştirmede hala risk oluşturabilir.",
"customCss": "özel css",
"customCss_description": "özel css içeriği. Not: içerik ve uzaktan URL'ler izin verilmeyen özelliklerdir. İçeriğinizin önizlemesi aşağıda gösterilmektedir. Ayarlamadığınız ek alanlar sterilleme nedeniyle mevcuttur.",
"customFontPath": "özel yazı tipi yolu",
"customFontPath_description": "uygulama için kullanılacak özel yazı tipinin yolunu ayarlar",
"disableAutomaticUpdates": "otomatik güncellemeleri devre dışı bırak",
"disableLibraryUpdateOnStartup": "başlangıçta yeni sürümler için denetimi devre dışı bırak",
"discordApplicationId": "{{discord}} uygulama kimliği",
"discordApplicationId_description": "{{discord}} \"Rich Presence\" için uygulama kimliği (varsayılan olarak {{defaultId}})",
"discordPausedStatus": "duraklatıldığında \"Rich Presence\"da göster",
"discordPausedStatus_description": "etkinleştirildiğinde, oynatıcı duraklatıldığında durum gösterilir",
"discordIdleStatus": "\"Rich presence\" boşta durumunu göster",
"discordIdleStatus_description": "etkinleştirildiğinde, oynatıcı boştayken durumu günceller",
"discordListening": "durumu dinleme olarak göster",
"discordListening_description": "durumu çalma yerine dinleme olarak göster",
"discordRichPresence": "{{discord}} Rich Presence",
"discordRichPresence_description": "{{discord}} \"Rich Presence\" oynatma durumunu etkinleştirin. Görüntü tuşları şunlardır: {{icon}}, {{playing}} ve {{paused}}",
"discordServeImage": "sunucudan {{discord}} resimleri servis et",
"discordServeImage_description": "sunucudan {{discord}} Rich Presence için kapak resmi paylaşın, yalnızca jellyfin ve navidrome için kullanılabilir",
"discordUpdateInterval": "{{discord}} Rich Presence güncelleme aralığı",
"discordUpdateInterval_description": "her güncelleme arasındaki saniye cinsinden süre (minimum 15 saniye)",
"doubleClickBehavior": "çift tıklandığında aranan tüm parçaları sıraya koyma",
"gaplessAudio": "aralıksız ses",
"gaplessAudio_description": "mpv için aralıksız ses ayarını belirler",
"gaplessAudio_optionWeak": "zayıf (tavsiye edilen)",
"genreBehavior": "tür sayfası varsayılan davranışı",
"genreBehavior_description": "bir türe tıklandığında varsayılan olarak parça mı yoksa albüm listesinde mi açılacağını belirler",
"globalMediaHotkeys": "evrensel medya kısayol tuşları",
"globalMediaHotkeys_description": "oynatmayı kontrol etmek için sistem medya kısayol tuşlarınızın kullanımını etkinleştirin veya devre dışı bırakın",
"homeConfiguration": "ana sayfa yapılandırma",
"homeConfiguration_description": "ana sayfada hangi öğelerin ve hangi sırayla gösterileceğini yapılandır",
"homeFeature": "ana sayfa öne çıkan görselleri",
"homeFeature_description": "ana sayfada büyük özellikli görsellerin gösterilip gösterilmeyeceğini kontrol eder",
"hotkey_rate0": "derecelendirme temizle",
"hotkey_rate1": "derecelendirme 1 yıldız",
"hotkey_rate2": "derecelendirme 2 yıldız",
"hotkey_rate3": "derecelendirme 3 yıldız",
"hotkey_rate4": "derecelendirme 4 yıldız",
"hotkey_rate5": "derecelendirme 5 yıldız",
"hotkey_skipBackward": "geri atla",
"hotkey_skipForward": "ileri atla",
"hotkey_toggleCurrentSongFavorite": "$t(common.currentSong) beğenilenlere ekle",
"hotkey_toggleFullScreenPlayer": "tam ekran oynatıcı tuşu",
"hotkey_togglePreviousSongFavorite": "$t(common.previousSong) beğenilenlere ekle",
"hotkey_toggleQueue": "kuyruğu aç",
"hotkey_toggleRepeat": "tekrarlamayı aç",
"hotkey_toggleShuffle": "karıştırmayı değiştir",
"hotkey_unfavoriteCurrentSong": "$t(common.currentSong) beğenilerden kaldır",
"hotkey_unfavoritePreviousSong": "$t(common.previousSong) beğenilerden kaldır",
"hotkey_volumeDown": "ses kısma",
"hotkey_volumeMute": "sessize alma",
"hotkey_volumeUp": "sesi yükselt",
"hotkey_zoomIn": "yakınlaştır",
"hotkey_zoomOut": "uzaklaştır",
"imageAspectRatio": "doğal kapak resmi en boy oranını kullanın",
"imageAspectRatio_description": "etkinleştirilirse, kapak resmi kendi doğal en boy oranı kullanılarak gösterilecektir. 1:1 olmayan resimler için kalan alan boş olacaktır",
"language": "dil",
"language_description": "uygulama için dili ayarlar ($t(common.restartRequired))",
"lastfm": "last.fm bağlantılarını göster",
"lastfm_description": "sanatçı/albüm sayfalarında last.fm bağlantılarını göster",
"lastfmApiKey": "{{lastfm}} API anahtarı",
"lastfmApiKey_description": "{{lastfm}} için API anahtarı. kapak resmi için gereklidir",
"lyricFetch": "internetten şarkı sözü getirme",
"lyricFetch_description": "çeşitli internet kaynaklarından şarkı sözleri getirme",
"lyricFetchProvider": "şarkı sözlerini almak için sağlayıcılar",
"lyricFetchProvider_description": "şarkı sözlerinin getirileceği sağlayıcıları seçin. sağlayıcıların sırası, sorgulanacakları sıradır",
"lyricOffset": "şarkı sözü kaydırma (ms)",
"lyricOffset_description": "şarkı sözünü belirtilen milisaniye miktarı kadar kaydırır",
"notify": "şarkı bildirimlerini etkinleştir",
"notify_description": "geçerli şarkıyı değiştirirken bildirimleri göster",
"minimizeToTray": "tepsiye yerleştir",
"minimizeToTray_description": "uygulamayı sistem tepsisine küçültme",
"minimumScrobblePercentage": "minimum \"scrobble\" (dinleme sayımı) süresi (yüzde)",
"minimumScrobblePercentage_description": "'scrobble' yapılmadan önce çalınması gereken minimum şarkı yüzdesi",
"minimumScrobbleSeconds": "minimum 'scrobble' (saniye)",
"minimumScrobbleSeconds_description": "'scrobble' yapılmadan önce çalınması gereken şarkının saniye cinsinden minimum süresi",
"mpvExecutablePath": "mpv çalıştırma yolu",
"mpvExecutablePath_description": "mpv çalıştırma yolunu ayarlar. boş bırakılırsa, varsayılan yol kullanılır",
"mpvExtraParameters": "mpv parametreleri",
"mpvExtraParameters_help": "her satır için tek",
"musicbrainz": "musicbrainz bağlantılarını göster",
"musicbrainz_description": "mbid'in bulunduğu sanatçı/albüm sayfalarında musicbrainz bağlantılarını göster",
"neteaseTranslation": "NetEase çevirilerini etkinleştirin",
"neteaseTranslation_description": "etkinleştirildiğinde, varsa NetEase platformunda çevrilmiş şarkı sözlerini alır ve görüntüler.",
"passwordStore": "passwords/secret store",
"passwordStore_description": "hangi parola/gizli deponun kullanılacağıdır. parolaları saklama konusunda sorun yaşıyorsanız bunu değiştirin.",
"playbackStyle": "oynatma stili",
"playbackStyle_description": "ses oynatıcı için kullanılacak oynatma stilini seçin",
"playbackStyle_optionCrossFade": "çapraz geçiş",
"playbackStyle_optionNormal": "normal",
"playButtonBehavior": "oynat düğmesi davranışı",
"playButtonBehavior_description": "kuyruğa şarkı eklerken oynat düğmesinin varsayılan davranışını ayarlar",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"playButtonBehavior_optionPlay": "$t(player.play)",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playerAlbumArtResolution": "oynatıcı albüm resmi çözünürlüğü",
"playerAlbumArtResolution_description": "büyük oynatıcının albüm resmi önizlemesi için çözünürlük. daha büyük değerler daha net görünmesini sağlar, ancak yüklemeyi yavaşlatabilir. varsayılan değer 0, otomatik olarak çalışır",
"playerbarOpenDrawer": "oynatma çubuğu tam ekran geçişi",
"playerbarOpenDrawer_description": "tam ekran oynatıcıyı açmak için oynatma çubuğuna tıklamaya izin verir",
"remotePassword": "uzaktan kontrol sunucusu şifresi",
"remotePassword_description": "uzaktan kumanda sunucusu için parolayı ayarlar. Bu kimlik bilgileri varsayılan olarak güvensiz bir şekilde aktarılır, bu nedenle önemsemediğiniz benzersiz bir parola kullanmalısınız",
"remotePort": "uzaktan kontrol sunucusu bağlantı noktası",
"remotePort_description": "uzaktan kumanda sunucusu için bağlantı noktasını ayarlar",
"remoteUsername": "uzaktan kontrol sunucusu kullanıcı adı",
"remoteUsername_description": "uzaktan kontrol sunucusu için kullanıcı adını ayarlar. hem kullanıcı adı hem de parola boşsa, kimlik doğrulama devre dışı bırakılır",
"replayGainClipping": "{{ReplayGain}} kırpma",
"replayGainClipping_description": "Kazancı otomatik olarak düşürerek {{ReplayGain}}'in neden olduğu kırpılmayı önleyin",
"replayGainFallback": "{{ReplayGain}} geri dönüş",
"replayGainFallback_description": "dosyada {{ReplayGain}} etiketi yoksa db'e uygulanacak kazanç",
"replayGainMode": "{{ReplayGain}} modu",
"replayGainMode_description": "ses seviyesi kazancını dosya meta verilerinde saklanan {{ReplayGain}} değerlerine göre ayarlayın",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"replayGainPreamp": "{{ReplayGain}} preamp (dB)",
"replayGainPreamp_description": "{{ReplayGain}} değerlerine uygulanan preamp kazancını ayarlar",
"sampleRate": "örnekleme hızı",
"sampleRate_description": "seçilen örnekleme frekansı mevcut ortamınkinden farklıysa kullanılacak çıkış örnekleme oranını seçin. 8000'den küçük değerler için varsayılan frekans kullanacaktır",
"savePlayQueue": "oynatma kuyruğunu kaydet",
"savePlayQueue_description": "uygulama kapatıldığında oynatma kuyruğunu kaydedin ve uygulama açıldığında geri yükleyin",
"scrobble": "scrobble",
"scrobble_description": "scrobble medya sunucunuzda oynatılır",
"showSkipButton": "atlama düğmelerini göster",
"showSkipButton_description": "oynatıcı çubuğundaki atlama düğmelerini göster veya gizle",
"showSkipButtons": "atlama düğmelerini göster",
"showSkipButtons_description": "oynatıcı çubuğundaki atlama düğmelerini göster veya gizle",
"sidebarCollapsedNavigation": "kenar çubuğu (daraltılmış) navigasyon",
"sidebarCollapsedNavigation_description": "daraltılmış kenar çubuğunda gezinmeyi göster veya gizle",
"sidebarConfiguration": "kenar çubuğu yapılandırması",
"sidebarConfiguration_description": "öğeleri ve bunların kenar çubuğunda görünme sırasını seçme",
"sidebarPlaylistList": "kenar çubuğu çalma listesi",
"sidebarPlaylistList_description": "kenar çubuğunda çalma listesi listesini gösterme veya gizleme",
"sidePlayQueueStyle": "yan oynatma kuyruğu stili",
"sidePlayQueueStyle_description": "yan oynatma kuyruğunun stilini ayarlar",
"sidePlayQueueStyle_optionAttached": "ekli",
"sidePlayQueueStyle_optionDetached": "ayrılmış",
"skipDuration": "atlama süresi",
"skipDuration_description": "oynatıcı çubuğundaki atlama düğmeleri kullanılırken atlanacak süreyi ayarlar",
"translationApiKey_description": "çeviri için api anahtarı (Yalnızca global hizmet uç noktasını destekler)",
"translationTargetLanguage": "çeviri hedef dili",
"translationTargetLanguage_description": "çeviri için hedef dil",
"trayEnabled": "tepsiyi göster",
"trayEnabled_description": "tepsi simgesini/menüsünü göster/gizle. devre dışı bırakılırsa, tepsiye küçültme/çıkış da devre dışı bırakır",
"useSystemTheme": "sistem temasını kullan",
"useSystemTheme_description": "sistem tarafından tanımlanan açık veya koyu mod tercihini takip et",
"volumeWheelStep": "ses tekerleği adımı",
"volumeWheelStep_description": "ses seviyesi kaydırıcısı üzerinde fare tekerleğini kaydırırken değiştirilecek ses seviyesi miktarı",
"volumeWidth": "ses kaydırıcı genişliği",
"volumeWidth_description": "ses seviyesi kaydırıcısının genişliği",
"webAudio": "ağ sesini kullanın",
"webAudio_description": "ağ sesi kullanın. bu, replaygain gibi gelişmiş özellikleri etkinleştirir. aksi bir durumla karşılaşırsanız devre dışı bırakın",
"preservePitch": "perdeyi koru",
"preservePitch_description": "oynatma hızını değiştirirken perdeyi korur",
"windowBarStyle": "pencere çubuğu stili",
"windowBarStyle_description": "pencere çubuğunun stilini seçin",
"zoom": "yakınlaştırma yüzdesi",
"zoom_description": "uygulama için yakınlaştırma yüzdesini ayarlar",
"doubleClickBehavior_description": "evet ise, bir parça aramasında eşleşen tüm parçalar sıraya alınır. aksi takdirde, yalnızca tıklanan parça sıraya alınır",
"enableRemote": "uzaktan kontrol sunucusunu etkinleştir",
"enableRemote_description": "uzaktan kumanda sunucusunun diğer cihazların uygulamayı kontrol etmesine izin vermesini sağlar",
"externalLinks": "harici bağlantıları göster",
"externalLinks_description": "sanatçı/albüm sayfalarında dış bağlantıların (Last.fm, MusicBrainz) gösterilmesini sağlar",
"exitToTray": "tepsiye çıkış",
"exitToTray_description": "uygulamadan sistem tepsisine çıkma",
"floatingQueueArea": "kayan liste üzerine gelinen alanı göster",
"floatingQueueArea_description": "oynatma kuyruğunu görüntülemek için ekranın sağ tarafında fareyle üzerine gelinen bir simge görüntüleyin",
"followLyric": "güncel şarkı sözlerini takip et",
"followLyric_description": "şarkı sözünü geçerli çalma konumuna kaydırma",
"preferLocalLyrics": "yerel sözleri tercih edin",
"preferLocalLyrics_description": "mümkün olduğunda uzak (remote) şarkı sözleri yerine yerel olarak depolanan şarkı sözlerini tercih edin",
"font": "font",
"font_description": "uygulama için kullanılacak yazı tipini ayarlar",
"fontType": "yazı tipi",
"fontType_description": "yerleşik yazı tipi Feishin tarafından sağlanan yazı tiplerinden birini seçer. sistem yazı tipi işletim sisteminiz tarafından sağlanan herhangi bir yazı tipini seçmenize izin verir. özel kendi yazı tipinizi sağlamanıza izin verir",
"fontType_optionBuiltIn": "yerleşik yazı tipi",
"fontType_optionCustom": "özel yazı tipi",
"fontType_optionSystem": "sistem yazı tipi",
"hotkey_browserBack": "tarayıcı geri",
"hotkey_browserForward": "tarayıcı ileri",
"hotkey_favoriteCurrentSong": "$t(common.currentSong) favorilere ekle",
"hotkey_favoritePreviousSong": "$t(common.previousSong) favorilere ekle",
"hotkey_globalSearch": "genel arama",
"hotkey_localSearch": "sayfa içi arama",
"hotkey_playbackNext": "sonraki parça",
"hotkey_playbackPause": "durdur",
"hotkey_playbackPlay": "çal",
"hotkey_playbackPlayPause": "çal / duraklat",
"translationApiKey": "çeviri api anahtarı",
"translationApiProvider_description": "çeviri için api sağlayıcısı",
"translationApiProvider": "çeviri api sağlayıcısı",
"transcodeFormat_description": "dönüştürülecek formatı seçer. sunucunun karar vermesi için boş bırakın",
"transcodeFormat": "dönüştürülecek format",
"transcodeBitrate_description": "kod dönüştürmek için bit hızını seçer. 0, sunucunun seçmesine izin ver anlamına gelir",
"transcodeBitrate": "kod dönüştürmek için bit hızı",
"transcode_description": "farklı formatlara kod dönüştürme sağlar",
"hotkey_playbackStop": "dur",
"hotkey_playbackPrevious": "önceki parça",
"skipPlaylistPage": "çalma listesi sayfasını atla",
"skipPlaylistPage_description": "bir çalma listesine giderken, varsayılan sayfa yerine çalma listesi şarkı listesi sayfasına gitme",
"startMinimized": "küçültülmüş olarak başlat",
"startMinimized_description": "uygulamayı sistem tepsisinde başlat",
"theme": "tema",
"theme_description": "uygulama için kullanılacak temayı ayarlar",
"themeDark": "tema (koyu)",
"themeDark_description": "uygulama için kullanılacak koyu temayı ayarlar",
"themeLight": "tema (açık)",
"themeLight_description": "uygulama için kullanılacak açık temayı ayarlar",
"transcodeNote": "1 (web) - 2 (mpv) şarkıdan sonra etkili olur",
"transcode": "kod dönüştürmeyi etkinleştir",
"discordDisplayType": "{{discord}} varlık gösterge türü",
"discordDisplayType_description": "durumunuzda dinlediğiniz şarkı olarak değiştirir",
"discordDisplayType_songname": "şarkı ismi",
"discordDisplayType_artistname": "Sanatçı adı(ları)",
"hotkey_navigateHome": "ana sayfaya git",
"preventSleepOnPlayback": "oynatma sırasında uykuyu önle",
"preventSleepOnPlayback_description": "müzik çalarken ekranın uyku moduna geçmesini önle"
},
"table": {
"column": {
"album": "albüm",
"albumArtist": "albüm sanatçısı",
"albumCount": "$t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "biyografi",
"bitrate": "bit hızı",
"bpm": "bpm (dakika başına vuruş)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"comment": "yorum",
"dateAdded": "tarih eklendi",
"discNumber": "disk",
"favorite": "favori",
"genre": "$t(entity.genre_one)",
"lastPlayed": "son çalınan",
"path": "yol",
"playCount": "oynatılıyor",
"rating": "derecelendirme",
"releaseDate": "çıkış tarihi",
"releaseYear": "yıl",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "başlık",
"trackNumber": "parça"
},
"config": {
"general": {
"autoFitColumns": "sütunları otomatik sığdır",
"followCurrentSong": "güncel şarkıyı takip et",
"displayType": "görüntüleme türü",
"gap": "$t(common.gap)",
"itemGap": "öğe boşluğu (px)",
"itemSize": "öğe boyutu (px)",
"size": "$t(common.size)",
"tableColumns": "tablo sütunları"
},
"label": {
"actions": "$t(common.action_other)",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"dateAdded": "tarih eklendi",
"discNumber": "disk numarası",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
"lastPlayed": "son çalınan",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"playCount": "çalma sayısı",
"rating": "$t(common.rating)",
"releaseDate": "çıkış tarihi",
"rowIndex": "satır indeksi",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"titleCombined": "$t(common.title) (birleşik)",
"trackNumber": "parça numarası",
"year": "$t(common.year)"
},
"view": {
"card": "kart",
"grid": "ızgara",
"list": "liste",
"poster": "poster",
"table": "tablo"
}
}
}
}
+46 -15
View File
@@ -3,8 +3,8 @@
"editPlaylist": "编辑$t(entity.playlist_one)",
"moveToTop": "移至顶部",
"clearQueue": "清空播放队列",
"addToFavorites": "添加到$t(entity.favorite_other)",
"addToPlaylist": "添加到$t(entity.playlist_one)",
"addToFavorites": "添加到 $t(entity.favorite_other)",
"addToPlaylist": "添加到 $t(entity.playlist_one)",
"createPlaylist": "创建$t(entity.playlist_one)",
"removeFromPlaylist": "从$t(entity.playlist_one)移除",
"viewPlaylists": "查看$t(entity.playlist_other)",
@@ -127,13 +127,13 @@
"playlist_other": "播放列表",
"artist_other": "艺术家",
"folderWithCount_other": "{{count}} 个文件夹",
"track_other": "曲",
"track_other": "曲",
"favorite_other": "收藏",
"artistWithCount_other": "{{count}} 位艺术家",
"folder_other": "文件夹",
"smartPlaylist": "智能$t(entity.playlist_one)",
"genreWithCount_other": "{{count}} 种流派",
"trackWithCount_other": "{{count}} 首曲",
"trackWithCount_other": "{{count}} 首曲",
"play_other": "{{count}} 次播放",
"song_other": "歌曲"
},
@@ -167,7 +167,7 @@
"skip_forward": "向前跳过",
"playbackSpeed": "播放速度",
"pause": "暂停",
"playSimilarSongs": "播放类似的曲",
"playSimilarSongs": "播放类似的曲",
"viewQueue": "查看播放队列"
},
"setting": {
@@ -205,7 +205,7 @@
"enableRemote_description": "启用远程控制服务器,以允许其他设备控制此应用",
"remotePort_description": "设置远程服务器端口",
"hotkey_skipBackward": "向后跳过",
"replayGainMode_description": "根据乐曲元数据中存储的{{ReplayGain}}值调整音量增益",
"replayGainMode_description": "根据文件元数据中存储的 {{ReplayGain}} 值调整音量增益",
"volumeWheelStep_description": "在音量滑块上滚动鼠标滚轮时要更改的音量大小",
"theme_description": "设置应用的主题",
"hotkey_playbackPause": "暂停",
@@ -290,7 +290,7 @@
"playbackStyle_optionNormal": "正常",
"windowBarStyle": "窗口顶栏风格",
"floatingQueueArea": "显示浮动队列悬停区域",
"replayGainFallback_description": "乐曲没有{{ReplayGain}}标签应用增益(以分贝为单位)",
"replayGainFallback_description": "如果文件没有 {{ReplayGain}} 标签,则在数据库中应用增益",
"hotkey_toggleRepeat": "切换循环",
"lyricOffset_description": "将歌词偏移指定的毫秒数",
"sidebarConfiguration_description": "选择侧边栏包含的项目与顺序",
@@ -399,7 +399,7 @@
"lastfmApiKey": "{{lastfm}} API 密钥",
"lastfmApiKey_description": "{{lastfm}} 的 API 密钥。封面艺术图所需",
"discordServeImage": "从服务器提供 {{discord}} 图像",
"discordServeImage_description": "分享 {{discord}} 封面艺术图,来自 rich presence 服务器,仅适用于 jellyfin 和 navidrome",
"discordServeImage_description": "从服务器本身分享 {{discord}} rich presence 的封面艺术,仅适用于 jellyfin 和 navidrome。 {{discord}} 使用机器人来获取图像,因此您的服务器必须可通过公共互联网访问。",
"musicbrainz": "显示 musicbrainz 链接",
"musicbrainz_description": "在 mbid 的艺术家/专辑页面上显示 musicbrainz 的链接",
"lastfm": "显示 last.fm 链接",
@@ -411,7 +411,26 @@
"preservePitch": "保持音高",
"preservePitch_description": "在调整播放速度时保持音高",
"notify": "启用歌曲通知",
"notify_description": "更改当前歌曲时显示通知"
"notify_description": "更改当前歌曲时显示通知",
"discordDisplayType": "{{discord}} 存在显示类型",
"discordDisplayType_description": "改变您在状态中收听的内容",
"discordDisplayType_songname": "歌曲名称",
"discordDisplayType_artistname": "艺术家名称",
"hotkey_navigateHome": "导航到主页",
"preventSleepOnPlayback": "防止播放时进入睡眠状态",
"preventSleepOnPlayback_description": "播放音乐时防止显示器进入睡眠状态",
"discordLinkType": "{{discord}} 状态链接",
"discordLinkType_description": "在 {{discord}} 的歌曲和艺术家字段中添加 {{lastfm}} 或 {{musicbrainz}} 的外部链接。{{musicbrainz}} 最准确,但需要标签,且不提供艺术家链接,而 {{lastfm}} 则始终提供链接。无需额外的网络请求",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} 和 {{lastfm}} 后备",
"artistBackground": "艺术家背景图片",
"artistBackground_description": "为包含艺术家作品的艺术家页面添加背景图片",
"artistBackgroundBlur": "艺术家背景图像模糊尺寸",
"artistBackgroundBlur_description": "调整应用于艺术家背景图像的模糊程度",
"releaseChannel_optionLatest": "stable",
"releaseChannel_optionBeta": "beta",
"releaseChannel": "发布渠道",
"releaseChannel_description": "选择稳定版本或测试版以进行自动更新"
},
"error": {
"remotePortWarning": "重启服务器使新端口生效",
@@ -457,7 +476,7 @@
"albumArtist": "$t(entity.albumArtist_one)",
"releaseYear": "发布年份",
"biography": "个人简介",
"songCount": "曲数量",
"songCount": "曲数量",
"random": "随机",
"lastPlayed": "上次播放过",
"toYear": "从年份",
@@ -532,14 +551,17 @@
"goBack": "返回",
"goForward": "前进",
"settings": "$t(common.setting_other)",
"quit": "$t(common.quit)"
"quit": "$t(common.quit)",
"privateModeOff": "关闭私人模式",
"privateModeOn": "开启私人模式"
},
"home": {
"mostPlayed": "最多播放",
"newlyAdded": "最近添加的发布",
"explore": "从库中搜索",
"recentlyPlayed": "最近播放",
"title": "$t(common.home)"
"title": "$t(common.home)",
"recentlyReleased": "最近发布"
},
"albumDetail": {
"moreFromArtist": "更多该$t(entity.artist_one)作品",
@@ -583,12 +605,14 @@
"playSimilarSongs": "$t(player.playSimilarSongs)",
"download": "下载",
"playShuffled": "$t(player.shuffle)",
"moveToNext": "$t(action.moveToNext)"
"moveToNext": "$t(action.moveToNext)",
"goToAlbum": "转到 $t(entity.album_one)",
"goToAlbumArtist": "转到 $t(entity.albumArtist_one)"
},
"trackList": {
"title": "$t(entity.track_other)",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"artistTracks": "{{artist}}的曲目"
"artistTracks": "{{artist}} 的曲目"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
@@ -651,7 +675,9 @@
"ignoreSsl": "忽略 ssl $t(common.restartRequired)",
"ignoreCors": "忽略 cors $t(common.restartRequired)",
"error_savePassword": "保存密码时出现错误",
"input_url": "url"
"input_url": "url",
"input_preferInstantMixDescription": "仅使用即时混音来获取类似的歌曲。如果您有修改此行为的插件,则很有用",
"input_preferInstantMix": "首选即时混音"
},
"addToPlaylist": {
"success": "添加$t(entity.trackWithCount, {\"count\": {{message}} })到$t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -693,6 +719,11 @@
"description": "描述",
"setExpiration": "设置过期时间",
"success": "共享链接已复制到剪贴板(或单击此处打开)"
},
"privateMode": {
"enabled": "启用私人模式,播放状态现在对外部集成隐藏",
"disabled": "私人模式已禁用,播放状态现在对启用的外部集成可见",
"title": "私人模式"
}
},
"table": {
+421 -215
View File
@@ -2,24 +2,24 @@
"common": {
"backward": "返回",
"biography": "簡介",
"bitrate": "比特率",
"bitrate": "位元率",
"bpm": "bpm",
"clear": "清空",
"collapse": "折疊",
"comingSoon": "即將上線…",
"comingSoon": "即將推出…",
"confirm": "確認",
"decrease": "降低",
"delete": "刪除",
"descending": "降",
"descending": "降",
"description": "描述",
"forceRestartRequired": "重啓應用使更改生效…關閉通知即可重",
"menu": "單",
"forceRestartRequired": "重新啟動應用程式使更改生效…關閉通知即可重",
"menu": "單",
"action_other": "操作",
"add": "添加",
"areYouSure": "是否繼續",
"ascending": "升",
"add": "新增",
"areYouSure": "你確定嗎",
"ascending": "升",
"disable": "禁用",
"disc": "",
"disc": "光碟",
"dismiss": "忽略",
"duration": "時長",
"edit": "編輯",
@@ -30,7 +30,7 @@
"filters": "篩選",
"forward": "前進",
"gap": "空隙",
"home": "頁",
"home": "頁",
"increase": "增高",
"left": "左",
"limit": "限制",
@@ -39,20 +39,20 @@
"ok": "好",
"owner": "所有者",
"path": "路徑",
"playerMustBePaused": "播放器須被暫停",
"previousSong": "上首$t(entity.track_one)",
"playerMustBePaused": "播放器須被暫停",
"previousSong": "上首$t(entity.track_one)",
"quit": "退出",
"random": "隨機",
"rating": "評分",
"refresh": "刷新",
"refresh": "重新整理",
"reset": "重置",
"resetToDefault": "重置爲默認",
"restartRequired": "需要重啓應用",
"resetToDefault": "恢復為預設值",
"restartRequired": "需要重新啟動應用程式",
"right": "右",
"save": "存",
"saveAndReplace": "存並替換",
"saveAs": "保存爲",
"search": "搜",
"save": "存",
"saveAndReplace": "存並取代",
"saveAs": "儲存為",
"search": "搜",
"sortOrder": "順序",
"title": "標題",
"trackNumber": "音軌編號",
@@ -63,38 +63,61 @@
"yes": "是",
"cancel": "取消",
"center": "中央",
"channel_other": "道",
"channel_other": "道",
"configure": "配置",
"create": "創建",
"currentSong": "前$t(entity.track_one)",
"currentSong": "前$t(entity.track_one)",
"minimize": "最小化",
"modified": "已修改",
"name": "名稱",
"no": "否",
"none": "無",
"noResultsFromQuery": "未查詢到匹配結果",
"note": "注釋"
"note": "注釋",
"additionalParticipants": "額外參與者",
"newVersion": "已安裝新版本 ({{version}})",
"viewReleaseNotes": "查看發行註記",
"albumGain": "專輯增益",
"albumPeak": "專輯peak",
"bitDepth": "位元深度",
"close": "關閉",
"codec": "編碼",
"mbid": "MusicBrainz ID",
"preview": "預覽",
"reload": "重新載入",
"sampleRate": "取樣率",
"setting": "設定",
"share": "分享",
"tags": "標籤",
"trackGain": "曲目增益",
"trackPeak": "歌曲峰值",
"translation": "翻譯"
},
"error": {
"endpointNotImplementedError": "{{serverType}} 尚未實現端點 {{endpoint}}",
"apiRouteError": "請求失敗:無法路由",
"audioDeviceFetchError": "無法獲取音頻設備",
"authenticationFailed": "證失敗",
"audioDeviceFetchError": "無法取得音訊設備",
"authenticationFailed": "證失敗",
"credentialsRequired": "需要憑證",
"genericError": "發生了錯誤",
"invalidServer": "無效的服務器",
"localFontAccessDenied": "無法取本地字體",
"localFontAccessDenied": "無法取本地字體",
"loginRateError": "登錄請求嘗試次數過多,請稍後再試",
"remoteDisableError": "$t(common.disable)遠程服務器時出現錯誤",
"remoteEnableError": "$t(common.enable)遠程服務器時出現錯誤",
"remotePortError": "設置遠程服務器端口時發生錯誤",
"remotePortWarning": "重啓服務器使新端口生效",
"serverRequired": "需要服器",
"sessionExpiredError": "會話已過期",
"systemFontError": "獲取系統字體時出現錯誤",
"serverNotSelectedError": "未選擇服器",
"remoteDisableError": "$t(common.disable)遠端伺服器時出現錯誤",
"remoteEnableError": "$t(common.enable)遠端伺服器時出現錯誤",
"remotePortError": "設定遠端伺服器端口時發生錯誤",
"remotePortWarning": "重啟伺服器使新端口生效",
"serverRequired": "需要服器",
"sessionExpiredError": "工作階段已過期",
"systemFontError": "嘗試取得系統字體時出現錯誤",
"serverNotSelectedError": "未選擇服器",
"mpvRequired": "需要 MPV",
"playbackError": "無法播放媒體"
"playbackError": "無法播放媒體",
"badAlbum": "您看到此頁面是因為這首歌不是專輯的一部分。如果您的音樂資料夾頂層有一首歌,則很可能會看到此問題。 Jellyfin 僅將資料夾中的曲目分組。",
"badValue": "無效選項“{{value}}”。該值不再存在",
"networkError": "發生網路錯誤",
"notificationDenied": "通知權限被拒絕。此設定無效",
"openError": "無法開啟檔案"
},
"page": {
"contextMenu": {
@@ -112,53 +135,69 @@
"moveToBottom": "$t(action.moveToBottom)",
"setRating": "$t(action.setRating)",
"moveToTop": "$t(action.moveToTop)",
"numberSelected": "{{count}} 已選擇",
"play": "$t(player.play)"
"numberSelected": "已選取 {{count}}",
"play": "$t(player.play)",
"download": "下載",
"moveToNext": "$t(action.moveToNext)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "分享項目",
"showDetails": "取得資訊",
"goToAlbum": "前往 $t(entity.album_one)",
"goToAlbumArtist": "前往 $t(entity.albumArtist_one)"
},
"globalSearch": {
"title": "令",
"title": "令",
"commands": {
"goToPage": "跳至頁面",
"searchFor": "搜 {{query}}",
"serverCommands": "服務器命令"
"searchFor": "搜 {{query}}",
"serverCommands": "伺服器指令"
}
},
"home": {
"explore": "從庫中搜",
"explore": "從資料庫中搜",
"recentlyPlayed": "最近播放",
"title": "$t(common.home)",
"mostPlayed": "最多播放",
"newlyAdded": "最近添加的發"
"newlyAdded": "最近新增的發",
"recentlyReleased": "最近發佈"
},
"appMenu": {
"openBrowserDevtools": "打開覽器開發者工具",
"openBrowserDevtools": "打開覽器開發者工具",
"collapseSidebar": "折疊側邊欄",
"expandSidebar": "展開側邊欄",
"goBack": "返回",
"goForward": "前進",
"quit": "$t(common.quit)",
"selectServer": "選擇服器",
"selectServer": "選擇服器",
"settings": "$t(common.setting_other)",
"version": "版本 {{version}}",
"manageServers": "管理服器"
"manageServers": "管理服器",
"privateModeOff": "關閉私人模式",
"privateModeOn": "開啟私人模式"
},
"fullscreenPlayer": {
"config": {
"showLyricProvider": "顯示歌詞提供者",
"useImageAspectRatio": "使用圖片縱橫比",
"dynamicBackground": "動態背景",
"followCurrentLyric": "跟隨前歌詞",
"followCurrentLyric": "跟隨前歌詞",
"lyricAlignment": "歌詞對齊",
"lyricGap": "歌詞間距",
"lyricSize": "歌詞字體大小",
"synchronized": "已同步",
"unsynchronized": "未同步",
"opacity": "透明度",
"showLyricMatch": "顯示匹配的歌詞"
"showLyricMatch": "顯示匹配的歌詞",
"dynamicImageBlur": "圖片模糊大小",
"dynamicIsImage": "啟用背景圖片",
"lyricOffset": "歌詞偏移時間 (ms)"
},
"lyrics": "歌詞",
"related": "相關",
"upNext": "即將播放"
"upNext": "即將播放",
"visualizer": "視覺化",
"noLyrics": "未找到歌詞"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
@@ -166,21 +205,27 @@
"setting": {
"hotkeysTab": "快捷鍵",
"playbackTab": "播放",
"windowTab": "窗",
"generalTab": "通用"
"windowTab": "窗",
"generalTab": "一般",
"advanced": "進階"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "更多$t(entity.artist_one)作品",
"moreFromGeneric": "更多{{item}}作品"
"moreFromArtist": "更多來自 $t(entity.artist_one)作品",
"moreFromGeneric": "更多{{item}}作品",
"released": "發行"
},
"albumList": {
"title": "$t(entity.album_other)"
"title": "$t(entity.album_other)",
"artistAlbums": "{{artist}} 的專輯",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)"
},
"genreList": {
"title": "$t(entity.genre_other)"
"title": "$t(entity.genre_other)",
"showAlbums": "顯示 $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "顯示 $t(entity.genre_one) $t(entity.track_other)"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
@@ -193,112 +238,145 @@
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"nowPlaying": "正在播放",
"playlists": "$t(entity.playlist_other)"
"playlists": "$t(entity.playlist_other)",
"myLibrary": "我的資料庫",
"shared": "已分享 $t(entity.playlist_other)"
},
"trackList": {
"title": "$t(entity.track_other)"
"title": "$t(entity.track_other)",
"artistTracks": "{{artist}} 的歌曲",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)"
},
"albumArtistDetail": {
"about": "關於{{artist}}",
"appearsOn": "出現在",
"recentReleases": "最近發行",
"viewDiscography": "查看音樂作品",
"relatedArtists": "關聯$t(entity.artist_other)",
"topSongs": "熱門歌曲",
"topSongsFrom": "{{title}} 的熱門歌曲",
"viewAll": "檢視所有",
"viewAllTracks": "檢視所有$t(entity.track_other)"
},
"manageServers": {
"title": "管理伺服器",
"serverDetails": "伺服器詳細資訊",
"url": "URL",
"username": "使用者名稱",
"editServerDetailsTooltip": "編輯伺服器詳細資訊",
"removeServer": "移除伺服器"
},
"itemDetail": {
"copyPath": "複製路徑至剪貼簿",
"copiedPath": "成功複製路徑",
"openFile": "在檔案管理器中顯示曲目"
},
"playlist": {
"reorder": "僅當按 ID 排序時才啟用重新排序"
}
},
"player": {
"playbackFetchInProgress": "正在載歌曲…",
"addLast": "添加到播放列表末尾",
"addNext": "添加爲播放列表下壹首",
"playbackFetchInProgress": "正在載歌曲…",
"addLast": "新增到尾端",
"addNext": "新增到下一首",
"favorite": "收藏",
"mute": "靜音",
"muted": "已靜音",
"playbackFetchNoResults": "未找到歌曲",
"playbackSpeed": "播放速度",
"playRandom": "隨機播放",
"previous": "上首",
"queue_clear": "清空播放列",
"previous": "上首",
"queue_clear": "清空播放列",
"queue_remove": "移除所選",
"repeat": "循環",
"repeat_all": "全部循環",
"repeat_off": "不循環",
"shuffle": "隨機播放",
"shuffle_off": "未用隨機播放",
"shuffle_off": "未用隨機播放",
"skip": "跳過",
"skip_back": "向後跳過",
"skip_forward": "向前跳過",
"stop": "停止",
"toggleFullscreenPlayer": "全屏",
"toggleFullscreenPlayer": "切換全螢幕播放器",
"unfavorite": "取消收藏",
"pause": "暫停",
"next": "下首",
"next": "下首",
"play": "播放",
"playbackFetchCancel": "請稍等…關閉通知以取消操作",
"playbackFetchCancel": "請稍等…關閉通知以取消",
"queue_moveToBottom": "使所選置頂",
"queue_moveToTop": "使所選置底"
"queue_moveToTop": "使所選置底",
"playSimilarSongs": "播放相似歌曲",
"viewQueue": "檢視佇列"
},
"setting": {
"audioPlayer_description": "選擇用播放的音播放器",
"audioPlayer_description": "選擇用播放的音播放器",
"themeLight": "主題(淺色)",
"themeLight_description": "應用將使用淺色主題",
"themeLight_description": "應用程式將使用淺色主題",
"discordRichPresence": "{{discord}} rich presence",
"hotkey_volumeDown": "音量降低",
"hotkey_volumeMute": "靜音",
"minimumScrobblePercentage": "最小 scrobble 時長(百分比)",
"minimumScrobblePercentage_description": "歌曲被記錄已播放(scrobble)所需的最小播放百分比",
"theme_description": "設應用的主題",
"minimumScrobblePercentage": "最小紀錄時長(百分比)",
"minimumScrobblePercentage_description": "歌曲被記錄已播放(scrobble)所需的最小播放百分比",
"theme_description": "設應用程式的主題",
"accentColor": "強調色",
"accentColor_description": "設應用的強調色",
"applicationHotkeys": "應用快捷鍵",
"applicationHotkeys_description": "配置應用快捷鍵。勾選設爲全局快捷鍵(僅桌面端)",
"audioDevice": "音設備",
"audioDevice_description": "選擇用播放的音設備(僅 web 播放器)",
"audioExclusiveMode": "音獨占模式",
"audioExclusiveMode_description": "用獨占輸出模式。在此模式下,系統通常被鎖定,只有 mpv 能夠輸出音",
"audioPlayer": "音播放器",
"accentColor_description": "設應用程式的強調色",
"applicationHotkeys": "應用程式快捷鍵",
"applicationHotkeys_description": "設定應用程式快捷鍵。切換勾選框來設為全局快捷鍵(僅桌面端)",
"audioDevice": "音設備",
"audioDevice_description": "選擇用播放的音設備(僅 web 播放器)",
"audioExclusiveMode": "音獨占模式",
"audioExclusiveMode_description": "用獨占輸出模式。在此模式下,系統通常被鎖定,只有 mpv 能夠輸出音",
"audioPlayer": "音播放器",
"crossfadeDuration": "淡入淡出持續時間",
"crossfadeDuration_description": "設淡入淡出持續時間",
"crossfadeDuration_description": "設淡入淡出持續時間",
"crossfadeStyle": "淡入淡出風格",
"crossfadeStyle_description": "選擇用于音頻播放器的淡入淡出風格",
"customFontPath": "自定字體路徑",
"customFontPath_description": "設應用使用的自定字體路徑",
"crossfadeStyle_description": "選擇用於音訊播放器的淡入淡出風格",
"customFontPath": "自定字體路徑",
"customFontPath_description": "設應用程式使用的自定字體路徑",
"disableAutomaticUpdates": "禁用自動更新",
"disableLibraryUpdateOnStartup": "禁用動時查新版本",
"discordApplicationId": "{{discord}} 應用 id",
"discordApplicationId_description": "{{discord}} rich presence 應用 id默認爲 {{defaultId}}",
"discordIdleStatus": "顯示 rich presence 置狀態",
"discordIdleStatus_description": "用後將會在播放器置時更新狀態",
"discordRichPresence_description": "在 {{discord}} rich presence 中顯示播放狀態。圖片鍵{{icon}}、{{playing}} 和 {{paused}}",
"disableLibraryUpdateOnStartup": "禁用動時查新版本",
"discordApplicationId": "{{discord}} 應用程式 id",
"discordApplicationId_description": "{{discord}} rich presence 應用程式 id預設為 {{defaultId}}",
"discordIdleStatus": "顯示 rich presence 置狀態",
"discordIdleStatus_description": "用後將會在播放器置時更新狀態",
"discordRichPresence_description": "在 {{discord}} rich presence 中顯示播放狀態。圖片鍵{{icon}}、{{playing}} 和 {{paused}}",
"discordUpdateInterval": "{{discord}} rich presence 更新間隔",
"discordUpdateInterval_description": "更新間隔秒數(至少 15 秒)",
"enableRemote": "用遠控制服器",
"enableRemote_description": "用遠控制服器,以允許其他設備控制此應用",
"enableRemote": "用遠控制服器",
"enableRemote_description": "用遠控制服器,以允許其他設備控制此應用程式",
"exitToTray": "退出時最小化到托盤",
"floatingQueueArea_description": "在幕右側顯示個懸停圖,以查看播放隊列",
"followLyric": "跟隨前歌詞",
"font_description": "設應用使用的字體",
"floatingQueueArea_description": "在幕右側顯示個懸停圖,以查看列",
"followLyric": "跟隨前歌詞",
"font_description": "設應用程式使用的字體",
"fontType": "字體類型",
"fontType_description": "內字體可以選擇 Feishin 提供的字體之。系統字體允許您選擇作系統提供的任何字體。自定選項允許您使用自己的字體",
"fontType_optionBuiltIn": "內字體",
"fontType_optionCustom": "自定字體",
"fontType_description": "內字體可以選擇 Feishin 提供的字體之。系統字體允許您選擇作系統提供的任何字體。自定選項允許您使用自己的字體",
"fontType_optionBuiltIn": "內字體",
"fontType_optionCustom": "自定字體",
"fontType_optionSystem": "系統字體",
"gaplessAudio": "無縫音頻",
"gaplessAudio_description": "調整 mpv 無縫音頻設置",
"gaplessAudio_optionWeak": "弱(推薦",
"gaplessAudio": "無間隔音訊",
"gaplessAudio_description": "調整 mpv 無間隔音訊設定",
"gaplessAudio_optionWeak": "弱(建議",
"globalMediaHotkeys": "全局媒體快捷鍵",
"hotkey_browserForward": "覽器前",
"hotkey_browserForward": "覽器前",
"hotkey_favoritePreviousSong": "收藏 $t(common.previousSong)",
"hotkey_globalSearch": "全局搜",
"hotkey_localSearch": "頁面內搜",
"hotkey_playbackNext": "下壹曲",
"hotkey_globalSearch": "全局搜",
"hotkey_localSearch": "頁面內搜",
"hotkey_playbackNext": "下一首",
"hotkey_playbackPause": "暫停",
"hotkey_playbackPlay": "播放",
"hotkey_playbackPlayPause": "播放/暫停",
"hotkey_playbackPrevious": "上壹曲",
"hotkey_rate2": "評 2 星",
"hotkey_rate1": "評 1 星",
"hotkey_rate3": "評 3 星",
"hotkey_rate4": "評 4 星",
"hotkey_rate5": "評 5 星",
"hotkey_skipBackward": "向回跳過",
"hotkey_skipForward": "向後跳過",
"hotkey_playbackPrevious": "上一首",
"hotkey_rate2": "評 2 星",
"hotkey_rate1": "評 1 星",
"hotkey_rate3": "評 3 星",
"hotkey_rate4": "評 4 星",
"hotkey_rate5": "評 5 星",
"hotkey_skipBackward": "退進",
"hotkey_skipForward": "快進",
"hotkey_toggleCurrentSongFavorite": "收藏 / 取消收藏$t(common.currentSong)",
"hotkey_toggleFullScreenPlayer": "全屏播放",
"hotkey_toggleFullScreenPlayer": "切換全螢幕播放",
"hotkey_togglePreviousSongFavorite": "收藏 / 取消收藏$t(common.previousSong)",
"hotkey_toggleQueue": "顯示 / 隱藏播放隊列",
"hotkey_toggleQueue": "顯示 / 隱藏列",
"hotkey_toggleRepeat": "切換循環播放設定",
"hotkey_toggleShuffle": "切換隨機播放設定",
"hotkey_unfavoriteCurrentSong": "取消收藏$t(common.currentSong)",
@@ -306,97 +384,192 @@
"hotkey_zoomIn": "放大",
"hotkey_zoomOut": "縮小",
"language": "語言",
"language_description": "設應用的語言($t(common.restartRequired)",
"lyricFetch": "從互聯網獲取歌詞",
"lyricFetch_description": "從多個互聯網源獲取歌詞",
"language_description": "設應用程式的語言($t(common.restartRequired)",
"lyricFetch": "從網路取得歌詞",
"lyricFetch_description": "從多個網路源取得歌詞",
"lyricFetchProvider": "歌詞源",
"lyricOffset": "歌詞偏移(毫秒)",
"lyricOffset_description": "將歌詞偏移指定的毫秒數",
"lyricFetchProvider_description": "選擇歌詞源。 歌詞源順序與查詢順序致",
"lyricFetchProvider_description": "選擇歌詞源。 歌詞源順序與搜尋順序致",
"minimizeToTray": "最小化到托盤",
"minimizeToTray_description": "將應用程最小化到系統托盤",
"minimumScrobbleSeconds": "最小 scrobble 時間(秒)",
"minimumScrobbleSeconds_description": "歌曲被記錄已播放(scrobble)所需的最小播放時間",
"mpvExecutablePath": "mpv 二進制文件路徑",
"playbackStyle_optionCrossFade": "交叉淡入淡出",
"playbackStyle_optionNormal": "通常",
"playButtonBehavior": "播放按鈕行爲",
"playButtonBehavior_description": "設置將歌曲添加到隊列時播放按鈕的默認行爲",
"minimizeToTray_description": "將應用程最小化到系統托盤",
"minimumScrobbleSeconds": "最小紀錄時間(秒)",
"minimumScrobbleSeconds_description": "歌曲被記錄已播放(scrobble)所需的最小播放時間",
"mpvExecutablePath": "mpv 執行檔路徑",
"playbackStyle_optionCrossFade": "淡入淡出",
"playbackStyle_optionNormal": "一般",
"playButtonBehavior": "播放按鈕動作",
"playButtonBehavior_description": "設定歌曲新增到佇列時播放按鈕的預設動作",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"remotePort": "遠程服務器端口",
"remoteUsername": "遠程服務器用戶名",
"remotePort": "遠端控制伺服器端口",
"remoteUsername": "遠端控制伺服器使用者名稱",
"replayGainClipping": "{{ReplayGain}}削波",
"replayGainFallback": "{{ReplayGain}}後備替代",
"replayGainFallback_description": "曲沒有{{ReplayGain}}標簽時用的增益(以分貝單位)",
"replayGainFallback_description": "曲沒有{{ReplayGain}}標簽時使用的增益(以分貝單位)",
"replayGainMode": "{{ReplayGain}}模式",
"replayGainMode_description": "根據樂曲元數據中存儲的{{ReplayGain}}值調整音量增益",
"replayGainMode_description": "根據歌曲標籤中儲存的{{ReplayGain}}值調整音量增益",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"replayGainPreamp": "{{ReplayGain}}前置放大(分貝)",
"replayGainPreamp_description": "調整用在{{ReplayGain}}值上的前置放大增益",
"savePlayQueue": "存播放列",
"sampleRate_description": "如果選擇的采樣頻率與前媒體的采樣頻率不同,請選擇要使用的輸出樣率。小 8000 的值將使用默認頻率",
"savePlayQueue_description": "當應用程關閉時存播放列,並在應用程打開時恢複它",
"scrobble": "記錄播放信息Scrobble",
"scrobble_description": "在妳的社交媒體中記錄播放信息",
"replayGainPreamp_description": "調整使用在{{ReplayGain}}值上的前置放大增益",
"savePlayQueue": "存播放列",
"sampleRate_description": "如果選擇的取樣率與前媒體的取樣率不同,請選擇要使用的輸出樣率。小 8000 的值將使用預設頻率",
"savePlayQueue_description": "當應用程關閉時存播放列,並在應用程打開時恢複它",
"scrobble": "記錄播放資訊Scrobble",
"scrobble_description": "在你的媒體伺服器中記錄播放資訊",
"showSkipButton": "顯示跳過按鈕",
"showSkipButton_description": "在播放條上顯示/隱藏跳過按鈕",
"sidebarPlaylistList": "側邊欄歌單列表",
"sidebarPlaylistList": "側邊欄歌單清單",
"sidebarCollapsedNavigation": "側邊欄(已折疊)導航",
"sidebarCollapsedNavigation_description": "在折疊的側邊欄中顯示或隱藏導航",
"sidebarConfiguration": "側邊欄設定",
"sidebarConfiguration_description": "選擇側邊欄包含的項目與順序",
"sidebarPlaylistList_description": "顯示或隱藏側邊欄歌單列表",
"sidePlayQueueStyle": "側邊播放列表樣式",
"sidePlayQueueStyle_description": "設置側邊播放列表樣式",
"sidebarPlaylistList_description": "顯示或隱藏側邊欄歌單清單",
"sidePlayQueueStyle": "側邊播放清單樣式",
"sidePlayQueueStyle_description": "設置側邊播放清單樣式",
"sidePlayQueueStyle_optionAttached": "吸附",
"sidePlayQueueStyle_optionDetached": "不吸附",
"skipDuration": "跳過時長",
"skipDuration_description": "設置每次按下跳過按鈕將會跳過的時長",
"skipPlaylistPage": "跳過單頁面",
"skipPlaylistPage_description": "打開單時,直接查看歌曲列表而非查看默認頁面",
"skipPlaylistPage": "跳過播放清單頁面",
"skipPlaylistPage_description": "打開播放清單時,直接查看歌曲列表而非查看預設頁面",
"theme": "主題",
"themeDark": "主題(深色)",
"useSystemTheme_description": "使用系統定義的淺色或深色主題",
"useSystemTheme": "跟隨系統",
"volumeWheelStep": "音量滾輪步",
"volumeWheelStep_description": "在音量滑塊上滾動鼠滾輪時要更改的音量大小",
"windowBarStyle": "窗頂欄風格",
"windowBarStyle_description": "選擇窗頂欄的風格",
"zoom": "縮放",
"zoom_description": "設應用程的縮放",
"volumeWheelStep": "音量滾輪步",
"volumeWheelStep_description": "在音量上滾動鼠滾輪時要更改的音量大小",
"windowBarStyle": "窗頂欄風格",
"windowBarStyle_description": "選擇窗頂欄的風格",
"zoom": "縮放比例",
"zoom_description": "設應用程的縮放比例",
"hotkey_volumeUp": "音量增高",
"sampleRate": "樣率",
"sampleRate": "樣率",
"showSkipButtons_description": "在播放條顯示/隱藏播放按鈕",
"playbackStyle": "播放風格",
"exitToTray_description": "退出應用時最小化到系統托盤而非關閉",
"floatingQueueArea": "顯示浮動列懸停區域",
"followLyric_description": "滾動歌詞到前播放位置",
"exitToTray_description": "退出應用程式時最小化到系統托盤而非關閉",
"floatingQueueArea": "顯示浮動列懸停區域",
"followLyric_description": "滾動歌詞到前播放位置",
"font": "字體",
"globalMediaHotkeys_description": "用或禁用系統媒體快捷鍵以控制播放",
"hotkey_browserBack": "覽器後退",
"globalMediaHotkeys_description": "用或禁用系統媒體快捷鍵以控制播放",
"hotkey_browserBack": "覽器返回",
"hotkey_favoriteCurrentSong": "收藏 $t(common.currentSong)",
"hotkey_playbackStop": "停止",
"hotkey_rate0": "清除評分",
"mpvExecutablePath_description": "設 mpv 二進制文件的路徑。如果留空,則使用默認路徑",
"mpvExecutablePath_description": "設 mpv 執行檔的路徑。如果留空,則使用預設路徑",
"mpvExtraParameters": "mpv 參數",
"playbackStyle_description": "選擇播放器的播放風格",
"playButtonBehavior_optionPlay": "$t(player.play)",
"remotePassword": "遠控制服器密碼",
"remotePassword_description": "設置遠程控制服務器的密碼。這些憑據默認以不安全的方式傳輸,因此您應該使用個您不在意的唯密碼",
"remotePort_description": "設置遠程服務器端口",
"remoteUsername_description": "設置遠程控制服務器的用戶名。如果用戶名和密碼都空,則身份驗證將被禁用",
"remotePassword": "遠控制服器密碼",
"remotePassword_description": "設定遠端控制伺服器的密碼。這些憑證預設以不安全的方式傳輸,因此您應該使用個您不在意的唯密碼",
"remotePort_description": "設定遠端控制伺服器的端口",
"remoteUsername_description": "設定遠端控制伺服器的使用者名稱。如果使用者名稱和密碼都空,則身份驗證將被禁用",
"replayGainClipping_description": "自動降低增益以防止{{ReplayGain}}造成削波",
"showSkipButtons": "顯示跳過按鈕",
"themeDark_description": "應用將使用深色主題",
"clearQueryCache_description": "feishin的“軟清除”。這將會刷新播放列表、元數據並重置存的歌詞。會保留設置、服務器憑據和緩存圖",
"clearCache": "清除覽器緩存",
"clearCache_description": "feishin的“硬清除”。除了清除feishin的緩存,清空浏覽器緩存(保存的圖和其他資源)。會保留服器憑和設",
"clearQueryCache": "清除feishin緩存",
"themeDark_description": "應用程式將使用深色主題",
"clearQueryCache_description": "Feishin的“軟清除”。這將會刷新播放清單、曲目標籤並重置存的歌詞。會保留設定、伺服器憑證和暫存圖",
"clearCache": "清除覽器快取",
"clearCache_description": "Feishin的“硬清除”。除了清除Feishin的快取、清除瀏覽器快取(儲存的圖和其他資源)。會保留服器憑和設",
"clearQueryCache": "清除Feishin快取",
"buttonSize": "播放器欄按鈕大小",
"buttonSize_description": "播放器欄按鈕大小"
"buttonSize_description": "播放器欄按鈕大小",
"albumBackground": "專輯背景圖片",
"albumBackground_description": "為包含專輯封面的專輯頁面新增背景圖片",
"albumBackgroundBlur": "專輯背景圖片模糊大小",
"albumBackgroundBlur_description": "調整應用於專輯背景圖片的模糊量",
"artistConfiguration": "專輯藝術家頁面設定",
"artistConfiguration_description": "設定專輯藝術家頁面上顯示的項目及其顯示順序",
"clearCacheSuccess": "成功清除快取",
"contextMenu": "右鍵選單配置",
"contextMenu_description": "允許您隱藏在右鍵選單項目時顯示的項目。未選取的項目將被隱藏",
"customCssEnable": "啟用自訂CSS",
"customCssEnable_description": "允許撰寫自訂CSS。",
"customCssNotice": "警告:雖然有一些清理措施(不允許 url() 和 content:),但使用自訂 CSS 仍然會透過更改介面帶來風險。",
"customCss": "自訂CSS",
"customCss_description": "自訂 CSS 內容。注意:內容和遠端 URL 是不允許使用的屬性。您的內容預覽如下所示。由於需要進行清理,因此存在一些您未設定的其他欄位。",
"discordPausedStatus": "暫停時顯示 rich presence",
"discordPausedStatus_description": "啟用後,播放器暫停時將顯示狀態",
"discordListening": "將狀態設為\"正在聽\"",
"discordListening_description": "將狀態顯示為\"正在聽\"而不是\"正在玩\"",
"discordServeImage": "從伺服器提供{{discord}}圖片",
"discordServeImage_description": "從伺服器本身分享 {{discord}} Rich Presence的封面圖片,僅支援 Jellyfin 與 Navidrome。{{discord}} 會透過機器人擷取圖片,因此您的伺服器必須能從公開網路連線。",
"doubleClickBehavior": "雙擊時將所有搜尋到的曲目加入佇列",
"doubleClickBehavior_description": "如果為 true,則歌曲搜尋中所有符合的歌曲都會被加入佇列。否則,只有被點擊的歌曲才會被加入佇列",
"externalLinks": "顯示外部連結",
"externalLinks_description": "在藝術家/專輯頁面顯示外部連結(Last.fm, MusicBrainz)",
"preferLocalLyrics": "偏好本地歌詞",
"preferLocalLyrics_description": "優先選擇本地歌詞,而不是遠端歌詞(如果可用)",
"genreBehavior": "曲風頁面預設動作",
"genreBehavior_description": "決定點擊某個曲風時是否預設開啟曲目或專輯列表",
"homeConfiguration": "首頁配置",
"homeConfiguration_description": "配置在首頁上顯示哪些項目以及顯示順序",
"homeFeature": "首頁特色輪播",
"homeFeature_description": "控制是否在首頁上顯示大型特色輪播",
"imageAspectRatio": "使用原生封面照長寬比",
"imageAspectRatio_description": "如果啟用,封面照將使用其原始長寬比顯示。對於非 1:1 的封面,剩餘空間將為空",
"lastfm": "顯示 last.fm 連結",
"lastfm_description": "在藝術家/專輯頁面顯示 last.fm 連結",
"lastfmApiKey": "{{lastfm}} API金鑰",
"lastfmApiKey_description": "{{lastfm}}的API金鑰。用於封面照",
"notify": "啟用歌曲通知",
"notify_description": "當切換目前歌曲時顯示通知",
"mpvExtraParameters_help": "一行一個",
"musicbrainz": "顯示 musicbrainz 連結",
"musicbrainz_description": "在存在 mbid 的藝術家/專輯頁面上顯示 musicbrainz 的鏈接",
"neteaseTranslation": "啟用網易翻譯",
"neteaseTranslation_description": "啟用後,將從網易取得並顯示翻譯的歌詞(如果有)。",
"passwordStore": "密碼/secret儲存",
"passwordStore_description": "使用什麼密碼/secret儲存。如果您在儲存密碼時遇到問題,請變更此項目。",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playerAlbumArtResolution": "播放器專輯封面解析度",
"playerAlbumArtResolution_description": "大型播放器專輯封面預覽的解析度。較大的解析度使其看起來更清晰,但可能會減慢載入速度。預設為 0,表示自動",
"playerbarOpenDrawer": "播放器列全螢幕切換",
"playerbarOpenDrawer_description": "允許點擊播放器列以開啟全螢幕播放器",
"startMinimized": "最小化啟動",
"startMinimized_description": "在系統托盤中啟動應用程式",
"transcodeNote": "將在播放1 (web) - 2 (mpv)首歌後生效",
"transcode": "啟用轉碼",
"transcode_description": "啟用轉碼到不同格式",
"transcodeBitrate": "要轉碼的比特率",
"transcodeBitrate_description": "選擇要轉碼的比特率。 0 表示讓伺服器選擇",
"transcodeFormat": "轉碼的格式",
"transcodeFormat_description": "選擇要轉碼的格式。留空來讓伺服器決定",
"translationApiProvider": "翻譯API提供者",
"translationApiProvider_description": "翻譯API的提供者",
"translationApiKey": "翻譯API金鑰",
"translationApiKey_description": "翻譯的API金鑰(僅支援全域服務端點)",
"translationTargetLanguage": "目標翻譯語言",
"translationTargetLanguage_description": "翻譯的目標語言",
"trayEnabled": "顯示托盤",
"trayEnabled_description": "顯示/隱藏托盤圖示/選單。如果停用,則也會停用最小化/退出到托盤",
"volumeWidth": "音量條寬度",
"volumeWidth_description": "音量條的寬度",
"webAudio": "使用網頁音訊",
"webAudio_description": "使用網頁音訊。這將啟用重播增益等進階功能。如果您遇到其他問題,請停用",
"preservePitch": "保持音高",
"preservePitch_description": "修改播放速度時保留音調",
"artistBackground": "藝人背景圖片",
"artistBackground_description": "為藝人頁面新增含藝人圖片的背景圖像",
"artistBackgroundBlur": "藝人背景圖片模糊程度",
"artistBackgroundBlur_description": "調整套用至藝人背景圖片的模糊程度",
"releaseChannel_optionLatest": "穩定版",
"releaseChannel_optionBeta": "測試版",
"releaseChannel_description": "選擇自動更新時要使用穩定版本或是測試版本",
"discordDisplayType": "{{discord}} presence 顯示類型",
"discordDisplayType_description": "變更您在狀態中正在聆聽的內容",
"discordDisplayType_songname": "歌曲名稱",
"discordDisplayType_artistname": "藝人名稱",
"discordLinkType": "{{discord}} presence 連結",
"discordLinkType_description": "在 {{discord}} Rich Presence中,為歌曲和藝人欄位新增 {{lastfm}} 或 {{musicbrainz}} 的外部連結。{{musicbrainz}} 的準確度最高,但需要標籤且不提供藝人連結;而 {{lastfm}} 通常都能提供連結。此功能不會產生額外的網路請求",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} 並以 {{lastfm}} 備用",
"hotkey_navigateHome": "導航至首頁",
"preventSleepOnPlayback": "防止播放時進入睡眠狀態",
"preventSleepOnPlayback_description": "在音樂播放時防止螢幕進入睡眠狀態",
"mediaSession": "啟用Media Session",
"mediaSession_description": "啟用 Windows Media Session 整合功能,於系統音量Overlay和鎖定畫面中顯示媒體資料與控制面板(僅限 Windows)"
},
"table": {
"config": {
@@ -405,7 +578,10 @@
"gap": "$t(common.gap)",
"size": "$t(common.size)",
"tableColumns": "列",
"autoFitColumns": "列寬自適應"
"autoFitColumns": "列寬自適應",
"followCurrentSong": "跟隨目前歌曲",
"itemGap": "項目間隔 (px)",
"itemSize": "項目大小 (px)"
},
"label": {
"actions": "$t(common.action_other)",
@@ -416,8 +592,8 @@
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"channels": "$t(common.channel_other)",
"dateAdded": "添加日期",
"discNumber": "碟編號",
"dateAdded": "新增日期",
"discNumber": "碟編號",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
@@ -425,20 +601,24 @@
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"playCount": "播放數",
"playCount": "播放數",
"releaseDate": "發布日期",
"rowIndex": "行號",
"size": "$t(common.size)",
"title": "$t(common.title)",
"titleCombined": "$t(common.title)(合",
"trackNumber": "音軌編號",
"titleCombined": "$t(common.title)(合",
"trackNumber": "曲目編號",
"year": "$t(common.year)",
"rating": "$t(common.rating)"
"rating": "$t(common.rating)",
"codec": "$t(common.codec)",
"songCount": "$t(entity.track_other)"
},
"view": {
"card": "卡片",
"poster": "海報",
"table": "表格"
"table": "表格",
"grid": "網格",
"list": "列表"
}
},
"column": {
@@ -450,8 +630,8 @@
"bitrate": "比特率",
"channels": "$t(common.channel_other)",
"comment": "評論",
"dateAdded": "添加日期",
"discNumber": "",
"dateAdded": "新增日期",
"discNumber": "光碟",
"favorite": "收藏",
"lastPlayed": "最後播放",
"path": "路徑",
@@ -463,28 +643,34 @@
"bpm": "bpm",
"songCount": "$t(entity.track_other)",
"title": "標題",
"trackNumber": "音軌編號",
"size": "$t(common.size)"
"trackNumber": "曲目編號",
"size": "$t(common.size)",
"codec": "$t(common.codec)"
}
},
"action": {
"addToFavorites": "添加到$t(entity.favorite_other)",
"clearQueue": "清空播放列",
"addToFavorites": "新增到$t(entity.favorite_other)",
"clearQueue": "清空播放列",
"createPlaylist": "創建$t(entity.playlist_one)",
"deletePlaylist": "刪除$t(entity.playlist_one)",
"addToPlaylist": "添加到$t(entity.playlist_one)",
"addToPlaylist": "新增到$t(entity.playlist_one)",
"deselectAll": "取消全選",
"editPlaylist": "編輯 $t(entity.playlist_one)",
"goToPage": "轉到頁面",
"moveToBottom": "至底部",
"moveToTop": "至頂部",
"goToPage": "前往頁面",
"moveToBottom": "至底部",
"moveToTop": "至頂部",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "從$t(entity.favorite_other)移除",
"removeFromPlaylist": "從$t(entity.playlist_one)移除",
"removeFromQueue": "從播放列中移除",
"removeFromQueue": "從播放列中移除",
"setRating": "評分",
"toggleSmartPlaylistEditor": "切換$t(entity.smartPlaylist)編輯器",
"viewPlaylists": "查看$t(entity.playlist_other)"
"viewPlaylists": "查看$t(entity.playlist_other)",
"moveToNext": "移至下一項",
"openIn": {
"lastfm": "在Last.fm開啟",
"musicbrainz": "在MusicBrainz開啟"
}
},
"entity": {
"album_other": "專輯",
@@ -493,16 +679,18 @@
"artist_other": "藝術家",
"artistWithCount_other": "{{count}} 位藝術家",
"favorite_other": "收藏",
"folder_other": "文件夾",
"folderWithCount_other": "{{count}} 個文件夾",
"genre_other": "流派",
"genreWithCount_other": "{{count}} 種流派",
"playlist_other": "播放列表",
"playlistWithCount_other": "{{count}} 個播放列表",
"smartPlaylist": "智$t(entity.playlist_one)",
"track_other": "曲",
"trackWithCount_other": "{{count}} 首曲",
"albumWithCount_other": "{{count}} 張專輯"
"folder_other": "資料夾",
"folderWithCount_other": "{{count}} 個資料夾",
"genre_other": "曲風",
"genreWithCount_other": "{{count}} 種曲風",
"playlist_other": "播放清單",
"playlistWithCount_other": "{{count}} 個播放清單",
"smartPlaylist": "智$t(entity.playlist_one)",
"track_other": "曲",
"trackWithCount_other": "{{count}} 首曲",
"albumWithCount_other": "{{count}} 張專輯",
"play_other": "{{count}}次播放",
"song_other": "歌曲"
},
"filter": {
"albumCount": "$t(entity.album_other)數",
@@ -512,15 +700,15 @@
"bpm": "bpm",
"channels": "$t(common.channel_other)",
"comment": "評論",
"communityRating": "社評分",
"communityRating": "社評分",
"criticRating": "評論家評分",
"dateAdded": "已添加日期",
"disc": "",
"dateAdded": "已新增日期",
"disc": "光碟",
"duration": "時長",
"id": "id",
"fromYear": "從年份",
"genre": "$t(entity.genre_one)",
"isCompilation": "合輯",
"isCompilation": "合輯",
"isFavorited": "已收藏",
"isPublic": "已公開",
"isRated": "已評分",
@@ -541,32 +729,34 @@
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"favorited": "已收藏",
"recentlyAdded": "最近添加",
"recentlyAdded": "最近新增",
"releaseYear": "發布年份",
"search": "搜",
"search": "搜",
"title": "標題",
"toYear": "從年份",
"trackNumber": "曲目"
},
"form": {
"addServer": {
"input_legacyAuthentication": "用舊版認證方式",
"input_name": "服器名",
"input_legacyAuthentication": "用舊版認證方式",
"input_name": "服器名",
"input_password": "密碼",
"input_savePassword": "存密碼",
"input_savePassword": "存密碼",
"input_url": "url",
"input_username": "用戶名",
"success": "服務器添加成功",
"title": "添加服務器",
"error_savePassword": "存密碼時出現錯誤",
"input_username": "使用者名稱",
"success": "伺服器新增成功",
"title": "新增伺服器",
"error_savePassword": "存密碼時出現錯誤",
"ignoreCors": "忽略 cors $t(common.restartRequired)",
"ignoreSsl": "忽略 ssl $t(common.restartRequired)"
"ignoreSsl": "忽略 ssl $t(common.restartRequired)",
"input_preferInstantMix": "偏好即時混音",
"input_preferInstantMixDescription": "僅使用即時混音功能來獲取相似歌曲。若您擁有能修改此行為的外掛,此功能將相當實用"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"input_skipDuplicates": "跳過重複",
"success": "添加 $t(entity.trackWithCount, {\"count\": {{message}} }) 到 $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "添加到$t(entity.playlist_one)"
"success": "新增 $t(entity.trackWithCount, {\"count\": {{message}} }) 到 $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "新增到$t(entity.playlist_one)"
},
"createPlaylist": {
"input_description": "$t(common.description)",
@@ -578,16 +768,17 @@
},
"lyricSearch": {
"input_name": "$t(common.name)",
"title": "搜歌詞",
"title": "搜歌詞",
"input_artist": "$t(entity.artist_one)"
},
"queryEditor": {
"input_optionMatchAll": "匹配全部",
"input_optionMatchAny": "匹配任何"
"input_optionMatchAny": "匹配任何",
"title": "查詢編輯器"
},
"updateServer": {
"success": "服器已更新成功",
"title": "更新服器"
"success": "服器已更新成功",
"title": "更新服器"
},
"deletePlaylist": {
"input_confirm": "輸入$t(entity.playlist_one)的名稱進行確認",
@@ -595,7 +786,22 @@
"success": "$t(entity.playlist_one)已成功刪除"
},
"editPlaylist": {
"title": "編輯$t(entity.playlist_one)"
"title": "編輯$t(entity.playlist_one)",
"publicJellyfinNote": "Jellyfin 出於某種原因,不會顯示播放清單是否公開。如果您希望保持公開狀態,請選擇以下輸入",
"success": "$t(entity.playlist_one) 更新成功"
},
"shareItem": {
"allowDownloading": "允許下載",
"description": "描述",
"setExpiration": "設定過期時間",
"success": "分享連結已複製到剪貼簿(或點擊此處開啟)",
"expireInvalid": "到期日必須是未來",
"createFailed": "無法建立分享(分享是否啟用?)"
},
"privateMode": {
"enabled": "已啟用私人模式,播放狀態將對外部整合隱藏",
"disabled": "已停用私人模式,播放狀態現對已啟用的外部整合可見",
"title": "私人模式"
}
}
}
@@ -0,0 +1,55 @@
import { createSocket } from 'dgram';
import { ipcMain } from 'electron';
import { DiscoveredServerItem, ServerType } from '/@/shared/types/types';
type JellyfinResponse = {
Address: string;
Id: string;
Name: string;
};
function discoverAll(reply: (server: DiscoveredServerItem) => void) {
return Promise.all([discoverJellyfin(reply)]);
}
function discoverJellyfin(reply: (server: DiscoveredServerItem) => void) {
const sock = createSocket('udp4');
sock.on('message', (msg) => {
try {
const response: JellyfinResponse = JSON.parse(msg.toString('utf-8'));
reply({
name: response.Name,
type: ServerType.JELLYFIN,
url: response.Address,
});
} catch (e) {
// Got a spurious response, ignore?
console.error(e);
}
});
sock.bind(() => {
sock.setBroadcast(true);
// Send a broadcast packet to both loopback and default route, allowing discovery of same-machine instances
sock.send('who is JellyfinServer?', 7359, '127.255.255.255');
sock.send('who is JellyfinServer?', 7359, '255.255.255.255');
});
return new Promise<void>((resolve) => {
setTimeout(() => {
sock.close();
resolve();
}, 3000);
});
}
ipcMain.on('autodiscover-ping', (ev) => {
if (ev.ports.length === 0) throw new Error('Expected a port to stream autodiscovery results');
const port = ev.ports[0];
discoverAll((result) => port.postMessage(result))
.then(() => port.close())
.catch((err) => console.error(err));
});
+1
View File
@@ -1,3 +1,4 @@
import './autodiscover';
import './lyrics';
import './player';
import './remote';
+4
View File
@@ -107,6 +107,10 @@ mprisPlayer.on('seek', (event: number) => {
});
});
mprisPlayer.on('raise', () => {
getMainWindow()?.show();
});
ipcMain.on('update-position', (_event, arg: number) => {
mprisPlayer.getPosition = () => arg * 1e6;
});
+63 -1
View File
@@ -9,6 +9,7 @@ import {
nativeImage,
nativeTheme,
net,
powerSaveBlocker,
protocol,
Rectangle,
screen,
@@ -22,6 +23,7 @@ import { access, constants, readFile, writeFile } from 'fs';
import path, { join } from 'path';
import { deflate, inflate } from 'zlib';
import packageJson from '../../package.json';
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
import { shutdownServer } from './features/core/remote';
import { store } from './features/core/settings';
@@ -42,6 +44,32 @@ export default class AppUpdater {
constructor() {
log.transports.file.level = 'info';
autoUpdater.logger = autoUpdaterLogInterface;
const isBetaVersion = packageJson.version.includes('-beta');
const releaseChannel = store.get('release_channel');
const isNotConfigured = !releaseChannel;
console.log('Release channel: ', releaseChannel);
console.log('Is beta version: ', isBetaVersion);
if (isNotConfigured) {
console.log(
'Release channel not configured, setting to ',
isBetaVersion ? 'beta' : 'latest',
);
store.set('release_channel', isBetaVersion ? 'beta' : 'latest');
}
if (releaseChannel === 'beta') {
autoUpdater.channel = 'beta';
autoUpdater.allowPrerelease = true;
autoUpdater.disableDifferentialDownload = true;
} else if (releaseChannel === 'latest') {
autoUpdater.channel = 'latest';
autoUpdater.allowDowngrade = true;
autoUpdater.allowPrerelease = false;
}
autoUpdater.checkForUpdatesAndNotify();
}
}
@@ -66,6 +94,7 @@ let mainWindow: BrowserWindow | null = null;
let tray: null | Tray = null;
let exitFromTray = false;
let forceQuit = false;
let powerSaveBlockerId: null | number = null;
if (process.env.NODE_ENV === 'production') {
import('source-map-support').then((sourceMapSupport) => {
@@ -488,6 +517,10 @@ async function createWindow(first = true): Promise<void> {
const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
if (process.platform !== 'darwin') {
Menu.setApplicationMenu(null);
}
// Open URLs in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url);
@@ -515,7 +548,14 @@ async function createWindow(first = true): Promise<void> {
}
}
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
const enableWindowsMediaSession = store.get('mediaSession', false) as boolean;
const shouldDisableMediaFeatures = process.platform !== 'win32' || !enableWindowsMediaSession;
if (shouldDisableMediaFeatures) {
app.commandLine.appendSwitch(
'disable-features',
'HardwareMediaKeyHandling,MediaSessionService',
);
}
// https://github.com/electron/electron/issues/46538#issuecomment-2808806722
app.commandLine.appendSwitch('gtk-version', '3');
@@ -615,6 +655,28 @@ ipcMain.on(
},
);
ipcMain.handle('power-save-blocker-start', () => {
if (powerSaveBlockerId !== null) {
return powerSaveBlockerId;
}
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
return powerSaveBlockerId;
});
ipcMain.handle('power-save-blocker-stop', () => {
if (powerSaveBlockerId !== null) {
const stopped = powerSaveBlocker.stop(powerSaveBlockerId);
powerSaveBlockerId = null;
return stopped;
}
return false;
});
ipcMain.handle('power-save-blocker-is-started', () => {
return powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId);
});
app.on('window-all-closed', () => {
globalShortcut.unregisterAll();
// Respect the OSX convention of having the application in memory even
+23
View File
@@ -0,0 +1,23 @@
import { ipcRenderer } from 'electron';
import { DiscoveredServerItem } from '../shared/types/types';
const discover = (onReply: (server: DiscoveredServerItem) => void): Promise<void> => {
const { port1: local, port2: remote } = new MessageChannel();
ipcRenderer.postMessage('autodiscover-ping', {}, [remote]);
local.onmessage = (ev) => {
onReply(ev.data);
};
return new Promise<void>((resolve) => {
local.addEventListener('close', () => resolve());
});
};
export const autodiscover = {
discover,
};
export type AutoDiscover = typeof autodiscover;
+2
View File
@@ -1,6 +1,7 @@
import { electronAPI } from '@electron-toolkit/preload';
import { contextBridge } from 'electron';
import { autodiscover } from './autodiscover';
import { browser } from './browser';
import { discordRpc } from './discord-rpc';
import { ipc } from './ipc';
@@ -13,6 +14,7 @@ import { utils } from './utils';
// Custom APIs for renderer
const api = {
autodiscover,
browser,
discordRpc,
ipc,
+5
View File
@@ -8,7 +8,12 @@ const send = (channel: string, ...args: any[]) => {
ipcRenderer.send(channel, ...args);
};
const invoke = (channel: string, ...args: any[]) => {
return ipcRenderer.invoke(channel, ...args);
};
export const ipc = {
invoke,
removeAllListeners,
send,
};
+1 -1
View File
@@ -118,7 +118,7 @@ export const contract = c.router({
},
getGenreList: {
method: 'GET',
path: 'genres',
path: 'musicgenres',
query: jfType._parameters.genreList,
responses: {
200: jfType._response.genreList,
@@ -542,10 +542,6 @@ export const JellyfinController: ControllerEndpoint = {
query: {
Fields: 'Genres, DateCreated, MediaSources, UserData, ParentId, People, Tags',
IncludeItemTypes: 'Audio',
Limit: query.limit,
SortBy: query.sortBy ? songListSortMap.jellyfin[query.sortBy] : undefined,
SortOrder: query.sortOrder ? sortOrderMap.jellyfin[query.sortOrder] : undefined,
StartIndex: query.startIndex,
UserId: apiClientProps.server?.userId,
},
});
@@ -556,7 +552,7 @@ export const JellyfinController: ControllerEndpoint = {
return {
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
startIndex: query.startIndex,
startIndex: 0,
totalRecordCount: res.body.TotalRecordCount,
};
},
@@ -631,30 +627,34 @@ export const JellyfinController: ControllerEndpoint = {
getSimilarSongs: async (args) => {
const { apiClientProps, query } = args;
// Prefer getSimilarSongs, where possible. Fallback to InstantMix
// where no similar songs were found.
const res = await jfApiClient(apiClientProps).getSimilarSongs({
params: {
itemId: query.songId,
},
query: {
Fields: 'Genres, DateCreated, MediaSources, ParentId',
Limit: query.count,
UserId: apiClientProps.server?.userId || undefined,
},
});
if (apiClientProps.server?.preferInstantMix !== true) {
// Prefer getSimilarSongs, where possible, and not overridden.
// InstantMix can be overridden by plugins, so this may be preferred by the user.
// Otherwise, similarSongs may have a better output than InstantMix, if sufficient
// data exists from the server.
const res = await jfApiClient(apiClientProps).getSimilarSongs({
params: {
itemId: query.songId,
},
query: {
Fields: 'Genres, DateCreated, MediaSources, ParentId',
Limit: query.count,
UserId: apiClientProps.server?.userId || undefined,
},
});
if (res.status === 200 && res.body.Items.length) {
const results = res.body.Items.reduce<Song[]>((acc, song) => {
if (song.Id !== query.songId) {
acc.push(jfNormalize.song(song, apiClientProps.server, ''));
if (res.status === 200 && res.body.Items.length) {
const results = res.body.Items.reduce<Song[]>((acc, song) => {
if (song.Id !== query.songId) {
acc.push(jfNormalize.song(song, apiClientProps.server, ''));
}
return acc;
}, []);
if (results.length > 0) {
return results;
}
return acc;
}, []);
if (results.length > 0) {
return results;
}
}
@@ -3,9 +3,7 @@ import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
import { NDSongListSort } from '/@/shared/api/navidrome.types';
import { ndNormalize } from '/@/shared/api/navidrome/navidrome-normalize';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import { ssNormalize } from '/@/shared/api/subsonic/subsonic-normalize';
import { SubsonicExtensions } from '/@/shared/api/subsonic/subsonic-types';
import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils';
import {
albumArtistListSortMap,
@@ -22,10 +20,11 @@ import {
sortOrderMap,
userListSortMap,
} from '/@/shared/types/domain-types';
import { ServerFeature, ServerFeatures } from '/@/shared/types/features-types';
import { ServerFeature } from '/@/shared/types/features-types';
const VERSION_INFO: VersionInfo = [
['0.55.0', { [ServerFeature.BFR]: [1] }],
['0.56.0', { [ServerFeature.TRACK_ALBUM_ARTIST_SEARCH]: [1] }],
['0.55.0', { [ServerFeature.BFR]: [1], [ServerFeature.TAGS]: [1] }],
['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
];
@@ -56,6 +55,9 @@ const excludeMissing = (server: null | ServerListItem) => {
return undefined;
};
const getArtistSongKey = (server: null | ServerListItem) =>
hasFeature(server, ServerFeature.TRACK_ALBUM_ARTIST_SEARCH) ? 'artists_id' : 'album_artist_id';
export const NavidromeController: ControllerEndpoint = {
addToPlaylist: async (args) => {
const { apiClientProps, body, query } = args;
@@ -187,6 +189,7 @@ export const NavidromeController: ControllerEndpoint = {
name: query.searchTerm,
...query._custom?.navidrome,
role: hasFeature(apiClientProps.server, ServerFeature.BFR) ? 'albumartist' : '',
...excludeMissing(apiClientProps.server),
},
});
@@ -232,6 +235,7 @@ export const NavidromeController: ControllerEndpoint = {
_sort: NDSongListSort.ALBUM,
_start: 0,
album_id: [query.id],
...excludeMissing(apiClientProps.server),
},
});
@@ -267,6 +271,10 @@ export const NavidromeController: ControllerEndpoint = {
getAlbumList: async (args) => {
const { apiClientProps, query } = args;
const genres = hasFeature(apiClientProps.server, ServerFeature.BFR)
? query.genres
: query.genres?.[0];
const res = await ndApiClient(apiClientProps).getAlbumList({
query: {
_end: query.startIndex + (query.limit || 0),
@@ -275,7 +283,7 @@ export const NavidromeController: ControllerEndpoint = {
_start: query.startIndex,
artist_id: query.artistIds?.[0],
compilation: query.compilation,
genre_id: query.genres?.[0],
genre_id: genres,
name: query.searchTerm,
...query._custom?.navidrome,
starred: query.favorite,
@@ -310,6 +318,7 @@ export const NavidromeController: ControllerEndpoint = {
name: query.searchTerm,
...query._custom?.navidrome,
role: query.role || undefined,
...excludeMissing(apiClientProps.server),
},
});
@@ -427,12 +436,10 @@ export const NavidromeController: ControllerEndpoint = {
id: query.id,
},
query: {
_end: query.startIndex + (query.limit || -1),
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : 'ASC',
_sort: query.sortBy
? songListSortMap.navidrome[query.sortBy]
: ndType._enum.songList.ID,
_start: query.startIndex,
_end: -1,
_order: 'ASC',
_start: 0,
...excludeMissing(apiClientProps.server),
},
});
@@ -442,7 +449,7 @@ export const NavidromeController: ControllerEndpoint = {
return {
items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server)),
startIndex: query?.startIndex || 0,
startIndex: 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
@@ -463,38 +470,20 @@ export const NavidromeController: ControllerEndpoint = {
ping.body.serverVersion = '0.55.0';
}
const navidromeFeatures: Record<string, number[]> = getFeatures(
VERSION_INFO,
ping.body.serverVersion!,
);
const navidromeFeatures = getFeatures(VERSION_INFO, ping.body.serverVersion!);
const subsonicArgs = await SubsonicController.getServerInfo(args);
if (ping.body.openSubsonic) {
const res = await ssApiClient(apiClientProps).getServerInfo();
if (res.status !== 200) {
throw new Error('Failed to get server extensions');
}
// The type here isn't necessarily an array (even though it's supposed to be). This is
// an implementation detail of Navidrome 0.50. Do a type check to make sure it's actually
// an array, and not an empty object.
if (Array.isArray(res.body.openSubsonicExtensions)) {
for (const extension of res.body.openSubsonicExtensions) {
navidromeFeatures[extension.name] = extension.versions;
}
}
}
const features: ServerFeatures = {
bfr: navidromeFeatures[ServerFeature.BFR],
lyricsMultipleStructured: navidromeFeatures[SubsonicExtensions.SONG_LYRICS],
playlistsSmart: navidromeFeatures[ServerFeature.PLAYLISTS_SMART],
const features = {
...navidromeFeatures,
...subsonicArgs.features,
publicPlaylist: [1],
sharingAlbumSong: navidromeFeatures[ServerFeature.SHARING_ALBUM_SONG],
tags: navidromeFeatures[ServerFeature.BFR],
};
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion! };
return {
features,
id: apiClientProps.server?.id,
version: ping.body.serverVersion!,
};
},
getSimilarSongs: async (args) => {
const { apiClientProps, query } = args;
@@ -531,7 +520,8 @@ export const NavidromeController: ControllerEndpoint = {
_order: 'ASC',
_sort: NDSongListSort.RANDOM,
_start: 0,
album_artist_id: query.albumArtistIds,
[getArtistSongKey(apiClientProps.server)]: query.albumArtistIds,
...excludeMissing(apiClientProps.server),
},
});
@@ -571,10 +561,9 @@ export const NavidromeController: ControllerEndpoint = {
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: songListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
album_artist_id: query.albumArtistIds,
album_id: query.albumIds,
artist_id: query.artistIds,
genre_id: query.genreIds,
[getArtistSongKey(apiClientProps.server)]: query.artistIds ?? query.albumArtistIds,
starred: query.favorite,
title: query.searchTerm,
...query._custom?.navidrome,
+1 -26
View File
@@ -9,7 +9,6 @@ import type {
LyricsQuery,
PlaylistDetailQuery,
PlaylistListQuery,
PlaylistSongListQuery,
RandomSongListQuery,
SearchQuery,
SimilarSongsQuery,
@@ -191,21 +190,6 @@ export const queryKeys: Record<
if (id) return [serverId, 'playlists', id, 'detail'] as const;
return [serverId, 'playlists', 'detail'] as const;
},
detailSongList: (serverId: string, id: string, query?: PlaylistSongListQuery) => {
const { filter, pagination } = splitPaginatedQuery(query);
if (query && id && pagination) {
return [serverId, 'playlists', id, 'detailSongList', filter, pagination] as const;
}
if (query && id) {
return [serverId, 'playlists', id, 'detailSongList', filter] as const;
}
if (id) return [serverId, 'playlists', id, 'detailSongList'] as const;
return [serverId, 'playlists', 'detailSongList'] as const;
},
list: (serverId: string, query?: PlaylistListQuery) => {
const { filter, pagination } = splitPaginatedQuery(query);
if (query && pagination) {
@@ -219,16 +203,7 @@ export const queryKeys: Record<
return [serverId, 'playlists', 'list'] as const;
},
root: (serverId: string) => [serverId, 'playlists'] as const,
songList: (serverId: string, id?: string, query?: PlaylistSongListQuery) => {
const { filter, pagination } = splitPaginatedQuery(query);
if (query && id && pagination) {
return [serverId, 'playlists', id, 'songList', filter, pagination] as const;
}
if (query && id) {
return [serverId, 'playlists', id, 'songList', filter] as const;
}
songList: (serverId: string, id?: string) => {
if (id) return [serverId, 'playlists', id, 'songList'] as const;
return [serverId, 'playlists', 'songList'] as const;
},
@@ -41,7 +41,7 @@ const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefin
[AlbumListSort.RATING]: undefined,
[AlbumListSort.RECENTLY_ADDED]: AlbumListSortType.NEWEST,
[AlbumListSort.RECENTLY_PLAYED]: AlbumListSortType.RECENT,
[AlbumListSort.RELEASE_DATE]: undefined,
[AlbumListSort.RELEASE_DATE]: AlbumListSortType.BY_YEAR,
[AlbumListSort.SONG_COUNT]: undefined,
[AlbumListSort.YEAR]: AlbumListSortType.BY_YEAR,
};
@@ -759,18 +759,14 @@ export const SubsonicController: ControllerEndpoint = {
throw new Error('Failed to get playlist song list');
}
let results =
const items =
res.body.playlist.entry?.map((song) => ssNormalize.song(song, apiClientProps.server)) ||
[];
if (query.sortBy && query.sortOrder) {
results = sortSongList(results, query.sortBy, query.sortOrder);
}
return {
items: results,
items,
startIndex: 0,
totalRecordCount: results?.length || 0,
totalRecordCount: items.length,
};
},
getRandomSongList: async (args) => {
+13 -7
View File
@@ -16,7 +16,6 @@ import 'overlayscrollbars/overlayscrollbars.css';
import '/styles/overlayscrollbars.css';
import i18n from '/@/i18n/i18n';
import { ContextMenuProvider } from '/@/renderer/features/context-menu';
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
import { PlayQueueHandlerContext } from '/@/renderer/features/player';
import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-context';
@@ -191,13 +190,20 @@ export const App = () => {
return (
<MantineProvider defaultColorScheme={mode as 'dark' | 'light'} theme={theme}>
<Notifications containerWidth="300px" position="bottom-center" zIndex={50000} />
<Notifications
containerWidth="300px"
position="bottom-center"
styles={{
root: {
marginBottom: 90,
},
}}
zIndex={50000}
/>
<PlayQueueHandlerContext.Provider value={providerValue}>
<ContextMenuProvider>
<WebAudioContext.Provider value={webAudioProvider}>
<AppRouter />
</WebAudioContext.Provider>{' '}
</ContextMenuProvider>
<WebAudioContext.Provider value={webAudioProvider}>
<AppRouter />
</WebAudioContext.Provider>
</PlayQueueHandlerContext.Provider>
<IsUpdatedDialog />
</MantineProvider>
@@ -225,6 +225,28 @@ export const AudioPlayer = forwardRef<AudioPlayerRef, AudioPlayerProps>((props,
setIsTransitioning(false);
};
const handleOnError = (playerRef: React.RefObject<ReactPlayer>) => {
return ({ target }: ErrorEvent) => {
const { current: player } = playerRef;
if (!player || !(target instanceof Audio)) {
return;
}
const { error } = target;
console.log('Playback error occurred:', error);
if (
error?.code !== MediaError.MEDIA_ERR_DECODE &&
error?.code !== MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
) {
return;
}
handleOnEnded();
};
};
useEffect(() => {
if (status === PlayerStatus.PLAYING) {
if (currentPlayer === 1) {
@@ -424,6 +446,7 @@ export const AudioPlayer = forwardRef<AudioPlayerRef, AudioPlayerProps>((props,
muted={muted}
// If there is no stream url, we do not need to handle when the audio finishes
onEnded={stream1 ? handleOnEnded : undefined}
onError={handleOnError(player1Ref)}
onProgress={
playbackStyle === PlaybackStyle.GAPLESS ? handleGapless1 : handleCrossfade1
}
@@ -443,6 +466,7 @@ export const AudioPlayer = forwardRef<AudioPlayerRef, AudioPlayerProps>((props,
height={0}
muted={muted}
onEnded={stream2 ? handleOnEnded : undefined}
onError={handleOnError(player2Ref)}
onProgress={
playbackStyle === PlaybackStyle.GAPLESS ? handleGapless2 : handleCrossfade2
}
@@ -1,59 +0,0 @@
import isElectron from 'is-electron';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button';
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
import { FileInput } from '/@/shared/components/file-input/file-input';
import { Text } from '/@/shared/components/text/text';
import { PlaybackType } from '/@/shared/types/types';
const localSettings = isElectron() ? window.api.localSettings : null;
export const MpvRequired = () => {
const [, setMpvPath] = useState('');
const settings = usePlaybackSettings();
const { setSettings } = useSettingsStoreActions();
const [disabled, setDisabled] = useState(false);
const { t } = useTranslation();
const handleSetMpvPath = (e: File | null) => {
if (!e) return;
localSettings?.set('mpv_path', e.path);
};
const handleSetDisableMpv = (disabled: boolean) => {
setDisabled(disabled);
localSettings?.set('disable_mpv', disabled);
setSettings({
playback: { ...settings, type: disabled ? PlaybackType.WEB : PlaybackType.LOCAL },
});
};
useEffect(() => {
if (!localSettings) return setMpvPath('');
const mpvPath = localSettings.get('mpv_path') as string;
return setMpvPath(mpvPath);
}, []);
return (
<>
<Text>Set your MPV executable location below and restart the application.</Text>
<Text>
MPV is available at the following:{' '}
<a href="https://mpv.io/installation/" rel="noreferrer" target="_blank">
https://mpv.io/
</a>
</Text>
<FileInput disabled={disabled} onChange={handleSetMpvPath} />
<Text>{t('setting.disable_mpv', { context: 'description' })}</Text>
<Checkbox
label={t('setting.disableMpv')}
onChange={(e) => handleSetDisableMpv(e.currentTarget.checked)}
/>
<Button onClick={() => localSettings?.restart()}>Restart</Button>
</>
);
};
@@ -83,9 +83,9 @@ export const AlbumDetailHeader = forwardRef(
},
{
id: 'songCount',
value: `${detailQuery?.data?.songCount} ${t('entity.track_other', {
value: t('entity.trackWithCount', {
count: detailQuery?.data?.songCount as number,
})}`,
}),
},
{
id: 'duration',
@@ -2,12 +2,21 @@ import debounce from 'lodash/debounce';
import { ChangeEvent, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
import {
MultiSelectWithInvalidData,
SelectWithInvalidData,
} from '/@/renderer/components/select-with-invalid-data';
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
import { useGenreList } from '/@/renderer/features/genres';
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
import {
AlbumListFilter,
getServerById,
useListStoreActions,
useListStoreByKey,
} from '/@/renderer/store';
import { NDSongQueryFields } from '/@/shared/api/navidrome.types';
import { hasFeature } from '/@/shared/api/utils';
import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group';
import { NumberInput } from '/@/shared/components/number-input/number-input';
@@ -23,6 +32,7 @@ import {
LibraryItem,
SortOrder,
} from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
interface NavidromeAlbumFiltersProps {
customFilters?: Partial<AlbumListFilter>;
@@ -42,6 +52,7 @@ export const NavidromeAlbumFilters = ({
const { t } = useTranslation();
const { filter } = useListStoreByKey<AlbumListQuery>({ key: pageKey });
const { setFilter } = useListStoreActions();
const server = getServerById(serverId);
const genreListQuery = useGenreList({
options: {
@@ -64,12 +75,14 @@ export const NavidromeAlbumFilters = ({
}));
}, [genreListQuery.data]);
const handleGenresFilter = debounce((e: null | string) => {
const hasBrf = hasFeature(server, ServerFeature.BFR);
const handleGenresFilter = debounce((e: null | string[]) => {
const updatedFilters = setFilter({
customFilters,
data: {
_custom: filter._custom,
genres: e ? [e] : undefined,
genres: e ? e : undefined,
},
itemType: LibraryItem.ALBUM,
key: pageKey,
@@ -269,15 +282,29 @@ export const NavidromeAlbumFilters = ({
min={0}
onChange={(e) => handleYearFilter(e)}
/>
<SelectWithInvalidData
clearable
data={genreList}
defaultValue={filter.genres && filter.genres[0]}
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
onChange={handleGenresFilter}
searchable
/>
{!hasBrf && (
<SelectWithInvalidData
clearable
data={genreList}
defaultValue={filter.genres && filter.genres[0]}
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
onChange={(value) => handleGenresFilter(value !== null ? [value] : null)}
searchable
/>
)}
</Group>
{hasBrf && (
<Group grow>
<MultiSelectWithInvalidData
clearable
data={genreList}
defaultValue={filter.genres}
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
onChange={handleGenresFilter}
searchable
/>
</Group>
)}
<Group grow>
<SelectWithInvalidData
clearable
@@ -9,8 +9,4 @@
gap: var(--theme-spacing-lg);
padding: 1rem 2rem 5rem;
overflow: hidden;
:global(.ag-theme-alpine-dark) {
--ag-header-background-color: rgb(0 0 0 / 0%) !important;
}
}
@@ -14,12 +14,15 @@ import { Text } from '/@/shared/components/text/text';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
interface AlbumArtistDetailHeaderProps {
background?: string;
loading: boolean;
background: {
background?: string;
blur: number;
loading: boolean;
};
}
export const AlbumArtistDetailHeader = forwardRef(
({ background, loading }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
({ background }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
const { albumArtistId, artistId } = useParams() as {
albumArtistId?: string;
artistId?: string;
@@ -76,12 +79,11 @@ export const AlbumArtistDetailHeader = forwardRef(
return (
<LibraryHeader
background={background}
imageUrl={detailQuery?.data?.imageUrl}
item={{ route: AppRoute.LIBRARY_ALBUM_ARTISTS, type: LibraryItem.ALBUM_ARTIST }}
loading={loading}
ref={ref}
title={detailQuery?.data?.name || ''}
{...background}
>
<Stack>
<Group>
@@ -9,13 +9,14 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
import { useFastAverageColor } from '/@/renderer/hooks';
import { useCurrentServer } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { useGeneralSettings, usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem } from '/@/shared/types/domain-types';
const AlbumArtistDetailRoute = () => {
const scrollAreaRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLDivElement>(null);
const server = useCurrentServer();
const { artistBackground, artistBackgroundBlur } = useGeneralSettings();
const { albumArtistId, artistId } = useParams() as {
albumArtistId?: string;
@@ -30,12 +31,16 @@ const AlbumArtistDetailRoute = () => {
query: { id: routeId },
serverId: server?.id,
});
const { background, colorId } = useFastAverageColor({
id: routeId,
const { background: backgroundColor, colorId } = useFastAverageColor({
id: artistId,
src: detailQuery.data?.imageUrl,
srcLoaded: !detailQuery.isLoading,
});
const backgroundUrl = detailQuery.data?.imageUrl || '';
const background = (artistBackground && `url(${backgroundUrl})`) || backgroundColor;
const handlePlay = () => {
handlePlayQueueAdd?.({
byItemType: {
@@ -65,8 +70,11 @@ const AlbumArtistDetailRoute = () => {
ref={scrollAreaRef}
>
<AlbumArtistDetailHeader
background={background}
loading={!background || colorId !== routeId}
background={{
background,
blur: (artistBackground && artistBackgroundBlur) || 0,
loading: !backgroundColor || colorId !== artistId,
}}
ref={headerRef}
/>
<AlbumArtistDetailContent background={background} />
@@ -12,6 +12,8 @@ export const QUEUE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
{ disabled: false, divider: true, id: 'deselectAll' },
{ id: 'download' },
{ divider: true, id: 'shareItem' },
{ id: 'goToAlbum' },
{ id: 'goToAlbumArtist' },
{ divider: true, id: 'showDetails' },
];
@@ -27,6 +29,8 @@ export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
{ children: true, disabled: false, divider: true, id: 'setRating' },
{ id: 'download' },
{ divider: true, id: 'shareItem' },
{ id: 'goToAlbum' },
{ id: 'goToAlbumArtist' },
{ divider: true, id: 'showDetails' },
];
@@ -51,6 +55,8 @@ export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
{ children: true, disabled: false, id: 'setRating' },
{ id: 'download' },
{ divider: true, id: 'shareItem' },
{ id: 'goToAlbum' },
{ id: 'goToAlbumArtist' },
{ divider: true, id: 'showDetails' },
];
@@ -66,6 +72,8 @@ export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
{ children: true, disabled: false, id: 'setRating' },
{ id: 'download' },
{ divider: true, id: 'shareItem' },
{ id: 'goToAlbum' },
{ id: 'goToAlbumArtist' },
{ divider: true, id: 'showDetails' },
];
@@ -79,6 +87,7 @@ export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
{ id: 'removeFromFavorites' },
{ children: true, disabled: false, divider: true, id: 'setRating' },
{ divider: true, id: 'shareItem' },
{ id: 'goToAlbumArtist' },
{ divider: true, id: 'showDetails' },
];
@@ -9,8 +9,17 @@ import {
import { closeAllModals, openContextModal, openModal } from '@mantine/modals';
import isElectron from 'is-electron';
import { AnimatePresence } from 'motion/react';
import { createContext, Fragment, ReactNode, useCallback, useMemo, useState } from 'react';
import {
createContext,
Fragment,
ReactNode,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, useNavigate } from 'react-router-dom';
import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller';
@@ -26,6 +35,7 @@ import { updateSong } from '/@/renderer/features/player/update-remote-song';
import { useDeletePlaylist } from '/@/renderer/features/playlists';
import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation';
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
import { AppRoute } from '/@/renderer/router/routes';
import {
getServerById,
useAuthStore,
@@ -88,24 +98,12 @@ export interface ContextMenuProviderProps {
children: ReactNode;
}
function RatingIcon({ rating }: { rating: number }) {
return (
<Rating
readOnly
style={{
pointerEvents: 'none',
size: 'var(--theme-font-size-md)',
}}
value={rating}
/>
);
}
export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const disabledItems = useSettingsStore((state) => state.general.disabledContextMenu);
const { t } = useTranslation();
const [opened, setOpened] = useState(false);
const clickOutsideRef = useClickOutside(() => setOpened(false));
const clickOutsideRef = useClickOutside(() => setOpened(false), ['mousedown', 'touchstart']);
const viewport = useViewportSize();
const server = useCurrentServer();
@@ -122,7 +120,26 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
yPos: 0,
});
const [rating, setRating] = useState<number>(0);
useEffect(() => {
if (opened && ctx.data.length > 0) {
if (ctx.data.length === 1) {
setRating(ctx.data[0].userRating ?? 0);
} else {
const firstRating = ctx.data[0].userRating ?? 0;
const allSameRating = ctx.data.every(
(item) => (item.userRating ?? 0) === firstRating,
);
setRating(allSameRating ? firstRating : 0);
}
} else {
setRating(0);
}
}, [ctx.data, opened]);
const handlePlayQueueAdd = usePlayQueueAdd();
const navigate = useNavigate();
const openContextMenu = useCallback(
(args: OpenContextMenuProps) => {
@@ -524,7 +541,6 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
});
},
onSuccess: () => {
ctx.context?.tableRef?.current?.api?.refreshInfiniteCache();
closeAllModals();
},
},
@@ -541,7 +557,6 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
});
}, [
ctx.context?.playlistId,
ctx.context?.tableRef,
ctx.data,
ctx.dataNodes,
removeFromPlaylistMutation,
@@ -552,7 +567,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const updateRatingMutation = useSetRating({});
const handleUpdateRating = useCallback(
(rating: number) => {
(newRating: number) => {
if (!ctx.dataNodes && !ctx.data) return;
let uniqueServerIds: string[] = [];
@@ -574,6 +589,8 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
}, [] as string[]);
}
const ratingToSet = newRating === rating ? 0 : newRating;
for (const serverId of uniqueServerIds) {
if (ctx.dataNodes) {
items = ctx.dataNodes
@@ -587,7 +604,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
{
query: {
item: items,
rating,
rating: ratingToSet,
},
serverId,
},
@@ -595,7 +612,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
onSuccess: () => {
if (ctx.dataNodes) {
for (const node of ctx.dataNodes) {
node.setData({ ...node.data, userRating: rating });
node.setData({ ...node.data, userRating: ratingToSet });
}
}
},
@@ -603,7 +620,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
);
}
},
[ctx.data, ctx.dataNodes, updateRatingMutation],
[ctx.data, ctx.dataNodes, updateRatingMutation, rating],
);
const playbackType = usePlaybackType();
@@ -724,6 +741,24 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
}
}, [ctx.data, server]);
const handleGoToAlbum = useCallback(() => {
const item = ctx.data[0];
if (item.albumId) {
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: item.albumId }));
}
}, [ctx.data, navigate]);
const handleGoToAlbumArtist = useCallback(() => {
const item = ctx.data[0];
if (item.albumArtists && item.albumArtists.length > 0) {
navigate(
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
albumArtistId: item.albumArtists[0].id,
}),
);
}
}, [ctx.data, navigate]);
const contextMenuItems: Record<ContextMenuItemType, ContextMenuItem> = useMemo(() => {
return {
addToFavorites: {
@@ -762,6 +797,23 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
leftIcon: <Icon icon="download" />,
onClick: handleDownload,
},
goToAlbum: {
disabled: ctx.data?.length !== 1 || !ctx.data[0]?.albumId,
id: 'goToAlbum',
label: t('page.contextMenu.goToAlbum', { postProcess: 'sentenceCase' }),
leftIcon: <Icon icon="album" />,
onClick: handleGoToAlbum,
},
goToAlbumArtist: {
disabled:
ctx.data?.length !== 1 ||
!ctx.data[0]?.albumArtists ||
ctx.data[0]?.albumArtists?.length === 0,
id: 'goToAlbumArtist',
label: t('page.contextMenu.goToAlbumArtist', { postProcess: 'sentenceCase' }),
leftIcon: <Icon icon="artist" />,
onClick: handleGoToAlbumArtist,
},
moveToBottomOfQueue: {
id: 'moveToBottomOfQueue',
label: t('page.contextMenu.moveToBottom', { postProcess: 'sentenceCase' }),
@@ -829,43 +881,22 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
onClick: handleRemoveSelected,
},
setRating: {
children: [
{
id: 'zeroStar',
label: <RatingIcon rating={0} />,
onClick: () => handleUpdateRating(0),
},
{
id: 'oneStar',
label: <RatingIcon rating={1} />,
onClick: () => handleUpdateRating(1),
},
{
id: 'twoStar',
label: <RatingIcon rating={2} />,
onClick: () => handleUpdateRating(2),
},
{
id: 'threeStar',
label: <RatingIcon rating={3} />,
onClick: () => handleUpdateRating(3),
},
{
id: 'fourStar',
label: <RatingIcon rating={4} />,
onClick: () => handleUpdateRating(4),
},
{
id: 'fiveStar',
label: <RatingIcon rating={5} />,
onClick: () => handleUpdateRating(5),
},
],
id: 'setRating',
label: t('action.setRating', { postProcess: 'sentenceCase' }),
leftIcon: <Icon icon="star" />,
onClick: () => {},
rightIcon: <Icon icon="arrowRightS" />,
rightIcon: (
<Group>
<Rating
onChange={(e) => {
handleUpdateRating(e);
setRating(e);
}}
size="xs"
value={rating}
/>
</Group>
),
},
shareItem: {
disabled: !hasFeature(server, ServerFeature.SHARING_ALBUM_SONG),
@@ -899,9 +930,12 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
handleRemoveSelected,
server,
handleShareItem,
handleGoToAlbum,
handleGoToAlbumArtist,
handleOpenItemDetails,
handlePlay,
handleUpdateRating,
rating,
]);
const mergedRef = useMergedRef(ref, clickOutsideRef);
@@ -950,12 +984,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
</ContextMenuButton>
</HoverCard.Target>
<HoverCard.Dropdown>
<Stack
gap={0}
// Pass in this ref to the stack component as well
// so that it is treated as "inside" for clickOutsideRef
ref={mergedRef}
>
<Stack gap={0}>
{contextMenuItems[
item.id
].children?.map((child) => (
@@ -15,6 +15,8 @@ export type ContextMenuItemType =
| 'deletePlaylist'
| 'deselectAll'
| 'download'
| 'goToAlbum'
| 'goToAlbumArtist'
| 'moveToBottomOfQueue'
| 'moveToNextOfQueue'
| 'moveToTopOfQueue'
@@ -57,6 +59,8 @@ export const CONFIGURABLE_CONTEXT_MENU_ITEMS: ContextMenuItemType[] = [
'setRating',
'download',
'shareItem',
'goToAlbum',
'goToAlbumArtist',
'showDetails',
];
@@ -1,11 +1,14 @@
import { SetActivity } from '@xhayper/discord-rpc';
import { SetActivity, StatusDisplayType } from '@xhayper/discord-rpc';
import isElectron from 'is-electron';
import { useCallback, useEffect, useState } from 'react';
import { controller } from '/@/renderer/api/controller';
import {
DiscordDisplayType,
DiscordLinkType,
getServerById,
useDiscordSetttings,
useAppStore,
useDiscordSettings,
useGeneralSettings,
usePlayerStore,
} from '/@/renderer/store';
@@ -15,8 +18,9 @@ import { PlayerStatus } from '/@/shared/types/types';
const discordRpc = isElectron() ? window.api.discordRpc : null;
export const useDiscordRpc = () => {
const discordSettings = useDiscordSetttings();
const discordSettings = useDiscordSettings();
const generalSettings = useGeneralSettings();
const { privateMode } = useAppStore();
const [lastUniqueId, setlastUniqueId] = useState('');
const setActivity = useCallback(
@@ -54,6 +58,12 @@ export const useDiscordRpc = () => {
const artists = song?.artists.map((artist) => artist.name).join(', ');
const statusDisplayMap = {
[DiscordDisplayType.ARTIST_NAME]: StatusDisplayType.STATE,
[DiscordDisplayType.FEISHIN]: StatusDisplayType.NAME,
[DiscordDisplayType.SONG_NAME]: StatusDisplayType.DETAILS,
};
const activity: SetActivity = {
details: song?.name.padEnd(2, ' ') || 'Idle',
instance: false,
@@ -61,12 +71,41 @@ export const useDiscordRpc = () => {
largeImageText: song?.album || 'Unknown album',
smallImageKey: undefined,
smallImageText: current[2] as string,
state: (artists && `By ${artists}`) || 'Unknown artist',
state: artists || 'Unknown artist',
statusDisplayType: statusDisplayMap[discordSettings.displayType],
// I would love to use the actual type as opposed to hardcoding to 2,
// but manually installing the discord-types package appears to break things
type: discordSettings.showAsListening ? 2 : 0,
};
if (
(discordSettings.linkType == DiscordLinkType.LAST_FM ||
discordSettings.linkType == DiscordLinkType.MBZ_LAST_FM) &&
song?.artistName
) {
activity.stateUrl =
'https://www.last.fm/music/' + encodeURIComponent(song.artists[0].name);
activity.detailsUrl =
'https://www.last.fm/music/' +
encodeURIComponent(song.albumArtists[0].name) +
'/' +
encodeURIComponent(song.album || '_') +
'/' +
encodeURIComponent(song.name);
}
if (
discordSettings.linkType == DiscordLinkType.MBZ ||
discordSettings.linkType == DiscordLinkType.MBZ_LAST_FM
) {
if (song?.mbzTrackId) {
activity.detailsUrl = 'https://musicbrainz.org/track/' + song.mbzTrackId;
} else if (song?.mbzRecordingId) {
activity.detailsUrl =
'https://musicbrainz.org/recording/' + song.mbzRecordingId;
}
}
if ((current[2] as PlayerStatus) === PlayerStatus.PLAYING) {
if (start && end) {
activity.startTimestamp = start;
@@ -134,20 +173,22 @@ export const useDiscordRpc = () => {
discordSettings.showPaused,
generalSettings.lastfmApiKey,
discordSettings.clientId,
discordSettings.displayType,
discordSettings.linkType,
lastUniqueId,
],
);
useEffect(() => {
if (!discordSettings.enabled) return discordRpc?.quit();
if (!discordSettings.enabled || privateMode) return discordRpc?.quit();
return () => {
discordRpc?.quit();
};
}, [discordSettings.clientId, discordSettings.enabled]);
}, [discordSettings.clientId, privateMode, discordSettings.enabled]);
useEffect(() => {
if (!discordSettings.enabled) return;
if (!discordSettings.enabled || privateMode) return;
const unsubSongChange = usePlayerStore.subscribe(
(state) => [state.current.song, state.current.time, state.current.status],
setActivity,
@@ -155,5 +196,5 @@ export const useDiscordRpc = () => {
return () => {
unsubSongChange();
};
}, [discordSettings.enabled, setActivity]);
}, [discordSettings.enabled, privateMode, setActivity]);
};
@@ -1,8 +1,6 @@
import { useQueryClient } from '@tanstack/react-query';
import { useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { queryKeys } from '/@/renderer/api/query-keys';
import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel';
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel';
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
@@ -32,12 +30,16 @@ import {
} from '/@/shared/types/domain-types';
import { Platform } from '/@/shared/types/types';
const BASE_QUERY_ARGS = {
limit: 15,
sortOrder: SortOrder.DESC,
startIndex: 0,
};
const HomeRoute = () => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const scrollAreaRef = useRef<HTMLDivElement>(null);
const server = useCurrentServer();
const itemsPerPage = 15;
const { windowBarStyle } = useWindowSettings();
const { homeFeature, homeItems } = useGeneralSettings();
@@ -56,59 +58,66 @@ const HomeRoute = () => {
serverId: server?.id,
});
const isJellyfin = server?.type === ServerType.JELLYFIN;
const featureItemsWithImage = useMemo(() => {
return feature.data?.items?.filter((item) => item.imageUrl) ?? [];
}, [feature.data?.items]);
const queriesEnabled = useMemo(() => {
return homeItems.reduce(
(previous: Record<HomeItem, boolean>, current) => ({
...previous,
[current.id]: !current.disabled,
}),
{} as Record<HomeItem, boolean>,
);
}, [homeItems]);
const random = useAlbumList({
options: {
enabled: queriesEnabled[HomeItem.RANDOM],
staleTime: 1000 * 60 * 5,
},
query: {
limit: itemsPerPage,
...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RANDOM,
sortOrder: SortOrder.ASC,
startIndex: 0,
},
serverId: server?.id,
});
const recentlyPlayed = useRecentlyPlayed({
options: {
enabled: queriesEnabled[HomeItem.RECENTLY_PLAYED] && !isJellyfin,
staleTime: 0,
},
query: {
limit: itemsPerPage,
...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
serverId: server?.id,
});
const recentlyAdded = useAlbumList({
options: {
enabled: queriesEnabled[HomeItem.RECENTLY_ADDED],
staleTime: 1000 * 60 * 5,
},
query: {
limit: itemsPerPage,
...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
serverId: server?.id,
});
const mostPlayedAlbums = useAlbumList({
options: {
enabled: server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME,
enabled: !isJellyfin && queriesEnabled[HomeItem.MOST_PLAYED],
staleTime: 1000 * 60 * 5,
},
query: {
limit: itemsPerPage,
...BASE_QUERY_ARGS,
sortBy: AlbumListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
serverId: server?.id,
});
@@ -116,27 +125,38 @@ const HomeRoute = () => {
const mostPlayedSongs = useSongList(
{
options: {
enabled: server?.type === ServerType.JELLYFIN,
enabled: isJellyfin && queriesEnabled[HomeItem.MOST_PLAYED],
staleTime: 1000 * 60 * 5,
},
query: {
limit: itemsPerPage,
...BASE_QUERY_ARGS,
sortBy: SongListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
serverId: server?.id,
},
300,
);
const recentlyReleased = useAlbumList({
options: {
enabled: queriesEnabled[HomeItem.RECENTLY_RELEASED],
staleTime: 1000 * 60 * 5,
},
query: {
...BASE_QUERY_ARGS,
sortBy: AlbumListSort.RELEASE_DATE,
},
serverId: server?.id,
});
const isLoading =
random.isLoading ||
recentlyPlayed.isLoading ||
recentlyAdded.isLoading ||
(server?.type === ServerType.JELLYFIN && mostPlayedSongs.isLoading) ||
((server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME) &&
mostPlayedAlbums.isLoading);
(random.isLoading && queriesEnabled[HomeItem.RANDOM]) ||
(recentlyPlayed.isLoading && queriesEnabled[HomeItem.RECENTLY_PLAYED] && !isJellyfin) ||
(recentlyAdded.isLoading && queriesEnabled[HomeItem.RECENTLY_ADDED]) ||
(recentlyReleased.isLoading && queriesEnabled[HomeItem.RECENTLY_RELEASED]) ||
(((isJellyfin && mostPlayedSongs.isLoading) ||
(!isJellyfin && mostPlayedAlbums.isLoading)) &&
queriesEnabled[HomeItem.MOST_PLAYED]);
if (isLoading) {
return <Spinner container />;
@@ -144,48 +164,35 @@ const HomeRoute = () => {
const carousels = {
[HomeItem.MOST_PLAYED]: {
data:
server?.type === ServerType.JELLYFIN
? mostPlayedSongs?.data?.items
: mostPlayedAlbums?.data?.items,
itemType: server?.type === ServerType.JELLYFIN ? LibraryItem.SONG : LibraryItem.ALBUM,
pagination: {
itemsPerPage,
},
sortBy:
server?.type === ServerType.JELLYFIN
? SongListSort.PLAY_COUNT
: AlbumListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC,
data: isJellyfin ? mostPlayedSongs?.data?.items : mostPlayedAlbums?.data?.items,
itemType: isJellyfin ? LibraryItem.SONG : LibraryItem.ALBUM,
query: isJellyfin ? mostPlayedSongs : mostPlayedAlbums,
title: t('page.home.mostPlayed', { postProcess: 'sentenceCase' }),
},
[HomeItem.RANDOM]: {
data: random?.data?.items,
itemType: LibraryItem.ALBUM,
sortBy: AlbumListSort.RANDOM,
sortOrder: SortOrder.ASC,
query: random,
title: t('page.home.explore', { postProcess: 'sentenceCase' }),
},
[HomeItem.RECENTLY_ADDED]: {
data: recentlyAdded?.data?.items,
itemType: LibraryItem.ALBUM,
pagination: {
itemsPerPage,
},
sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC,
query: recentlyAdded,
title: t('page.home.newlyAdded', { postProcess: 'sentenceCase' }),
},
[HomeItem.RECENTLY_PLAYED]: {
data: recentlyPlayed?.data?.items,
itemType: LibraryItem.ALBUM,
pagination: {
itemsPerPage,
},
sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.DESC,
query: recentlyPlayed,
title: t('page.home.recentlyPlayed', { postProcess: 'sentenceCase' }),
},
[HomeItem.RECENTLY_RELEASED]: {
data: recentlyReleased?.data?.items,
itemType: LibraryItem.ALBUM,
query: recentlyReleased,
title: t('page.home.recentlyReleased', { postProcess: 'sentenceCase' }),
},
};
const sortedCarousel = homeItems
@@ -193,7 +200,7 @@ const HomeRoute = () => {
if (item.disabled) {
return false;
}
if (server?.type === ServerType.JELLYFIN && item.id === HomeItem.RECENTLY_PLAYED) {
if (isJellyfin && item.id === HomeItem.RECENTLY_PLAYED) {
return false;
}
@@ -204,36 +211,6 @@ const HomeRoute = () => {
uniqueId: item.id,
}));
const invalidateCarouselQuery = (carousel: {
itemType: LibraryItem;
sortBy: AlbumListSort | SongListSort;
sortOrder: SortOrder;
}) => {
if (carousel.itemType === LibraryItem.ALBUM) {
queryClient.invalidateQueries({
exact: false,
queryKey: queryKeys.albums.list(server?.id, {
limit: itemsPerPage,
sortBy: carousel.sortBy,
sortOrder: carousel.sortOrder,
startIndex: 0,
}),
});
}
if (carousel.itemType === LibraryItem.SONG) {
queryClient.invalidateQueries({
exact: false,
queryKey: queryKeys.songs.list(server?.id, {
limit: itemsPerPage,
sortBy: carousel.sortBy,
sortOrder: carousel.sortOrder,
startIndex: 0,
}),
});
}
};
return (
<AnimatedPage>
<NativeScrollArea
@@ -266,7 +243,7 @@ const HomeRoute = () => {
slugs: [
{
idProperty:
server?.type === ServerType.JELLYFIN &&
isJellyfin &&
carousel.itemType === LibraryItem.SONG
? 'albumId'
: 'id',
@@ -297,8 +274,7 @@ const HomeRoute = () => {
slugs: [
{
idProperty:
server?.type === ServerType.JELLYFIN &&
carousel.itemType === LibraryItem.SONG
isJellyfin && carousel.itemType === LibraryItem.SONG
? 'albumId'
: 'id',
slugProperty: 'albumId',
@@ -310,7 +286,7 @@ const HomeRoute = () => {
<Group>
<TextTitle order={3}>{carousel.title}</TextTitle>
<ActionIcon
onClick={() => invalidateCarouselQuery(carousel)}
onClick={() => carousel.query.refetch()}
variant="transparent"
>
<Icon icon="refresh" />
@@ -81,7 +81,7 @@ const formatArtists = (artists: null | RelatedArtist[] | undefined) =>
{artist.name || '—'}
</Text>
) : (
<Text overflow="visible" size="md">
<Text component="span" overflow="visible" size="md">
{artist.name || '-'}
</Text>
)}
@@ -24,7 +24,7 @@ interface LyricsActionsProps {
onRemoveLyric: () => void;
onResetLyric: () => void;
onSearchOverride: (params: LyricsOverride) => void;
onTranslateLyric: () => void;
onTranslateLyric?: () => void;
setIndex: (idx: number) => void;
}
@@ -135,7 +135,7 @@ export const LyricsActions = ({
</div>
<div style={{ position: 'absolute', right: 0, top: -50 }}>
{isDesktop && sources.length ? (
{isDesktop && sources.length && onTranslateLyric ? (
<Button
disabled={isActionsDisabled}
onClick={onTranslateLyric}
@@ -24,7 +24,9 @@
position: relative;
display: flex;
width: 100%;
min-width: 0;
height: 100%;
overflow: hidden;
&:hover {
.actions-container {
+15 -5
View File
@@ -32,7 +32,8 @@ import { FullLyricsMetadata, LyricSource, LyricsOverride } from '/@/shared/types
export const Lyrics = () => {
const currentSong = useCurrentSong();
const lyricsSettings = useLyricsSettings();
const { translationApiKey, translationApiProvider, translationTargetLanguage } =
useLyricsSettings();
const { t } = useTranslation();
const [index, setIndex] = useState(0);
const [translatedLyrics, setTranslatedLyrics] = useState<null | string>(null);
@@ -97,8 +98,6 @@ export const Lyrics = () => {
const originalLyrics = Array.isArray(lyrics.lyrics)
? lyrics.lyrics.map(([, line]) => line).join('\n')
: lyrics.lyrics;
const { translationApiKey, translationApiProvider, translationTargetLanguage } =
lyricsSettings;
const TranslatedText: null | string = await translateLyrics(
originalLyrics,
translationApiKey,
@@ -107,7 +106,14 @@ export const Lyrics = () => {
);
setTranslatedLyrics(TranslatedText);
setShowTranslation(true);
}, [lyrics, lyricsSettings, translatedLyrics, showTranslation]);
}, [
translatedLyrics,
lyrics,
translationApiKey,
translationApiProvider,
translationTargetLanguage,
showTranslation,
]);
const { isInitialLoading: isOverrideLoading } = useSongLyricsByRemoteId({
options: {
@@ -194,7 +200,11 @@ export const Lyrics = () => {
onRemoveLyric={handleOnRemoveLyric}
onResetLyric={handleOnResetLyric}
onSearchOverride={handleOnSearchOverride}
onTranslateLyric={handleOnTranslateLyric}
onTranslateLyric={
translationApiProvider && translationApiKey
? handleOnTranslateLyric
: undefined
}
setIndex={setIndex}
/>
</div>
@@ -11,23 +11,25 @@ import { PlayButton, PlayerButton } from '/@/renderer/features/player/components
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
import { openShuffleAllModal } from '/@/renderer/features/player/components/shuffle-all-modal';
import { useCenterControls } from '/@/renderer/features/player/hooks/use-center-controls';
import { useMediaSession } from '/@/renderer/features/player/hooks/use-media-session';
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
import {
useAppStore,
useAppStoreActions,
useCurrentPlayer,
useCurrentSong,
useCurrentStatus,
useCurrentTime,
useRepeatStatus,
useSetCurrentTime,
useShuffleStatus,
} from '/@/renderer/store';
import {
useHotkeySettings,
usePlaybackType,
useRepeatStatus,
useSetCurrentTime,
useSettingsStore,
} from '/@/renderer/store/settings.store';
useShuffleStatus,
} from '/@/renderer/store';
import { Icon } from '/@/shared/components/icon/icon';
import { Text } from '/@/shared/components/text/text';
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types';
interface CenterControlsProps {
@@ -37,16 +39,14 @@ interface CenterControlsProps {
export const CenterControls = ({ playersRef }: CenterControlsProps) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isSeeking, setIsSeeking] = useState(false);
const currentSong = useCurrentSong();
const skip = useSettingsStore((state) => state.general.skipButtons);
const buttonSize = useSettingsStore((state) => state.general.buttonSize);
const playbackType = usePlaybackType();
const player1 = playersRef?.current?.player1;
const player2 = playersRef?.current?.player2;
const status = useCurrentStatus();
const player = useCurrentPlayer();
const setCurrentTime = useSetCurrentTime();
const repeat = useRepeatStatus();
const shuffle = useShuffleStatus();
const { bindings } = useHotkeySettings();
@@ -66,31 +66,6 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
} = useCenterControls({ playersRef });
const handlePlayQueueAdd = usePlayQueueAdd();
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
const currentTime = useCurrentTime();
const currentPlayerRef = player === 1 ? player1 : player2;
const duration = formatDuration(songDuration * 1000 || 0);
const formattedTime = formatDuration(currentTime * 1000 || 0);
useEffect(() => {
let interval: ReturnType<typeof setInterval>;
if (status === PlayerStatus.PLAYING && !isSeeking) {
if (!isElectron() || playbackType === PlaybackType.WEB) {
// Update twice a second for slightly better performance
interval = setInterval(() => {
if (currentPlayerRef) {
setCurrentTime(currentPlayerRef.getCurrentTime());
}
}, 500);
}
}
return () => clearInterval(interval);
}, [currentPlayerRef, isSeeking, setCurrentTime, playbackType, status]);
const [seekValue, setSeekValue] = useState(0);
useHotkeys([
[bindings.playPause.isGlobal ? '' : bindings.playPause.hotkey, handlePlayPause],
[bindings.play.isGlobal ? '' : bindings.play.hotkey, handlePlay],
@@ -110,9 +85,20 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
],
]);
useMediaSession({
handleNextTrack,
handlePause,
handlePlay,
handlePrevTrack,
handleSeekSlider,
handleSkipBackward,
handleSkipForward,
handleStop,
});
return (
<>
<div className={styles.controlsContainer}>
<div className={styles.controlsContainer} style={{ zIndex: 50001 }}>
<div className={styles.buttonsContainer}>
<PlayerButton
icon={<Icon fill="default" icon="mediaStop" size={buttonSize - 2} />}
@@ -251,41 +237,110 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
/>
</div>
</div>
<div className={styles.sliderContainer}>
<div className={styles.sliderValueWrapper}>
<Text fw={600} isMuted isNoSelect size="xs">
{formattedTime}
</Text>
</div>
<div className={styles.sliderWrapper}>
<PlayerbarSlider
label={(value) => formatDuration(value * 1000)}
max={songDuration}
min={0}
onChange={(e) => {
setIsSeeking(true);
setSeekValue(e);
}}
onChangeEnd={(e) => {
// There is a timing bug in Mantine in which the onChangeEnd
// event fires before onChange. Add a small delay to force
// onChangeEnd to happen after onCHange
setTimeout(() => {
handleSeekSlider(e);
setIsSeeking(false);
}, 50);
}}
size={6}
value={!isSeeking ? currentTime : seekValue}
w="100%"
/>
</div>
<div className={styles.sliderValueWrapper}>
<Text fw={600} isMuted isNoSelect size="xs">
{duration}
</Text>
</div>
</div>
<PlayerSeekSlider
handleSeekSlider={handleSeekSlider}
player1={player1}
player2={player2}
/>
</>
);
};
const PlayerSeekSlider = ({
handleSeekSlider,
player1,
player2,
}: {
handleSeekSlider: (e: any | number) => void;
player1: any;
player2: any;
}) => {
const player = useCurrentPlayer();
const playbackType = usePlaybackType();
const setCurrentTime = useSetCurrentTime();
const status = useCurrentStatus();
const currentSong = useCurrentSong();
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
const currentTime = useCurrentTime();
const currentPlayerRef = player === 1 ? player1 : player2;
const [isSeeking, setIsSeeking] = useState(false);
useEffect(() => {
let interval: ReturnType<typeof setInterval>;
if (status === PlayerStatus.PLAYING && !isSeeking) {
if (!isElectron() || playbackType === PlaybackType.WEB) {
// Update twice a second for slightly better performance
interval = setInterval(() => {
if (currentPlayerRef) {
setCurrentTime(currentPlayerRef.getCurrentTime());
}
}, 500);
}
}
return () => clearInterval(interval);
}, [currentPlayerRef, isSeeking, setCurrentTime, playbackType, status]);
const { showTimeRemaining } = useAppStore();
const { setShowTimeRemaining } = useAppStoreActions();
const formattedDuration = formatDuration(songDuration * 1000 || 0);
const formattedTimeRemaining = formatDuration((currentTime - songDuration) * 1000 || 0);
const formattedTime = formatDuration(currentTime * 1000 || 0);
const [seekValue, setSeekValue] = useState(0);
return (
<div className={styles.sliderContainer}>
<div className={styles.sliderValueWrapper}>
<Text
className={PlaybackSelectors.elapsedTime}
fw={600}
isMuted
isNoSelect
size="xs"
style={{ userSelect: 'none' }}
>
{formattedTime}
</Text>
</div>
<div className={styles.sliderWrapper}>
<PlayerbarSlider
label={(value) => formatDuration(value * 1000)}
max={songDuration}
min={0}
onChange={(e) => {
setIsSeeking(true);
setSeekValue(e);
}}
onChangeEnd={(e) => {
// There is a timing bug in Mantine in which the onChangeEnd
// event fires before onChange. Add a small delay to force
// onChangeEnd to happen after onCHange
setTimeout(() => {
handleSeekSlider(e);
setIsSeeking(false);
}, 50);
}}
size={6}
value={!isSeeking ? currentTime : seekValue}
w="100%"
/>
</div>
<div className={styles.sliderValueWrapper}>
<Text
className={PlaybackSelectors.totalDuration}
fw={600}
isMuted
isNoSelect
onClick={() => setShowTimeRemaining(!showTimeRemaining)}
role="button"
size="xs"
style={{ cursor: 'pointer', userSelect: 'none' }}
>
{showTimeRemaining ? formattedTimeRemaining : formattedDuration}
</Text>
</div>
</div>
);
};
@@ -24,6 +24,7 @@ import { Image } from '/@/shared/components/image/image';
import { Separator } from '/@/shared/components/separator/separator';
import { Text } from '/@/shared/components/text/text';
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
import { LibraryItem } from '/@/shared/types/domain-types';
export const LeftControls = () => {
@@ -46,6 +47,11 @@ export const LeftControls = () => {
);
const handleToggleFullScreenPlayer = (e?: KeyboardEvent | MouseEvent<HTMLDivElement>) => {
// don't toggle if right click
if (e && 'button' in e && e.button === 2) {
return;
}
e?.stopPropagation();
setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded });
};
@@ -55,6 +61,15 @@ export const LeftControls = () => {
setSideBar({ image: true });
};
const handleToggleContextMenu = (e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
if (isSongDefined && !isFullScreenPlayerExpanded) {
handleGeneralContextMenu(e, [currentSong!]);
}
};
const stopPropagation = (e?: MouseEvent) => e?.stopPropagation();
useHotkeys([
@@ -79,6 +94,7 @@ export const LeftControls = () => {
initial={{ opacity: 0, x: -50 }}
key="playerbar-image"
onClick={handleToggleFullScreenPlayer}
onContextMenu={handleToggleContextMenu}
role="button"
transition={{ duration: 0.2, ease: 'easeIn' }}
>
@@ -89,7 +105,10 @@ export const LeftControls = () => {
openDelay={500}
>
<Image
className={styles.playerbarImage}
className={clsx(
styles.playerbarImage,
PlaybackSelectors.playerCoverArt,
)}
loading="eager"
src={currentSong?.imageUrl ?? ''}
/>
@@ -124,9 +143,11 @@ export const LeftControls = () => {
<div className={styles.lineItem} onClick={stopPropagation}>
<Group align="center" gap="xs" wrap="nowrap">
<Text
className={PlaybackSelectors.songTitle}
component={Link}
fw={500}
isLink
onContextMenu={handleToggleContextMenu} // Ajout du clic droit
overflow="hidden"
to={AppRoute.NOW_PLAYING}
>
@@ -148,7 +169,11 @@ export const LeftControls = () => {
</Group>
</div>
<div
className={clsx(styles.lineItem, styles.secondary)}
className={clsx(
styles.lineItem,
styles.secondary,
PlaybackSelectors.songArtist,
)}
onClick={stopPropagation}
>
{artists?.map((artist, index) => (
@@ -174,7 +199,11 @@ export const LeftControls = () => {
))}
</div>
<div
className={clsx(styles.lineItem, styles.secondary)}
className={clsx(
styles.lineItem,
styles.secondary,
PlaybackSelectors.songAlbum,
)}
onClick={stopPropagation}
>
<Text
@@ -6,6 +6,7 @@ import styles from './player-button.module.css';
import { ActionIcon, ActionIconProps } from '/@/shared/components/action-icon/action-icon';
import { Tooltip, TooltipProps } from '/@/shared/components/tooltip/tooltip';
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
interface PlayerButtonProps extends Omit<ActionIconProps, 'icon' | 'variant'> {
icon: ReactNode;
@@ -62,9 +63,13 @@ interface PlayButtonProps extends Omit<ActionIconProps, 'icon' | 'variant'> {
export const PlayButton = forwardRef<HTMLButtonElement, PlayButtonProps>(
({ isPaused, onClick, ...props }: PlayButtonProps, ref) => {
const playerStateClass = isPaused
? PlaybackSelectors.playerStatePaused
: PlaybackSelectors.playerStatePlaying;
return (
<ActionIcon
className={styles.main}
className={clsx(styles.main, playerStateClass)}
icon={isPaused ? 'mediaPlay' : 'mediaPause'}
iconProps={{
size: 'lg',
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import { MouseEvent, useCallback } from 'react';
import styles from './playerbar.module.css';
@@ -6,6 +7,7 @@ import { AudioPlayer } from '/@/renderer/components';
import { CenterControls } from '/@/renderer/features/player/components/center-controls';
import { LeftControls } from '/@/renderer/features/player/components/left-controls';
import { RightControls } from '/@/renderer/features/player/components/right-controls';
import { usePowerSaveBlocker } from '/@/renderer/features/player/hooks/use-power-save-blocker';
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
import {
@@ -24,6 +26,7 @@ import {
usePlaybackType,
useSettingsStore,
} from '/@/renderer/store/settings.store';
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
import { PlaybackType } from '/@/shared/types/types';
export const Playerbar = () => {
@@ -41,6 +44,8 @@ export const Playerbar = () => {
const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore();
const setFullScreenPlayerStore = useSetFullScreenPlayerStore();
usePowerSaveBlocker();
const handleToggleFullScreenPlayer = (e?: KeyboardEvent | MouseEvent<HTMLDivElement>) => {
e?.stopPropagation();
setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded });
@@ -53,7 +58,7 @@ export const Playerbar = () => {
return (
<div
className={styles.container}
className={clsx(styles.container, PlaybackSelectors.mediaPlayer)}
onClick={playerbarOpenDrawer ? handleToggleFullScreenPlayer : undefined}
>
<div className={styles.controlsGrid}>
@@ -115,7 +115,9 @@ export const RightControls = () => {
};
const isSongDefined = Boolean(currentSong?.id);
const showRating = isSongDefined && server?.type === ServerType.NAVIDROME;
const showRating =
isSongDefined &&
(server?.type === ServerType.NAVIDROME || server?.type === ServerType.SUBSONIC);
useHotkeys([
[bindings.volumeDown.isGlobal ? '' : bindings.volumeDown.hotkey, handleVolumeDown],
@@ -78,6 +78,7 @@ export const useHandlePlayQueueAdd = () => {
// Allow this to be undefined for "play shuffled". If undefined, default to 0,
// otherwise, choose the selected item in the queue
let initialSongIndex: number | undefined;
let toastId: null | string = null;
if (byItemType) {
let songList: SongListResponse | undefined;
@@ -87,9 +88,8 @@ export const useHandlePlayQueueAdd = () => {
timeoutIds.current = {
...timeoutIds.current,
[fetchId]: setTimeout(() => {
toast.info({
toastId = toast.info({
autoClose: false,
id: fetchId,
message: t('player.playbackFetchCancel', {
postProcess: 'sentenceCase',
}),
@@ -148,7 +148,9 @@ export const useHandlePlayQueueAdd = () => {
clearTimeout(timeoutIds.current[fetchId] as ReturnType<typeof setTimeout>);
delete timeoutIds.current[fetchId];
toast.hide(fetchId);
if (toastId) {
toast.hide(toastId);
}
} catch (err: any) {
if (instanceOfCancellationError(err)) {
return null;
@@ -0,0 +1,139 @@
import { useEffect } from 'react';
import {
useCurrentSong,
useCurrentStatus,
usePlaybackSettings,
useSettingsStore,
} from '/@/renderer/store';
import { PlayerStatus } from '/@/shared/types/types';
export const useMediaSession = ({
handleNextTrack,
handlePause,
handlePlay,
handlePrevTrack,
handleSeekSlider,
handleSkipBackward,
handleSkipForward,
handleStop,
}: {
handleNextTrack: () => void;
handlePause: () => void;
handlePlay: () => void;
handlePrevTrack: () => void;
handleSeekSlider: (e: any | number) => void;
handleSkipBackward: (seconds: number) => void;
handleSkipForward: (seconds: number) => void;
handleStop: () => void;
}) => {
const { mediaSession: mediaSessionEnabled } = usePlaybackSettings();
const playerStatus = useCurrentStatus();
const currentSong = useCurrentSong();
const mediaSession = navigator.mediaSession;
const skip = useSettingsStore((state) => state.general.skipButtons);
useEffect(() => {
if (!mediaSessionEnabled || !mediaSession) {
return;
}
mediaSession.setActionHandler('nexttrack', () => {
console.log('nexttrack');
handleNextTrack();
});
mediaSession.setActionHandler('pause', () => {
console.log('pause');
handlePause();
});
mediaSession.setActionHandler('play', () => {
console.log('play');
handlePlay();
});
mediaSession.setActionHandler('previoustrack', () => {
console.log('previoustrack');
handlePrevTrack();
});
mediaSession.setActionHandler('seekto', (e) => {
handleSeekSlider(e.seekTime);
});
mediaSession.setActionHandler('stop', () => {
handleStop();
});
mediaSession.setActionHandler('seekbackward', (e) => {
handleSkipBackward(e.seekOffset || skip?.skipBackwardSeconds || 5);
});
mediaSession.setActionHandler('seekforward', (e) => {
handleSkipForward(e.seekOffset || skip?.skipForwardSeconds || 5);
});
return () => {
mediaSession.setActionHandler('nexttrack', null);
mediaSession.setActionHandler('pause', null);
mediaSession.setActionHandler('play', null);
mediaSession.setActionHandler('previoustrack', null);
mediaSession.setActionHandler('seekto', null);
mediaSession.setActionHandler('stop', null);
mediaSession.setActionHandler('seekbackward', null);
mediaSession.setActionHandler('seekforward', null);
};
}, [
handleNextTrack,
handlePause,
handlePlay,
handlePrevTrack,
handleSeekSlider,
handleSkipBackward,
handleSkipForward,
handleStop,
mediaSession,
mediaSessionEnabled,
skip?.skipBackwardSeconds,
skip?.skipForwardSeconds,
]);
useEffect(() => {
if (!mediaSessionEnabled || !mediaSession) {
return;
}
const updateMetadata = () => {
mediaSession.metadata = new MediaMetadata({
album: currentSong?.album ?? '',
artist: currentSong?.artistName ?? '',
artwork: currentSong?.imageUrl
? [{ src: currentSong.imageUrl, type: 'image/png' }]
: [],
title: currentSong?.name ?? '',
});
};
updateMetadata();
return () => {
mediaSession.metadata = null;
};
}, [currentSong, mediaSession, mediaSessionEnabled]);
useEffect(() => {
if (!mediaSessionEnabled || !mediaSession) {
return;
}
if (mediaSession) {
const status = playerStatus === PlayerStatus.PLAYING ? 'playing' : 'paused';
mediaSession.playbackState = status;
}
return () => {
mediaSession.playbackState = 'none';
};
}, [playerStatus, mediaSession, mediaSessionEnabled]);
};
@@ -0,0 +1,50 @@
import isElectron from 'is-electron';
import { useCallback, useEffect } from 'react';
import { useCurrentStatus } from '/@/renderer/store';
import { useWindowSettings } from '/@/renderer/store';
import { PlayerStatus } from '/@/shared/types/types';
const ipc = isElectron() ? window.api.ipc : null;
export const usePowerSaveBlocker = () => {
const status = useCurrentStatus();
const { preventSleepOnPlayback } = useWindowSettings();
const startPowerSaveBlocker = useCallback(async () => {
if (!ipc) return;
try {
await ipc.invoke('power-save-blocker-start');
} catch (error) {
console.error('Failed to start power save blocker:', error);
}
}, []);
const stopPowerSaveBlocker = useCallback(async () => {
if (!ipc) return;
try {
await ipc.invoke('power-save-blocker-stop');
} catch (error) {
console.error('Failed to stop power save blocker:', error);
}
}, []);
useEffect(() => {
if (!preventSleepOnPlayback) return;
if (status === PlayerStatus.PLAYING) {
startPowerSaveBlocker();
} else {
stopPowerSaveBlocker();
}
}, [status, preventSleepOnPlayback, startPowerSaveBlocker, stopPowerSaveBlocker]);
// Clean up on unmount
useEffect(() => {
return () => {
stopPowerSaveBlocker();
};
}, [stopPowerSaveBlocker]);
};
@@ -1,8 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSendScrobble } from '/@/renderer/features/player/mutations/scrobble-mutation';
import { usePlayerStore } from '/@/renderer/store';
import { usePlaybackSettings } from '/@/renderer/store/settings.store';
import { useAppStore, usePlaybackSettings, usePlayerStore } from '/@/renderer/store';
import { QueueSong, ServerType } from '/@/shared/types/domain-types';
import { PlayerStatus } from '/@/shared/types/types';
@@ -59,13 +58,14 @@ const checkScrobbleConditions = (args: {
export const useScrobble = () => {
const scrobbleSettings = usePlaybackSettings().scrobble;
const isScrobbleEnabled = scrobbleSettings?.enabled;
const isPrivateModeEnabled = useAppStore().privateMode;
const sendScrobble = useSendScrobble();
const [isCurrentSongScrobbled, setIsCurrentSongScrobbled] = useState(false);
const handleScrobbleFromSeek = useCallback(
(currentTime: number) => {
if (!isScrobbleEnabled) return;
if (!isScrobbleEnabled || isPrivateModeEnabled) return;
const currentSong = usePlayerStore.getState().current.song;
@@ -84,7 +84,7 @@ export const useScrobble = () => {
serverId: currentSong?.serverId,
});
},
[isScrobbleEnabled, sendScrobble],
[isScrobbleEnabled, isPrivateModeEnabled, sendScrobble],
);
const progressIntervalId = useRef<null | ReturnType<typeof setInterval>>(null);
@@ -108,18 +108,19 @@ export const useScrobble = () => {
) {
const artists =
currentSong.artists?.length > 0
? currentSong.artists.map((artist) => artist.name).join(', ')
? currentSong.artists.map((artist) => artist.name).join(' · ')
: currentSong.artistName;
new Notification(`Now playing ${currentSong.name}`, {
body: `by ${artists} on ${currentSong.album}`,
new Notification(`${currentSong.name}`, {
body: `${artists}\n${currentSong.album}`,
icon: currentSong.imageUrl || undefined,
silent: true,
});
}
}, 1000);
}
if (!isScrobbleEnabled) return;
if (!isScrobbleEnabled || isPrivateModeEnabled) return;
if (progressIntervalId.current) {
clearInterval(progressIntervalId.current);
@@ -201,6 +202,7 @@ export const useScrobble = () => {
scrobbleSettings?.scrobbleAtDuration,
scrobbleSettings?.scrobbleAtPercentage,
isScrobbleEnabled,
isPrivateModeEnabled,
isCurrentSongScrobbled,
sendScrobble,
handleScrobbleFromSeek,
@@ -209,7 +211,7 @@ export const useScrobble = () => {
const handleScrobbleFromStatusChange = useCallback(
(current: PlayerEvent, previous: PlayerEvent) => {
if (!isScrobbleEnabled) return;
if (!isScrobbleEnabled || isPrivateModeEnabled) return;
const currentSong = usePlayerStore.getState().current.song;
@@ -293,6 +295,7 @@ export const useScrobble = () => {
},
[
isScrobbleEnabled,
isPrivateModeEnabled,
sendScrobble,
handleScrobbleFromSeek,
scrobbleSettings?.scrobbleAtDuration,
@@ -306,7 +309,7 @@ export const useScrobble = () => {
// need to perform another check to see if the scrobble conditions are met
const handleScrobbleFromSongRestart = useCallback(
(currentTime: number) => {
if (!isScrobbleEnabled) return;
if (!isScrobbleEnabled || isPrivateModeEnabled) return;
const currentSong = usePlayerStore.getState().current.song;
@@ -349,6 +352,7 @@ export const useScrobble = () => {
},
[
isScrobbleEnabled,
isPrivateModeEnabled,
scrobbleSettings?.scrobbleAtDuration,
scrobbleSettings?.scrobbleAtPercentage,
isCurrentSongScrobbled,
+12 -6
View File
@@ -4,17 +4,19 @@ import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
PlaylistSongListQuery,
PlaylistSongListQueryClientSide,
ServerListItem,
SongDetailQuery,
SongListQuery,
SongListResponse,
SongListSort,
SortOrder,
sortSongList,
} from '/@/shared/types/domain-types';
export const getPlaylistSongsById = async (args: {
id: string;
query?: Partial<PlaylistSongListQuery>;
query?: Partial<PlaylistSongListQueryClientSide>;
queryClient: QueryClient;
server: ServerListItem;
}) => {
@@ -22,13 +24,9 @@ export const getPlaylistSongsById = async (args: {
const queryFilter: PlaylistSongListQuery = {
id,
sortBy: SongListSort.ID,
sortOrder: SortOrder.ASC,
startIndex: 0,
...query,
};
const queryKey = queryKeys.playlists.songList(server?.id, id, queryFilter);
const queryKey = queryKeys.playlists.songList(server?.id, id);
const res = await queryClient.fetchQuery(
queryKey,
@@ -46,6 +44,14 @@ export const getPlaylistSongsById = async (args: {
},
);
if (res) {
res.items = sortSongList(
res.items,
query?.sortBy || SongListSort.ID,
query?.sortOrder || SortOrder.ASC,
);
}
return res;
};
@@ -36,6 +36,7 @@ export const AddToPlaylistContextModal = ({
const { albumId, artistId, genreId, songId } = innerProps;
const server = useCurrentServer();
const [isLoading, setIsLoading] = useState(false);
const [isDropdownOpened, setIsDropdownOpened] = useState(true);
const addToPlaylistMutation = useAddToPlaylist({});
@@ -144,12 +145,7 @@ export const AddToPlaylistContextModal = ({
const uniqueSongIds: string[] = [];
if (values.skipDuplicates) {
const query = {
id: playlistId,
startIndex: 0,
};
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId, query);
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId);
const playlistSongsRes = await queryClient.fetchQuery(queryKey, ({ signal }) => {
if (!server)
@@ -163,9 +159,6 @@ export const AddToPlaylistContextModal = ({
},
query: {
id: playlistId,
sortBy: SongListSort.ID,
sortOrder: SortOrder.ASC,
startIndex: 0,
},
});
});
@@ -229,6 +222,7 @@ export const AddToPlaylistContextModal = ({
clearable
data={playlistSelect}
disabled={playlistList.isLoading}
dropdownOpened={isDropdownOpened}
label={t('form.addToPlaylist.input', {
context: 'playlists',
postProcess: 'titleCase',
@@ -236,6 +230,11 @@ export const AddToPlaylistContextModal = ({
searchable
size="md"
{...form.getInputProps('playlistId')}
onChange={(e) => {
setIsDropdownOpened(false);
form.getInputProps('playlistId').onChange(e);
}}
onClick={() => setIsDropdownOpened(true)}
/>
<Switch
label={t('form.addToPlaylist.input', {
@@ -2,7 +2,6 @@ import type {
BodyScrollEvent,
ColDef,
GridReadyEvent,
IDatasource,
PaginationChangedEvent,
RowDoubleClickedEvent,
RowDragEvent,
@@ -27,7 +26,6 @@ import {
} from '/@/renderer/features/context-menu/context-menu-items';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
import { useAppFocus } from '/@/renderer/hooks';
import {
useCurrentServer,
@@ -42,13 +40,15 @@ import { PersistedTableColumn, usePlayButtonBehavior } from '/@/renderer/store/s
import { toast } from '/@/shared/components/toast/toast';
import {
LibraryItem,
PlaylistSongListQuery,
PlaylistSongListQueryClientSide,
QueueSong,
ServerType,
Song,
SongListResponse,
SongListSort,
SortOrder,
} from '/@/shared/types/domain-types';
import { ListDisplayType, ServerType } from '/@/shared/types/types';
import { ListDisplayType } from '/@/shared/types/types';
interface PlaylistDetailContentProps {
songs?: Song[];
@@ -63,7 +63,7 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
const currentSong = useCurrentSong();
const server = useCurrentServer();
const page = usePlaylistDetailStore();
const filters: Partial<PlaylistSongListQuery> = useMemo(() => {
const filters: PlaylistSongListQueryClientSide = useMemo(() => {
return {
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
@@ -88,20 +88,6 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
const iSClientSide = server?.type === ServerType.SUBSONIC;
const checkPlaylistList = usePlaylistSongList({
options: {
enabled: !iSClientSide,
},
query: {
id: playlistId,
limit: 1,
startIndex: 0,
},
serverId: server?.id,
});
const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns, false, 'generic'),
[page.table.columns],
@@ -109,51 +95,9 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
const onGridReady = useCallback(
(params: GridReadyEvent) => {
if (!iSClientSide) {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const query: PlaylistSongListQuery = {
id: playlistId,
limit,
startIndex,
...filters,
};
const queryKey = queryKeys.playlists.songList(
server?.id || '',
playlistId,
query,
);
if (!server) return;
const songsRes = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getPlaylistSongList({
apiClientProps: {
server,
signal,
},
query,
}),
);
params.successCallback(
songsRes?.items || [],
songsRes?.totalRecordCount || 0,
);
},
rowCount: undefined,
};
params.api.setDatasource(dataSource);
}
params.api?.ensureIndexVisible(pagination.scrollOffset, 'top');
},
[filters, iSClientSide, pagination.scrollOffset, playlistId, queryClient, server],
[pagination.scrollOffset],
);
const handleDragEnd = useCallback(
@@ -175,12 +119,32 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
},
});
setTimeout(() => {
queryClient.invalidateQueries({
queryKey: queryKeys.playlists.songList(server?.id || '', playlistId),
});
e.api.refreshInfiniteCache();
}, 200);
queryClient.setQueryData<SongListResponse>(
queryKeys.playlists.songList(server?.id || '', playlistId),
(previous) => {
if (previous?.items) {
const from = e.node.rowIndex!;
const to = e.overIndex;
const item = previous.items[from];
const remaining = previous.items.toSpliced(from, 1);
remaining.splice(to, 0, item);
return {
error: previous.error,
items: remaining,
startIndex: previous.startIndex,
totalRecordCount: previous.totalRecordCount,
};
}
return previous;
},
);
// Nodes have to be redrawn, otherwise the row indexes will be wrong
// Maybe it's possible to only redraw necessary rows to not be as expensive?
tableRef.current?.api.redrawRows();
} catch (error) {
toast.error({
message: (error as Error).message,
@@ -189,7 +153,7 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
}
}
},
[playlistId, queryClient, server],
[playlistId, queryClient, server, tableRef],
);
const handleGridSizeChange = () => {
@@ -286,7 +250,9 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
const { rowClassRules } = useCurrentSongRowStyles({ tableRef });
const canDrag =
filters.sortBy === SongListSort.ID && !detailQuery?.data?.rules && !iSClientSide;
filters.sortBy === SongListSort.ID &&
!detailQuery?.data?.rules &&
server?.type !== ServerType.SUBSONIC;
return (
<>
@@ -303,9 +269,6 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
status,
}}
getRowId={(data) => data.data.uniqueId}
infiniteInitialRowCount={
iSClientSide ? undefined : checkPlaylistList.data?.totalRecordCount || 100
}
// https://github.com/ag-grid/ag-grid/issues/5284
// Key is used to force remount of table when display, rowHeight, or server changes
key={`table-${page.display}-${page.table.rowHeight}-${server?.id}`}
@@ -326,7 +289,7 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
rowData={songs}
rowDragEntireRow={canDrag}
rowHeight={page.table.rowHeight || 40}
rowModelType={iSClientSide ? 'clientSide' : 'infinite'}
rowModelType="clientSide"
shouldUpdateSong
/>
</VirtualGridAutoSizerContainer>
@@ -1,29 +1,26 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { IDatasource } from '@ag-grid-community/core';
import { closeAllModals, openModal } from '@mantine/modals';
import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce';
import { MouseEvent, MutableRefObject, useCallback } from 'react';
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams } from 'react-router';
import i18n from '/@/i18n/i18n';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form';
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
import { OrderToggleButton } from '/@/renderer/features/shared';
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
import { SearchInput } from '/@/renderer/features/shared/components/search-input';
import { useContainerQuery } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes';
import {
PersistedTableColumn,
SongListFilter,
useCurrentServer,
usePlaylistDetailStore,
useSetPlaylistDetailFilters,
@@ -40,13 +37,7 @@ import { Icon } from '/@/shared/components/icon/icon';
import { ConfirmModal } from '/@/shared/components/modal/modal';
import { Text } from '/@/shared/components/text/text';
import { toast } from '/@/shared/components/toast/toast';
import {
LibraryItem,
PlaylistSongListQuery,
ServerType,
SongListSort,
SortOrder,
} from '/@/shared/types/domain-types';
import { ServerType, SongListSort, SortOrder } from '/@/shared/types/domain-types';
import { ListDisplayType, Play } from '/@/shared/types/types';
const FILTERS = {
@@ -155,7 +146,7 @@ const FILTERS = {
},
{
defaultOrder: SortOrder.ASC,
name: i18n.t('filter.playCount', { postProcess: 'titleCase' }),
name: i18n.t('filter.genre', { postProcess: 'titleCase' }),
value: SongListSort.GENRE,
},
{
@@ -240,11 +231,6 @@ const FILTERS = {
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
value: SongListSort.RECENTLY_ADDED,
},
{
defaultOrder: SortOrder.DESC,
name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }),
value: SongListSort.RECENTLY_PLAYED,
},
{
defaultOrder: SortOrder.DESC,
name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }),
@@ -254,11 +240,13 @@ const FILTERS = {
};
interface PlaylistDetailSongListHeaderFiltersProps {
handlePlay: (playType: Play) => void;
handleToggleShowQueryBuilder: () => void;
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const PlaylistDetailSongListHeaderFilters = ({
handlePlay,
handleToggleShowQueryBuilder,
tableRef,
}: PlaylistDetailSongListHeaderFiltersProps) => {
@@ -270,16 +258,13 @@ export const PlaylistDetailSongListHeaderFilters = ({
const setPage = useSetPlaylistStore();
const setFilter = useSetPlaylistDetailFilters();
const page = usePlaylistDetailStore();
const filters: Partial<PlaylistSongListQuery> = {
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
};
const searchTerm = page?.table.id[playlistId]?.filter?.searchTerm;
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC;
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
const isSmartPlaylist = detailQuery.data?.rules;
const handlePlayQueueAdd = usePlayQueueAdd();
const cq = useContainerQuery();
const setPagination = useSetPlaylistTablePagination();
@@ -287,8 +272,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
const sortByLabel =
(server?.type &&
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy)
?.name) ||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === sortBy)?.name) ||
'Unknown';
const handleItemSize = (e: number) => {
@@ -297,93 +281,48 @@ export const PlaylistDetailSongListHeaderFilters = ({
const debouncedHandleItemSize = debounce(handleItemSize, 20);
const handleFilterChange = useCallback(
async (filters: SongListFilter) => {
if (server?.type !== ServerType.SUBSONIC) {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const handleFilterChange = useCallback(async () => {
tableRef.current?.api.redrawRows();
tableRef.current?.api.ensureIndexVisible(0, 'top');
const queryKey = queryKeys.playlists.songList(
server?.id || '',
playlistId,
{
id: playlistId,
limit,
startIndex,
...filters,
},
);
const songsRes = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getPlaylistSongList({
apiClientProps: {
server,
signal,
},
query: {
id: playlistId,
limit,
startIndex,
...filters,
},
}),
{ cacheTime: 1000 * 60 * 1 },
);
params.successCallback(
songsRes?.items || [],
songsRes?.totalRecordCount || 0,
);
},
rowCount: undefined,
};
tableRef.current?.api.setDatasource(dataSource);
tableRef.current?.api.purgeInfiniteCache();
tableRef.current?.api.ensureIndexVisible(0, 'top');
} else {
tableRef.current?.api.redrawRows();
tableRef.current?.api.ensureIndexVisible(0, 'top');
}
if (page.display === ListDisplayType.TABLE_PAGINATED) {
setPagination({ data: { currentPage: 0 } });
}
},
[tableRef, page.display, server, playlistId, queryClient, setPagination],
);
if (page.display === ListDisplayType.TABLE_PAGINATED) {
setPagination({ data: { currentPage: 0 } });
}
}, [tableRef, page.display, setPagination]);
const handleRefresh = () => {
queryClient.invalidateQueries(queryKeys.albums.list(server?.id || ''));
handleFilterChange({ ...page?.table.id[playlistId].filter, ...filters });
queryClient.invalidateQueries(queryKeys.playlists.songList(server?.id || '', playlistId));
handleFilterChange();
};
const handleSetSortBy = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value || !server?.type) return;
const sortOrder = FILTERS[server.type as keyof typeof FILTERS].find(
const newSortOrder = FILTERS[server.type as keyof typeof FILTERS].find(
(f) => f.value === e.currentTarget.value,
)?.defaultOrder;
const updatedFilters = setFilter(playlistId, {
setFilter(playlistId, {
sortBy: e.currentTarget.value as SongListSort,
sortOrder: sortOrder || SortOrder.ASC,
sortOrder: newSortOrder || SortOrder.ASC,
});
handleFilterChange(updatedFilters);
handleFilterChange();
},
[handleFilterChange, playlistId, server?.type, setFilter],
);
const handleToggleSortOrder = useCallback(() => {
const newSortOrder = filters.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
const updatedFilters = setFilter(playlistId, { sortOrder: newSortOrder });
handleFilterChange(updatedFilters);
}, [filters.sortOrder, handleFilterChange, playlistId, setFilter]);
const newSortOrder = sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
setFilter(playlistId, { sortOrder: newSortOrder });
handleFilterChange();
}, [sortOrder, handleFilterChange, playlistId, setFilter]);
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
setFilter(playlistId, { searchTerm: e.target.value });
handleFilterChange();
}, 500);
const handleSetViewType = useCallback(
(displayType: ListDisplayType) => {
@@ -428,13 +367,6 @@ export const PlaylistDetailSongListHeaderFilters = ({
}
};
const handlePlay = async (playType: Play) => {
handlePlayQueueAdd?.({
byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST },
playType,
});
};
const deletePlaylistMutation = useDeletePlaylist({});
const handleDeletePlaylist = useCallback(() => {
@@ -484,7 +416,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
<DropdownMenu.Dropdown>
{FILTERS[server?.type as keyof typeof FILTERS].map((filter) => (
<DropdownMenu.Item
isSelected={filter.value === filters.sortBy}
isSelected={filter.value === sortBy}
key={`filter-${filter.name}`}
onClick={handleSetSortBy}
value={filter.value}
@@ -498,7 +430,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
<Divider orientation="vertical" />
<OrderToggleButton
onToggle={handleToggleSortOrder}
sortOrder={filters.sortOrder || SortOrder.ASC}
sortOrder={sortOrder || SortOrder.ASC}
/>
<DropdownMenu position="bottom-start">
<DropdownMenu.Target>
@@ -560,6 +492,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
)}
</DropdownMenu.Dropdown>
</DropdownMenu>
<SearchInput defaultValue={searchTerm} onChange={handleSearch} />
</Group>
<Group>
<ListConfigMenu
@@ -5,25 +5,27 @@ import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
import { PageHeader } from '/@/renderer/components/page-header/page-header';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { PlaylistDetailSongListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header-filters';
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
import { useCurrentServer } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { formatDurationString } from '/@/renderer/utils';
import { Badge } from '/@/shared/components/badge/badge';
import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack';
import { LibraryItem } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types';
interface PlaylistDetailHeaderProps {
handlePlay: (playType: Play) => void;
handleToggleShowQueryBuilder: () => void;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const PlaylistDetailSongListHeader = ({
handlePlay,
handleToggleShowQueryBuilder,
itemCount,
tableRef,
@@ -32,19 +34,12 @@ export const PlaylistDetailSongListHeader = ({
const { playlistId } = useParams() as { playlistId: string };
const server = useCurrentServer();
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
const handlePlayQueueAdd = usePlayQueueAdd();
const handlePlay = async (playType: Play) => {
handlePlayQueueAdd?.({
byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST },
playType,
});
};
const playButtonBehavior = usePlayButtonBehavior();
if (detailQuery.isLoading) return null;
const isSmartPlaylist = detailQuery?.data?.rules;
const playlistDuration = detailQuery?.data?.duration;
return (
<Stack gap={0}>
@@ -52,6 +47,7 @@ export const PlaylistDetailSongListHeader = ({
<LibraryHeaderBar>
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<LibraryHeaderBar.Title>{detailQuery?.data?.name}</LibraryHeaderBar.Title>
{!!playlistDuration && <Badge>{formatDurationString(playlistDuration)}</Badge>}
<Badge>
{itemCount === null || itemCount === undefined ? (
<SpinnerIcon />
@@ -64,6 +60,7 @@ export const PlaylistDetailSongListHeader = ({
</PageHeader>
<FilterBar>
<PlaylistDetailSongListHeaderFilters
handlePlay={handlePlay}
handleToggleShowQueryBuilder={handleToggleShowQueryBuilder}
tableRef={tableRef}
/>
@@ -29,9 +29,6 @@ export const useAddToPlaylist = (args: MutationHookArgs) => {
queryClient.invalidateQueries(queryKeys.playlists.list(serverId), { exact: false });
queryClient.invalidateQueries(queryKeys.playlists.detail(serverId, variables.query.id));
queryClient.invalidateQueries(
queryKeys.playlists.detailSongList(serverId, variables.query.id),
);
queryClient.invalidateQueries(
queryKeys.playlists.songList(serverId, variables.query.id),
);
@@ -29,7 +29,7 @@ export const useRemoveFromPlaylist = (options?: MutationOptions) => {
queryClient.invalidateQueries(queryKeys.playlists.list(serverId), { exact: false });
queryClient.invalidateQueries(queryKeys.playlists.detail(serverId, variables.query.id));
queryClient.invalidateQueries(
queryKeys.playlists.detailSongList(serverId, variables.query.id),
queryKeys.playlists.songList(serverId, variables.query.id),
);
},
...options,
@@ -20,7 +20,7 @@ export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>)
query,
});
},
queryKey: queryKeys.playlists.songList(server?.id || '', query.id, query),
queryKey: queryKeys.playlists.songList(server?.id || '', query.id),
...options,
});
};
@@ -1,11 +1,13 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { closeAllModals, openModal } from '@mantine/modals';
import Fuse from 'fuse.js';
import { motion } from 'motion/react';
import { useRef, useState } from 'react';
import { useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, useNavigate, useParams } from 'react-router';
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
import { PlaylistDetailSongListContent } from '/@/renderer/features/playlists/components/playlist-detail-song-list-content';
import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header';
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
@@ -22,12 +24,8 @@ import { Box } from '/@/shared/components/box/box';
import { Group } from '/@/shared/components/group/group';
import { Text } from '/@/shared/components/text/text';
import { toast } from '/@/shared/components/toast/toast';
import {
PlaylistSongListQuery,
ServerType,
SongListSort,
SortOrder,
} from '/@/shared/types/domain-types';
import { ServerType, SongListSort, SortOrder, sortSongList } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types';
const PlaylistDetailSongListRoute = () => {
const { t } = useTranslation();
@@ -35,6 +33,7 @@ const PlaylistDetailSongListRoute = () => {
const tableRef = useRef<AgGridReactType | null>(null);
const { playlistId } = useParams() as { playlistId: string };
const server = useCurrentServer();
const handlePlayQueueAdd = useHandlePlayQueueAdd();
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
const createPlaylistMutation = useCreatePlaylist({});
@@ -148,26 +147,61 @@ const PlaylistDetailSongListRoute = () => {
};
const page = usePlaylistDetailStore();
const filters: Partial<PlaylistSongListQuery> = {
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
};
const itemCountCheck = usePlaylistSongList({
const playlistSongs = usePlaylistSongList({
query: {
id: playlistId,
limit: 1,
startIndex: 0,
...filters,
},
serverId: server?.id,
});
const itemCount = itemCountCheck.data?.totalRecordCount || itemCountCheck.data?.items.length;
const filterSortedSongs = useMemo(() => {
let items = playlistSongs.data?.items;
if (items) {
const searchTerm = page?.table.id[playlistId]?.filter?.searchTerm;
if (searchTerm) {
const fuse = new Fuse(items, {
fieldNormWeight: 1,
ignoreLocation: true,
keys: [
'name',
'album',
{
getFn: (song) => song.artists.map((artist) => artist.name),
name: 'artist',
},
],
threshold: 0,
});
items = fuse.search(searchTerm).map((item) => item.item);
}
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC;
return sortSongList(items, sortBy, sortOrder);
} else {
return [];
}
}, [playlistSongs.data?.items, page?.table.id, playlistId]);
const itemCount =
typeof playlistSongs.data?.totalRecordCount === 'number'
? filterSortedSongs.length
: undefined;
const handlePlay = (play: Play) => {
handlePlayQueueAdd?.({
byData: filterSortedSongs,
playType: play,
});
};
return (
<AnimatedPage key={`playlist-detail-songList-${playlistId}`}>
<PlaylistDetailSongListHeader
handlePlay={handlePlay}
handleToggleShowQueryBuilder={handleToggleShowQueryBuilder}
itemCount={itemCount}
tableRef={tableRef}
@@ -203,12 +237,7 @@ const PlaylistDetailSongListRoute = () => {
</Box>
</motion.div>
)}
<PlaylistDetailSongListContent
songs={
server?.type === ServerType.SUBSONIC ? itemCountCheck.data?.items : undefined
}
tableRef={tableRef}
/>
<PlaylistDetailSongListContent songs={filterSortedSongs} tableRef={tableRef} />
</AnimatedPage>
);
};
@@ -37,6 +37,7 @@ export const LibraryCommandItem = ({
const handlePlay = useCallback(
(e: SyntheticEvent, id: string, playType: Play) => {
e.stopPropagation();
e.preventDefault();
handlePlayQueueAdd?.({
byItemType: {
id: [id],
@@ -3,7 +3,7 @@ import { useFocusTrap } from '@mantine/hooks';
import { closeAllModals } from '@mantine/modals';
import isElectron from 'is-electron';
import { nanoid } from 'nanoid/non-secure';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { api } from '/@/renderer/api';
@@ -14,21 +14,28 @@ import { useAuthStoreActions } from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button';
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
import { Group } from '/@/shared/components/group/group';
import { Paper } from '/@/shared/components/paper/paper';
import { PasswordInput } from '/@/shared/components/password-input/password-input';
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
import { Stack } from '/@/shared/components/stack/stack';
import { TextInput } from '/@/shared/components/text-input/text-input';
import { Text } from '/@/shared/components/text/text';
import { toast } from '/@/shared/components/toast/toast';
import { AuthenticationResponse } from '/@/shared/types/domain-types';
import { ServerType, toServerType } from '/@/shared/types/types';
import { AuthenticationResponse, ServerListItem } from '/@/shared/types/domain-types';
import { DiscoveredServerItem, ServerType, toServerType } from '/@/shared/types/types';
const autodiscover = isElectron() ? window.api.autodiscover : null;
const localSettings = isElectron() ? window.api.localSettings : null;
interface AddServerFormProps {
onCancel: (() => void) | null;
}
interface ServerDetails {
icon: string;
name: string;
}
function ServerIconWithLabel({ icon, label }: { icon: string; label: string }) {
return (
<Stack align="center" justify="center">
@@ -38,33 +45,62 @@ function ServerIconWithLabel({ icon, label }: { icon: string; label: string }) {
);
}
const SERVER_TYPES = [
{
label: <ServerIconWithLabel icon={JellyfinIcon} label="Jellyfin" />,
value: ServerType.JELLYFIN,
function useAutodiscovery() {
const [isDone, setDone] = useState(false);
const [servers, setServers] = useState<DiscoveredServerItem[]>([]);
useEffect(() => {
setServers([]);
autodiscover
?.discover((newServer) => {
setServers((tail) => [...tail, newServer]);
})
.then(() => {
setDone(true);
});
}, []);
return { isDone, servers };
}
const SERVER_TYPES: Record<ServerType, ServerDetails> = {
[ServerType.JELLYFIN]: {
icon: JellyfinIcon,
name: 'Jellyfin',
},
{
label: <ServerIconWithLabel icon={NavidromeIcon} label="Navidrome" />,
value: ServerType.NAVIDROME,
[ServerType.NAVIDROME]: {
icon: NavidromeIcon,
name: 'Navidrome',
},
{
label: <ServerIconWithLabel icon={SubsonicIcon} label="OpenSubsonic" />,
value: ServerType.SUBSONIC,
[ServerType.SUBSONIC]: {
icon: SubsonicIcon,
name: 'OpenSubsonic',
},
];
};
const ALL_SERVERS = Object.keys(SERVER_TYPES).map((serverType) => {
const info = SERVER_TYPES[serverType];
return {
label: <ServerIconWithLabel icon={info.icon} label={info.name} />,
value: serverType,
};
});
export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
const { t } = useTranslation();
const focusTrapRef = useFocusTrap(true);
const [isLoading, setIsLoading] = useState(false);
const { addServer, setCurrentServer } = useAuthStoreActions();
const { servers: discovered } = useAutodiscovery();
const form = useForm({
initialValues: {
legacyAuth: false,
name: (localSettings ? localSettings.env.SERVER_NAME : window.SERVER_NAME) ?? '',
password: '',
savePassword: false,
preferInstantMix: undefined,
savePassword: undefined,
type:
(localSettings
? localSettings.env.SERVER_TYPE
@@ -85,6 +121,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
const isSubmitDisabled = !form.values.name || !form.values.url || !form.values.username;
const fillServerDetails = (server: DiscoveredServerItem) => {
form.setValues({ ...server });
};
const handleSubmit = form.onSubmit(async (values) => {
const authFunction = api.controller.authenticate;
@@ -112,17 +152,28 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
});
}
const serverItem = {
const serverItem: ServerListItem = {
credential: data.credential,
id: nanoid(),
name: values.name,
ndCredential: data.ndCredential,
type: values.type as ServerType,
url: values.url.replace(/\/$/, ''),
userId: data.userId,
username: data.username,
};
if (values.preferInstantMix !== undefined) {
serverItem.preferInstantMix = values.preferInstantMix;
}
if (values.savePassword !== undefined) {
serverItem.savePassword = values.savePassword;
}
if (data.ndCredential !== undefined) {
serverItem.ndCredential = data.ndCredential;
}
addServer(serverItem);
setCurrentServer(serverItem);
closeAllModals();
@@ -151,84 +202,119 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
});
return (
<form onSubmit={handleSubmit}>
<Stack m={5} ref={focusTrapRef}>
<SegmentedControl
data={SERVER_TYPES}
disabled={Boolean(serverLock)}
p="md"
withItemsBorders={false}
{...form.getInputProps('type')}
/>
<Group grow>
<TextInput
data-autofocus
disabled={Boolean(serverLock)}
label={t('form.addServer.input', {
context: 'name',
postProcess: 'titleCase',
})}
{...form.getInputProps('name')}
/>
<TextInput
disabled={Boolean(serverLock)}
label={t('form.addServer.input', {
context: 'url',
postProcess: 'titleCase',
})}
{...form.getInputProps('url')}
/>
</Group>
<TextInput
label={t('form.addServer.input', {
context: 'username',
postProcess: 'titleCase',
})}
{...form.getInputProps('username')}
/>
<PasswordInput
label={t('form.addServer.input', {
context: 'password',
postProcess: 'titleCase',
})}
{...form.getInputProps('password')}
/>
{localSettings && form.values.type === ServerType.NAVIDROME && (
<Checkbox
label={t('form.addServer.input', {
context: 'savePassword',
postProcess: 'titleCase',
})}
{...form.getInputProps('savePassword', {
type: 'checkbox',
})}
/>
)}
{form.values.type === ServerType.SUBSONIC && (
<Checkbox
label={t('form.addServer.input', {
context: 'legacyAuthentication',
postProcess: 'titleCase',
})}
{...form.getInputProps('legacyAuth', { type: 'checkbox' })}
/>
)}
<Group grow justify="flex-end">
{onCancel && (
<Button onClick={onCancel} variant="subtle">
{t('common.cancel', { postProcess: 'titleCase' })}
</Button>
)}
<Button
disabled={isSubmitDisabled}
loading={isLoading}
type="submit"
variant="filled"
>
{t('common.add', { postProcess: 'titleCase' })}
</Button>
</Group>
<>
<Stack>
{discovered.map((server) => (
<Paper key={server.url} p="10px">
<Group>
<img height="32" src={SERVER_TYPES[server.type].icon} width="32" />
<div
onClick={() => fillServerDetails(server)}
style={{ cursor: 'pointer' }}
>
<Text fw={700}>{server.name}</Text>
<Text>
{SERVER_TYPES[server.type].name} server at {server.url}
</Text>
</div>
</Group>
</Paper>
))}
</Stack>
</form>
<form onSubmit={handleSubmit}>
<Stack m={5} ref={focusTrapRef}>
<SegmentedControl
data={ALL_SERVERS}
disabled={Boolean(serverLock)}
p="md"
withItemsBorders={false}
{...form.getInputProps('type')}
/>
<Group grow>
<TextInput
data-autofocus
disabled={Boolean(serverLock)}
label={t('form.addServer.input', {
context: 'name',
postProcess: 'titleCase',
})}
{...form.getInputProps('name')}
/>
<TextInput
disabled={Boolean(serverLock)}
label={t('form.addServer.input', {
context: 'url',
postProcess: 'titleCase',
})}
{...form.getInputProps('url')}
/>
</Group>
<TextInput
label={t('form.addServer.input', {
context: 'username',
postProcess: 'titleCase',
})}
{...form.getInputProps('username')}
/>
<PasswordInput
label={t('form.addServer.input', {
context: 'password',
postProcess: 'titleCase',
})}
{...form.getInputProps('password')}
/>
{localSettings && form.values.type === ServerType.NAVIDROME && (
<Checkbox
label={t('form.addServer.input', {
context: 'savePassword',
postProcess: 'titleCase',
})}
{...form.getInputProps('savePassword', {
type: 'checkbox',
})}
/>
)}
{form.values.type === ServerType.SUBSONIC && (
<Checkbox
label={t('form.addServer.input', {
context: 'legacyAuthentication',
postProcess: 'titleCase',
})}
{...form.getInputProps('legacyAuth', { type: 'checkbox' })}
/>
)}
{form.values.type === ServerType.JELLYFIN && (
<Checkbox
description={t('form.addServer.input', {
context: 'preferInstantMixDescription',
postProcess: 'sentenceCase',
})}
label={t('form.addServer.input', {
context: 'preferInstantMix',
postProcess: 'titleCase',
})}
{...form.getInputProps('preferInstantMix', {
type: 'checkbox',
})}
/>
)}
<Group grow justify="flex-end">
{onCancel && (
<Button onClick={onCancel} variant="subtle">
{t('common.cancel', { postProcess: 'titleCase' })}
</Button>
)}
<Button
disabled={isSubmitDisabled}
loading={isLoading}
type="submit"
variant="filled"
>
{t('common.add', { postProcess: 'titleCase' })}
</Button>
</Group>
</Stack>
</form>
</>
);
};
@@ -7,7 +7,6 @@ import { useTranslation } from 'react-i18next';
import i18n from '/@/i18n/i18n';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { queryClient } from '/@/renderer/lib/react-query';
import { useAuthStoreActions } from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button';
@@ -49,7 +48,8 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
legacyAuth: false,
name: server?.name,
password: password || '',
savePassword: server.savePassword || false,
preferInstantMix: server.preferInstantMix,
savePassword: server.savePassword,
type: server?.type,
url: server?.url,
username: server?.username,
@@ -86,17 +86,28 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
});
}
const serverItem = {
const serverItem: ServerListItem = {
credential: data.credential,
id: server.id,
name: values.name,
ndCredential: data.ndCredential,
savePassword: values.savePassword,
type: values.type,
url: values.url,
userId: data.userId,
username: data.username,
};
if (values.preferInstantMix !== undefined) {
serverItem.preferInstantMix = values.preferInstantMix;
}
if (values.savePassword !== undefined) {
serverItem.savePassword = values.savePassword;
}
if (data.ndCredential !== undefined) {
serverItem.ndCredential = data.ndCredential;
}
updateServer(server.id, serverItem);
toast.success({
message: t('form.updateServer.title', { postProcess: 'sentenceCase' }),
@@ -118,7 +129,7 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
}
}
queryClient.invalidateQueries({ queryKey: queryKeys.server.root(server.id) });
queryClient.removeQueries();
} catch (err: any) {
setIsLoading(false);
return toast.error({ message: err?.message });
@@ -189,6 +200,21 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
})}
/>
)}
{form.values.type === ServerType.JELLYFIN && (
<Checkbox
description={t('form.addServer.input', {
context: 'preferInstantMixDescription',
postProcess: 'sentenceCase',
})}
label={t('form.addServer.input', {
context: 'preferInstantMix',
postProcess: 'titleCase',
})}
{...form.getInputProps('preferInstantMix', {
type: 'checkbox',
})}
/>
)}
<Group justify="flex-end">
<Button onClick={onCancel} variant="subtle">
{t('common.cancel', { postProcess: 'titleCase' })}
@@ -1,9 +1,11 @@
import { StylesSettings } from '/@/renderer/features/settings/components/advanced/styles-settings';
import { UpdateSettings } from '/@/renderer/features/settings/components/window/update-settings';
import { Stack } from '/@/shared/components/stack/stack';
export const AdvancedTab = () => {
return (
<Stack gap="md">
<UpdateSettings />
<StylesSettings />
</Stack>
);
@@ -548,9 +548,57 @@ export const ControlSettings = () => {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
isHidden: !settings.albumBackground,
title: t('setting.albumBackgroundBlur', { postProcess: 'sentenceCase' }),
},
{
control: (
<Switch
aria-label={t('setting.artistBackground', { postProcess: 'sentenceCase' })}
defaultChecked={settings.artistBackground}
onChange={(e) =>
setSettings({
general: {
...settings,
artistBackground: e.currentTarget.checked,
},
})
}
/>
),
description: t('setting.artistBackground', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.artistBackground', { postProcess: 'sentenceCase' }),
},
{
control: (
<Slider
defaultValue={settings.artistBackgroundBlur}
label={(e) => `${e} rem`}
max={6}
min={0}
onChangeEnd={(e) => {
setSettings({
general: {
...settings,
artistBackgroundBlur: e,
},
});
}}
step={0.5}
w={100}
/>
),
description: t('setting.artistBackgroundBlur', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: !settings.artistBackground,
title: t('setting.artistBackgroundBlur', { postProcess: 'sentenceCase' }),
},
{
control: (
<Switch
@@ -5,6 +5,7 @@ const HOME_ITEMS: Array<[string, string]> = [
[HomeItem.RANDOM, 'page.home.explore'],
[HomeItem.RECENTLY_PLAYED, 'page.home.recentlyPlayed'],
[HomeItem.RECENTLY_ADDED, 'page.home.newlyAdded'],
[HomeItem.RECENTLY_RELEASED, 'page.home.recentlyReleased'],
[HomeItem.MOST_PLAYED, 'page.home.mostPlayed'],
];
@@ -52,6 +52,10 @@ const BINDINGS_MAP: Record<BindingActions, string> = {
postProcess: 'sentenceCase',
}),
localSearch: i18n.t('setting.hotkey', { context: 'localSearch', postProcess: 'sentenceCase' }),
navigateHome: i18n.t('setting.hotkey', {
context: 'navigateHome',
postProcess: 'sentenceCase',
}),
next: i18n.t('setting.hotkey', { context: 'playbackNext', postProcess: 'sentenceCase' }),
pause: i18n.t('setting.hotkey', { context: 'playbackPause', postProcess: 'sentenceCase' }),
play: i18n.t('setting.hotkey', { context: 'playbackPlay', postProcess: 'sentenceCase' }),
@@ -181,6 +181,7 @@ export const LyricSettings = () => {
{
control: (
<Select
clearable
data={['Microsoft Azure', 'Google Cloud']}
onChange={(value) => {
setSettings({ lyrics: { ...settings, translationApiProvider: value } });
@@ -0,0 +1,45 @@
import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next';
import {
SettingOption,
SettingsSection,
} from '/@/renderer/features/settings/components/settings-section';
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
import { Switch } from '/@/shared/components/switch/switch';
const isWindows = window.api.utils.isWindows();
const isDesktop = isElectron();
export const MediaSessionSettings = () => {
const { t } = useTranslation();
const { mediaSession } = usePlaybackSettings();
const { toggleMediaSession } = useSettingsStoreActions();
function handleMediaSessionChange() {
const current = mediaSession;
toggleMediaSession();
window.api.ipc.send('settings-set', { property: 'mediaSession', value: !current });
}
const mediaSessionOptions: SettingOption[] = [
{
control: (
<Switch
aria-label="Toggle media Session"
defaultChecked={mediaSession}
onChange={handleMediaSessionChange}
/>
),
description: t('setting.mediaSession', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: isDesktop && !isWindows,
note: t('common.restartRequired', { postProcess: 'sentenceCase' }),
title: t('setting.mediaSession', { postProcess: 'sentenceCase' }),
},
];
return <SettingsSection divider options={mediaSessionOptions} />;
};
@@ -3,6 +3,7 @@ import { lazy, Suspense, useMemo } from 'react';
import { AudioSettings } from '/@/renderer/features/settings/components/playback/audio-settings';
import { LyricSettings } from '/@/renderer/features/settings/components/playback/lyric-settings';
import { MediaSessionSettings } from '/@/renderer/features/settings/components/playback/media-session-settings';
import { ScrobbleSettings } from '/@/renderer/features/settings/components/playback/scrobble-settings';
import { TranscodeSettings } from '/@/renderer/features/settings/components/playback/transcode-settings';
import { useSettingsStore } from '/@/renderer/store';
@@ -31,6 +32,7 @@ export const PlaybackTab = () => {
<AudioSettings hasFancyAudio={hasFancyAudio} />
<Suspense fallback={<></>}>{hasFancyAudio && <MpvSettings />}</Suspense>
<TranscodeSettings />
<MediaSessionSettings />
<ScrobbleSettings />
<LyricSettings />
</Stack>
@@ -6,16 +6,19 @@ import {
SettingsSection,
} from '/@/renderer/features/settings/components/settings-section';
import {
useDiscordSetttings,
DiscordDisplayType,
DiscordLinkType,
useDiscordSettings,
useGeneralSettings,
useSettingsStoreActions,
} from '/@/renderer/store';
import { Select } from '/@/shared/components/select/select';
import { Switch } from '/@/shared/components/switch/switch';
import { TextInput } from '/@/shared/components/text-input/text-input';
export const DiscordSettings = () => {
const { t } = useTranslation();
const settings = useDiscordSetttings();
const settings = useDiscordSettings();
const generalSettings = useGeneralSettings();
const { setSettings } = useSettingsStoreActions();
@@ -120,6 +123,98 @@ export const DiscordSettings = () => {
postProcess: 'sentenceCase',
}),
},
{
control: (
<Select
aria-label={t('setting.discordDisplayType')}
clearable={false}
data={[
{ label: 'Feishin', value: DiscordDisplayType.FEISHIN },
{
label: t('setting.discordDisplayType', {
context: 'songname',
postProcess: 'sentenceCase',
}),
value: DiscordDisplayType.SONG_NAME,
},
{
label: t('setting.discordDisplayType_artistname', {
context: 'artistname',
postProcess: 'sentenceCase',
}),
value: DiscordDisplayType.ARTIST_NAME,
},
]}
defaultValue={settings.displayType}
onChange={(e) => {
if (!e) return;
setSettings({
discord: {
...settings,
displayType: e as DiscordDisplayType,
},
});
}}
/>
),
description: t('setting.discordDisplayType', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: !isElectron(),
title: t('setting.discordDisplayType', {
discord: 'Discord',
musicbrainz: 'musicbrainz',
postProcess: 'sentenceCase',
}),
},
{
control: (
<Select
aria-label={t('setting.discordLinkType')}
clearable={false}
data={[
{
label: t('setting.discordLinkType_none', {
postProcess: 'sentenceCase',
}),
value: DiscordLinkType.NONE,
},
{ label: 'last.fm', value: DiscordLinkType.LAST_FM },
{ label: 'musicbrainz', value: DiscordLinkType.MBZ },
{
label: t('setting.discordLinkType_mbz_lastfm', {
lastfm: 'last.fm',
musicbrainz: 'musicbrainz',
}),
value: DiscordLinkType.MBZ_LAST_FM,
},
]}
defaultValue={settings.linkType}
onChange={(e) => {
if (!e) return;
setSettings({
discord: {
...settings,
linkType: e as DiscordLinkType,
},
});
}}
/>
),
description: t('setting.discordLinkType', {
context: 'description',
discord: 'Discord',
lastfm: 'last.fm',
musicbrainz: 'musicbrainz',
postProcess: 'sentenceCase',
}),
isHidden: !isElectron(),
title: t('setting.discordLinkType', {
discord: 'Discord',
postProcess: 'sentenceCase',
}),
},
{
control: (
<Switch
@@ -6,10 +6,10 @@ import {
SettingsSection,
} from '/@/renderer/features/settings/components/settings-section';
import { useSettingsStoreActions, useWindowSettings } from '/@/renderer/store';
import { Select } from '/@/shared/components/select/select';
import { Switch } from '/@/shared/components/switch/switch';
const localSettings = isElectron() ? window.api.localSettings : null;
const utils = isElectron() ? window.api.utils : null;
export const UpdateSettings = () => {
const { t } = useTranslation();
@@ -17,6 +17,47 @@ export const UpdateSettings = () => {
const { setSettings } = useSettingsStoreActions();
const updateOptions: SettingOption[] = [
{
control: (
<Select
data={[
{
label: t('setting.releaseChannel', {
context: 'optionLatest',
postProcess: 'titleCase',
}),
value: 'latest',
},
{
label: t('setting.releaseChannel', {
context: 'optionBeta',
postProcess: 'titleCase',
}),
value: 'beta',
},
]}
defaultValue={
(localSettings?.get('release_channel') as string | undefined) || 'latest'
}
onChange={(value) => {
if (!value) return;
localSettings?.set('release_channel', value);
setSettings({
window: {
...settings,
releaseChannel: value as 'beta' | 'latest',
},
});
}}
/>
),
description: t('setting.releaseChannel', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: !isElectron(),
title: t('setting.releaseChannel', { postProcess: 'sentenceCase' }),
},
{
control: (
<Switch
@@ -44,5 +85,5 @@ export const UpdateSettings = () => {
},
];
return <SettingsSection divider={utils?.isLinux()} options={updateOptions} />;
return <SettingsSection divider={true} options={updateOptions} />;
};
@@ -203,6 +203,34 @@ export const WindowSettings = () => {
isHidden: !isElectron() || !settings.tray,
title: t('setting.startMinimized', { postProcess: 'sentenceCase' }),
},
{
control: (
<Switch
aria-label="Toggle prevent sleep on playback"
defaultChecked={settings.preventSleepOnPlayback}
disabled={!isElectron()}
onChange={(e) => {
if (!e) return;
localSettings?.set(
'window_prevent_sleep_on_playback',
e.currentTarget.checked,
);
setSettings({
window: {
...settings,
preventSleepOnPlayback: e.currentTarget.checked,
},
});
}}
/>
),
description: t('setting.preventSleepOnPlayback', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: !isElectron(),
title: t('setting.preventSleepOnPlayback', { postProcess: 'sentenceCase' }),
},
];
return <SettingsSection options={windowOptions} />;
@@ -2,7 +2,6 @@ import isElectron from 'is-electron';
import { DiscordSettings } from '/@/renderer/features/settings/components/window/discord-settings';
import { PasswordSettings } from '/@/renderer/features/settings/components/window/password-settings';
import { UpdateSettings } from '/@/renderer/features/settings/components/window/update-settings';
import { WindowSettings } from '/@/renderer/features/settings/components/window/window-settings';
import { Stack } from '/@/shared/components/stack/stack';
@@ -13,7 +12,6 @@ export const WindowTab = () => {
<Stack gap="md">
<WindowSettings />
<DiscordSettings />
<UpdateSettings />
{utils?.isLinux() && (
<>
<PasswordSettings />
@@ -1,5 +1,6 @@
import { useForm } from '@mantine/form';
import { closeModal, ContextModalProps } from '@mantine/modals';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { useShareItem } from '/@/renderer/features/sharing/mutations/share-item-mutation';
@@ -26,8 +27,7 @@ export const ShareItemContextModal = ({
const shareItemMutation = useShareItem({});
// Uses the same default as Navidrome: 1 year
const defaultDate = new Date();
defaultDate.setFullYear(defaultDate.getFullYear() + 1);
const defaultDate = dayjs().add(1, 'year').format('YYYY-MM-DD HH:mm:ss');
const form = useForm({
initialValues: {
@@ -37,7 +37,7 @@ export const ShareItemContextModal = ({
},
validate: {
expires: (value) =>
value > new Date()
dayjs(value).isAfter(dayjs())
? null
: t('form.shareItem.expireInvalid', {
postProcess: 'sentenceCase',
@@ -51,7 +51,7 @@ export const ShareItemContextModal = ({
body: {
description: values.description,
downloadable: values.allowDownloading,
expires: values.expires.getTime(),
expires: dayjs(values.expires).valueOf(),
resourceIds: itemIds.join(),
resourceType,
},
@@ -105,7 +105,7 @@ export const ShareItemContextModal = ({
postProcess: 'titleCase',
})}
minDate={new Date()}
placeholder={defaultDate.toLocaleDateString()}
placeholder={defaultDate}
popoverProps={{ withinPortal: true }}
valueFormat="MM/DD/YYYY HH:mm"
{...form.getInputProps('expires')}
@@ -27,7 +27,8 @@
}
.image-container {
position: relative;
position: absolute;
bottom: 0;
width: var(--sidebar-image-height);
height: var(--sidebar-image-height);
cursor: pointer;
@@ -61,6 +62,10 @@
.accordion-control {
height: 2.5rem;
border-radius: var(--theme-radius-md);
&:hover {
background: var(--theme-colors-background);
}
}
.accordion-content {
@@ -1,11 +1,13 @@
import clsx from 'clsx';
import { AnimatePresence, motion } from 'motion/react';
import { CSSProperties, useMemo } from 'react';
import { CSSProperties, MouseEvent, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import styles from './sidebar.module.css';
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
import { ActionBar } from '/@/renderer/features/sidebar/components/action-bar';
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
@@ -31,6 +33,7 @@ import { Group } from '/@/shared/components/group/group';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Text } from '/@/shared/components/text/text';
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
import { LibraryItem } from '/@/shared/types/domain-types';
import { Platform } from '/@/shared/types/types';
export const Sidebar = () => {
@@ -39,7 +42,7 @@ export const Sidebar = () => {
const sidebar = useSidebarStore();
const { setSideBar } = useAppStoreActions();
const { sidebarPlaylistList } = useGeneralSettings();
const imageUrl = useCurrentSong()?.imageUrl;
const currentSong = useCurrentSong();
const translatedSidebarItemMap = useMemo(
() => ({
@@ -56,12 +59,13 @@ export const Sidebar = () => {
}),
[t],
);
const upsizedImageUrl = imageUrl
const upsizedImageUrl = currentSong?.imageUrl
?.replace(/size=\d+/, 'size=450')
.replace(/width=\d+/, 'width=450')
.replace(/height=\d+/, 'height=450');
const showImage = sidebar.image;
const isSongDefined = Boolean(currentSong?.id);
const setFullScreenPlayerStore = useSetFullScreenPlayerStore();
const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore();
@@ -69,6 +73,20 @@ export const Sidebar = () => {
setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded });
};
const handleGeneralContextMenu = useHandleGeneralContextMenu(
LibraryItem.SONG,
SONG_CONTEXT_MENU_ITEMS,
);
const handleToggleContextMenu = (e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
if (isSongDefined && !isFullScreenPlayerExpanded) {
handleGeneralContextMenu(e, [currentSong!]);
}
};
const { sidebarItems } = useGeneralSettings();
const { windowBarStyle } = useWindowSettings();
@@ -89,15 +107,12 @@ export const Sidebar = () => {
const scrollAreaHeight = useMemo(() => {
if (showImage) {
if (windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS) {
return `calc(100% - 105px - ${sidebar.leftWidth})`;
}
return `calc(100% - ${sidebar.leftWidth})`;
// Subtract the height of the top bar and padding
return `calc(100% - 65px - var(--mantine-spacing-xs) - ${sidebar.leftWidth})`;
}
return '100%';
}, [showImage, sidebar.leftWidth, windowBarStyle]);
}, [showImage, sidebar.leftWidth]);
const isCustomWindowBar =
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS;
@@ -170,6 +185,7 @@ export const Sidebar = () => {
initial={{ opacity: 0, y: 200 }}
key="sidebar-image"
onClick={expandFullScreenPlayer}
onContextMenu={handleToggleContextMenu}
role="button"
style={
{
@@ -2,11 +2,20 @@ import debounce from 'lodash/debounce';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
import {
MultiSelectWithInvalidData,
SelectWithInvalidData,
} from '/@/renderer/components/select-with-invalid-data';
import { useGenreList } from '/@/renderer/features/genres';
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
import {
getServerById,
SongListFilter,
useListFilterByKey,
useListStoreActions,
} from '/@/renderer/store';
import { NDSongQueryFields } from '/@/shared/api/navidrome.types';
import { hasFeature } from '/@/shared/api/utils';
import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group';
import { NumberInput } from '/@/shared/components/number-input/number-input';
@@ -14,6 +23,7 @@ import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
interface NavidromeSongFiltersProps {
customFilters?: Partial<SongListFilter>;
@@ -31,6 +41,7 @@ export const NavidromeSongFilters = ({
const { t } = useTranslation();
const { setFilter } = useListStoreActions();
const filter = useListFilterByKey<SongListQuery>({ key: pageKey });
const server = getServerById(serverId);
const isGenrePage = customFilters?.genreIds !== undefined;
@@ -58,12 +69,14 @@ export const NavidromeSongFilters = ({
}));
}, [genreListQuery.data]);
const handleGenresFilter = debounce((e: null | string) => {
const hasBrf = hasFeature(server, ServerFeature.BFR);
const handleGenresFilter = debounce((e: null | string[]) => {
const updatedFilters = setFilter({
customFilters,
data: {
_custom: filter._custom,
genreIds: e ? [e] : undefined,
genreIds: e ? e : undefined,
},
itemType: LibraryItem.SONG,
key: pageKey,
@@ -148,18 +161,30 @@ export const NavidromeSongFilters = ({
value={filter._custom?.navidrome?.year}
width={50}
/>
{!isGenrePage && (
{!isGenrePage && !hasBrf && (
<SelectWithInvalidData
clearable
data={genreList}
defaultValue={filter.genreIds ? filter.genreIds[0] : undefined}
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
onChange={handleGenresFilter}
onChange={(value) => handleGenresFilter(value !== null ? [value] : null)}
searchable
width={150}
/>
)}
</Group>
{!isGenrePage && hasBrf && (
<Group grow>
<MultiSelectWithInvalidData
clearable
data={genreList}
defaultValue={filter.genreIds}
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
onChange={handleGenresFilter}
searchable
/>
</Group>
)}
{tagsQuery.data?.enumTags?.length &&
tagsQuery.data.enumTags.length > 0 &&
tagsQuery.data.enumTags.map((tag) => (

Some files were not shown because too many files have changed in this diff Show More