Compare commits

...

127 Commits

Author SHA1 Message Date
jeffvli ecda918b46 update to v1.8.0 2026-03-09 21:53:26 -07:00
Tarulia 93834788b5 use electron-builder's static AppImage runtime (#1816)
electron-builder added compatibility with the AppImage Type2 runtime
(i.e. FUSE-less runtime). However, this is - for now anyway - an opt-in
feature, which this commit enables.
2026-03-09 21:52:48 -07:00
jeffvli 66e7b44d75 add client-side sort to artist favorite songs (#1691) 2026-03-09 20:58:02 -07:00
jeffvli 8825fc1e84 unhide image on error on libraryheader 2026-03-09 20:49:02 -07:00
jeffvli ac0cc19c04 prefer artistInfo artist image 2026-03-09 20:49:02 -07:00
jeffvli de29465b1f enable skeleton animations by default 2026-03-09 20:49:02 -07:00
jeffvli 31a201ca32 rewrite Image component
- remove react-image dependency
- use manual blob load
- abort load when exiting viewport
2026-03-09 20:49:02 -07:00
Hosted Weblate 3644ea2969 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translated using Weblate

Currently translated at 100.0% (1175 of 1175 strings) (Czech)
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Translated using Weblate

Currently translated at 100.0% (1175 of 1175 strings) (Spanish)
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

Currently translated at 98.2% (1155 of 1175 strings) (French)
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/
Translation: feishin/Translation
2026-03-10 03:08:35 +01:00
ashe e46c61e026 Remote URL hardcoding compatibility (#1810)
* Remote URL compatibility
---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
2026-03-09 19:08:28 -07:00
Oliver Tzeng 3873218e94 fix translated zh-Hant and zh-Hans (#1807) 2026-03-09 17:08:07 -07:00
Jeff b4a61cbd6e use ad-hoc code signing for macOS build, disable hardenedRuntime (#1815) 2026-03-09 17:05:12 -07:00
jeffvli accc6e53f0 bump electron-builder for pnpm v10.3 ( #1796) 2026-03-09 12:54:32 -07:00
jeffvli adf48decc4 run PR publish on electron-builder config changes 2026-03-09 09:52:42 -07:00
jeffvli 49ff928414 refactor artist header to better handle artist image from info endpoint 2026-03-09 01:54:04 -07:00
Hosted Weblate 71efd4a6d7 Translated using Weblate
Currently translated at 97.8% (1149 of 1174 strings) (Chinese (Simplified Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/

Co-authored-by: 無情天 <kofzhanganguo@126.com>
2026-03-09 09:38:55 +01:00
jeffvli 01e4907295 update to v1.7.0 2026-03-09 01:36:19 -07:00
jeffvli 6a47e99680 add manual update notice for macOS, disable autoinstall (#1725) 2026-03-09 01:31:33 -07:00
jeffvli 078d8068e0 fix file path replacement to handle both add/replace (#1749) 2026-03-09 00:55:13 -07:00
jeffvli 58ae76ce2a use album order on artist page for queue add (#1754) 2026-03-09 00:47:55 -07:00
jeffvli bc6cd5b014 refactor grid columns internally into album artist sections to handle null cases 2026-03-08 22:15:54 -07:00
jeffvli 17deac8d65 decouple AlbumArtistInfo from AlbumArtistDetail (#1809) 2026-03-08 22:06:18 -07:00
jeffvli 7dbf8dd9fe add conditional render to queue/lyric state on mobile player (#1797) 2026-03-08 21:06:22 -07:00
Hosted Weblate 27ab9f89c9 Translated using Weblate
Currently translated at 100.0% (1174 of 1174 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

Currently translated at 100.0% (1174 of 1174 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fordas <fordas15@gmail.com>
2026-03-08 13:09:50 +00:00
jeffvli 602808c742 directly replace playlist rules on save and replace 2026-03-07 21:18:55 -08:00
jeffvli c1051956ad add size to album detail header 2026-03-07 20:25:04 -08:00
York 6d2c084355 fix: sleep timer end-of-song mode (#1706) 2026-03-07 20:23:19 -08:00
Hosted Weblate 6e3f0f2253 Translated using Weblate
Currently translated at 100.0% (1174 of 1174 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Co-authored-by: York <goog10216922@gmail.com>
2026-03-07 10:09:54 +01:00
jeffvli d3d3688d60 add Symbola font for explicit status indicator 2026-03-07 00:50:00 -08:00
Kendall Garner 49c28299af remove package.lock, make lint happy 2026-03-06 21:04:51 -08:00
Kendall Garner 0bb6fea3db don't assume server exists in enable either 2026-03-06 20:59:13 -08:00
Kendall Garner 7027084394 fix(autosave): server isn't guaranteed to exist 2026-03-06 20:52:41 -08:00
Kendall Garner 7c4cbaad9a feat(player): add server-side autosave capability 2026-03-06 20:01:35 -08:00
jeffvli e603048a80 add settings override with env variables 2026-03-06 19:32:38 -08:00
jeffvli 9e08157517 add has_rating filter for Navidrome song list 2026-03-06 18:03:14 -08:00
Hosted Weblate d4c2b1e914 Translated using Weblate
Currently translated at 84.1% (985 of 1170 strings) (Japanese)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/

Co-authored-by: UnknownExplorer13 <kyedylanfenton@gmail.com>
2026-03-06 19:09:49 +00:00
Hosted Weblate 65dd67ec96 Translated using Weblate
Currently translated at 100.0% (1170 of 1170 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Co-authored-by: linger <linger0517@gmail.com>
2026-03-06 12:09:50 +00:00
Hosted Weblate 76259309af Translated using Weblate
Currently translated at 74.7% (875 of 1170 strings) (Finnish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fi/

Co-authored-by: jonoafi <joona@jonottaa.com>
2026-03-06 00:09:53 +01:00
Hosted Weblate 3e5a9db279 Translated using Weblate
Currently translated at 100.0% (1170 of 1170 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

Currently translated at 100.0% (1170 of 1170 strings) (Polish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/

Translated using Weblate

Currently translated at 100.0% (1170 of 1170 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
2026-03-05 15:09:54 +00:00
Hosted Weblate c155bbdb37 Translated using Weblate
Currently translated at 100.0% (1169 of 1169 strings) (Indonesian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/id/

Co-authored-by: Arif Budiman <arifpedia@gmail.com>
2026-03-05 07:39:54 +01:00
jeffvli ecbc03f052 add dummy onErrorPause handler for radio player 2026-03-04 22:39:46 -08:00
jeffvli 6ef9efc8bf add 10s retry for playback on network err (#1779) 2026-03-04 22:32:33 -08:00
jeffvli 513e9e822d attempt to fix viewport size for mobile browsers (#1787) 2026-03-04 21:51:04 -08:00
jeffvli dbc215c44f add toggle visibility behavior to tray icon (#1793) 2026-03-04 21:38:11 -08:00
jeffvli 43c5cf4275 fix primary color css variable to use new shade value 2026-03-04 21:26:47 -08:00
Benjamin 41f1f376bc feat: customizable item layout on fullscreen player (#1769)
* change container display to release type, readd badge styling to improve contrast

* make everything customizable
2026-03-04 21:23:14 -08:00
jeffvli cad7fef454 add type assertion for primaryShade 2026-03-04 21:01:56 -08:00
jeffvli 93791aea15 add setting to override theme primary shade (#1791) 2026-03-04 20:58:30 -08:00
Hosted Weblate 884dcde289 Translated using Weblate
Currently translated at 67.2% (782 of 1163 strings) (Finnish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fi/

Co-authored-by: Lauri Koo <late91@gmail.com>
2026-03-05 05:09:50 +01:00
Hosted Weblate 21f993a951 Translated using Weblate
Currently translated at 100.0% (1163 of 1163 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Co-authored-by: linger <linger0517@gmail.com>
2026-03-04 05:09:49 +00:00
Hosted Weblate 4784228831 Translated using Weblate
Currently translated at 100.0% (1163 of 1163 strings) (Polish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/

Co-authored-by: skajmer <skajmer@protonmail.com>
2026-03-03 21:09:48 +00:00
Hosted Weblate c6e3e0c07e Translated using Weblate
Currently translated at 100.0% (1163 of 1163 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

Currently translated at 100.0% (1163 of 1163 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fordas <fordas15@gmail.com>
2026-03-03 15:09:50 +00:00
Kendall Garner 30685e7252 fix(subsonic): do favorite filter processing on artist favorite 2026-03-03 06:54:59 -08:00
Kendall Garner 12a398a65c fix(server selector): better handling for large server/folder list 2026-03-02 22:30:14 -08:00
Kendall Garner 71360e37de fix(favorites): filter favorite artists on favorite page for Jellyfin 2026-03-02 21:02:24 -08:00
Kendall Garner f99b8ea9ee feat(queue): add go to current button 2026-03-02 20:30:35 -08:00
Kendall Garner 2854b928f6 fix(player): handle items in the queue moved to next before current index 2026-03-02 20:05:01 -08:00
Hosted Weblate 14e1f1d003 Translated using Weblate
Currently translated at 73.7% (857 of 1162 strings) (Russian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ru/

Translated using Weblate

Currently translated at 100.0% (1162 of 1162 strings) (Tamil)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ta/

Co-authored-by: Платон Петров <pplaton6@gmail.com>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
2026-03-01 23:10:04 +01:00
Hosted Weblate 85c490bd06 Translated using Weblate
Currently translated at 100.0% (1162 of 1162 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Co-authored-by: York <goog10216922@gmail.com>
2026-03-01 08:09:52 +01:00
Hosted Weblate 96d78f8bda Translated using Weblate
Currently translated at 44.6% (519 of 1162 strings) (Norwegian Bokmål)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nb_NO/

Co-authored-by: klodrik <klodrik@zoominn.no>
2026-02-28 14:09:50 +00:00
jeffvli 5f65aebe63 convert nd album rating filter to yes/no (#1775) 2026-02-27 19:30:33 -08:00
Hosted Weblate cd96da9cd5 Translated using Weblate
Currently translated at 100.0% (1162 of 1162 strings) (Polish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/

Translated using Weblate

Currently translated at 100.0% (1162 of 1162 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Translated using Weblate

Currently translated at 100.0% (1162 of 1162 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
2026-02-27 23:09:53 +00:00
Hosted Weblate 674b66b682 Translated using Weblate
Currently translated at 84.6% (984 of 1162 strings) (Japanese)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/

Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com>
2026-02-27 15:09:56 +01:00
jeffvli 934851456b prevent click propagation on detached queue button (#1762) 2026-02-27 00:53:09 -08:00
jeffvli afca396654 attempt to fix mpv autoNext behavior (#1768) 2026-02-27 00:39:27 -08:00
jeffvli b62f62671d fix table config drag and drop interaction on bottom edge 2026-02-26 20:59:28 -08:00
Norman eb8913479b Add album grouping column (#1722)
* Add album grouping column

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
2026-02-26 20:34:55 -08:00
Alexander Welsing 4918b412b2 Album radio (#1759)
* added album radio feature

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
2026-02-26 20:33:00 -08:00
jeffvli a78f5803a5 fix mediasession play hotkey on after pause on web (#1758)
- chrome (and other browsers) determine that the audio element is inactive if the volume is set to 0 when paused, leading to the resume (play) mediasession event to no longer be available
2026-02-26 01:11:24 -08:00
dependabot[bot] 0d1799cbf7 Bump the npm_and_yarn group across 1 directory with 6 updates (#1763)
Bumps the npm_and_yarn group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [axios](https://github.com/axios/axios) | `1.13.2` | `1.13.5` |
| [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.3.3` | `5.3.6` |
| [lodash](https://github.com/lodash/lodash) | `4.17.21` | `4.17.23` |
| [qs](https://github.com/ljharb/qs) | `6.14.1` | `6.14.2` |
| [minimatch](https://github.com/isaacs/minimatch) | `3.1.2` | `3.1.5` |
| [tar](https://github.com/isaacs/node-tar) | `6.2.1` | `7.5.9` |



Updates `axios` from 1.13.2 to 1.13.5
- [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.13.2...v1.13.5)

Updates `fast-xml-parser` from 5.3.3 to 5.3.6
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.3.3...v5.3.6)

Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

Updates `qs` from 6.14.1 to 6.14.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

Updates `tar` from 6.2.1 to 7.5.9
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.2.1...v7.5.9)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.13.5
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: fast-xml-parser
  dependency-version: 5.3.6
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: tar
  dependency-version: 7.5.9
  dependency-type: indirect
  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>
2026-02-25 22:22:53 -08:00
jeffvli 438db40d0e remove audio prefix from subsonic contentType property 2026-02-25 22:22:13 -08:00
dependabot[bot] aca5e1fe87 Bump react-router in the npm_and_yarn group across 1 directory (#1534)
Bumps the npm_and_yarn group with 1 update in the / directory: [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router).


Updates `react-router` from 7.11.0 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.12.0/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.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>
2026-02-25 22:06:26 -08:00
Tarulia 8aaf24ff87 Redo Icons in SVG (#1731)
Also includes exports from SVG for all relevant files:
- `assets/icons/` in 16-1024px size PNGs
- `assets/icons/icon.ico` using same sizes as the old one
- `assets/icons/` IconTemplate
- `media/` black and white logo-only variants
- `resource/icon.png`
2026-02-25 21:35:13 -08:00
Hosted Weblate 75dbea1ab7 Translated using Weblate
Currently translated at 100.0% (1160 of 1160 strings) (Catalan)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/

Co-authored-by: HUMET <ressaguer@proton.me>
2026-02-25 16:09:51 +00:00
jeffvli 61616dd2b9 move remote hook to global scope (#1752) 2026-02-24 00:45:43 -08:00
jeffvli ceef7964af add originalyear to smart playlist filters (#1746) 2026-02-24 00:34:07 -08:00
jeffvli ec12e89653 fix missing blur config from expanded album image 2026-02-23 23:48:56 -08:00
Hosted Weblate 386ca41a5d Translated using Weblate
Currently translated at 99.3% (1152 of 1160 strings) (French)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/

Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
2026-02-24 01:09:49 +00:00
Hosted Weblate a8fd5a4f46 Translated using Weblate
Currently translated at 100.0% (1160 of 1160 strings) (Dutch)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nl/

Co-authored-by: bokse <weblate@bokse.nl>
2026-02-23 16:09:54 +01:00
Hosted Weblate 4590383a97 Translated using Weblate
Currently translated at 83.1% (964 of 1160 strings) (Japanese)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/

Translated using Weblate

Currently translated at 100.0% (1160 of 1160 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com>
2026-02-22 14:09:50 +01:00
Hosted Weblate 405208cf92 Translated using Weblate
Currently translated at 97.8% (1135 of 1160 strings) (Chinese (Simplified Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/

Co-authored-by: 無情天 <kofzhanganguo@126.com>
2026-02-22 00:09:48 +01:00
Hosted Weblate f19c37276a Translated using Weblate
Currently translated at 100.0% (1160 of 1160 strings) (Polish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/

Co-authored-by: skajmer <skajmer@protonmail.com>
2026-02-21 15:09:49 +01:00
Hosted Weblate 3269034bfb Translated using Weblate
Currently translated at 88.9% (1032 of 1160 strings) (German)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/

Translated using Weblate

Currently translated at 99.1% (1150 of 1160 strings) (French)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/

Translated using Weblate

Currently translated at 42.4% (492 of 1160 strings) (Norwegian Bokmål)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nb_NO/

Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Co-authored-by: PhillyMay <mein.alias@outlook.com>
Co-authored-by: klodrik <klodrik@zoominn.no>
2026-02-20 22:09:52 +00:00
Lyosha e5f99af43b Fix image resolution setting value checking (#1741) 2026-02-20 13:10:26 -08:00
Hosted Weblate 2866ca9537 Translated using Weblate
Currently translated at 100.0% (1160 of 1160 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

Currently translated at 40.6% (471 of 1160 strings) (Norwegian Bokmål)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nb_NO/

Translated using Weblate

Currently translated at 99.1% (1150 of 1160 strings) (French)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/

Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Co-authored-by: klodrik <klodrik@zoominn.no>
2026-02-20 17:09:51 +00:00
Kendall Garner a377eae2f4 fix(build): do not add hash to favicon and assets 2026-02-20 08:48:36 -08:00
Hosted Weblate dbd8ce2380 Translated using Weblate
Currently translated at 100.0% (1160 of 1160 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Co-authored-by: linger <linger0517@gmail.com>
2026-02-20 12:09:53 +01:00
Kendall Garner 0741351318 fix(share): only copy to clipboard if available and secure 2026-02-19 19:55:10 -08:00
Hosted Weblate ab9e02adfc Translated using Weblate
Currently translated at 39.6% (459 of 1158 strings) (Norwegian Bokmål)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nb_NO/

Co-authored-by: klodrik <klodrik@zoominn.no>
2026-02-19 19:09:47 +01:00
Kendall Garner 12ff690619 fix(window): recreate window on macos when closed but not quit 2026-02-18 21:50:44 -08:00
jeffvli 5039012fcb catch errors on desktop scrobble notification failure (#1723) 2026-02-18 20:54:31 -08:00
jeffvli 75af57a7b3 use correct player seek event for mpris updater (#1740) 2026-02-18 20:37:18 -08:00
jeffvli 9027eee99f fix lyrics type in player sidebar 2026-02-18 20:28:14 -08:00
jeffvli 50fe373f1e simplify lyrics implementation
- removes complex lyrics fetch and override logic, and instead uses a single query as a source of truth for the lyrics
- properly handles loading state, invalidation, and refetch
2026-02-18 20:25:52 -08:00
Alexander Welsing 2c546867a8 change "Fields" query parameter to array (#1733)
* change "Fields" query parameter to array

* platformToTarget.key() -> to array
2026-02-17 18:45:31 -08:00
Kendall Garner 9dad934a40 transcode player bar waveform 2026-02-17 14:30:26 -08:00
Hosted Weblate 34dbb4c794 Translated using Weblate
Currently translated at 77.2% (894 of 1158 strings) (Italian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/it/

Co-authored-by: Giuseppe Filomeno <giusefilo@gmail.com>
2026-02-17 19:10:04 +00:00
Hosted Weblate 7472af66ef Translated using Weblate
Currently translated at 99.3% (1150 of 1158 strings) (French)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/

Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
2026-02-17 00:09:52 +01:00
Adam f293fb287d Add hook script to update and publish appstream metainfo on electron build (#1719) 2026-02-16 12:54:11 -08:00
libussa 7656e84c20 fix stale SERVER_URL when changing env var in Docker (#1714)
settings.js (which injects SERVER_URL into the browser) was served
without Cache-Control headers, causing Cloudflare and other reverse
proxies to cache the old value indefinitely. Additionally, when
SERVER_LOCK is enabled, the persisted server URL in localStorage was
never compared against the current window.SERVER_URL, so same-browser
sessions kept using the old server even after settings.js was updated.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 12:44:29 -08:00
Kendall Garner c524e8d3b7 fix(left-controls): use joined artsts instead of array by artist id 2026-02-16 07:52:36 -08:00
Hosted Weblate 7f13ce491b Translated using Weblate
Currently translated at 92.0% (1066 of 1158 strings) (French)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/

Co-authored-by: Nicolas DERIVE <kalon33@ubuntu.com>
2026-02-16 15:09:54 +00:00
Hosted Weblate 6c16aabce0 Translated using Weblate
Currently translated at 88.3% (1023 of 1158 strings) (German)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/

Co-authored-by: PhillyMay <mein.alias@outlook.com>
2026-02-15 13:10:11 +01:00
Hosted Weblate f5240b1766 Translated using Weblate
Currently translated at 78.7% (912 of 1158 strings) (German)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/

Co-authored-by: PhillyMay <mein.alias@outlook.com>
2026-02-15 04:09:48 +01:00
Hosted Weblate cc5a95d725 Translated using Weblate
Currently translated at 100.0% (1158 of 1158 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Translated using Weblate

Currently translated at 100.0% (1158 of 1158 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: York <goog10216922@gmail.com>
2026-02-14 20:09:53 +01:00
Hosted Weblate 8ba63988d8 Translated using Weblate
Currently translated at 97.8% (1133 of 1158 strings) (Chinese (Simplified Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/

Translated using Weblate

Currently translated at 100.0% (1158 of 1158 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

Currently translated at 100.0% (1158 of 1158 strings) (Polish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/

Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
2026-02-14 15:09:49 +01:00
jeffvli 289f307a52 update to v1.6.0 2026-02-13 23:08:33 -08:00
jeffvli 91ac36c835 fix Subsonic root folder condition (#1686)
- some older subsonic servers used numeric ids which causes the rootFolderId regex to fail which resulted in the getFolder endpoint to always attempt to fetch the root
2026-02-13 23:03:24 -08:00
jeffvli 503e4b2bac fix list refresh not working on discography/genre pages 2026-02-13 21:38:12 -08:00
jeffvli c39ddc3b45 refactor PlaylistQueryEditor to new file 2026-02-13 21:05:37 -08:00
jeffvli 1163c4ad5e add JSON editor for playlist query builder (#1711) 2026-02-13 21:05:34 -08:00
jeffvli e497734c07 allow all rule groups to be empty (#1710) 2026-02-13 20:26:58 -08:00
Kendall Garner 77fef33cbf improve album artist favorite performance and search (#1709)
* improve album artist favorite performance and search

* adjust top songs / favorite songs sections

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
2026-02-13 20:13:28 -08:00
Hosted Weblate 81189db1e1 Translated using Weblate
Currently translated at 100.0% (1156 of 1156 strings) (Catalan)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/

Co-authored-by: HUMET <ressaguer@proton.me>
2026-02-14 03:46:02 +00:00
Maevi 054a3d005e Listen on IPv6 by default in docker image (#1707) 2026-02-13 19:45:55 -08:00
jeffvli dfbff64430 fix duplicate server add when SERVER_LOCK is configured (#1623) 2026-02-13 19:42:36 -08:00
jeffvli 2b4046a82e fix double click propagation on the table expand button 2026-02-13 17:49:49 -08:00
jeffvli 9eb879fc37 hide favorite / rating from playlist album view items 2026-02-13 17:39:35 -08:00
jeffvli 9e63ee2735 add loading spinners for some lazy loaded content 2026-02-13 15:04:54 -08:00
jeffvli 9950e51d45 remove lazy loading from context menu initialization 2026-02-13 15:01:18 -08:00
jeffvli 70fdd4bdc3 refactor album expansion to global scope 2026-02-13 14:59:15 -08:00
jeffvli e855f7dd01 remove invalid detail view type for song list config 2026-02-13 12:19:14 -08:00
jeffvli 123842dfda attempt to optimize the tagList query
- this query in some cases can return a very large amount of data, depending on the size of the user's library
- increasing the cacheTime reduces the frequency of fetches while disabling structuralSharing reduces the need for react-query to do a deep equality comparison for the cache
2026-02-13 11:53:42 -08:00
Hosted Weblate 1338513f82 Translated using Weblate
Currently translated at 100.0% (1156 of 1156 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Co-authored-by: Fjuro <fjuro@alius.cz>
2026-02-13 19:09:45 +01:00
Hosted Weblate c9c88dd82d Translated using Weblate
Currently translated at 100.0% (1156 of 1156 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Translated using Weblate

Currently translated at 100.0% (1156 of 1156 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: York <goog10216922@gmail.com>
2026-02-13 14:09:48 +01:00
York 02a5395453 fix: regenerate macOS icon (.icns) to fix glitched small icons (#1705) 2026-02-13 00:15:12 -08:00
jeffvli 7ba2f6b827 fix removed filter functions 2026-02-12 22:04:08 -08:00
jeffvli f1b5dc8ef3 add additional client-side filters to playlist songs 2026-02-12 22:00:07 -08:00
209 changed files with 10462 additions and 22700 deletions
+2
View File
@@ -1,11 +1,13 @@
name: Publish (PR)
on:
workflow_dispatch:
pull_request:
branches:
- development
paths:
- 'src/**'
- 'electron-builder*.yml'
jobs:
wait-for-lint:
+1 -1
View File
@@ -20,7 +20,7 @@ COPY --chown=nginx:nginx --from=builder /app/out/web /usr/share/nginx/html
COPY ./settings.js.template /etc/nginx/templates/settings.js.template
COPY ng.conf.template /etc/nginx/templates/default.conf.template
ENV SERVER_LOCK=false SERVER_NAME="" SERVER_TYPE="" SERVER_URL=""
ENV SERVER_LOCK=false SERVER_NAME="" SERVER_TYPE="" SERVER_URL="" REMOTE_URL=""
ENV LEGACY_AUTHENTICATION="" ANALYTICS_DISABLED="" PUBLIC_PATH="/"
EXPOSE 9180
+6 -1
View File
@@ -114,6 +114,7 @@ services:
- SERVER_LOCK=true # When true AND name/type/url are set, only username/password can be toggled
- SERVER_TYPE=jellyfin # the allowed types are: jellyfin, navidrome, subsonic. These values are case insensitive
- SERVER_URL= # http://address:port or https://address:port
= REMOTE_URL= # http://address or https://address
- LEGACY_AUTHENTICATION=false # When SERVER_LOCK is true, sets the legacy (plaintext) authentication flag for Subsonic/OpenSubsonic servers
- ANALYTICS_DISABLED=true # Set to true to disable Umami analytics tracking
ports:
@@ -134,7 +135,11 @@ services:
4. _Optional_ - To hard code the server url, pass the following environment variables: `SERVER_NAME`, `SERVER_TYPE` (one of `jellyfin` or `navidrome` or `subsonic`), `SERVER_URL`. To prevent users from changing these settings, pass `SERVER_LOCK=true`. This can only be set if all three of the previous values are set. When `SERVER_LOCK=true`, you can also set `LEGACY_AUTHENTICATION=true` or `LEGACY_AUTHENTICATION=false` to configure the legacy authentication flag for the server (only applicable for Subsonic/OpenSubsonic servers).
5. _Optional_ - To disable Umami analytics tracking in the Docker/web version, set the environment variable `ANALYTICS_DISABLED=true`. When enabled, the analytics script will not be loaded and all tracking will be disabled.
5. _Optional_ - If your server uses a separate public-facing URL than what integrating applications use internally to communicate with your server, such as a separate Navidrome `ShareURL`, set `REMOTE_URL` to said public-facing URL.
6. _Optional_ - To disable Umami analytics tracking in the Docker/web version, set the environment variable `ANALYTICS_DISABLED=true`. When enabled, the analytics script will not be loaded and all tracking will be disabled.
7. _Optional_ - App settings (theme, language, sidebar options, etc.) can be overridden with environment variables on first run. The variables use the `FS_` prefix (e.g. `FS_GENERAL_THEME=defaultDark`, `FS_GENERAL_LANGUAGE=de`). See [the settings environment variable documentation](docs/ENV_SETTINGS.md) for the full list.
## FAQ
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+1
View File
@@ -8,6 +8,7 @@ services:
- SERVER_LOCK=false # When true AND name/type/url are set, only username/password can be toggled
- SERVER_TYPE=jellyfin # the allowed types are: jellyfin, navidrome, subsonic. These values are case insensitive
- SERVER_URL=http://localhost:8096 # http://address:port or https://address:port
# - REMOTE_URL=http://share.localhost # Used for compatibility with external functionality, such as custom sharing URLs on Navidrome
- LEGACY_AUTHENTICATION=false # When SERVER_LOCK is true, sets the legacyauth flag for server authentication (true or false)
- ANALYTICS_DISABLED=false # Set to true to disable Umami analytics tracking
ports:
+126
View File
@@ -0,0 +1,126 @@
# Environment variables for settings (web / Docker)
These variables override app settings **on first run** when no persisted settings exist. They are injected via `settings.js` (from `settings.js.template`) and only apply to the **web** build.
**Format:** All values are strings; booleans use `true`/`false`, numbers are numeric strings. Leave unset or empty to use the default.
---
## General
| Setting | Default | Env variable | Available values / Description |
|-------------|---------|--------------|--------------------------------|
| `general.accent` | `rgb(53, 116, 252)` | `FS_GENERAL_ACCENT` | CSS `rgb(r, g, b)` string (e.g. `rgb(53, 116, 252)`). Invalid values are ignored. |
| `general.albumBackground` | `false` | `FS_GENERAL_ALBUM_BACKGROUND` | `true` / `false` — Show album background image. |
| `general.albumBackgroundBlur` | `3` | `FS_GENERAL_ALBUM_BACKGROUND_BLUR` | Blur amount for album background (number). |
| `general.artistBackground` | `true` | `FS_GENERAL_ARTIST_BACKGROUND` | `true` / `false` — Show artist background image. |
| `general.artistBackgroundBlur` | `3` | `FS_GENERAL_ARTIST_BACKGROUND_BLUR` | Blur amount for artist background (number). |
| `general.blurExplicitImages` | `false` | `FS_GENERAL_BLUR_EXPLICIT_IMAGES` | `true` / `false` — Blur explicit images. |
| `general.combinedLyricsAndVisualizer` | `false` | `FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER` | `true` / `false` — Combine lyrics and visualizer panel. |
| `general.enableGridMultiSelect` | `false` | `FS_GENERAL_ENABLE_GRID_MULTI_SELECT` | `true` / `false` — Enable multi-select in grid views. |
| `general.externalLinks` | `true` | `FS_GENERAL_EXTERNAL_LINKS` | `true` / `false` — Show external links in UI. |
| `general.followCurrentSong` | `true` | `FS_GENERAL_FOLLOW_CURRENT_SONG` | `true` / `false` — Follow current song in list. |
| `general.followSystemTheme` | `false` | `FS_GENERAL_FOLLOW_SYSTEM_THEME` | `true` / `false` — Use OS light/dark preference. |
| `general.homeFeature` | `true` | `FS_GENERAL_HOME_FEATURE` | `true` / `false` — Show home featured carousel. |
| `general.homeFeatureStyle` | `single` | `FS_GENERAL_HOME_FEATURE_STYLE` | `multiple` / `single` — Home featured carousel style. |
| `general.language` | `en` | `FS_GENERAL_LANGUAGE` | UI language code (e.g. `en`, `de`, `fr`). |
| `general.theme` | `defaultDark` | `FS_GENERAL_THEME` | One of: `ayuDark`, `ayuLight`, `catppuccinLatte`, `catppuccinMocha`, `defaultDark`, `defaultLight`, `dracula`, `githubDark`, `githubLight`, `glassyDark`, `gruvboxDark`, `gruvboxLight`, `highContrastDark`, `highContrastLight`, `materialDark`, `materialLight`, `monokai`, `nightOwl`, `nord`, `oneDark`, `rosePine`, `rosePineDawn`, `rosePineMoon`, `shadesOfPurple`, `solarizedDark`, `solarizedLight`, `tokyoNight`, `vscodeDarkPlus`, `vscodeLightPlus`. |
| `general.themeDark` | `defaultDark` | `FS_GENERAL_THEME_DARK` | Same as theme (used when system is dark). |
| `general.themeLight` | `defaultLight` | `FS_GENERAL_THEME_LIGHT` | Same as theme (used when system is light). |
| `general.lastfmApiKey` | *(empty)* | `FS_GENERAL_LASTFM_API_KEY` | Last.fm API key. |
| `general.lastFM` | `true` | `FS_GENERAL_LAST_FM` | `true` / `false` — Enable Last.fm. |
| `general.musicBrainz` | `true` | `FS_GENERAL_MUSIC_BRAINZ` | `true` / `false` — MusicBrainz links. |
| `general.nativeAspectRatio` | `false` | `FS_GENERAL_NATIVE_ASPECT_RATIO` | `true` / `false` — Use native cover art aspect ratio. |
| `general.pathReplace` | *(empty)* | `FS_GENERAL_PATH_REPLACE` | Path pattern to replace (e.g. server path in Docker). |
| `general.pathReplaceWith` | *(empty)* | `FS_GENERAL_PATH_REPLACE_WITH` | Replacement path. |
| `general.playerbarOpenDrawer` | `false` | `FS_GENERAL_PLAYERBAR_OPEN_DRAWER` | `true` / `false` — Open queue/lyrics as drawer from player bar. |
| `general.primaryShade` | `6` | `FS_GENERAL_PRIMARY_SHADE` | Mantine primary shade 09 (number). |
| `general.resume` | `true` | `FS_GENERAL_RESUME` | `true` / `false` — Resume playback on load. |
| `general.showLyricsInSidebar` | `true` | `FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR` | `true` / `false` — Show lyrics in sidebar. |
| `general.showRatings` | `true` | `FS_GENERAL_SHOW_RATINGS` | `true` / `false` — Show star ratings. |
| `general.showVisualizerInSidebar` | `true` | `FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR` | `true` / `false` — Show visualizer in sidebar. |
| `general.sidebarCollapsedNavigation` | `true` | `FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION` | `true` / `false` — Start with collapsed sidebar nav. |
| `general.sidebarCollapseShared` | `false` | `FS_GENERAL_SIDEBAR_COLLAPSE_SHARED` | `true` / `false` — Share sidebar collapse state. |
| `general.sidebarPlaylistList` | `true` | `FS_GENERAL_SIDEBAR_PLAYLIST_LIST` | `true` / `false` — Show playlist list in sidebar. |
| `general.sidebarPlaylistSorting` | `false` | `FS_GENERAL_SIDEBAR_PLAYLIST_SORTING` | `true` / `false` — Enable playlist sorting in sidebar. |
| `general.sideQueueType` | `sideQueue` | `FS_GENERAL_SIDE_QUEUE_TYPE` | `sideDrawerQueue` / `sideQueue` — Side play queue style. |
| `general.useThemeAccentColor` | `false` | `FS_GENERAL_USE_THEME_ACCENT_COLOR` | `true` / `false` — Use themes accent color instead of custom. |
| `general.useThemePrimaryShade` | `true` | `FS_GENERAL_USE_THEME_PRIMARY_SHADE` | `true` / `false` — Use themes primary shade. |
| `general.zoomFactor` | `100` | `FS_GENERAL_ZOOM_FACTOR` | UI zoom percentage (number). |
---
## Playback
| Setting path | Default | Env variable | Available values / Description |
|-------------|---------|--------------|--------------------------------|
| `playback.mediaSession` | `false` | `FS_PLAYBACK_MEDIA_SESSION` | `true` / `false` — Media Session API (e.g. browser/media keys). |
| `playback.webAudio` | `true` | `FS_PLAYBACK_WEB_AUDIO` | `true` / `false` — Use Web Audio for playback. |
| `playback.audioFadeOnStatusChange` | `true` | `FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE` | `true` / `false` — Fade on play/pause. |
| `playback.preservePitch` | `true` | `FS_PLAYBACK_PRESERVE_PITCH` | `true` / `false` — Preserve pitch when changing speed. |
| `playback.scrobble.enabled` | `true` | `FS_PLAYBACK_SCROBBLE_ENABLED` | `true` / `false` — Enable scrobbling. |
| `playback.scrobble.notify` | `false` | `FS_PLAYBACK_SCROBBLE_NOTIFY` | `true` / `false` — Scrobble notifications. |
| `playback.scrobble.scrobbleAtDuration` | `240` | `FS_PLAYBACK_SCROBBLE_AT_DURATION` | Seconds of playback before scrobble. |
| `playback.scrobble.scrobbleAtPercentage` | `75` | `FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE` | Percentage of track before scrobble. |
| `playback.transcode.enabled` | `false` | `FS_PLAYBACK_TRANSCODE_ENABLED` | `true` / `false` — Enable transcoding. |
---
## Discord
| Setting path | Default | Env variable | Available values / Description |
|-------------|---------|--------------|--------------------------------|
| `discord.enabled` | `false` | `FS_DISCORD_ENABLED` | `true` / `false` — Discord rich presence. |
| `discord.clientId` | *(built-in)* | `FS_DISCORD_CLIENT_ID` | Custom Discord application ID. |
| `discord.displayType` | `feishin` | `FS_DISCORD_DISPLAY_TYPE` | `artist` / `feishin` / `song`. |
| `discord.linkType` | `none` | `FS_DISCORD_LINK_TYPE` | `last_fm` / `musicbrainz` / `musicbrainz_last_fm` / `none`. |
| `discord.showAsListening` | `false` | `FS_DISCORD_SHOW_AS_LISTENING` | `true` / `false`. |
| `discord.showPaused` | `true` | `FS_DISCORD_SHOW_PAUSED` | `true` / `false` — Show paused state. |
| `discord.showServerImage` | `false` | `FS_DISCORD_SHOW_SERVER_IMAGE` | `true` / `false`. |
| `discord.showStateIcon` | `true` | `FS_DISCORD_SHOW_STATE_ICON` | `true` / `false`. |
---
## Lyrics
| Setting path | Default | Env variable | Available values / Description |
|-------------|---------|--------------|--------------------------------|
| `lyrics.fetch` | `true` | `FS_LYRICS_FETCH` | `true` / `false` — Fetch lyrics. |
| `lyrics.follow` | `true` | `FS_LYRICS_FOLLOW` | `true` / `false` — Follow current line. |
| `lyrics.delayMs` | `0` | `FS_LYRICS_DELAY_MS` | Sync delay in milliseconds. |
| `lyrics.preferLocalLyrics` | `true` | `FS_LYRICS_PREFER_LOCAL` | `true` / `false` — Prefer local lyric files. |
| `lyrics.showMatch` | `true` | `FS_LYRICS_SHOW_MATCH` | `true` / `false`. |
| `lyrics.showProvider` | `true` | `FS_LYRICS_SHOW_PROVIDER` | `true` / `false`. |
| `lyrics.enableAutoTranslation` | `false` | `FS_LYRICS_ENABLE_AUTO_TRANSLATION` | `true` / `false`. |
| `lyrics.translationApiKey` | *(empty)* | `FS_LYRICS_TRANSLATION_API_KEY` | API key for lyric translation. |
| `lyrics.translationTargetLanguage` | `en` | `FS_LYRICS_TRANSLATION_TARGET_LANGUAGE` | Target language code. |
| `lyrics.alignment` | `center` | `FS_LYRICS_ALIGNMENT` | `center` / `left` / `right`. |
---
## Auto DJ
| Setting path | Default | Env variable | Available values / Description |
|-------------|---------|--------------|--------------------------------|
| `autoDJ.enabled` | `false` | `FS_AUTO_DJ_ENABLED` | `true` / `false`. |
| `autoDJ.itemCount` | `5` | `FS_AUTO_DJ_ITEM_COUNT` | Number of items to add. |
| `autoDJ.timing` | `1` | `FS_AUTO_DJ_TIMING` | Timing value (number). |
---
## CSS
| Setting path | Default | Env variable | Available values / Description |
|-------------|---------|--------------|--------------------------------|
| `css.content` | *(empty)* | `FS_CSS_CONTENT` | Custom CSS string (sanitized like in-app custom CSS). Set `FS_CSS_ENABLED=true` to apply. |
| `css.enabled` | `false` | `FS_CSS_ENABLED` | `true` / `false` — Enable custom CSS. |
---
## Font
| Setting path | Default | Env variable | Available values / Description |
|-------------|---------|--------------|--------------------------------|
| `font.type` | `builtIn` | `FS_FONT_TYPE` | `builtIn` / `system` / `custom`. |
| `font.builtIn` | `Inter` | `FS_FONT_BUILT_IN` | Built-in font name. |
| `font.system` | *(empty)* | `FS_FONT_SYSTEM` | System font name (when type is `system`). |
+14 -7
View File
@@ -32,18 +32,22 @@ nsis:
mac:
target:
target: default
arch:
- arm64
- x64
- target: dmg
arch:
- arm64
- x64
- target: zip
arch:
- arm64
- x64
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: true
entitlements: assets/entitlements.mac.plist
entitlementsInherit: assets/entitlements.mac.plist
hardenedRuntime: false
identity: "-"
gatekeeperAssess: false
notarize: false
dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
@@ -56,6 +60,9 @@ linux:
icon: assets/icons/icon.png
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: "1.0.2"
npmRebuild: false
publish:
+13 -7
View File
@@ -32,15 +32,18 @@ nsis:
mac:
target:
target: default
arch:
- arm64
- x64
- target: dmg
arch:
- arm64
- x64
- target: zip
arch:
- arm64
- x64
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: true
entitlements: assets/entitlements.mac.plist
entitlementsInherit: assets/entitlements.mac.plist
hardenedRuntime: false
identity: "-"
gatekeeperAssess: false
notarize: false
@@ -56,6 +59,9 @@ linux:
icon: assets/icons/icon.png
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: "1.0.2"
npmRebuild: false
publish:
provider: github
+14 -7
View File
@@ -32,15 +32,18 @@ nsis:
mac:
target:
target: default
arch:
- arm64
- x64
- target: dmg
arch:
- arm64
- x64
- target: zip
arch:
- arm64
- x64
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: true
entitlements: assets/entitlements.mac.plist
entitlementsInherit: assets/entitlements.mac.plist
hardenedRuntime: false
identity: "-"
gatekeeperAssess: false
notarize: false
@@ -56,7 +59,11 @@ linux:
icon: assets/icons/icon.png
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: "1.0.2"
npmRebuild: false
afterAllArtifactBuild: scripts/after-all-artifact-build.mjs
publish:
provider: github
owner: jeffvli
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 214 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1"><linearGradient
id="linearGradient1"><stop
style="stop-color:#dfdfdf;stop-opacity:1;"
offset="0"
id="stop1" /><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop2" /></linearGradient><filter
style="color-interpolation-filters:sRGB"
id="filter249"
x="-0.61395349"
y="-0.61395349"
width="2.227907"
height="2.5069767"><feFlood
result="flood"
in="SourceGraphic"
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
id="feFlood247" /><feGaussianBlur
result="blur"
in="SourceGraphic"
stdDeviation="1.000000"
id="feGaussianBlur247" /><feOffset
result="offset"
in="blur"
dx="0.000000"
dy="2"
id="feOffset247" /><feComposite
result="comp1"
operator="in"
in="flood"
in2="offset"
id="feComposite248" /><feComposite
result="fbSourceGraphic"
operator="over"
in="SourceGraphic"
id="feComposite249"
in2="comp1" /><feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix122" /><feFlood
id="feFlood122"
result="flood"
in="fbSourceGraphic"
flood-opacity="0.196078"
flood-color="rgb(0,0,0)" /><feGaussianBlur
id="feGaussianBlur122"
result="blur"
in="fbSourceGraphic"
stdDeviation="10.000000" /><feOffset
id="feOffset122"
result="offset"
in="blur"
dx="0.000000"
dy="10.000000" /><feComposite
id="feComposite122"
result="comp1"
operator="in"
in="flood"
in2="offset" /><feComposite
id="feComposite123"
result="comp2"
operator="over"
in="fbSourceGraphic"
in2="comp1" /></filter><linearGradient
xlink:href="#linearGradient1"
id="linearGradient2"
x1="256"
y1="0"
x2="256"
y2="512"
gradientUnits="userSpaceOnUse" /></defs><g
id="layer1"
style="display:inline"><circle
style="display:inline;fill:url(#linearGradient2);stroke-width:25;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke"
id="background"
cx="256"
cy="256"
r="256" /><circle
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.19597;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;filter:url(#filter249)"
id="dot"
cx="256"
cy="240.31155"
r="21.5" /><path
id="bottom"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter249)"
d="M 220.84961,277.95117 183.5,315.59961 219.5,351.69922 239.5,332 c 0,0 5.85615,-6.19922 16.5,-6.19922 10.64385,0 16.5,6.19922 16.5,6.19922 l 20,19.69922 36,-36.09961 -37.34961,-37.64844 A 51.5,51.5 0 0 1 256,291.8125 51.5,51.5 0 0 1 220.84961,277.95117 Z" /><path
id="main"
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;filter:url(#filter249)"
d="m 256,145.40039 c -7.11895,0 -13.56326,2.88552 -18.22852,7.55078 L 66.96875,323.46875 C 62.354158,328.08334 59.5,334.45837 59.5,341.5 c 0,14.08326 11.416739,25.5 25.5,25.5 7.04163,0 13.41666,-2.85416 18.03125,-7.46875 L 206.92578,255.93359 A 51.5,51.5 0 0 1 204.5,240.3125 a 51.5,51.5 0 0 1 51.5,-51.5 51.5,51.5 0 0 1 51.5,51.5 51.5,51.5 0 0 1 -2.42578,15.62109 L 408.96875,359.53125 C 413.58334,364.14585 419.95837,367 427,367 c 14.08326,0 25.5,-11.41674 25.5,-25.5 0,-7.04163 -2.85415,-13.41666 -7.46875,-18.03125 L 274.22852,152.95117 C 269.56326,148.2859 263.11895,145.40039 256,145.40039 Z" /></g></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

+3
View File
@@ -1,5 +1,6 @@
server {
listen 9180;
listen [::]:9180;
sendfile on;
default_type application/octet-stream;
@@ -19,9 +20,11 @@ server {
location ${PUBLIC_PATH}settings.js {
alias /etc/nginx/conf.d/settings.js;
add_header Cache-Control "no-store";
}
location ${PUBLIC_PATH}/settings.js {
alias /etc/nginx/conf.d/settings.js;
add_header Cache-Control "no-store";
}
}
-18321
View File
File diff suppressed because it is too large Load Diff
+7 -8
View File
@@ -1,6 +1,6 @@
{
"name": "feishin",
"version": "1.5.0",
"version": "1.8.0",
"description": "A modern self-hosted music player.",
"keywords": [
"subsonic",
@@ -89,7 +89,7 @@
"@wavesurfer/react": "^1.0.11",
"@xhayper/discord-rpc": "^1.3.0",
"audiomotion-analyzer": "^4.5.1",
"axios": "^1.13.2",
"axios": "^1.13.5",
"butterchurn": "^3.0.0-beta.5",
"butterchurn-presets": "^3.0.0-beta.4",
"cheerio": "^1.1.2",
@@ -103,7 +103,7 @@
"electron-store": "^8.2.0",
"electron-updater": "^6.6.2",
"fast-average-color": "^9.5.0",
"fast-xml-parser": "^5.3.2",
"fast-xml-parser": "^5.3.6",
"format-duration": "^3.0.2",
"fuse.js": "^7.1.0",
"i18next": "^25.6.2",
@@ -111,7 +111,7 @@
"idb-keyval": "^6.2.2",
"immer": "^10.2.0",
"is-electron": "^2.2.2",
"lodash": "^4.17.21",
"lodash": "^4.17.23",
"md5": "^2.3.0",
"motion": "^12.23.24",
"mpris-service": "^2.1.2",
@@ -120,16 +120,15 @@
"nuqs": "^2.7.1",
"overlayscrollbars": "^2.11.1",
"overlayscrollbars-react": "^0.5.6",
"qs": "^6.14.1",
"qs": "^6.14.2",
"react": "^19.1.0",
"react-call": "^1.8.1",
"react-dom": "^19.1.0",
"react-error-boundary": "^5.0.0",
"react-i18next": "^16.3.3",
"react-icons": "^5.5.0",
"react-image": "^4.1.0",
"react-player": "^2.16.0",
"react-router": "^7.9.6",
"react-router": "^7.13.1",
"react-split-pane": "^3.0.4",
"react-virtualized-auto-sizer": "^1.0.26",
"react-window": "1.8.11",
@@ -158,7 +157,7 @@
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"electron": "^39.4.0",
"electron-builder": "^26.0.12",
"electron-builder": "^26.8.2",
"electron-devtools-installer": "^4.0.0",
"electron-vite": "^4.0.1",
"eslint": "^9.24.0",
+990 -966
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+45
View File
@@ -0,0 +1,45 @@
import { execSync } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Electron-builder afterAllArtifactBuild hook
* Runs the app stream update script only for Linux builds
* Returns the metainfo file path to be included in published artifacts
*/
// This is not a typescript file, and is called by electron-builder, so we cannot use typescript features here.
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async function afterAllArtifactBuild(buildResult) {
// Check if this build includes Linux as a target
const isLinux = Array.from(buildResult.platformToTargets.keys()).some(
(platform) => platform.name === 'linux',
);
if (isLinux) {
const updateScriptPath = path.join(__dirname, 'update-app-stream.mjs');
const projectRoot = path.resolve(__dirname, '..');
const metainfoFile = path.resolve(projectRoot, 'org.jeffvli.feishin.metainfo.xml');
console.log('Running app stream update for Linux build...');
try {
execSync(`node ${updateScriptPath} --replace-if-version-missing`, {
cwd: projectRoot,
stdio: 'inherit',
});
// Return the metainfo file to be included in published artifacts
return [metainfoFile];
} catch (error) {
console.error('Failed to update app stream:', error.message);
throw error;
}
}
// Return empty array if not a Linux build
return [];
}
+32 -11
View File
@@ -3,30 +3,51 @@ import fs from 'fs';
import path from 'path';
const args = process.argv.slice(2);
if (args.length > 3) {
console.error('Usage: node update-app-stream.js [package-file] [date] [metainfo-file]');
// Parse flags and positional arguments
const flags = args.filter((arg) => arg.startsWith('--'));
const positionalArgs = args.filter((arg) => !arg.startsWith('--'));
const replaceIfVersionMissing = flags.includes('--replace-if-version-missing');
if (positionalArgs.length > 3) {
console.error(
'Usage: node update-app-stream.js [package-file] [date] [metainfo-file] [--replace-if-version-missing]',
);
process.exit(1);
}
const packageFile = args[0] || path.resolve(process.cwd(), 'package.json');
const packageFile = positionalArgs[0] || path.resolve(process.cwd(), 'package.json');
const packageContent = fs.readFileSync(packageFile, 'utf8');
const packageJson = JSON.parse(packageContent);
const version = packageJson.version;
const time = Math.floor((Date.parse(args[1]) || Date.now()) / 1000);
const metainfoFile = args[2] || path.resolve(process.cwd(), 'org.jeffvli.feishin.metainfo.xml');
const time = Math.floor((Date.parse(positionalArgs[1]) || Date.now()) / 1000);
const metainfoFile =
positionalArgs[2] || path.resolve(process.cwd(), 'org.jeffvli.feishin.metainfo.xml');
const parser = new XMLParser({ ignoreAttributes: false });
const metainfoContent = fs.readFileSync(metainfoFile, 'utf8');
const metainfo = parser.parse(metainfoContent);
if (!metainfo.component.releases.release.find((release) => release['@_version'] === version)) {
metainfo.component.releases.release.unshift({
'@_date': new Date(time * 1000).toISOString().split('T')[0],
'@_type': version.includes('-') ? 'development' : 'stable',
'@_version': version,
});
const newRelease = {
'@_date': new Date(time * 1000).toISOString().split('T')[0],
'@_type': version.includes('-') ? 'development' : 'stable',
'@_version': version,
};
if (replaceIfVersionMissing) {
// Replace all releases with only the current version
metainfo.component.releases.release = [newRelease];
} else {
// Default behavior: add new release if it doesn't exist
const releaseExists =
metainfo.component.releases.release.findIndex(
(release) => release['@_version'] === version,
) !== -1;
if (!releaseExists) {
metainfo.component.releases.release.unshift(newRelease);
}
}
const builder = new XMLBuilder({ format: true, ignoreAttributes: false, indentBy: ' ' });
+87 -1
View File
@@ -1 +1,87 @@
"use strict";window.SERVER_URL="${SERVER_URL}";window.SERVER_NAME="${SERVER_NAME}";window.SERVER_TYPE="${SERVER_TYPE}";window.SERVER_LOCK="${SERVER_LOCK}";window.LEGACY_AUTHENTICATION="${LEGACY_AUTHENTICATION}";window.ANALYTICS_DISABLED="${ANALYTICS_DISABLED}";
"use strict";
window.SERVER_URL = "${SERVER_URL}";
window.REMOTE_URL = "${REMOTE_URL}";
window.SERVER_NAME = "${SERVER_NAME}";
window.SERVER_TYPE = "${SERVER_TYPE}";
window.SERVER_LOCK = "${SERVER_LOCK}";
window.LEGACY_AUTHENTICATION = "${LEGACY_AUTHENTICATION}";
window.ANALYTICS_DISABLED = "${ANALYTICS_DISABLED}";
window.FS_GENERAL_ACCENT = "${FS_GENERAL_ACCENT}";
window.FS_GENERAL_ALBUM_BACKGROUND = "${FS_GENERAL_ALBUM_BACKGROUND}";
window.FS_GENERAL_ALBUM_BACKGROUND_BLUR = "${FS_GENERAL_ALBUM_BACKGROUND_BLUR}";
window.FS_GENERAL_ARTIST_BACKGROUND = "${FS_GENERAL_ARTIST_BACKGROUND}";
window.FS_GENERAL_ARTIST_BACKGROUND_BLUR = "${FS_GENERAL_ARTIST_BACKGROUND_BLUR}";
window.FS_GENERAL_BLUR_EXPLICIT_IMAGES = "${FS_GENERAL_BLUR_EXPLICIT_IMAGES}";
window.FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER = "${FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER}";
window.FS_GENERAL_ENABLE_GRID_MULTI_SELECT = "${FS_GENERAL_ENABLE_GRID_MULTI_SELECT}";
window.FS_GENERAL_EXTERNAL_LINKS = "${FS_GENERAL_EXTERNAL_LINKS}";
window.FS_GENERAL_FOLLOW_CURRENT_SONG = "${FS_GENERAL_FOLLOW_CURRENT_SONG}";
window.FS_GENERAL_FOLLOW_SYSTEM_THEME = "${FS_GENERAL_FOLLOW_SYSTEM_THEME}";
window.FS_GENERAL_HOME_FEATURE = "${FS_GENERAL_HOME_FEATURE}";
window.FS_GENERAL_HOME_FEATURE_STYLE = "${FS_GENERAL_HOME_FEATURE_STYLE}";
window.FS_GENERAL_LANGUAGE = "${FS_GENERAL_LANGUAGE}";
window.FS_GENERAL_LAST_FM = "${FS_GENERAL_LAST_FM}";
window.FS_GENERAL_LASTFM_API_KEY = "${FS_GENERAL_LASTFM_API_KEY}";
window.FS_GENERAL_MUSIC_BRAINZ = "${FS_GENERAL_MUSIC_BRAINZ}";
window.FS_GENERAL_NATIVE_ASPECT_RATIO = "${FS_GENERAL_NATIVE_ASPECT_RATIO}";
window.FS_GENERAL_PATH_REPLACE = "${FS_GENERAL_PATH_REPLACE}";
window.FS_GENERAL_PATH_REPLACE_WITH = "${FS_GENERAL_PATH_REPLACE_WITH}";
window.FS_GENERAL_PLAYERBAR_OPEN_DRAWER = "${FS_GENERAL_PLAYERBAR_OPEN_DRAWER}";
window.FS_GENERAL_PRIMARY_SHADE = "${FS_GENERAL_PRIMARY_SHADE}";
window.FS_GENERAL_RESUME = "${FS_GENERAL_RESUME}";
window.FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR = "${FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR}";
window.FS_GENERAL_SHOW_RATINGS = "${FS_GENERAL_SHOW_RATINGS}";
window.FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR = "${FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR}";
window.FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION = "${FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION}";
window.FS_GENERAL_SIDEBAR_COLLAPSE_SHARED = "${FS_GENERAL_SIDEBAR_COLLAPSE_SHARED}";
window.FS_GENERAL_SIDEBAR_PLAYLIST_LIST = "${FS_GENERAL_SIDEBAR_PLAYLIST_LIST}";
window.FS_GENERAL_SIDEBAR_PLAYLIST_SORTING = "${FS_GENERAL_SIDEBAR_PLAYLIST_SORTING}";
window.FS_GENERAL_SIDE_QUEUE_TYPE = "${FS_GENERAL_SIDE_QUEUE_TYPE}";
window.FS_GENERAL_THEME = "${FS_GENERAL_THEME}";
window.FS_GENERAL_THEME_DARK = "${FS_GENERAL_THEME_DARK}";
window.FS_GENERAL_THEME_LIGHT = "${FS_GENERAL_THEME_LIGHT}";
window.FS_GENERAL_USE_THEME_ACCENT_COLOR = "${FS_GENERAL_USE_THEME_ACCENT_COLOR}";
window.FS_GENERAL_USE_THEME_PRIMARY_SHADE = "${FS_GENERAL_USE_THEME_PRIMARY_SHADE}";
window.FS_GENERAL_ZOOM_FACTOR = "${FS_GENERAL_ZOOM_FACTOR}";
window.FS_PLAYBACK_MEDIA_SESSION = "${FS_PLAYBACK_MEDIA_SESSION}";
window.FS_PLAYBACK_WEB_AUDIO = "${FS_PLAYBACK_WEB_AUDIO}";
window.FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE = "${FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE}";
window.FS_PLAYBACK_PRESERVE_PITCH = "${FS_PLAYBACK_PRESERVE_PITCH}";
window.FS_PLAYBACK_SCROBBLE_ENABLED = "${FS_PLAYBACK_SCROBBLE_ENABLED}";
window.FS_PLAYBACK_SCROBBLE_NOTIFY = "${FS_PLAYBACK_SCROBBLE_NOTIFY}";
window.FS_PLAYBACK_SCROBBLE_AT_DURATION = "${FS_PLAYBACK_SCROBBLE_AT_DURATION}";
window.FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE = "${FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE}";
window.FS_PLAYBACK_TRANSCODE_ENABLED = "${FS_PLAYBACK_TRANSCODE_ENABLED}";
window.FS_DISCORD_ENABLED = "${FS_DISCORD_ENABLED}";
window.FS_DISCORD_CLIENT_ID = "${FS_DISCORD_CLIENT_ID}";
window.FS_DISCORD_DISPLAY_TYPE = "${FS_DISCORD_DISPLAY_TYPE}";
window.FS_DISCORD_LINK_TYPE = "${FS_DISCORD_LINK_TYPE}";
window.FS_DISCORD_SHOW_AS_LISTENING = "${FS_DISCORD_SHOW_AS_LISTENING}";
window.FS_DISCORD_SHOW_PAUSED = "${FS_DISCORD_SHOW_PAUSED}";
window.FS_DISCORD_SHOW_SERVER_IMAGE = "${FS_DISCORD_SHOW_SERVER_IMAGE}";
window.FS_DISCORD_SHOW_STATE_ICON = "${FS_DISCORD_SHOW_STATE_ICON}";
window.FS_LYRICS_FETCH = "${FS_LYRICS_FETCH}";
window.FS_LYRICS_FOLLOW = "${FS_LYRICS_FOLLOW}";
window.FS_LYRICS_DELAY_MS = "${FS_LYRICS_DELAY_MS}";
window.FS_LYRICS_PREFER_LOCAL = "${FS_LYRICS_PREFER_LOCAL}";
window.FS_LYRICS_SHOW_MATCH = "${FS_LYRICS_SHOW_MATCH}";
window.FS_LYRICS_SHOW_PROVIDER = "${FS_LYRICS_SHOW_PROVIDER}";
window.FS_LYRICS_ENABLE_AUTO_TRANSLATION = "${FS_LYRICS_ENABLE_AUTO_TRANSLATION}";
window.FS_LYRICS_TRANSLATION_API_KEY = "${FS_LYRICS_TRANSLATION_API_KEY}";
window.FS_LYRICS_TRANSLATION_TARGET_LANGUAGE = "${FS_LYRICS_TRANSLATION_TARGET_LANGUAGE}";
window.FS_LYRICS_ALIGNMENT = "${FS_LYRICS_ALIGNMENT}";
window.FS_AUTO_DJ_ENABLED = "${FS_AUTO_DJ_ENABLED}";
window.FS_AUTO_DJ_ITEM_COUNT = "${FS_AUTO_DJ_ITEM_COUNT}";
window.FS_AUTO_DJ_TIMING = "${FS_AUTO_DJ_TIMING}";
window.FS_CSS_CONTENT = "${FS_CSS_CONTENT}";
window.FS_CSS_ENABLED = "${FS_CSS_ENABLED}";
window.FS_FONT_TYPE = "${FS_FONT_TYPE}";
window.FS_FONT_BUILT_IN = "${FS_FONT_BUILT_IN}";
window.FS_FONT_SYSTEM = "${FS_FONT_SYSTEM}";
+16 -8
View File
@@ -12,7 +12,7 @@
"search": "$t(common.search)",
"settings": "$t(common.setting, {\"count\": 2})",
"tracks": "$t(entity.track, {\"count\": 2})",
"nowPlaying": "ara sona",
"nowPlaying": "s'està reproduint",
"shared": "$t(entity.playlist, {\"count\": 2}) compartides",
"favorites": "$t(entity.favorite, {\"count\": 2})",
"radio": "$t(entity.radioStation, {\"count\": 2})",
@@ -454,7 +454,9 @@
"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?)"
"createFailed": "no s'ha pogut crear el recurs compartit (està habilitat, l'ús compartit?)",
"copyToClipboard": "Copiar al porta-retalls: Ctrl+C, Enter",
"successMustClick": "Compartició creada correctament. Feu clic aquí per obrir-la."
},
"updateServer": {
"success": "s'ha actualitzat el servidor amb èxit",
@@ -568,7 +570,7 @@
"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)",
"audioDevice_description": "seleccioneu el dispositiu d'àudio que voleu utilitzar per a la reproducció",
"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",
@@ -614,9 +616,9 @@
"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": "mostra 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": "mosta l'estat d'activitat quan està inactiu",
"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",
@@ -894,7 +896,9 @@
"automaticUpdates_description": "cerca i instal·la actualitzacions automàticament",
"releaseChannel_optionAlpha": "alfa (diària)",
"blurExplicitImages": "desenfoca imatges explícites",
"blurExplicitImages_description": "les caràtules d'àlbums i cançons marcades com a explícites quedaran desenfocades"
"blurExplicitImages_description": "les caràtules d'àlbums i cançons marcades com a explícites quedaran desenfocades",
"discordStateIcon": "mostra la icona de reproducció",
"discordStateIcon_description": "mostra una petita icona de reproducció a l'estat d'activitat. l'icona de pausa es mostra quan \"mostra l'estat d'activitat quan està en pausa\" està activat"
},
"table": {
"column": {
@@ -1048,7 +1052,9 @@
"path": "ruta",
"songCount": "nombre de cançons",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "ordena per nom"
"sortName": "ordena per nom",
"matchAnd": "i",
"matchOr": "o"
},
"player": {
"muted": "silenciat",
@@ -1129,7 +1135,9 @@
"saveQueueFailed": "error en desar la cua",
"settingsSyncError": "hi ha discrepàncies entre la configuració del renderitzador i el procés principal. reinicieu l'aplicació per aplicar els canvis",
"noNetwork": "servidor no disponible",
"noNetworkDescription": "no s'ha pogut connectar amb el servidor"
"noNetworkDescription": "no s'ha pogut connectar amb el servidor",
"invalidJson": "JSON invàlid",
"serverLockSingleServer": "només es permet un servidor quan el servidor està bloquejat"
},
"releaseType": {
"primary": {
+29 -8
View File
@@ -47,7 +47,8 @@
"sleepTimer_off": "vypnuto",
"sleepTimer_timeRemaining": "zbývá {{time}}",
"sleepTimer_setCustom": "nastavit časovač",
"sleepTimer_cancel": "zrušit časovač"
"sleepTimer_cancel": "zrušit časovač",
"albumRadio": "rádio alba"
},
"setting": {
"crossfadeStyle_description": "vyberte způsob prolnutí u přehrávače zvuku",
@@ -400,7 +401,17 @@
"automaticUpdates": "Automatické aktualizace",
"automaticUpdates_description": "Kontrolovat a automaticky instalovat aktualizace",
"discordStateIcon": "zobrazit ikonu přehrávání",
"discordStateIcon_description": "zobrazit malou ikonu přehrávání ve stavu na Discordu. ikona pozastavení bude zobrazena vždy, když je povolena možnost „Zobrazit stav při pozastavení“"
"discordStateIcon_description": "zobrazit malou ikonu přehrávání ve stavu na Discordu. ikona pozastavení bude zobrazena vždy, když je povolena možnost „Zobrazit stav při pozastavení“",
"useThemePrimaryShade": "použít primární odstín motivu",
"useThemePrimaryShade_description": "použít primární odstín definovaný ve zvoleném motivu pro primární varianty barev",
"primaryShade": "primární odstín",
"primaryShade_description": "přepsat primární odstín (09) používaný pro tlačítka, odkazy a další prvky obarvené primární barvou",
"playerItemConfiguration_description": "nastavit, které položky budou zobrazeny a v jakém pořadí, v celoobrazovkovém přehrávači",
"playerItemConfiguration": "nastavení položek přehrávače",
"autosave": "automaticky ukládat frontu přehrávání",
"autosave_description": "zapnout automatické ukládání fronty přehrávání na server. toto je možné pouze při použití Navidrome/Subsonic a není možné mít kombinovanou frontu přehrávání.",
"autosaveCount": "četnost automatického ukládání fronty přehrávání",
"autosaveCount_description": "kolik změn skladeb se může provést před uložením fronty. 1 (minimum) znamená při každé změně skladby"
},
"action": {
"editPlaylist": "upravit $t(entity.playlist, {\"count\": 1})",
@@ -440,7 +451,8 @@
"openApplicationDirectory": "otevřít adresář aplikace",
"addOrRemoveFromSelection": "přidat nebo odebrat z výběru",
"selectRangeOfItems": "vyberte rozsah položek",
"selectAll": "vybrat vše"
"selectAll": "vybrat vše",
"goToCurrent": "přejít na aktuální položku"
},
"common": {
"backward": "zpátky",
@@ -566,7 +578,8 @@
"example": "příklad",
"filter_single": "jeden",
"filter_multiple": "několik",
"rename": "přejmenovat"
"rename": "přejmenovat",
"newVersionAvailable": "je dostupná nová verze"
},
"table": {
"config": {
@@ -643,7 +656,8 @@
"bitDepth": "$t(common.bitDepth)",
"sampleRate": "$t(common.sampleRate)",
"composer": "skladatel",
"titleArtist": "$t(common.title) (umělec)"
"titleArtist": "$t(common.title) (umělec)",
"albumGroup": "skupina alb"
}
},
"column": {
@@ -705,7 +719,10 @@
"saveQueueFailed": "nepodařilo se uložit frontu",
"settingsSyncError": "byly zjištěny nesrovnalosti mezi nastavením v rendereru a hlavním procesem. restartujte aplikaci, aby se změny projevily",
"noNetwork": "server je nedostupný",
"noNetworkDescription": "k tomuto serveru se nepodařilo připojit"
"noNetworkDescription": "k tomuto serveru se nepodařilo připojit",
"invalidJson": "neplatný JSON",
"serverLockSingleServer": "při uzamčení serveru je povolen pouze jeden server",
"playbackPausedDueToError": "přehrávání bylo pozastaveno z důvodu chyby"
},
"filter": {
"mostPlayed": "nejvíce přehráváno",
@@ -751,7 +768,9 @@
"album": "$t(entity.album, {\"count\": 1})",
"trackNumber": "skladba",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "název v řazení"
"sortName": "název v řazení",
"matchAnd": "a",
"matchOr": "nebo"
},
"page": {
"sidebar": {
@@ -1035,7 +1054,9 @@
"description": "popis",
"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?)"
"createFailed": "nepodařilo se vytvořit sdílení (je sdílení povoleno?)",
"copyToClipboard": "Zkopírovat do schránky: Ctrl+C, Enter",
"successMustClick": "sdílení úspěšně vytvořeno. klikněte sem pro otevření"
},
"privateMode": {
"enabled": "soukromý režim povolen, stav přehrávání je nyní skryt před externími integracemi",
+230 -24
View File
@@ -34,7 +34,10 @@
"deleteRadioStation": "$t(entity.radioStation, {\"count\": 1}) löschen",
"selectAll": "alle auswählen",
"openApplicationDirectory": "Anwendungsverzeichnis öffnen",
"addOrRemoveFromSelection": "Zur Auswahl hinzufügen oder entfernen"
"addOrRemoveFromSelection": "Zur Auswahl hinzufügen oder entfernen",
"selectRangeOfItems": "Wählen sie eine Reihe von Elementen",
"holdToMoveToTop": "Halten um nach oben zu bewegen",
"holdToMoveToBottom": "Halten um nach unten zu bewegen"
},
"common": {
"backward": "zurück",
@@ -152,7 +155,12 @@
"releaseType": "Veröffentlichungsformat",
"view": "Betrachten",
"countSelected": "{{count}} ausgewählt",
"mood": "Stimmung"
"mood": "Stimmung",
"example": "Beispiel",
"rename": "Umbenennen",
"filter_single": "einzeln",
"filter_multiple": "mehrfach",
"retry": "Wiederholen"
},
"error": {
"remotePortWarning": "Starten Sie den Server neu, um den neuen Port anzuwenden",
@@ -182,7 +190,10 @@
"saveQueueFailed": "Wiedergabeliste konnte nicht gespeichert werden",
"multipleServerSaveQueueError": "die Wiedergabeliste enthält einen oder mehrere Titel, die nicht vom aktuellen Server stammen. dies wird nicht unterstützt",
"noNetwork": "Server nicht verfügbar",
"noNetworkDescription": "Verbindung zum Server konnte nicht hergestellt werden"
"noNetworkDescription": "Verbindung zum Server konnte nicht hergestellt werden",
"invalidJson": "JSON ungültig",
"serverLockSingleServer": "Nur ein Server ist erlaubt, wenn der Server gesperrt ist",
"settingsSyncError": "Es wurden Unstimmigkeiten zwischen den Einstellungen im Renderer und dem Hauptprozess gefunden. Starte die Anwendung neu, um die Änderungen zu übernehmen"
},
"filter": {
"mostPlayed": "Meistgespielt",
@@ -227,7 +238,9 @@
"owner": "$t(common.owner)",
"genre": "$t(entity.genre, {\"count\": 1})",
"artist": "$t(entity.artist, {\"count\": 1})",
"explicitStatus": "$t(common.explicitStatus)"
"explicitStatus": "$t(common.explicitStatus)",
"matchAnd": "und",
"matchOr": "oder"
},
"form": {
"deletePlaylist": {
@@ -256,7 +269,10 @@
"ignoreCors": "CORS ignorieren $t(common.restartRequired)",
"error_savePassword": "Beim Speichern des Passworts ist ein Fehler aufgetreten",
"input_preferInstantMix": "Instant-Mix bevorzugen",
"input_preferInstantMixDescription": "nur Instant-Mix verwenden, um ähnliche Songs zu erhalten. Nützlich bei Verwendung von Plugins, die in dieses Verhalten eingreifen"
"input_preferInstantMixDescription": "nur Instant-Mix verwenden, um ähnliche Songs zu erhalten. Nützlich bei Verwendung von Plugins, die in dieses Verhalten eingreifen",
"input_preferRemoteUrl": "öffentliche URL bevorzugen",
"input_remoteUrl": "Öffentliche URL",
"input_remoteUrlPlaceholder": "Optional: öffentliche URL für externe Funktionen"
},
"addToPlaylist": {
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) zu $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} }) hinzugefügt",
@@ -282,7 +298,7 @@
"editPlaylist": {
"title": "Bearbeite $t(entity.playlist, {\"count\": 1})",
"success": "$t(entity.playlist, {\"count\": 1}) 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",
"publicJellyfinNote": "Jellyfin legt aus irgendwelchen Gründen nicht offen ob eine Wiedergabeliste öffentlich ist oder nicht. Wenn du möchtest, dass sie öffentlich bleibt, wähle bitte diese Option aus",
"editNote": "Manuelles Bearbeiten wird für große Wiedergabelisten nicht empfohlen. Bist Du sicher, dass Du die aktuelle Wiedergabeliste unter dem Risiko von Datenverlust überschrieben möchtest?"
},
"lyricSearch": {
@@ -329,7 +345,9 @@
"input_streamUrl": "Stream URL"
},
"lyricsExport": {
"input_offset": "$t(setting.lyricOffset)"
"input_offset": "$t(setting.lyricOffset)",
"export": "Songtexte exportieren",
"input_synced": "Synchronisierte Songtexte exportieren"
}
},
"entity": {
@@ -378,7 +396,8 @@
"view": {
"table": "Tabelle",
"grid": "Raster",
"list": "Liste"
"list": "Liste",
"detail": "Detail"
},
"general": {
"tableColumns": "Tabellenspalten",
@@ -397,7 +416,20 @@
"size_large": "groß",
"pagination": "Seitenzahlen",
"pagination_itemsPerPage": "Elemente pro Seite",
"pagination_infinite": "unendlich"
"pagination_infinite": "unendlich",
"moveUp": "Nach oben bewegen",
"moveDown": "Nach unten bewegen",
"pinToLeft": "links anheften",
"pinToRight": "rechts anheften",
"itemGap": "Item Abstand (px)",
"itemSize": "Item Größe (px)",
"itemsPerRow": "Items pro Zeile",
"pagination_paginate": "paginiert",
"alternateRowColors": "Zeilenfarben abwechseln",
"horizontalBorders": "Zeilenbegrenzungen",
"rowHoverHighlight": "Zeilenhervorhebungen beim hovern",
"showHeader": "Spaltenüberschrift anzeigen",
"verticalBorders": "Spaltenbegrenzungen"
},
"label": {
"dateAdded": "Hinzugefügt am",
@@ -432,7 +464,9 @@
"image": "Bild",
"sampleRate": "$t(common.sampleRate)",
"songCount": "$t(entity.track, {\"count\": 2})",
"genreBadge": "$t(entity.genre, {\"count\": 1}) (Abzeichen)"
"genreBadge": "$t(entity.genre, {\"count\": 1}) (Abzeichen)",
"composer": "Komponist",
"titleArtist": "$t(common.title) (Interpret)"
}
},
"column": {
@@ -597,7 +631,8 @@
"transcoding": "Transcoding",
"logger": "Logger",
"playerFilters": "Player-Filter",
"remote": "Fernsteuerung"
"remote": "Fernsteuerung",
"lyricsDisplay": "Songtexte Anzeige"
},
"albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})"
@@ -633,7 +668,9 @@
"groupingTypeAll": "alle Veröffentlichungsformate",
"groupingTypePrimary": "primäre Veröffentlichungsformate",
"favoriteSongs": "Lieblingssongs",
"favoriteSongsFrom": "Liebslingssongs von {{title}}"
"favoriteSongsFrom": "Liebslingssongs von {{title}}",
"topSongsCommunity": "Community",
"topSongsPersonal": "Persönlich"
},
"manageServers": {
"title": "Servers verwalten",
@@ -665,7 +702,12 @@
"privateMode": "(Privater Modus)"
},
"collections": {
"saveAsCollection": "Als Sammlung speichern"
"saveAsCollection": "Als Sammlung speichern",
"overrideExisting": "Bestehende überschreiben"
},
"releasenotes": {
"commitsSinceStable": "Commits seit {{stable}}",
"noStableReleaseToCompare": "Kein stable Relase zum vergleichen verfügbar"
}
},
"player": {
@@ -692,7 +734,7 @@
"queue_moveToBottom": "Ausgewählte nach oben verschieben",
"shuffle_off": "Zufallswiedergabe deaktiviert",
"stop": "stopp",
"toggleFullscreenPlayer": "vollbildmodus",
"toggleFullscreenPlayer": "Vollbildmodus",
"skip_back": "zurückspulen",
"pause": "Pause",
"unfavorite": "Aus Favoriten entfernen",
@@ -705,7 +747,18 @@
"holdToShuffle": "Halten für Zufallswiedergabe",
"restoreQueueFromServer": "Wiedergabeliste von Server wiederherstellen",
"saveQueueToServer": "Wiedergabeliste auf Server speichern",
"lyrics": "Songtexte"
"lyrics": "Songtexte",
"artistRadio": "Künstler Radio",
"sleepTimer_endOfSong": "Ende des aktuellen Liedes",
"sleepTimer_off": "aus",
"sleepTimer_timeRemaining": "{{time}} verbleibend",
"sleepTimer_cancel": "Timer abbrechen",
"sleepTimer_setCustom": "Timer stellen",
"sleepTimer": "Sleep Timer",
"sleepTimer_custom": "Benutzerdefiniert",
"sleepTimer_hours": "{{count}} std",
"sleepTimer_minutes": "{{count}} min",
"trackRadio": "Song Radio"
},
"setting": {
"audioDevice_description": "wählen Sie das Audiogerät aus, das für die Wiedergabe verwendet werden soll (nur Webplayer)",
@@ -801,8 +854,8 @@
"fontType_description": "Die integrierte Schriftart wählt eine der von feishin bereitgestellten Schriftarten aus. Mit der Systemschriftart können Sie jede von Ihrem Betriebssystem bereitgestellte Schriftart auswählen. Benutzerdefiniert erlaubt es eine eigene Schriftart bereitzustellen",
"playButtonBehavior": "Verhalten der Wiedergabetaste",
"volumeWheelStep": "Lautstärkeänderung mit Mausrad",
"sidebarPlaylistList_description": "Ein- oder Ausblenden der Playlisten-Liste in der Seitenleiste",
"sidebarPlaylistSorting_description": "sortiere Playlists in der Seitenleiste per Drag & Drop anstelle der standardmäßigen Serverreihenfolge",
"sidebarPlaylistList_description": "Ein- oder Ausblenden der Wiedergabelisten in der Seitenleiste",
"sidebarPlaylistSorting_description": "sortiere Wiedergabelisten in der Seitenleiste per Drag & Drop anstelle der standardmäßigen Serverreihenfolge",
"sidePlayQueueStyle_description": "legt den Stil der Wiedergabeliste in der Seitenleiste fest",
"replayGainMode": "{{ReplayGain}} Modus",
"playbackStyle_optionNormal": "Normal",
@@ -817,7 +870,7 @@
"useSystemTheme_description": "Folgt dem hellen oder dunklen Erscheinungsbild des Systems",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"lyricFetch_description": "Songtexte aus verschiedenen Internetquellen abrufen",
"lyricFetchProvider_description": "Wählen Sie die Anbieter aus, von denen Sie Liedtexte abrufen möchten. Die Reihenfolge der Anbieter ist die Reihenfolge, in der sie abgefragt werden",
"lyricFetchProvider_description": "Wähle den Anbieter zum Abrufen von Songtexten aus",
"globalMediaHotkeys_description": "aktivieren oder deaktivieren Sie die Verwendung der Medien-Kurzbefehle Ihres Systems zur Steuerung der Wiedergabe",
"hotkey_zoomOut": "Herauszoomen",
"hotkey_unfavoriteCurrentSong": "$t(common.currentSong) aus Favoriten entfernen",
@@ -826,10 +879,10 @@
"remoteUsername": "Benutzername des Fernsteuerungsserver",
"hotkey_browserBack": "Browser zurück",
"showSkipButton": "Schaltflächen zum Überspringen anzeigen",
"sidebarPlaylistList": "Seitenleiste Playlisten-Liste",
"sidebarPlaylistSorting": "Playlist-Sortierung in der Seitenleiste",
"sidebarPlaylistList": "Wiedergabelisten in Seitenleiste",
"sidebarPlaylistSorting": "Wiedergabelisten-Sortierung in der Seitenleiste",
"minimizeToTray": "Zur Taskleiste minimieren",
"skipPlaylistPage": "Playlisten-Seite überspringen",
"skipPlaylistPage": "Wiedergabeliste-Seite überspringen",
"themeDark": "Erscheinungsbild (dunkel)",
"sidebarCollapsedNavigation": "Navigation in der Seitenleiste (komprimiert)",
"gaplessAudio_optionWeak": "schwach (empfohlen)",
@@ -856,7 +909,7 @@
"sidePlayQueueStyle_optionDetached": "lösgelöst",
"windowBarStyle_description": "Legt das Erscheinungsbild des Fensterrahmens fest",
"hotkey_toggleCurrentSongFavorite": "$t(common.currentSong) zu Favoriten hinzufügen",
"clearQueryCache_description": "\"Weiches\" Zurücksetzen. Dies wird Playlisten, Musik-Metadaten und gespeicherte Liedtexte zurücksetzen, Zugangsinformationen und zwischengespeicherte Bilder werden behalten",
"clearQueryCache_description": "\"Weiches\" Zurücksetzen. Dies wird Wiedergabelisten, Musik-Metadaten und gespeicherte Songtexte zurücksetzen, Zugangsinformationen und zwischengespeicherte Bilder werden behalten",
"discordRichPresence_description": "Aktiviert den Wiedergabestatus in {{discord}} Rich Presence. Angezeigte Bilder sind: {{icon}}, {{playing}}, und {{paused}}",
"clearCache": "Browser-Zwischenspeicher löschen",
"clearQueryCache": "feishins Zwischenspeicher leeren",
@@ -873,7 +926,7 @@
"discordListening_description": "Status als hört zu statt als spielt anzeigen",
"lastfm": "zeige last.fm links",
"lastfm_description": "zeige links zu Last.fm auf dem Künstler/Album-Seiten",
"musicbrainz": "Zeig MusicBrainz links",
"musicbrainz": "Zeige MusicBrainz links",
"customCssEnable": "benutzerdefiniertes CSS aktivieren",
"albumBackground_description": "fügt ein Hintergrundbild für die Albumseiten hinzu, welche das Albumcover zeigen",
"albumBackgroundBlur": "Größe der Album-Bildunschärfe",
@@ -990,7 +1043,58 @@
"imageResolution": "Bildauflösung",
"imageResolution_optionTable": "Tabelle",
"imageResolution_optionSidebar": "Seitenleiste",
"preservePitch": "Tonhöhe erhalten"
"preservePitch": "Tonhöhe erhalten",
"analyticsEnable": "Nutzungsbasierte Analyse senden",
"automaticUpdates": "Automatische Updates",
"automaticUpdates_description": "Updates automatisch suchen und installieren",
"releaseChannel_optionAlpha": "Alpha (nightly)",
"useThemeAccentColor": "Akzentfarbe des Themas nutzen",
"analyticsEnable_description": "Anonymisierte Nutzungsdaten werden an den Entwickler gesendet, um die Anwendung zu verbessern",
"artistReleaseTypeConfiguration_description": "Konfigurieren, welche Release-Typen und in welcher Reihenfolge diese auf der Album-Künstlerseite angezeigt werden",
"homeConfiguration_description": "Konfigurieren, welche Elemente und in welcher Reihenfolge diese auf der Startseite angezeigt werden",
"passwordStore_description": "Verwendeter Passwort/Geheimnis Speicher. Sollten Probleme beim Speichern von Passwörtern auftreten, wähle eine andere Methode",
"passwordStore": "Passwort/Geheimnis Speicher",
"audioFadeOnStatusChange_description": "ermöglicht Ein- und Ausblenden, wenn sich der Wiedergabe-/Pause-Status ändert",
"audioFadeOnStatusChange": "Audio Ein-/Ausblenden bei Statusveränderung",
"showRatings_description": "Aktiviere die Anzeige einer Bewertung in Sternen",
"showRatings": "Zeige Sternebewertungen",
"blurExplicitImages": "Explizite Bilder unkenntlich machen",
"blurExplicitImages_description": "Album- und Song-Cover, die als explizit gekennzeichnet sind, werden unscharf dargestellt",
"enableGridMultiSelect": "Raster-Mehrfachauswahl aktivieren",
"enableGridMultiSelect_description": "Wenn aktiviert, können in Rasteransichten mehrere Elemente ausgewählt werden. Wenn deaktiviert, führt ein Klick auf Rasterelement-Bilder zur Artikelseite",
"playerbarOpenDrawer_description": "Ermöglicht das Anklicken der Playerleiste, um den Vollbild-Player zu öffnen",
"playerbarOpenDrawer": "Playerleiste Vollbild-Umschalter",
"playerbarSlider": "Playerleiste-Schieberegler",
"playerbarSlider_description": "Die Wellenform Darstellung wird nicht empfohlen, wenn eine langsame oder tarifierte Internetverbindung genutzt wird",
"playerbarSliderType_optionSlider": "Schieberegler",
"playerbarSliderType_optionWaveform": "Wellenform",
"playerbarWaveformAlign": "Wellenform ausrichten",
"playerbarWaveformBarWidth": "Breite der Wellenform Leiste",
"playerbarWaveformGap": "Wellenform Lücke",
"playerbarWaveformRadius": "Wellenform Radius",
"artistRadioCount": "Interpreten/Song Radio Länge",
"artistRadioCount_description": "legt die Anzahl der Songs fest, die beim Interpreten-Radio und Song-Radio abgerufen werden",
"sidebarPlaylistListFilterRegex_description": "versteckt Wiedergabelisten in der Seitenleiste, die diesem regulären Ausdruck entsprechen",
"sidebarPlaylistListFilterRegex_placeholder": "z.B. ^Täglicher Mix.*",
"sidebarPlaylistListFilterRegex": "Wiedergabelisten Regex-Filter",
"showVisualizerInSidebar_description": "Ein Panel wird zur Player-Seitenleiste hinzugefügt, das den Visualizer anzeigt",
"showVisualizerInSidebar": "Visualizer in Seitenleiste anzeigen",
"combinedLyricsAndVisualizer_description": "Songtexte und Visualizer im selben Panel anzeigen",
"combinedLyricsAndVisualizer": "Songtexte und Visualizer geimeinsam in der Seitenleiste anzeigen",
"mpvExtraParameters_description": "zusätzliche Argumente die an mpv übergeben werden sollen",
"mpvExtraParameters_help": "Eins pro Zeile",
"pathReplace": "Dateipfad-Ersetzung",
"pathReplace_description": "Ersetze den Standard Dateipfad des Servers",
"pathReplace_optionRemovePrefix": "Präfix entfernen",
"pathReplace_optionAddPrefix": "Präfix hinzufügen",
"imageResolution_description": "Die Auflösung für die in der App verwendeten Bilder. Bei einem Wert von 0 wird die originale Bildauflösung verwendet",
"preservePitch_description": "Behält beim Anpassen der Wiedergabegeschwindigkeit die Tonhöhe bei",
"preventSleepOnPlayback_description": "Verhindert das Abschalten des Displays während der Musikwiedergabe",
"preventSleepOnPlayback": "Verhindert den Energiesparmodus während der Musikwiedergabe",
"trayEnabled_description": "Tray-Symbol anzeigen/verbergen. Bei Deaktivierung werden auch Minimieren/Beenden zum Tray deaktiviert",
"queryBuilder": "Abfrage-Editor",
"queryBuilderCustomFields_inputLabel": "Label",
"queryBuilderCustomFields_description": "Füge benutzerdefinierte Felder für den Abfrage-Editor hinzu"
},
"dragDropZone": {
"error_oneFileOnly": "Bitte wähle nur 1 Datei",
@@ -1050,5 +1154,107 @@
"secondShort": "Sek",
"hourShort": "Std",
"dayShort": "Tag"
},
"visualizer": {
"options": {
"weightingFilter": {
"z": "Z",
"d": "D",
"c": "C",
"none": "Keine",
"a": "A",
"b": "B"
},
"frequencyScale": {
"linear": "Lineare Skala",
"log": "Log Skala",
"mel": "Mel Skala",
"bark": "Bark-Skala",
"none": "Keine"
},
"gradient": {
"classic": "Klassisch",
"prism": "Prisma",
"rainbow": "Regenbogen",
"steelblue": "Stahlblau",
"orangered": "Orange-Rot"
},
"channelLayout": {
"dualHorizontal": "Dual-Horizontal",
"dualVertical": "Dual-Vertikal"
}
},
"minimumFrequency": "Mindestfrequenz",
"minimumDecibels": "Minimale Dezibel",
"visualizerType": "Visualizer Art",
"cyclePresets": "Vorlagen durchrotieren",
"cycleTime": "Abspieldauer je Vorlage (Sekunden)",
"includeAllPresets": "Alle Vorlagen verwenden",
"ignoredPresets": "Ignorierte Vorlagen",
"selectedPresets": "Ausgewählte Vorlagen",
"randomizeNextPreset": "Nächste Vorlage zufällig wählen",
"blendTime": "Übergangsdauer",
"presets": "Vorlage",
"selectPreset": "Vorlage auswählen",
"applyPreset": "Vorlage anwenden",
"saveAsPreset": "Als Vorlage speichern",
"updatePreset": "Vorlage aktualisieren",
"copyConfiguration": "Konfiguration kopieren",
"pasteConfiguration": "Konfiguration einfügen",
"pasteConfigurationPlaceholder": "JSON Konfiguration hier einfügen...",
"pasteFromClipboard": "Aus Zwischenablage einfügen",
"applyConfiguration": "Konfiguration anwenden",
"configCopied": "Konfiguration in Zwischenablage kopiert",
"configCopyFailed": "Konfiguration konnte nicht kopiert werden",
"configPasted": "Konfiguration erfolgreich angewandt",
"configPasteFailed": "Konfiguration konnte nicht angewandt werden. Bitte Format überprüfen.",
"configPasteReadFailed": "Zwischenablage konnte nicht ausgelesen werden",
"presetName": "Vorlagen Name",
"presetNamePlaceholder": "Name der Vorlage eingeben",
"general": "Allgemein",
"mode": "Modus",
"mode1To8": "Modus 1 - 8",
"mode10": "Modus 10",
"lineWidth": "Linienbreite",
"channelLayout": "Kanallayout",
"maxFPS": "Max FPS",
"opacity": "Deckkraft",
"customGradients": "Benutzerdefinierte Gradienten",
"addCustomGradient": "Benutzerdefinierten Gradienten hinzufügen",
"gradientName": "Gradientenname",
"gradientNamePlaceholder": "Gradientenname",
"vertical": "Vertikal",
"horizontal": "Horizontal",
"addColor": "Farbe hinzufügen",
"position": "Position",
"level": "Ebene",
"remove": "Entfernen",
"pasteGradient": "Gradient einfügen",
"pasteGradientPlaceholder": "Gradient JSON hier einfügen...",
"custom": "Benutzerdefiniert",
"builtIn": "Eingebaut",
"colors": "Farben",
"colorMode": "Farbmodus",
"gradient": "Gradienten",
"gradientLeft": "Gradienten links",
"gradientRight": "Gradienten rechts",
"fft": "FFT",
"fftSize": "FFT Größe",
"smoothing": "Glätten",
"frequencyRangeAndScaling": "Frequenzbereich und Skalierung",
"maximumFrequency": "Maximale Frequenz",
"sensitivity": "Empfindlichkeit",
"weightingFilter": "Gewichtungsfilter",
"maximumDecibels": "Maximale Dezibel",
"linearAmplitude": "Lineare Amplitude",
"linearBoost": "Linearer Boost",
"radialSpectrum": "Radiales Spektrum",
"radial": "Radial",
"radialInvert": "Radial invertiert",
"radius": "Radius",
"miscellaneousSettings": "Verschiedenes Einstellungen",
"ansiBands": "ANSI Bänder",
"lowResolution": "Niedrige Auflösung",
"showFPS": "FPS anzeigen"
}
}
Executable → Regular
+22 -1
View File
@@ -5,6 +5,7 @@
"addOrRemoveFromSelection": "add or remove from selection",
"selectRangeOfItems": "select a range of items",
"clearQueue": "clear queue",
"goToCurrent": "go to current item",
"createPlaylist": "create $t(entity.playlist, {\"count\": 1})",
"createRadioStation": "create $t(entity.radioStation, {\"count\": 1})",
"deletePlaylist": "delete $t(entity.playlist, {\"count\": 1})",
@@ -159,7 +160,8 @@
"clean": "clean",
"gridRows": "grid rows",
"tableColumns": "table columns",
"itemsMore": "{{count}} more"
"itemsMore": "{{count}} more",
"newVersionAvailable": "a new version is available"
},
"entity": {
"album_one": "album",
@@ -211,6 +213,7 @@
"credentialsRequired": "credentials required",
"endpointNotImplementedError": "endpoint {{endpoint}} is not implemented for {{serverType}}",
"genericError": "an error occurred",
"invalidJson": "invalid JSON",
"invalidServer": "invalid server",
"localFontAccessDenied": "access denied to local fonts",
"loginRateError": "too many login attempts, please try again in a few seconds",
@@ -222,11 +225,13 @@
"notificationDenied": "permissions for notifications were denied. this setting has no effect",
"openError": "could not open file",
"playbackError": "an error occurred when trying to play the media",
"playbackPausedDueToError": "playback was paused due to an error",
"remoteDisableError": "an error occurred when trying to $t(common.disable) the remote server",
"remoteEnableError": "an error occurred when trying to $t(common.enable) the remote server",
"remotePortError": "an error occurred when trying to set the remote server port",
"remotePortWarning": "restart the server to apply the new port",
"saveQueueFailed": "failed to save queue",
"serverLockSingleServer": "only one server is allowed when server is locked",
"serverNotSelectedError": "no server selected",
"serverRequired": "server required",
"sessionExpiredError": "your session has expired",
@@ -236,6 +241,8 @@
"filter": {
"album": "$t(entity.album, {\"count\": 1})",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"matchAnd": "and",
"matchOr": "or",
"albumCount": "$t(entity.album, {\"count\": 2}) count",
"artist": "$t(entity.artist, {\"count\": 1})",
"biography": "biography",
@@ -387,9 +394,11 @@
},
"shareItem": {
"allowDownloading": "allow downloading",
"copyToClipboard": "Copy to clipboard: Ctrl+C, Enter",
"description": "description",
"setExpiration": "set expiration",
"success": "share link copied to clipboard (or click here to open)",
"successMustClick": "share created successfully. click here to open",
"expireInvalid": "expiration must be in the future",
"createFailed": "failed to create share (is sharing enabled?)"
},
@@ -631,6 +640,7 @@
"addNext": "next",
"addLastShuffled": "last (shuffled)",
"addNextShuffled": "next (shuffled)",
"albumRadio": "album radio",
"artistRadio": "artist radio",
"holdToShuffle": "hold to shuffle",
"favorite": "favorite",
@@ -712,10 +722,18 @@
"autoDJ_itemCount_description": "the number of items attempted to be added to the queue when auto DJ is enabled",
"autoDJ_timing": "timing",
"autoDJ_timing_description": "the number of songs remaining in the queue before auto DJ is triggered",
"autosave": "automatically save play queue",
"autosave_description": "enable automatically saving the play queue to your server. this is only possible when using Navidrome/Subsonic, and you cannot have a mixed play queue.",
"autosaveCount": "automatic play queue save frequency",
"autosaveCount_description": "how many track changes before the queue is saved. 1 (minimum) means every song change",
"accentColor_description": "sets the accent color for the application",
"accentColor": "accent color",
"useThemeAccentColor": "use theme accent color",
"useThemeAccentColor_description": "use the primary color defined in the selected theme instead of the custom accent color",
"useThemePrimaryShade": "use theme primary shade",
"useThemePrimaryShade_description": "use the primary shade defined in the selected theme for primary color variants",
"primaryShade": "primary shade",
"primaryShade_description": "override the primary shade (09) used for buttons, links, and other primary-colored elements",
"albumBackground_description": "adds a background image for album pages containing the album art",
"albumBackground": "album background image",
"albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image",
@@ -1001,6 +1019,8 @@
"sidebarCollapsedNavigation": "sidebar (collapsed) navigation",
"sidebarConfiguration_description": "select the items and order in which they appear in the sidebar",
"sidebarConfiguration": "sidebar configuration",
"playerItemConfiguration_description": "configure what items are shown, and in what order, on the fullscreen player",
"playerItemConfiguration": "player item configuration",
"sidebarPlaylistList_description": "show or hide the playlist list in the sidebar",
"sidebarPlaylistList": "sidebar playlist list",
"sidebarPlaylistSorting_description": "allows manual playlist sorting in the sidebar using drag and drop instead of the default server order",
@@ -1124,6 +1144,7 @@
"label": {
"actions": "$t(common.action, {\"count\": 2})",
"album": "$t(entity.album, {\"count\": 1})",
"albumGroup": "album group",
"albumCount": "$t(entity.album, {\"count\": 2})",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"artist": "$t(entity.artist, {\"count\": 1})",
+30 -9
View File
@@ -47,14 +47,15 @@
"sleepTimer_timeRemaining": "{{time}} restante",
"sleepTimer_off": "Apagado",
"sleepTimer_endOfSong": "Fin de la canción actual",
"sleepTimer": "Temporizador de apagado"
"sleepTimer": "Temporizador de apagado",
"albumRadio": "Radio del álbum"
},
"setting": {
"crossfadeStyle_description": "selecciona el estilo de crossfade a usar por el reproductor de audio",
"remotePort_description": "establece el puerto para el control remoto del servidor",
"hotkey_skipBackward": "retroceder",
"replayGainMode_description": "ajusta el volumen de ganancia acorde a los valores de {{ReplayGain}} almacenados en los metadatos del archivo",
"audioDevice_description": "selecciona el dispositivo de audio a usar durante la reproducción (solo reproductor web)",
"audioDevice_description": "selecciona el dispositivo de audio a usar durante la reproducción",
"theme_description": "establece el tema a usar por la aplicación",
"hotkey_playbackPause": "pausa",
"replayGainFallback": "{{ReplayGain}} alternativa",
@@ -400,7 +401,17 @@
"automaticUpdates": "Actualizaciones automáticas",
"automaticUpdates_description": "Busca e instala actualizaciones automáticamente",
"discordStateIcon": "Mostrar icono de reproducción",
"discordStateIcon_description": "Muestra un icono pequeño de reproducción en el estado de actividad. El icono de pausa se muestra siempre cuando \"Mostrar estado de actividad cuando esté en pausa\" esté activado"
"discordStateIcon_description": "Muestra un icono pequeño de reproducción en el estado de actividad. El icono de pausa se muestra siempre cuando \"Mostrar estado de actividad cuando esté en pausa\" esté activado",
"playerItemConfiguration": "Configuración de elementos del reproductor",
"playerItemConfiguration_description": "Configura qué elementos se muestran, y en qué orden, en el reproductor a pantalla completa",
"primaryShade": "Tono principal",
"useThemePrimaryShade": "Usar tono principal del tema",
"useThemePrimaryShade_description": "Usa el tono principal definido en el tema seleccionado para las variantes de color primario",
"primaryShade_description": "Sobreescribe el tono principal (0-9) usado para los botones, enlaces, y otros elementos de colores primarios",
"autosave": "Guardar automáticamente la cola de reproducción",
"autosaveCount": "Frecuencia de guardado automática de la cola de reproducción",
"autosave_description": "Permite guardar automáticamente la cola de reproducción en tu servidor. Esto solo es posible cuando se usa Navidrome/Subsonic, y no puedes tener una cola de reproducción mezclada.",
"autosaveCount_description": "Cuántas pistas cambian antes de que la cola sea guardada. 1 (mínimo) quiere decir que todas las canciones cambian"
},
"action": {
"editPlaylist": "editar $t(entity.playlist, {\"count\": 1})",
@@ -440,7 +451,8 @@
"openApplicationDirectory": "Abrir directorio de la aplicación",
"addOrRemoveFromSelection": "Añadir o quitar de la selección",
"selectRangeOfItems": "Seleccionar un intervalo de elementos",
"selectAll": "Seleccionar todo"
"selectAll": "Seleccionar todo",
"goToCurrent": "Ir al elemento actual"
},
"common": {
"backward": "hacia atrás",
@@ -566,7 +578,8 @@
"example": "Ejemplo",
"filter_single": "simple",
"filter_multiple": "multi",
"rename": "Renombrar"
"rename": "Renombrar",
"newVersionAvailable": "Una nueva versión está disponible"
},
"error": {
"remotePortWarning": "reiniciar el servidor para aplicar el nuevo puerto",
@@ -597,7 +610,10 @@
"multipleServerSaveQueueError": "La cola de reproducción tiene una o más canciones que no son del servidor actual. Esto no está soportado",
"settingsSyncError": "Se encontraron discrepancias entre las opciones del renderizador y el proceso principal. Reinicia la aplicación para aplicar los cambios",
"noNetwork": "Servidor no disponible",
"noNetworkDescription": "No se pudo conectar a este servidor"
"noNetworkDescription": "No se pudo conectar a este servidor",
"invalidJson": "JSON inválido",
"serverLockSingleServer": "Solo se permite un servidor cuando el servidor está bloqueado",
"playbackPausedDueToError": "La reproducción fue pausada debido a un error"
},
"filter": {
"mostPlayed": "más reproducidos",
@@ -643,7 +659,9 @@
"album": "$t(entity.album, {\"count\": 1})",
"albumCount": "Número de $t(entity.album, {\"count\": 2})",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "Ordenar por nombre"
"sortName": "Ordenar por nombre",
"matchAnd": "y",
"matchOr": "o"
},
"page": {
"sidebar": {
@@ -927,7 +945,9 @@
"description": "Descripción",
"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"
"expireInvalid": "La expiración debe ser en el futuro",
"copyToClipboard": "Copiar al portapapeles: Ctrl+C, Enter",
"successMustClick": "Compartir creado correctamente. Haz clic aquí para abrir"
},
"privateMode": {
"enabled": "Modo privado activado, el estado de reproducción ahora está oculto de integraciones externas",
@@ -1031,7 +1051,8 @@
"bitDepth": "$t(common.bitDepth)",
"sampleRate": "$t(common.sampleRate)",
"titleArtist": "$t(common.title) (artista)",
"composer": "Compositor"
"composer": "Compositor",
"albumGroup": "Grupo del álbum"
},
"general": {
"gap": "$t(common.gap)",
+223 -24
View File
@@ -4,7 +4,7 @@
"search": "etsi",
"sortOrder": "järjestys",
"setting_one": "asetus",
"setting_other": "",
"setting_other": "asetukset",
"title": "otsikko",
"trackNumber": "raita",
"action_one": "toiminto",
@@ -102,7 +102,24 @@
"recordLabel": "levy-yhtiö",
"releaseType": "julkaisun tyyppi",
"explicit": "eksplisiittinen",
"clean": "puhdas"
"clean": "puhdas",
"countSelected": "{{count}} valittuna",
"doNotShowAgain": "älä näytä uudelleen",
"view": "katso",
"example": "esimerkki",
"externalLinks": "ulkoiset linkit",
"faster": "nopeammin",
"filter_single": "yksi",
"filter_multiple": "useampi",
"mood": "mieliala",
"noFilters": "suodattimia ei ole määritetty",
"retry": "yritä uudelleen",
"rename": "nimeä uudelleen",
"slower": "hitaammin",
"sort": "järjestä",
"gridRows": "ruudukon rivejä",
"tableColumns": "taulukon sarakkeita",
"itemsMore": "{{count}} lisää"
},
"entity": {
"album_one": "albumi",
@@ -139,7 +156,11 @@
"play_one": "{{count}} toisto",
"play_other": "{{count}} toistoa",
"song_one": "kappale",
"song_other": "kappaleet"
"song_other": "kappaleet",
"radioStation_one": "radioasema",
"radioStation_other": "radioasemaa",
"radioStationWithCount_one": "{{count}} radioasema",
"radioStationWithCount_other": "{{count}} radioasemaa"
},
"action": {
"clearQueue": "tyhjennä jono",
@@ -153,7 +174,7 @@
"musicbrainz": "Avaa MusicBrainz:ssä"
},
"goToPage": "mene sivulle",
"moveToBottom": "siirry pohjalle",
"moveToBottom": "siirrä alimmaksi",
"moveToTop": "siirry ylös",
"addToFavorites": "lisää kohteeseen $t(entity.favorite, {\"count\": 2})",
"addToPlaylist": "lisää kohteeseen $t(entity.playlist, {\"count\": 1})",
@@ -163,7 +184,24 @@
"deletePlaylist": "poista $t(entity.playlist, {\"count\": 1})",
"removeFromPlaylist": "poista kohteesta $t(entity.playlist, {\"count\": 1})",
"setRating": "aseta arvostelu",
"moveToNext": "siirry seuraavaan"
"moveToNext": "siirry seuraavaan",
"selectRangeOfItems": "valitse useita peräkkäisiä kohteita",
"goToCurrent": "siirry nykyiseen kohteeseen",
"createRadioStation": "luo $t(entity.radioStation, {\"count\": 1})",
"deleteRadioStation": "poista $t(entity.radioStation, {\"count\": 1})",
"selectAll": "valitse kaikki",
"downloadStarted": "aloitettiin lataamaan {{count}} kohdetta",
"moveUp": "siirrä ylöspäin",
"moveDown": "siirrä alaspäin",
"holdToMoveToTop": "pidä pohjassa siirtääksesi ylimmäksi",
"holdToMoveToBottom": "pidä pohjassa siirtääksesi alimmaksi",
"moveItems": "siirrä kohteet",
"shuffle": "sekoita",
"shuffleAll": "sekoita kaikki",
"shuffleSelected": "sekoita valitut",
"viewMore": "katso lisää",
"openApplicationDirectory": "avaa ohjelman kansio",
"addOrRemoveFromSelection": "lisää tai poista valinnasta"
},
"error": {
"remoteEnableError": "virhe tapahtui yrittäessä $t(common.enable) etäpalvelinta",
@@ -189,7 +227,15 @@
"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",
"notificationDenied": "luvat ilmouilmoituksia varten evättiin. tällä asetuksella ei ole vaikutusta"
"notificationDenied": "luvat ilmouilmoituksia varten evättiin. tällä asetuksella ei ole vaikutusta",
"invalidJson": "virheellinen JSON",
"multipleServerSaveQueueError": "soittojonossa on yksi tai useampi kappale, jotka eivät ole nykyiseltä palvelimelta. tätä ei ole tuettu",
"noNetwork": "palvelin ei ole käytettävissä",
"noNetworkDescription": "ei voida yhdistää palvelimeen",
"serverLockSingleServer": "lukitussa tilassa sallitaan vain yksi palvelin",
"settingsSyncError": "rendererin ja pääprosessin asetukset eivät täsmää. Käynnistä sovellus uudelleen, jotta muutokset otetaan käyttöön",
"playbackPausedDueToError": "toisto tauotettiin virheen takia",
"saveQueueFailed": "jonon tallentaminen epäonnistui"
},
"filter": {
"album": "$t(entity.album, {\"count\": 1})",
@@ -198,7 +244,7 @@
"biography": "biografia",
"bitrate": "bittinopeus",
"bpm": "lyöntiä minuutissa (bpm)",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"title": "otsikko",
"playCount": "toistomäärä",
"dateAdded": "lisätty päivänä",
@@ -234,7 +280,10 @@
"owner": "$t(common.owner)",
"path": "polku",
"songCount": "kappalemäärä",
"explicitStatus": "$t(common.explicitStatus)"
"explicitStatus": "$t(common.explicitStatus)",
"matchAnd": "ja",
"matchOr": "tai",
"sortName": "järjestä nimen mukaan"
},
"form": {
"addServer": {
@@ -250,7 +299,10 @@
"input_username": "käyttäjänimi",
"success": "palvelin lisätty onnistuneesti",
"input_preferInstantMix": "suosi pika-miksausta",
"input_preferInstantMixDescription": "käytä vain pika-miksausta saadaksesi samankaltaisia kappaleita. käytännöllinen jos sinulla on lisäosia, jotka muuttavat tätä käytöstä"
"input_preferInstantMixDescription": "käytä vain pika-miksausta saadaksesi samankaltaisia kappaleita. käytännöllinen jos sinulla on lisäosia, jotka muuttavat tätä käytöstä",
"input_preferRemoteUrl": "suosi julkista url-osoitetta",
"input_remoteUrl": "julkinen url-osoite",
"input_remoteUrlPlaceholder": "valinnainen: julkinen url-osoite ulkoisille toiminnoille"
},
"createPlaylist": {
"input_public": "julkinen",
@@ -280,7 +332,8 @@
"editPlaylist": {
"success": "$t(entity.playlist, {\"count\": 1}) päivitetty onnistuneesti",
"title": "muokkaa $t(entity.playlist, {\"count\": 1})",
"publicJellyfinNote": "Jellyfin ei jostain syystä kerro onko soittolista julkinen vai ei. Jos haluat sen pysyvän julkisena, pidä seuraava valinta valittuna"
"publicJellyfinNote": "Jellyfin ei jostain syystä kerro onko soittolista julkinen vai ei. Jos haluat sen pysyvän julkisena, pidä seuraava valinta valittuna",
"editNote": "manuaalisia muokkauksia ei suositella suurille soittolistoille. haluatko varmasti hyväksyä riskin, että nykyinen soittolista ylikirjoitetaan ja tietoja voi hävitä?"
},
"lyricSearch": {
"input_artist": "$t(entity.artist, {\"count\": 1})",
@@ -293,17 +346,53 @@
"description": "kuvaus",
"setExpiration": "aseta vanheneminen",
"success": "jakolinkki kopioitu leikepöydälle (tai klikkaa tästä avataksesi)",
"expireInvalid": "vanhetumisen pitää olla tulevaisuudessa"
"expireInvalid": "vanhetumisen pitää olla tulevaisuudessa",
"copyToClipboard": "Kopioi leikepöydälle: Ctrl+C, Enter",
"successMustClick": "jako luotu onnistuneesti. paina tästä avataksesi"
},
"queryEditor": {
"input_optionMatchAny": "sovita joku",
"input_optionMatchAll": "sovita kaikki",
"title": "kyselyeditori"
"title": "kyselyeditori",
"addRuleGroup": "lisää sääntöryhmä",
"removeRuleGroup": "poista sääntöryhmä",
"resetToDefault": "palauta oletukset",
"clearFilters": "poista suodattimet"
},
"privateMode": {
"enabled": "yksityinen tila käytössä, toistotila on nyt piilotettu ulkoisilta integraatioilta",
"disabled": "yksityinen tila poissa käytössä, toistotila on nyt näkyvillä ulkoisille integraatioille",
"title": "yksityinen tila"
},
"largeFetchConfirmation": {
"title": "lisää kohteet jonoon",
"description": "lisää kaikki suodatetun näkymän kohteet"
},
"createRadioStation": {
"success": "radiokanava luotu onnistuneesti",
"title": "luo radiokanava",
"input_homepageUrl": "kotisivun osoite",
"input_name": "nimi",
"input_streamUrl": "suoratoisto-osoite"
},
"lyricsExport": {
"export": "vie sanoitukset",
"input_synced": "vie ajastetut sanoitukset",
"input_offset": "$t(setting.lyricOffset)"
},
"saveQueue": {
"success": "toistojono tallennettu palvelimelle"
},
"shuffleAll": {
"title": "soita satunnainen",
"input_genre": "$t(entity.genre, {\"count\": 1})",
"input_limit": "kuinka monta kappaletta?",
"input_minYear": "vuodesta",
"input_maxYear": "vuoteen",
"input_played": "toiston suodatin",
"input_played_optionAll": "kaikki raidat",
"input_played_optionUnplayed": "vain toistamattomat raidat",
"input_played_optionPlayed": "vain toistetut raidat"
}
},
"setting": {
@@ -540,7 +629,12 @@
"releaseChannel_optionLatest": "viimeisin",
"releaseChannel": "julkaisulinja",
"releaseChannel_description": "valitse vakaiden ja beetaversioiden välillä automaattisille päivityksille",
"discordDisplayType_artistname": "artistin nimi / artistien nimet"
"discordDisplayType_artistname": "artistin nimi / artistien nimet",
"autoDJ": "auto DJ",
"autoDJ_description": "lisää automaattisesti samanlaisia kappaleita jonoon",
"autoDJ_itemCount": "kohteiden määrä",
"autoDJ_itemCount_description": "jonoon lisättäväksi yritettyjen kohteiden määrä, kun auto DJ on käytössä",
"autoDJ_timing": "ajastus"
},
"page": {
"itemDetail": {
@@ -573,7 +667,11 @@
"collapseSidebar": "kutista sivupalkki",
"version": "versio {{version}}",
"privateModeOff": "käännä yksityinen tila pois käytöstä",
"privateModeOn": "käännä yksityinen tila käyttöön"
"privateModeOn": "käännä yksityinen tila käyttöön",
"commandPalette": "avaa komentopaletti",
"selectMusicFolder": "valitse musiikkikansio",
"noMusicFolder": "musiikkikansiota ei ole valittu",
"multipleMusicFolders": "{{count}} musiikkikansio(ta) valittu"
},
"contextMenu": {
"playSimilarSongs": "$t(player.playSimilarSongs)",
@@ -599,7 +697,9 @@
"moveToNext": "$t(action.moveToNext)",
"removeFromQueue": "$t(action.removeFromQueue)",
"goToAlbum": "mene $t(entity.album, {\"count\": 1})",
"goToAlbumArtist": "mene $t(entity.albumArtist, {\"count\": 1})"
"goToAlbumArtist": "mene $t(entity.albumArtist, {\"count\": 1})",
"moveItems": "$t(action.moveItems)",
"goTo": "mene"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist, {\"count\": 2})",
@@ -614,14 +714,35 @@
"nowPlaying": "nyt soi",
"playlists": "$t(entity.playlist, {\"count\": 2})",
"search": "$t(common.search)",
"myLibrary": "oma kirjasto"
"myLibrary": "oma kirjasto",
"collections": "kokoelmat",
"favorites": "$t(entity.favorite, {\"count\": 2})",
"radio": "$t(entity.radioStation, {\"count\": 2})"
},
"setting": {
"generalTab": "yleinen",
"windowTab": "ikkuna",
"hotkeysTab": "pikanäppäimet",
"playbackTab": "toisto",
"advanced": "edistyneet"
"advanced": "edistyneet",
"analytics": "tilastot",
"updates": "päivitä",
"cache": "välimuisti",
"application": "aplikaatio",
"queryBuilder": "kyselynrakentaja",
"theme": "teema",
"controls": "säätimet",
"sidebar": "sivupalkki",
"remote": "kauko-ohjain",
"exportImport": "tuo/vie",
"scrobble": "scrobblata",
"audio": "audio",
"lyrics": "sanat",
"lyricsDisplay": "sanojen näyttö",
"transcoding": "transkoodaus",
"discord": "discord",
"logger": "lokittaja",
"playerFilters": "soittimen suodattimet"
},
"fullscreenPlayer": {
"upNext": "seuraavaksi",
@@ -665,10 +786,11 @@
"title": "$t(common.home)",
"mostPlayed": "eniten soitetut",
"newlyAdded": "hiljattain lisätyt julkaisut",
"recentlyReleased": "hiljattain julkaistu"
"recentlyReleased": "hiljattain julkaistu",
"genres": "$t(entity.genre, {\"count\": 2})"
},
"albumArtistDetail": {
"about": "{{artist}}{sta/stä",
"about": "{{artist}}",
"viewDiscography": "katsele diskografiaa",
"relatedArtists": "liittyvät $t(entity.artist, {\"count\": 2})",
"appearsOn": "esiintyy",
@@ -676,7 +798,13 @@
"topSongsFrom": "parhaat kappaleet albumilta {{title}}",
"recentReleases": "hiljattaiset julkaisut",
"viewAll": "katsele kaikkia",
"viewAllTracks": "katsele kaikkia $t(entity.track, {\"count\": 2})"
"viewAllTracks": "katsele kaikkia $t(entity.track, {\"count\": 2})",
"favoriteSongs": "suosikki kappaleet",
"groupingTypeAll": "kaikki julkaisun tyypit",
"groupingTypePrimary": "ensisijaiset tyypin julkaisut",
"topSongsCommunity": "yhteisö",
"topSongsPersonal": "henkilökohtainen",
"favoriteSongsFrom": "suosikkikappale {{title}}:sta"
},
"playlistList": {
"title": "$t(entity.playlist, {\"count\": 2})"
@@ -696,11 +824,33 @@
"artistTracks": "artistin {{artist}} kappaleet",
"genreTracks": "\"{{genre}}\" $t(entity.track, {\"count\": 2})",
"title": "$t(entity.track, {\"count\": 2})"
},
"radioList": {
"title": "radiokanavat"
},
"releasenotes": {
"commitsSinceStable": "muutokset {{stable}} verrattuna",
"noNewCommits": "ei uusia muutoksia tällä välillä",
"noStableReleaseToCompare": "vertailukelpoista vakaata versiota ei löytynyt"
},
"favorites": {
"title": "$t(entity.favorite, {\"count\": 2})"
},
"windowBar": {
"paused": "(Tauotettu) ",
"privateMode": "(Yksityinen tila)"
},
"folderList": {
"title": "$t(entity.folder, {\"count\": 2})"
},
"collections": {
"overrideExisting": "syrjäytä olemassa olevat",
"saveAsCollection": "tallenna kokoelmana"
}
},
"player": {
"addLast": "lisää viimeinen",
"addNext": "lisää seuraava",
"addLast": "viimeinen",
"addNext": "seuraava",
"favorite": "suosikki",
"queue_moveToTop": "siirrä valittu alas",
"queue_remove": "poista valittu",
@@ -719,7 +869,7 @@
"repeat_all": "kertaa kaikki",
"playbackFetchCancel": "tämä vie aikaa... sulje ilmoitus peruaksesi",
"mute": "mykistä",
"shuffle": "soita sekoitettuna",
"shuffle": "soita (sekoitettuna)",
"next": "seuraava",
"play": "toista",
"playbackFetchInProgress": "ladataan kappaleita…",
@@ -729,7 +879,25 @@
"playSimilarSongs": "toista samanlaisia kappaleita",
"repeat_off": "kertaus pois päältä",
"shuffle_off": "sekoitus pois päältä",
"toggleFullscreenPlayer": "vaihda kokoruudun soittimeen"
"toggleFullscreenPlayer": "vaihda kokoruudun soittimeen",
"addLastShuffled": "viimeinen (sekoitettu)",
"addNextShuffled": "seuraava (sekoitettu)",
"albumRadio": "albumiradio",
"artistRadio": "artistiradio",
"holdToShuffle": "pidä sekoittaaksesi",
"lyrics": "sanat",
"restoreQueueFromServer": "palauta tiedustelu palvelimelta",
"saveQueueToServer": "tallenna tiedustelu palvelimelle",
"trackRadio": "raitaradio",
"sleepTimer": "uniajastin",
"sleepTimer_endOfSong": "nykyisen kappaleen loppu",
"sleepTimer_minutes": "{{count}} min",
"sleepTimer_hours": "{{count}} t",
"sleepTimer_custom": "mukautettu",
"sleepTimer_off": "pois",
"sleepTimer_timeRemaining": "{{time}} jäljellä",
"sleepTimer_setCustom": "aseta ajastin",
"sleepTimer_cancel": "peruuta ajastin"
},
"table": {
"config": {
@@ -828,5 +996,36 @@
"soundtrack": "elokuvamusiikki",
"spokenWord": "puhetta"
}
},
"datetime": {
"minuteShort": "m",
"secondShort": "s",
"hourShort": "t",
"dayShort": "p"
},
"filterOperator": {
"after": "jälkeen",
"afterDate": "jälkeen (päivän)",
"before": "ennen",
"beforeDate": "ennen (päivää)",
"contains": "sisältää",
"endsWith": "loppuu",
"inPlaylist": "on",
"inTheLast": "on viimeisenä",
"inTheRange": "on välillä",
"inTheRangeDate": "on valitulla aikavälillä (päivä)",
"is": "on",
"isNot": "ei ole",
"isGreaterThan": "enemmän kuin",
"isLessThan": "vähemmän kuin",
"matchesRegex": "vastaa säännöllistä lausetta (regex)",
"notContains": "ei sisällä",
"notInPlaylist": "ei ole",
"notInTheLast": "ei ole viimeisenä",
"startsWith": "alkaa"
},
"queryBuilder": {
"standardTags": "standardi tagit",
"customTags": "mukautetut tagit"
}
}
+171 -46
View File
@@ -1,12 +1,12 @@
{
"player": {
"repeat_all": "répète tout",
"repeat_all": "répèter tout",
"stop": "stop",
"repeat": "répéter",
"queue_remove": "effacer la sélection",
"playRandom": "lecture aléatoire",
"skip": "sauter",
"previous": "précédant",
"previous": "précédent",
"toggleFullscreenPlayer": "plein écran",
"skip_back": "reculer",
"favorite": "favori",
@@ -23,7 +23,7 @@
"muted": "en sourdine",
"queue_moveToTop": "déplacer la sélection vers le bas",
"queue_moveToBottom": "déplacer la sélection vers le haut",
"shuffle_off": "aléatoire désactivée",
"shuffle_off": "aléatoire désactivé",
"addLast": "dernier",
"mute": "muet",
"skip_forward": "avancer",
@@ -38,7 +38,16 @@
"restoreQueueFromServer": "restaurer la file d'attente depuis le serveur",
"saveQueueToServer": "enregistrer la file d'attente sur le serveur",
"artistRadio": "radio de l'artiste",
"trackRadio": "radio du titre"
"trackRadio": "radio du titre",
"sleepTimer": "minuterie de veille",
"sleepTimer_endOfSong": "fin du titre en cours",
"sleepTimer_minutes": "{{count}} min",
"sleepTimer_hours": "{{count}} h",
"sleepTimer_custom": "personnalisé",
"sleepTimer_off": "éteint",
"sleepTimer_timeRemaining": "{{time}} restante(s)",
"sleepTimer_setCustom": "définir le minuteur",
"sleepTimer_cancel": "annuler le minuteur"
},
"action": {
"editPlaylist": "éditer $t(entity.playlist, {\"count\": 1})",
@@ -71,8 +80,8 @@
"viewMore": "voir plus",
"moveUp": "monter",
"moveDown": "descendre",
"holdToMoveToTop": "Maintenir pour déplacer en haut",
"holdToMoveToBottom": "Maintenir pour déplacer en bas",
"holdToMoveToTop": "maintenir pour déplacer en haut",
"holdToMoveToBottom": "maintenir pour déplacer en bas",
"createRadioStation": "créer $t(entity.radioStation, {\"count\": 1})",
"deleteRadioStation": "supprimer $t(entity.radioStation, {\"count\": 1})",
"addOrRemoveFromSelection": "ajouter ou supprimer de la sélection",
@@ -182,7 +191,7 @@
"viewReleaseNotes": "voir la note de version",
"sampleRate": "taux d'échantillonnage",
"bitDepth": "format d'échantillonnage",
"explicitStatus": "status explicite",
"explicitStatus": "statut explicite",
"explicit": "explicite",
"clean": "propre",
"private": "privé",
@@ -229,17 +238,19 @@
"loginRateError": "trop de tentative de connexion, merci de réessayer dans quelques secondes",
"openError": "impossible d'ouvrir le fichier",
"networkError": "une erreur de réseau est survenue",
"badAlbum": "vous voyez cette page parce que cette chanson ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez une chanson qui n'est pas dans votre répertoire de musique. Jellyfin gère les chansons uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\"",
"badValue": "option {{value}} invalide. Cette valeur n'existe plus",
"badAlbum": "vous voyez cette page parce que ce titre ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez un titre à la racine de votre dossier musique. Jellyfin gère les chansons uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\"",
"badValue": "option {{value}} invalide. cette valeur n'existe plus",
"notificationDenied": "les autorisations pour les notifications ont été refusées. ce paramètre n'a aucun effet",
"multipleServerSaveQueueError": "la file d'attente de lecture contient un ou plusieurs morceaux qui ne proviennent pas du serveur actuel. Ceci n'est pas prise en charge",
"saveQueueFailed": "échec de l'enregistrement de la file d'attente",
"settingsSyncError": "des incohérences ont été détectées entre les paramètres du moteur de rendu et ceux du processus principal. redémarrez l'application pour appliquer les modifications",
"noNetwork": "serveur indisponible",
"noNetworkDescription": "impossible de se connecter à ce serveur"
"noNetworkDescription": "impossible de se connecter à ce serveur",
"invalidJson": "JSON invalide",
"serverLockSingleServer": "un seul serveur est autorisé quand le serveur est verrouillé"
},
"filter": {
"mostPlayed": "plus joués",
"mostPlayed": "les plus joués",
"playCount": "nombre d'écoutes",
"isCompilation": "est une compilation",
"recentlyPlayed": "récemment joué",
@@ -256,13 +267,13 @@
"communityRating": "note de la communauté",
"path": "chemin",
"favorited": "favori",
"isRecentlyPlayed": "est récemment joué",
"isRecentlyPlayed": "a été joué récemment",
"isFavorited": "est favori",
"bpm": "BPM",
"releaseYear": "année de sortie",
"disc": "disque",
"biography": "biographie",
"songCount": "nombre de chansons",
"songCount": "nombre de titre",
"duration": "durée",
"random": "aléatoire",
"lastPlayed": "écouté récemment",
@@ -282,7 +293,9 @@
"isPublic": "est public",
"album": "$t(entity.album, {\"count\": 1})",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "tri par nom"
"sortName": "tri par nom",
"matchAnd": "et",
"matchOr": "ou"
},
"page": {
"sidebar": {
@@ -297,10 +310,11 @@
"home": "$t(common.home)",
"artists": "$t(entity.artist, {\"count\": 2})",
"albumArtists": "$t(entity.albumArtist, {\"count\": 2})",
"shared": "partagé $t(entity.playlist, {\"count\": 2})",
"shared": "$t(entity.playlist, {\"count\": 2}) partagée",
"myLibrary": "Bibliothèque",
"favorites": "$t(entity.favorite, {\"count\": 2})",
"radio": "$t(entity.radioStation, {\"count\": 2})"
"radio": "$t(entity.radioStation, {\"count\": 2})",
"collections": "collections"
},
"fullscreenPlayer": {
"config": {
@@ -317,7 +331,7 @@
"lyricGap": "espacement des lettres",
"dynamicIsImage": "activer l'image d'arrière-plan",
"dynamicImageBlur": "intensité du flou sur l'image d'arrière-plan",
"lyricOffset": "paroles décalées (ms)"
"lyricOffset": "décalage des paroles (ms)"
},
"upNext": "à suivre",
"lyrics": "paroles",
@@ -341,7 +355,7 @@
"commandPalette": "ouvrir la palette de commandes",
"selectMusicFolder": "sélectionner le dossier musique",
"noMusicFolder": "aucun dossier musique de sélectionner",
"multipleMusicFolders": "{{count}} dossiers musique sélectionner"
"multipleMusicFolders": "{{count}} dossiers musique sélectionnés"
},
"home": {
"mostPlayed": "Les plus joués",
@@ -361,7 +375,7 @@
"generalTab": "général",
"hotkeysTab": "raccourcis",
"windowTab": "fenêtre",
"playbackTab": "lecteur",
"playbackTab": "lecture",
"advanced": "avancé",
"analytics": "analytique",
"updates": "mise à jour",
@@ -454,7 +468,7 @@
"groupingTypePrimary": "types de parution principale",
"topSongsCommunity": "communauté",
"topSongsPersonal": "personnel",
"favoriteSongsFrom": "meilleurs titres de {{title}}"
"favoriteSongsFrom": "titres favori de {{title}}"
},
"itemDetail": {
"copyPath": "copier le chemin dans le presse-papiers",
@@ -462,7 +476,7 @@
"copiedPath": "chemin copié avec succès"
},
"playlist": {
"reorder": "le tri n'est possible que lors du tri par identifiant"
"reorder": "la réorganisation n'est possible que lors du tri par identifiant"
},
"manageServers": {
"serverDetails": "détails du serveur",
@@ -483,15 +497,20 @@
},
"releasenotes": {
"commitsSinceStable": "commits depuis {{stable}}",
"noNewCommits": "pas de nouveaux commits dans cette plage"
"noNewCommits": "pas de nouveaux commits dans cette plage",
"noStableReleaseToCompare": "aucune version stable disponible avec laquelle comparer"
},
"windowBar": {
"paused": "(Pause) ",
"privateMode": "(Mode Privé)"
},
"collections": {
"overrideExisting": "ignorer l'existant",
"saveAsCollection": "enregistrer comme collection"
}
},
"setting": {
"audioDevice_description": "sélectionnez le périphérique audio à utiliser pour la lecture (lecteur Web uniquement)",
"audioDevice_description": "sélectionnez le périphérique audio à utiliser pour la lecture",
"audioExclusiveMode_description": "activer le mode de sortie exclusif. Dans ce mode, le système est généralement verrouillé et seul mpv pourra émettre de l'audio",
"audioPlayer_description": "sélectionnez le lecteur audio à utiliser pour la lecture",
"crossfadeDuration_description": "définit la durée du fondu enchaîné",
@@ -509,7 +528,7 @@
"hotkey_skipBackward": "reculer",
"hotkey_playbackPause": "pause",
"hotkey_volumeUp": "monter le volume",
"discordIdleStatus_description": "quand activé, mettre à jour le status pendant que le lecteur est inactif",
"discordIdleStatus_description": "quand activé, mettre à jour le statut pendant que le lecteur est inactif",
"showSkipButtons": "affiche les boutons suivants et précédents",
"minimumScrobblePercentage": "durée minimal du scobble (pourcentage)",
"lyricFetch": "récupérer les paroles depuis internet",
@@ -561,7 +580,7 @@
"playbackStyle": "style de lecture",
"hotkey_toggleShuffle": "basculer la lecture aléatoire",
"playbackStyle_description": "sélectionnez le style de lecture à utiliser pour le lecteur audio",
"discordRichPresence_description": "active l'état de lecteur dans le status d'activité {{discord}}. Les images clés sont : {{icon}}, {{playing}}, et {{paused}}",
"discordRichPresence_description": "active l'état de lecteur dans le statut 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 Jouer/Pause, lors de l'ajout de titres à la file d'attente",
@@ -712,10 +731,10 @@
"preferLocalLyrics_description": "privilégier les paroles locales aux paroles distantes lorsqu'elles sont disponibles",
"preferLocalLyrics": "privilégier les paroles locales",
"discordPausedStatus_description": "quand activé, le status s'affichera lorsque le lecteur est en pause",
"discordPausedStatus": "afficher le status d'activité en pause",
"discordPausedStatus": "afficher le statut dactivité en pause",
"preservePitch": "préserver la hauteur",
"preservePitch_description": "préserver la hauteur lors du changement de la vitesse de lecture",
"discordDisplayType": "type d'affichage du status {{discord}}",
"discordDisplayType": "type d'affichage du statut {{discord}}",
"discordDisplayType_description": "modifie ce que vous écoutez dans votre statut",
"discordDisplayType_songname": "nom du morceau",
"discordDisplayType_artistname": "nom(s) dartiste",
@@ -726,8 +745,8 @@
"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",
"artistBackground": "image d'arrière-plan d'artiste",
"artistBackground_description": "ajoute une image d'arrière-plan pour les pages d'artiste contenant l'œuvre de l'artiste",
"artistBackground": "image d'arrière-plan de l'artiste",
"artistBackground_description": "ajoute une image d'arrière-plan pour les pages d'artiste contenant une image de l'artiste",
"artistBackgroundBlur": "intensité du flou sur l'image d'arrière-plan d'artiste",
"artistBackgroundBlur_description": "ajuste la quantité de flou appliquée à l'image d'arrière-plan de l'artiste",
"releaseChannel_optionLatest": "dernière",
@@ -770,8 +789,8 @@
"showLyricsInSidebar": "afficher les paroles dans la barre de lecture latérale",
"showVisualizerInSidebar_description": "un panneau sera ajouté à la barre de lecture latérale qui affiche le visualiseur",
"showVisualizerInSidebar": "afficher le visualiseur dans la barre de lecture latérale",
"audioFadeOnStatusChange": "diminution du volume sonore lors du changement d'état",
"audioFadeOnStatusChange_description": "permet le fondu enchaîné et le fondu au noir quand la lecture/pause change d'états",
"audioFadeOnStatusChange": "diminution du volume sonore lors du changement d'état du statut",
"audioFadeOnStatusChange_description": "permet le fondu enchaîné et le fondu au noir quand la lecture/pause change d'états du statut",
"queryBuilder": "constructeur de requêtes",
"queryBuilderCustomFields_inputLabel": "label",
"queryBuilderCustomFields_inputTag": "tag",
@@ -795,7 +814,7 @@
"playerFilters_description": "exclure les titres de la file d'attente selon les critères suivants",
"playerbarSlider_description": "la forme d'onde n'est pas recommandée sur une connexion lente ou limitée",
"useThemeAccentColor": "utiliser la couleur d'accent du thème",
"useThemeAccentColor_description": "utiliser la couleur principale définie dans le thème sélectionné au lieu de la couleur d'accent personnalisée",
"useThemeAccentColor_description": "utiliser la couleur principale définie dans le thème sélectionné au lieu de la couleur d'accentuation personnalisée",
"artistReleaseTypeConfiguration": "configuration du type de sortie de l'artiste",
"artistReleaseTypeConfiguration_description": "configure quel type de sortie est affiché, et dans quel ordre, sur la page artiste de l'album",
"mpvExtraParameters": "paramètres supplémentaires de mpv",
@@ -816,7 +835,31 @@
"showRatings_description": "contrôle si la notation à étoiles s'affiche dans l'interface",
"showRatings": "affiche la notation à étoiles",
"combinedLyricsAndVisualizer_description": "combine les paroles et le visualisateur dans le même panneau",
"combinedLyricsAndVisualizer": "combine les paroles et le visualisateur dans la barre latérale"
"combinedLyricsAndVisualizer": "combine les paroles et le visualisateur dans la barre latérale",
"analyticsEnable": "Envoyer des métriques d'utilisation",
"analyticsEnable_description": "Des métriques d'utilisation anonymisées sont envoyées au développeur pour aider à améliorer l'application",
"automaticUpdates": "Mises à jour automatiques",
"automaticUpdates_description": "Vérifier l'existence de mises à jour et les installer automatiquement",
"releaseChannel_optionAlpha": "alpha (toutes les nuits)",
"discordStateIcon": "afficher licône de lecture",
"discordStateIcon_description": "affiche une petite icône de lecture dans le statut d'activité. l'icône de pause est toujours affichée lorsque \"Afficher le statut d'activité en pause\" est activé",
"homeFeatureStyle_description": "contrôle le style du carousel d'accueil à la une",
"homeFeatureStyle": "style de carousel à la une de l'accueil",
"homeFeatureStyle_optionMultiple": "multiple",
"homeFeatureStyle_optionSingle": "simple",
"blurExplicitImages": "flouter les images explicites",
"blurExplicitImages_description": "les pochettes d'albums et de chansons étiquetées comme explicites seront floutées",
"enableGridMultiSelect": "activer la sélection multiple dans la grille",
"enableGridMultiSelect_description": "quand activé, permet la sélection de plusieurs entrées dans la vue en grille. quand désactivé, cliquer sur un item de la grille mène vers la page de l'entrée",
"sidebarPlaylistSorting_description": "permet le tri manuel des listes de lecture dans la barre latérale en utilisant le drag and drop plutôt que l'ordre par défaut du serveur",
"sidebarPlaylistSorting": "tri des listes de lecture dans la barre latérale",
"sidebarPlaylistListFilterRegex_description": "masquer les listes de lecture dans la barre latérale qui correspondent à cette expression régulière",
"sidebarPlaylistListFilterRegex_placeholder": "ex. ^Mix Journalier*",
"sidebarPlaylistListFilterRegex": "filtre d'expression régulière de liste de lecture",
"autosave": "sauvegarder automatiquement la file d'attente",
"autosave_description": "activez la sauvegarde automatique de la file d'attente de lecture sur votre serveur. Cette fonction est uniquement disponible avec Navidrome/Subsonic et ne permet pas d'utiliser une file d'attente mixte.",
"autosaveCount": "fréquence de sauvegarde automatique de la file d'attente",
"autosaveCount_description": "nombre de changement de piste avant la sauvegarde de la file d'attente. 1 (minimum) signifie chaque changement de titre"
},
"form": {
"deletePlaylist": {
@@ -827,17 +870,17 @@
"addServer": {
"title": "ajouter un serveur",
"input_username": "nom d'utilisateur",
"input_url": "url",
"input_url": "URL",
"input_password": "mot de passe",
"input_legacyAuthentication": "activer l'authtication legacy",
"input_legacyAuthentication": "activer l'authentification legacy",
"input_name": "nom du serveur",
"success": "serveur ajouté avec succès",
"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",
"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",
"input_preferInstantMix": "préférer le mix instantané",
"input_preferInstantMixDescription": "utiliser uniquement le mix instantané pour jouer des pistes similaires. utile si vous avez des plugins qui modifient ce comportement",
"input_preferRemoteUrl": "préférer une URL publique",
"input_remoteUrl": "URL publique",
"input_remoteUrlPlaceholder": "optionnel : URL publique pour les fonctionnalités externes"
@@ -888,7 +931,9 @@
"setExpiration": "définir une expiration",
"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é ?)"
"createFailed": "échec de la création du lien de partage (le partage est-il activé ?)",
"copyToClipboard": "Copier vers le presse-papiers : Ctrl+C, Entrer",
"successMustClick": "partage créé avec succès. cliquez ici pour ouvrir"
},
"privateMode": {
"enabled": "le mode privé est activé, le statut de lecture est maintenant caché des intégrations externes",
@@ -897,7 +942,7 @@
},
"largeFetchConfirmation": {
"title": "ajouter des entrées à la file d'attente",
"description": "Cette action ajoutera tous les éléments dans la vue filtrée actuelle"
"description": "Cette action ajoutera toutes les entrées de la vue filtrée actuelle"
},
"shuffleAll": {
"title": "jouer aléatoirement",
@@ -1017,12 +1062,14 @@
"alternateRowColors": "alterner les couleurs des lignes",
"horizontalBorders": "bordures de ligne",
"rowHoverHighlight": "surligner les lignes au survol",
"verticalBorders": "bordure de colonne"
"verticalBorders": "bordure de colonne",
"showHeader": "affiche l'en-tête"
},
"view": {
"table": "liste",
"grid": "grille",
"list": "liste"
"list": "liste",
"detail": "détail"
},
"label": {
"releaseDate": "date de sortie",
@@ -1057,7 +1104,9 @@
"genreBadge": "$t(entity.genre, {\"count\": 1}) (badges)",
"image": "image",
"bitDepth": "$t(common.bitDepth)",
"sampleRate": "$t(common.sampleRate)"
"sampleRate": "$t(common.sampleRate)",
"composer": "compositeur",
"titleArtist": "$t(common.title) (artiste)"
}
},
"column": {
@@ -1098,7 +1147,7 @@
"releaseType": {
"primary": {
"album": "$t(entity.album, {\"count\": 1})",
"broadcast": "diffuser",
"broadcast": "diffusion",
"ep": "ep",
"other": "autre",
"single": "single"
@@ -1215,8 +1264,84 @@
"minimumDecibels": "décibels minimum",
"maximumDecibels": "décibels maximum",
"linearAmplitude": "amplitude linéaire",
"linearBoost": "boost linéaire",
"peakBehavior": "comportement des piques",
"showPeaks": "afficher les piques"
"linearBoost": "Augmentation Linéaire",
"peakBehavior": "Comportement des Crêtes",
"showPeaks": "afficher les crêtes",
"fft": "FFT",
"fftSize": "Taille de la FFT",
"fadePeaks": "adoucir les crêtes",
"peakLine": "Ligne de Crête",
"gravity": "gravité",
"peakFadeTime": "temps d'adoucissement des crêtes (ms)",
"peakHoldTime": "temps d'attente des crêtes (ms)",
"radialSpectrum": "Spectre Radial",
"radial": "Radial",
"radialInvert": "Inversion Radiale",
"spinSpeed": "Vitesse de Rotation",
"radius": "Rayon",
"reflexMirror": "Miroir Réflexe",
"reflexFit": "Ajustement du Réflexe",
"reflexRatio": "Rapport de Réflexe",
"reflexAlpha": "Alpha du Réflexe",
"reflexBrightness": "Luminosité du Réflexe",
"mirror": "Mirroir",
"miscellaneousSettings": "Paramètres divers",
"alphaBars": "Barres alpha",
"ansiBands": "Bandes ANSI",
"ledBars": "Barres LED",
"trueLeds": "True LEDs",
"roundBars": "Barres arrondies",
"lowResolution": "Basse Résolution",
"showFPS": "Afficher les FPS",
"showScaleX": "Afficher l’échelle X",
"noteLabels": "Étiquettes de notes",
"showScaleY": "Afficher l’échelle Y",
"options": {
"mode": {
"0": "[0] Fréquences discrètes",
"1": "[1] 1/24ᵉ octave / 240 bandes",
"2": "[2] 1/12ᵉ octave / 120 bandes",
"3": "[3] 1/8ᵉ octave / 80 bandes",
"4": "[4] 1/6ᵉ octave / 60 bandes",
"5": "[5] 1/4ᵉ octave / 40 bandes",
"6": "[6] 1/3ᵉ octave / 30 bandes",
"7": "[7] Demi-octave / 20 bandes",
"8": "[8] Octave complète / 10 bandes",
"10": "[10] Linéaire / Graphique en aires"
},
"colorMode": {
"gradient": "Dégradé",
"barIndex": "Indice de Barre",
"barLevel": "Niveau de Barre"
},
"gradient": {
"classic": "Classique",
"prism": "Prisme",
"rainbow": "Arc-en-ciel",
"steelblue": "Bleu Acier",
"orangered": "Orange rougeâtre"
},
"channelLayout": {
"single": "Simple",
"dualCombined": "Combiné Double",
"dualHorizontal": "Double Horizontale",
"dualVertical": "Double Verticale"
},
"frequencyScale": {
"none": "Aucun",
"bark": "Échelle Bark",
"linear": "Échelle Linéaire",
"log": "Échelle Logarithmique",
"mel": "Échelle Mel"
},
"weightingFilter": {
"none": "Aucun",
"a": "A",
"b": "B",
"c": "C",
"d": "D",
"z": "Z"
}
}
}
}
+57 -11
View File
@@ -37,7 +37,8 @@
"shuffleAll": "acak semua",
"shuffleSelected": "acak yang dipilih",
"viewMore": "lihat lebih banyak",
"openApplicationDirectory": "buka direktori aplikasi"
"openApplicationDirectory": "buka direktori aplikasi",
"goToCurrent": "pergi ke item saat ini"
},
"common": {
"clear": "bersihkan",
@@ -208,7 +209,9 @@
"noNetworkDescription": "tidak dapat terhubung ke server ini",
"notificationDenied": "izin untuk notifikasi ditolak. pengaturan ini tidak berpengaruh",
"saveQueueFailed": "gagal menyimpan antrean",
"settingsSyncError": "ditemukan ketidaksesuaian antara pengaturan di perender dan proses utama. mulai ulang aplikasi untuk menerapkan perubahan"
"settingsSyncError": "ditemukan ketidaksesuaian antara pengaturan di perender dan proses utama. mulai ulang aplikasi untuk menerapkan perubahan",
"invalidJson": "JSON tidak valid",
"serverLockSingleServer": "hanya satu server yang diizinkan ketika server dikunci"
},
"filter": {
"album": "$t(entity.album, {\"count\": 1})",
@@ -254,7 +257,9 @@
"title": "judul",
"path": "path(jalur)",
"sortName": "nama pengurutan",
"explicitStatus": "$t(common.explicitStatus)"
"explicitStatus": "$t(common.explicitStatus)",
"matchAnd": "dan",
"matchOr": "atau"
},
"form": {
"addServer": {
@@ -322,7 +327,9 @@
"setExpiration": "Atur masa berlaku",
"success": "Tautan berbagi berhasil disalin ke papan klip (atau klik di sini untuk membuka)",
"expireInvalid": "Masa berlaku harus di masa depan",
"createFailed": "Tidak dapat membuat sumber daya berbagi (Apakah berbagi diaktifkan?)"
"createFailed": "Tidak dapat membuat sumber daya berbagi (Apakah berbagi diaktifkan?)",
"copyToClipboard": "Salin ke clipboard: Ctrl+C, Enter",
"successMustClick": "berbagi berhasil dibuat. klik di sini untuk membuka"
},
"updateServer": {
"success": "Server berhasil diperbarui",
@@ -376,7 +383,11 @@
"viewAllTracks": "Lihat semua $t(entity.track, {\"count\": 2})",
"appearsOn": "Tampil di",
"groupingTypeAll": "semua jenis rilis",
"groupingTypePrimary": "jenis rilis utama"
"groupingTypePrimary": "jenis rilis utama",
"favoriteSongs": "lagu favorit",
"topSongsCommunity": "komunitas",
"topSongsPersonal": "pribadi",
"favoriteSongsFrom": "lagu favorit dari {{title}}"
},
"albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})"
@@ -565,6 +576,11 @@
"collections": {
"overrideExisting": "timpa yang ada",
"saveAsCollection": "simpan sebagai koleksi"
},
"releasenotes": {
"commitsSinceStable": "commit sejak {{stable}}",
"noNewCommits": "tidak ada commit baru dalam rentang ini",
"noStableReleaseToCompare": "tidak ada rilis stabil yang tersedia untuk dibandingkan"
}
},
"player": {
@@ -606,7 +622,17 @@
"lyrics": "lirik",
"restoreQueueFromServer": "pulihkan antrean dari server",
"saveQueueToServer": "simpan antrean ke server",
"trackRadio": "radio trek"
"trackRadio": "radio trek",
"albumRadio": "radio album",
"sleepTimer": "pengatur waktu tidur",
"sleepTimer_endOfSong": "akhir lagu saat ini",
"sleepTimer_minutes": "{{count}} menit",
"sleepTimer_hours": "{{count}} jam",
"sleepTimer_custom": "kustom",
"sleepTimer_off": "mati",
"sleepTimer_timeRemaining": "{{time}} tersisa",
"sleepTimer_setCustom": "atur pengatur waktu",
"sleepTimer_cancel": "batalkan pengatur waktu"
},
"setting": {
"accentColor": "warna sorotan",
@@ -620,7 +646,7 @@
"artistConfiguration": "Pengaturan halaman artis album",
"artistConfiguration_description": "Atur elemen apa yang ditampilkan dan urutannya di halaman artis album",
"audioDevice": "perangkat audio",
"audioDevice_description": "pilih perangkat audio yang digunakan untuk pemutaran (hanya pemutar web)",
"audioDevice_description": "pilih perangkat audio yang digunakan untuk pemutaran",
"audioExclusiveMode": "mode audio eksklusif",
"audioExclusiveMode_description": "aktifkan mode audio eksklusif. Dalam mode ini, sistem biasanya diblokir, dan hanya mpv yang akan diizinkan untuk output audio",
"audioPlayer": "pemutar audio",
@@ -839,7 +865,7 @@
"releaseChannel_optionBeta": "beta",
"releaseChannel_optionLatest": "terbaru",
"releaseChannel": "kanal rilis",
"releaseChannel_description": "pilih antara rilis stabil atau rilis beta untuk pembaruan otomatis",
"releaseChannel_description": "pilih antara rilis stabil, beta, atau alpha (nightly) untuk pembaruan otomatis",
"discordDisplayType_artistname": "nama artis",
"discordDisplayType_description": "mengubah apa yang Anda dengarkan di status Anda",
"discordDisplayType_songname": "nama lagu",
@@ -947,7 +973,25 @@
"queryBuilderCustomFields_inputLabel": "label",
"queryBuilderCustomFields_inputTag": "tag",
"queryBuilderCustomFields": "kolom kustom",
"queryBuilderCustomFields_description": "tambahkan kolom kustom untuk digunakan di pembuat kueri"
"queryBuilderCustomFields_description": "tambahkan kolom kustom untuk digunakan di pembuat kueri",
"useThemePrimaryShade": "gunakan warna utama tema",
"useThemePrimaryShade_description": "gunakan warna utama yang ditentukan dalam tema yang dipilih untuk varian warna utama",
"primaryShade": "warna utama tema",
"primaryShade_description": "timpa warna utama (09) yang digunakan untuk tombol, tautan, dan elemen berwarna utama lainnya",
"analyticsEnable": "Kirim analitik berbasis penggunaan",
"analyticsEnable_description": "Data penggunaan yang dianonimkan dikirim ke pengembang untuk membantu meningkatkan aplikasi",
"automaticUpdates": "Pembaruan otomatis",
"automaticUpdates_description": "Periksa dan instal pembaruan secara otomatis",
"releaseChannel_optionAlpha": "alpha (nightly)",
"discordStateIcon": "tampilkan ikon sedang diputar",
"discordStateIcon_description": "tampilkan ikon kecil sedang diputar di status rich presence. ikon dijeda selalu ditampilkan ketika \"Tampilkan rich presence saat dijeda\" diaktifkan",
"blurExplicitImages": "buramkan gambar eksplisit",
"blurExplicitImages_description": "sampul album dan lagu yang ditandai sebagai eksplisit akan diburamkan",
"playerItemConfiguration_description": "konfigurasikan item apa yang ditampilkan, dan dalam urutan apa, pada pemutar layar penuh",
"playerItemConfiguration": "konfigurasi item pemutar",
"sidebarPlaylistListFilterRegex_description": "sembunyikan playlist di bilah sisi yang cocok dengan ekspresi reguler ini",
"sidebarPlaylistListFilterRegex_placeholder": "mis. ^Daily Mix.*",
"sidebarPlaylistListFilterRegex": "regex filter playlist"
},
"table": {
"column": {
@@ -1047,12 +1091,14 @@
"genreBadge": "$t(entity.genre, {\"count\": 1}) (lencana)",
"image": "gambar",
"sampleRate": "$t(common.sampleRate)",
"titleArtist": "$t(common.title) (artis)"
"titleArtist": "$t(common.title) (artis)",
"albumGroup": "grup album"
},
"view": {
"table": "tabel",
"grid": "kisi",
"list": "daftar"
"list": "daftar",
"detail": "detail"
}
}
},
+276 -30
View File
@@ -21,7 +21,20 @@
"openIn": {
"lastfm": "Apri in Last.fm",
"musicbrainz": "Apri in MusicBrainz"
}
},
"addOrRemoveFromSelection": "aggiungi o rimuovi dalla selezione",
"selectRangeOfItems": "seleziona un intervallo di elementi",
"createRadioStation": "crea $t(entity.radioStation, {\"count\": 1})",
"deleteRadioStation": "rimuovi $t(entity.radioStation, {\"count\": 1})",
"selectAll": "seleziona tutto",
"downloadStarted": "download di {{count}} elementi iniziato",
"moveUp": "sposta sopra",
"moveDown": "sposta in basso",
"holdToMoveToTop": "tieni premuto per muovere in cima",
"holdToMoveToBottom": "tieni premuto per muoverlo in fondo",
"moveItems": "muovi elementi",
"shuffle": "casuale",
"viewMore": "mostra di più"
},
"common": {
"backward": "indietro",
@@ -76,8 +89,8 @@
"delete": "elimina",
"forceRestartRequired": "riavvia per applicare le modifiche... chiudi la notifica per riavviare",
"setting_one": "impostazione",
"setting_many": "",
"setting_other": "",
"setting_many": "impostazioni",
"setting_other": "impostazioni",
"version": "versione",
"title": "titolo",
"filter_one": "filtro",
@@ -123,7 +136,24 @@
"trackPeak": "picco di volume del brano",
"translation": "traduzione",
"bitDepth": "bit depth (profondità di bit)",
"sampleRate": "sample rate (frequenza di campionamento)"
"sampleRate": "sample rate (frequenza di campionamento)",
"countSelected": "{{count}} selezionati",
"doNotShowAgain": "non mostrarlo di nuovo",
"view": "visualizza",
"example": "esempio",
"externalLinks": "link esterni",
"faster": "più rapido",
"filter_single": "singolo",
"noFilters": "nessun filtro configurato",
"private": "privato",
"public": "pubblico",
"retry": "riprova",
"recordLabel": "registra etichetta",
"rename": "rinomina",
"sort": "ordina",
"explicit": "esplicito",
"clean": "pulisci",
"itemsMore": "ancora {{count}}"
},
"player": {
"repeat_all": "ripeti coda",
@@ -140,7 +170,7 @@
"shuffle": "riproduzione casuale",
"playbackFetchNoResults": "nessuna canzone trovata",
"playbackFetchInProgress": "caricamento canzoni…",
"addNext": "aggiungi successivo",
"addNext": "successivo",
"playbackSpeed": "velocità di riproduzione",
"playbackFetchCancel": "ci sta mettendo un po'... chiudi la notifica per annullare",
"play": "riproduci",
@@ -152,18 +182,30 @@
"queue_moveToTop": "sposta selezionati in fondo",
"queue_moveToBottom": "sposta selezionati in cima",
"shuffle_off": "non mescolare",
"addLast": "aggiungi in coda",
"addLast": "per ultima",
"mute": "silenzia",
"skip_forward": "salta avanti",
"playSimilarSongs": "riproduci brani simili",
"viewQueue": "visualizza coda"
"viewQueue": "visualizza coda",
"holdToShuffle": "tieni premuto per la riproduzione casuale",
"lyrics": "testi",
"restoreQueueFromServer": "ripristina coda dal server",
"saveQueueToServer": "salva coda sul server",
"trackRadio": "radio della traccia",
"sleepTimer_minutes": "{{count}} minuti",
"sleepTimer_hours": "{{count}} ore",
"sleepTimer_custom": "personalizzato",
"sleepTimer_off": "spento",
"sleepTimer_timeRemaining": "{{time}} rimanente",
"sleepTimer_setCustom": "imposta timer",
"sleepTimer_cancel": "cancella timer"
},
"setting": {
"crossfadeStyle_description": "seleziona lo stile dissolvenza da usare per il player audio",
"remotePort_description": "imposta la porta del server di controllo remoto",
"hotkey_skipBackward": "salta a precedente",
"volumeWheelStep_description": "la quantità di volume da cambiare quando si scorre la rotellina del mouse sullo slider del volume",
"audioDevice_description": "seleziona il device audioda usare per la riproduzione (solo web player)",
"audioDevice_description": "seleziona il dispositivo audio da usare per la riproduzione",
"theme_description": "imposta il tema da usare per l'applicazione",
"hotkey_playbackPause": "pausa",
"hotkey_volumeUp": "alza volume",
@@ -260,7 +302,7 @@
"useSystemTheme_description": "segui le preferenze del tema definite dal sistema",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"lyricFetch_description": "ottieni testi da varie sorgenti internet",
"lyricFetchProvider_description": "seleziona i provider da dove prendere i testi. l'ordine dei provider è l'ordine in cui vengono fatte le richieste",
"lyricFetchProvider_description": "seleziona i provider dai quali prendere i testi",
"globalMediaHotkeys_description": "attiva/disattiva l'uso dei tasti media globali per controllare la riproduzione",
"customFontPath": "percorso font personalizzato",
"followLyric": "segui testo corrente",
@@ -393,7 +435,35 @@
"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"
"discordLinkType_mbz_lastfm": "{{musicbrainz}} con {{lastfm}} fallback",
"autoDJ": "auto DJ",
"autoDJ_description": "aggiungi automaticamente canzoni simili alla coda",
"autoDJ_itemCount": "conteggio elementi",
"analyticsDisable_description": "Alcuni dati anonimi sull'utilizzo vengono inviati allo sviluppatore per migliorare l'applicazione",
"artistBackground": "immagine dello sfondo dell'artista",
"releaseChannel_optionBeta": "beta",
"releaseChannel_optionLatest": "ultima",
"releaseChannel": "canale di rilascio",
"releaseChannel_description": "seleziona tra rilascio stabile, beta o alpha (nightly) per gli aggiornamenti automatici",
"discordRichPresence": "{{discord}} rich presence",
"releaseChannel_optionAlpha": "alpha (nightly)",
"automaticUpdates_description": "Controlla e installa aggiornamenti automaticamente",
"automaticUpdates": "Aggiornamenti automatici",
"exportImportSettings_notValidJSON": "il file passato non è un JSON valido",
"language": "lingua",
"logLevel_optionDebug": "debug",
"logLevel_optionError": "errore",
"logLevel_optionInfo": "info",
"pathReplace_optionRemovePrefix": "rimuovi prefisso",
"pathReplace_optionAddPrefix": "aggiungi prefisso",
"playerFilters": "Filtra canzoni dalla coda",
"imageResolution_optionHeader": "header",
"playerbarWaveformAlign_optionTop": "in cima",
"sidebarPlaylistListFilterRegex_placeholder": "es. ^Daily Mix.*",
"transcode": "abilita transcodifica",
"queryBuilderCustomFields_inputLabel": "etichetta",
"queryBuilderCustomFields_inputTag": "tag",
"queryBuilderCustomFields": "campi personalizzati"
},
"error": {
"remotePortWarning": "riavvia il server per applicare la nuova porta",
@@ -419,14 +489,17 @@
"badValue": "opzione non valida \"{{value}}\". valore inesistente",
"networkError": "si è verificato un errore di rete",
"openError": "impossibile aprire il file",
"notificationDenied": "i permessi per le notifiche non sono stati concessi. questa configurazione non ha effetto"
"notificationDenied": "i permessi per le notifiche non sono stati concessi. questa configurazione non ha effetto",
"invalidJson": "JSON non valido",
"noNetwork": "server non disponibile",
"noNetworkDescription": "impossibile connettersi al server"
},
"filter": {
"mostPlayed": "più riprodotti",
"comment": "commento",
"playCount": "numero di riproduzioni",
"recentlyUpdated": "aggiornati recentemente",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"isCompilation": "è una compilation",
"recentlyPlayed": "riprodotti recentemente",
"isRated": "è valutato",
@@ -463,7 +536,9 @@
"fromYear": "dall'anno",
"criticRating": "voto della critica",
"album": "$t(entity.album, {\"count\": 1})",
"trackNumber": "traccia"
"trackNumber": "traccia",
"matchOr": "o",
"sortName": "ordina nome"
},
"page": {
"sidebar": {
@@ -479,7 +554,10 @@
"artists": "$t(entity.artist, {\"count\": 2})",
"albumArtists": "$t(entity.albumArtist, {\"count\": 2})",
"myLibrary": "la mia libreria",
"shared": "condivisa $t(entity.playlist, {\"count\": 2})"
"shared": "condivisa $t(entity.playlist, {\"count\": 2})",
"collections": "collezioni",
"favorites": "$t(entity.favorite, {\"count\": 2})",
"radio": "$t(entity.radioStation, {\"count\": 2})"
},
"fullscreenPlayer": {
"config": {
@@ -516,7 +594,10 @@
"goBack": "torna indietro",
"goForward": "vai avanti",
"privateModeOff": "disabilita modalità privata",
"privateModeOn": "abilita modalità privata"
"privateModeOn": "abilita modalità privata",
"selectMusicFolder": "seleziona cartella con musica",
"noMusicFolder": "nessuna cartella con musica selezionata",
"multipleMusicFolders": "{{count}} cartelle con musica selezionate"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
@@ -542,7 +623,9 @@
"shareItem": "condividi elemento",
"showDetails": "mostra info",
"goToAlbum": "vai a $t(entity.album, {\"count\": 1})",
"goToAlbumArtist": "vai a $t(entity.albumArtist, {\"count\": 1})"
"goToAlbumArtist": "vai a $t(entity.albumArtist, {\"count\": 1})",
"moveItems": "$t(action.moveItems)",
"goTo": "vai a"
},
"home": {
"mostPlayed": "più riprodotti",
@@ -550,7 +633,8 @@
"title": "$t(common.home)",
"explore": "esplora dalla tua libreria",
"recentlyPlayed": "riprodotti recentemente",
"recentlyReleased": "appena pubblicato"
"recentlyReleased": "appena pubblicato",
"genres": "$t(entity.genre, {\"count\": 2})"
},
"albumDetail": {
"moreFromArtist": "di più da questo $t(entity.artist, {\"count\": 1})",
@@ -562,7 +646,20 @@
"generalTab": "generale",
"hotkeysTab": "tasti a scelta rapida",
"windowTab": "finestra",
"advanced": "avanzate"
"advanced": "avanzate",
"updates": "aggiorna",
"cache": "cache",
"application": "applicazione",
"theme": "tema",
"controls": "controlla",
"remote": "remoto",
"exportImport": "importa/esporta",
"scrobble": "scrobble",
"audio": "audio",
"lyrics": "testi",
"transcoding": "transcodifica",
"discord": "discord",
"lyricsDisplay": "mostra testi"
},
"albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})"
@@ -602,7 +699,10 @@
"topSongs": "brani migliori",
"topSongsFrom": "brani migliori da {{title}}",
"viewAll": "mostra tutto",
"viewAllTracks": "mostra tutto $t(entity.track, {\"count\": 2})"
"viewAllTracks": "mostra tutto $t(entity.track, {\"count\": 2})",
"favoriteSongs": "canzoni preferite",
"topSongsPersonal": "personale",
"favoriteSongsFrom": "canzoni preferite da {{title}}"
},
"manageServers": {
"title": "gestisci servers",
@@ -619,6 +719,16 @@
},
"playlist": {
"reorder": "riordino abilitato solo quando si ordina per id"
},
"radioList": {
"title": "stazioni radio"
},
"folderList": {
"title": "$t(entity.folder, {\"count\": 2})"
},
"collections": {
"overrideExisting": "sovrascrivi esistente",
"saveAsCollection": "salva come collezione"
}
},
"form": {
@@ -648,13 +758,17 @@
"ignoreCors": "ignora cors ($t(common.restartRequired))",
"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"
"input_preferInstantMixDescription": "usa solo mix istantaneo per ottenere canzoni simili. utile se si dispone di plugin che modificano questo comportamento",
"input_preferRemoteUrl": "preferisci url pubblico",
"input_remoteUrl": "url pubblico"
},
"addToPlaylist": {
"success": "aggiunto $t(entity.trackWithCount, {\"count\": {{message}} }) a $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "aggiungi a $t(entity.playlist, {\"count\": 1})",
"input_skipDuplicates": "salta duplicati",
"input_playlists": "$t(entity.playlist, {\"count\": 2})"
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
"create": "crea $t(entity.playlist, {\"count\": 1})",
"searchOrCreate": "cerca $t(entity.playlist, {\"count\": 2}) o digita per crearne una nuova"
},
"updateServer": {
"title": "aggiorna server",
@@ -663,7 +777,9 @@
"queryEditor": {
"input_optionMatchAll": "soddisfa tutti",
"input_optionMatchAny": "soddisfa qualsiasi",
"title": "editor di query"
"title": "editor di query",
"resetToDefault": "ripristina predefinito",
"clearFilters": "rimuovi filtri"
},
"lyricSearch": {
"input_name": "$t(common.name)",
@@ -687,6 +803,27 @@
"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"
},
"largeFetchConfirmation": {
"title": "aggiungi elementi alla coda"
},
"createRadioStation": {
"success": "stazione radio creata con successo",
"title": "crea stazione radio",
"input_name": "nome"
},
"lyricsExport": {
"export": "esporta testi",
"input_synced": "esporta testi sincronizzati",
"input_offset": "$t(setting.lyricOffset)"
},
"shuffleAll": {
"input_limit": "quante canzoni?",
"input_minYear": "dall'anno",
"input_maxYear": "all'anno",
"input_played_optionAll": "tutte le tracce",
"input_played_optionUnplayed": "solo tracce non ancora riprodotte",
"input_played_optionPlayed": "solo tracce riprodotte"
}
},
"table": {
@@ -699,12 +836,29 @@
"size": "$t(common.size)",
"followCurrentSong": "segui il brano corrente",
"itemGap": "spaziatura tra gli elementi (px)",
"itemSize": "dimensione dellelemento (px)"
"itemSize": "dimensione dellelemento (px)",
"advancedSettings": "impostazioni avanzate",
"moveUp": "muovi sopra",
"moveDown": "muovi sotto",
"pinToLeft": "fissa a sinistra",
"pinToRight": "fissa a destra",
"alignLeft": "allinea a sinistra",
"alignCenter": "allina al centro",
"alignRight": "allinea a destra",
"itemsPerRow": "elementi per riga",
"size_default": "predefinito",
"size_compact": "compatto",
"size_large": "largo",
"pagination": "paginazione",
"pagination_itemsPerPage": "elementi per pagina",
"pagination_infinite": "infinita",
"pagination_paginate": "impaginato"
},
"view": {
"table": "tabella",
"grid": "griglia",
"list": "lista"
"list": "lista",
"detail": "dettaglio"
},
"label": {
"releaseDate": "data rilascio",
@@ -724,17 +878,23 @@
"biography": "$t(common.biography)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"playCount": "numero riproduzioni",
"bitrate": "$t(common.bitrate)",
"actions": "$t(common.action_other)",
"actions": "$t(common.action, {\"count\": 2})",
"genre": "$t(entity.genre, {\"count\": 1})",
"discNumber": "numero disco",
"favorite": "$t(common.favorite)",
"year": "$t(common.year)",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"codec": "$t(common.codec)",
"songCount": "$t(entity.track, {\"count\": 2})"
"songCount": "$t(entity.track, {\"count\": 2})",
"bitDepth": "$t(common.bitDepth)",
"albumCount": "$t(entity.album, {\"count\": 2})",
"composer": "compositore",
"image": "immagine",
"sampleRate": "$t(common.sampleRate)",
"titleArtist": "$t(common.title) (artista)"
}
},
"column": {
@@ -759,9 +919,12 @@
"albumArtist": "artista album",
"path": "percorso",
"discNumber": "disco",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"size": "$t(common.size)",
"codec": "$t(common.codec)"
"codec": "$t(common.codec)",
"bitDepth": "$t(common.bitDepth)",
"sampleRate": "$t(common.sampleRate)",
"owner": "proprietario"
}
},
"entity": {
@@ -816,6 +979,89 @@
"play_other": "{{count}} riproduzioni",
"song_one": "traccia",
"song_many": "tracce",
"song_other": "tracce"
"song_other": "tracce",
"radioStation_one": "stazione radio",
"radioStation_many": "stazioni radio",
"radioStation_other": "stazioni radio",
"radioStationWithCount_one": "{{count}} stazione radio",
"radioStationWithCount_many": "{{count}} stazioni radio",
"radioStationWithCount_other": "{{count}} stazioni radio"
},
"datetime": {
"minuteShort": "m",
"secondShort": "s",
"hourShort": "o",
"dayShort": "g"
},
"filterOperator": {
"contains": "contiene",
"endsWith": "finisce con",
"inPlaylist": "è in",
"is": "è",
"isNot": "non è",
"isGreaterThan": "è più grande di",
"isLessThan": "è meno di",
"notContains": "non contiene",
"notInPlaylist": "non è in",
"startsWith": "inizia con"
},
"releaseType": {
"primary": {
"album": "$t(entity.album, {\"count\": 1})",
"broadcast": "broadcast",
"ep": "ep",
"other": "altro",
"single": "singolo"
},
"secondary": {
"audiobook": "audiolibro",
"audioDrama": "audio drama",
"compilation": "compilation",
"interview": "intervista",
"live": "live",
"mixtape": "mixtape",
"remix": "remix",
"soundtrack": "soundtrack"
}
},
"queryBuilder": {
"customTags": "tag personalizzati"
},
"dragDropZone": {
"error_oneFileOnly": "Per favore seleziona solo 1 file",
"error_readingFile": "errore nella lettura del file: {{errorMessage}}",
"mainText": "rilascia un file qui"
},
"visualizer": {
"ignoredPresets": "Preset Ignorati",
"includeAllPresets": "Includi Tutti i Preset",
"selectedPresets": "Preset Selezionati",
"presets": "Preset",
"selectPreset": "Seleziona Preset",
"applyPreset": "Applica Preset",
"saveAsPreset": "Salva come Preset",
"updatePreset": "Aggiorna Preset",
"copyConfiguration": "Copia Configurazione",
"pasteConfiguration": "Incolla Configurazione",
"pasteConfigurationPlaceholder": "Incolla la configurazione JSON qui...",
"pasteFromClipboard": "Incolla dalla Clipboard",
"applyConfiguration": "Applica Configurazione",
"presetName": "Nome Preset",
"presetNamePlaceholder": "Inserisci il nome del preset",
"general": "Generale",
"mode": "Modalità",
"maxFPS": "Max FPS",
"opacity": "Opacità",
"customGradients": "Gradienti Personalizzati",
"gradientNamePlaceholder": "Nome Gradiente",
"vertical": "Verticale",
"horizontal": "Orizzontale",
"addColor": "Aggiungi Colore",
"position": "Posizione",
"level": "Livello",
"remove": "Rimuovi",
"pasteGradient": "Incolla Gradiente",
"custom": "Personalizzato",
"gradient": "Gradiente"
}
}
+44 -17
View File
@@ -30,12 +30,21 @@
"skip_forward": "次へスキップ",
"pause": "一時停止",
"playSimilarSongs": "似たような曲を再生する",
"viewQueue": "キューを表示する",
"viewQueue": "キューを表示",
"lyrics": "歌詞",
"restoreQueueFromServer": "サーバーからキューを復元",
"saveQueueToServer": "サーバーにキューを保存",
"addLastShuffled": "最後 (シャッフル)",
"addNextShuffled": "次 (シャッフル)"
"addNextShuffled": "次 (シャッフル)",
"sleepTimer_minutes": "{{count}} 分",
"sleepTimer_hours": "{{count}} 時間",
"sleepTimer": "スリープタイマー",
"sleepTimer_endOfSong": "現在の曲の終わり",
"sleepTimer_custom": "カスタム",
"sleepTimer_off": "オフ",
"sleepTimer_timeRemaining": "残り {{time}}",
"sleepTimer_setCustom": "タイマーを設定",
"sleepTimer_cancel": "タイマーをキャンセル"
},
"setting": {
"crossfadeStyle_description": "オーディオプレーヤーが使用するクロスフェードのスタイルを選択します",
@@ -43,7 +52,7 @@
"hotkey_skipBackward": "前にスキップ",
"replayGainMode_description": "ファイルのメタデータに保存されている {{ReplayGain}} 値に従って音量ゲインを調整します",
"volumeWheelStep_description": "音量スライダーでマウスホイールをスクロールしたときに変化する音量を設定します",
"audioDevice_description": "再生に使用するオーディオデバイスを選択します (Web プレーヤーのみ)",
"audioDevice_description": "再生に使用するオーディオデバイスを選択します",
"theme_description": "アプリケーションに使用するテーマを設定します",
"hotkey_playbackPause": "一時停止",
"replayGainFallback": "{{ReplayGain}} フォールバック",
@@ -73,7 +82,7 @@
"scrobble_description": "再生した音楽をメディアサーバーから Scrobble します",
"hotkey_browserForward": "ブラウザ 進む",
"audioExclusiveMode_description": "排他出力モードを有効にします。このモードでは、システムの他の出力がロックされ、MPV のみがオーディオを出力できるようになります",
"discordUpdateInterval": "{{discord}} Rich Presenceアップデート間隔",
"discordUpdateInterval": "{{discord}} Rich Presence の更新間隔",
"themeLight": "テーマ (ライト)",
"fontType_optionBuiltIn": "組み込みフォント",
"hotkey_playbackPlayPause": "再生 / 一時停止",
@@ -100,7 +109,7 @@
"hotkey_favoritePreviousSong": "$t(common.previousSong) をお気に入り",
"replayGainMode_optionAlbum": "$t(entity.album, {\"count\": 1})",
"lyricOffset": "歌詞のオフセット (ミリ秒)",
"discordUpdateInterval_description": "更新間隔 (秒単位, 最小15秒)",
"discordUpdateInterval_description": "更新間隔 (秒単位最小 15 秒)",
"fontType_optionCustom": "カスタムフォント",
"themeDark_description": "アプリケーションに使用するダークテーマを設定します",
"audioExclusiveMode": "オーディオ排他モード",
@@ -246,8 +255,8 @@
"artistConfiguration_description": "アルバムアーティストページに表示するアイテムと順序を設定します",
"buttonSize_description": "プレーヤーバーのボタンのサイズ",
"buttonSize": "プレーヤーバーのボタンサイズ",
"clearCache_description": "Feishin の「ハードクリア」。Feishin のキャッシュをクリアするだけでなく、ブラウザのキャッシュ (保存された画像やその他のアセット) も空にします。サーバーの資格情報や設定は保持されます",
"clearCache": "ブラウザのキャッシュをクリアする",
"clearCache_description": "Feishin の「ハードクリア」。Feishin のキャッシュをクリアするだけでなく、ブラウザのキャッシュ (保存された画像やその他のアセット) も空にします。サーバーの資格情報や設定は保持されます",
"clearCache": "ブラウザのキャッシュをクリア",
"clearCacheSuccess": "キャッシュが正常にクリアされました",
"clearQueryCache_description": "Feishin の「ソフトクリア」。これにより、プレイリストとトラックのメタデータが更新され、保存された歌詞がリセットされます。設定、サーバーの資格情報、キャッシュされた画像は保持されます",
"clearQueryCache": "Feishin のキャッシュをクリアする",
@@ -262,11 +271,11 @@
"releaseChannel_optionBeta": "ベータ",
"releaseChannel_optionLatest": "最新",
"releaseChannel": "リリースチャンネル",
"releaseChannel_description": "自動更新のために安定版リリースまたはベータ版リリース選択してください",
"releaseChannel_description": "自動更新のために安定版、ベータ版、またはアルファ版 (nightly build) リリースから選択してください",
"discordDisplayType_artistname": "アーティスト名",
"discordDisplayType_songname": "曲名",
"discordLinkType_description": "{{discord}} Rich Presence において、曲とアーティストのフィールドに {{lastfm}} または {{musicbrainz}} への外部リンクを追加します。{{musicbrainz}} は最も正確ですが、タグが必要でアーティストリンクを提供しません。一方、{{lastfm}} は常にリンクを提供します。追加のネットワークリクエストは発生しません",
"discordPausedStatus": "一時停止時に Rich Presence を表示する",
"discordPausedStatus": "一時停止時に Rich Presence を表示",
"discordRichPresence": "{{discord}} Rich Presence",
"discordServeImage_description": "{{discord}} Rich Presence 用のカバーアートをサーバーから共有します。Jellyfin と Navidrome でのみ利用できます。{{discord}} は bot を使用して画像を取得するため、サーバーはパブリックインターネットからアクセスできる必要があります",
"exportImportSettings_control_exportText": "設定をエクスポート",
@@ -290,7 +299,7 @@
"discordDisplayType": "{{discord}} Presence 表示タイプ",
"discordLinkType_none": "$t(common.none)",
"discordListening_description": "Playing ではなく Listening としてステータスを表示します",
"discordListening": "ステータスを Listening として表示する",
"discordListening": "ステータスを Listening として表示",
"discordPausedStatus_description": "有効にすると、プレーヤーが一時停止されているときにもステータスを表示します",
"discordDisplayType_description": "ステータスで聴いている内容を変更します",
"discordLinkType": "{{discord}} Presence リンク",
@@ -359,7 +368,14 @@
"pathReplace": "ファイルパスの置換",
"pathReplace_description": "サーバーのデフォルトのファイルパスを置き換えます",
"pathReplace_optionRemovePrefix": "接頭辞を削除",
"pathReplace_optionAddPrefix": "接頭辞を追加"
"pathReplace_optionAddPrefix": "接頭辞を追加",
"analyticsEnable": "使用状況に基づく分析を送信する",
"analyticsEnable_description": "匿名化された利用データは、アプリケーションの改善のために開発者に送信されます",
"automaticUpdates": "自動更新",
"automaticUpdates_description": "更新を自動的に確認してインストールします",
"releaseChannel_optionAlpha": "アルファ (nightly)",
"discordStateIcon": "再生中アイコンを表示",
"discordStateIcon_description": "Rich Presence ステータスに小さな再生アイコンを表示します。「一時停止時に Rich Presence を表示」が有効になっている場合は、常に一時停止アイコンが表示されます"
},
"action": {
"editPlaylist": "$t(entity.playlist, {\"count\": 1}) を編集",
@@ -648,7 +664,9 @@
"noNetwork": "サーバーが利用できません",
"noNetworkDescription": "このサーバーに接続できませんでした",
"saveQueueFailed": "キューを保存できませんでした",
"settingsSyncError": "レンダラーとメインプロセスの設定に矛盾が見つかりました。変更を適用するにはアプリケーションを再起動してください"
"settingsSyncError": "レンダラーとメインプロセスの設定に矛盾が見つかりました。変更を適用するにはアプリケーションを再起動してください",
"invalidJson": "無効な JSON",
"serverLockSingleServer": "サーバーがロックされている場合、1 つのサーバーのみが許可されます"
},
"filter": {
"mostPlayed": "最も多く再生",
@@ -745,7 +763,7 @@
"manageServers": "サーバーの管理",
"expandSidebar": "サイドバーを展開",
"collapseSidebar": "サイドバーを折りたたむ",
"openBrowserDevtools": "ブラウザの開発者ツールを開く",
"openBrowserDevtools": "ブラウザの開発者ツールを開く",
"quit": "$t(common.quit)",
"goBack": "戻る",
"goForward": "進む",
@@ -858,12 +876,15 @@
"recentReleases": "最近のリリース",
"viewDiscography": "ディスコグラフィーを見る",
"topSongs": "人気曲",
"topSongsFrom": "{{title}} からの人気曲",
"topSongsFrom": "{{title}} の人気曲",
"viewAll": "すべて表示",
"viewAllTracks": "$t(entity.track, {\"count\": 2}) をすべて表示",
"relatedArtists": "関連の $t(entity.artist, {\"count\": 2})",
"groupingTypeAll": "すべてのリリースタイプ",
"groupingTypePrimary": "主なリリースタイプ"
"groupingTypePrimary": "主なリリースタイプ",
"favoriteSongs": "お気に入りの曲",
"topSongsCommunity": "コミュニティ",
"favoriteSongsFrom": "{{title}} のお気に入りの曲"
},
"manageServers": {
"title": "サーバーの管理",
@@ -897,6 +918,11 @@
"collections": {
"overrideExisting": "既存のものを上書き",
"saveAsCollection": "コレクションとして保存"
},
"releasenotes": {
"commitsSinceStable": "{{stable}} 以降のコミット",
"noNewCommits": "この範囲に新しいコミットはありません",
"noStableReleaseToCompare": "比較可能な安定版リリースはありません"
}
},
"form": {
@@ -995,7 +1021,7 @@
"shuffleAll": {
"title": "ランダムに再生",
"input_genre": "$t(entity.genre, {\"count\": 1})",
"input_limit": "曲が多すぎます",
"input_limit": "何曲?",
"input_minYear": "年から",
"input_maxYear": "年まで",
"input_played_optionAll": "すべてのトラック",
@@ -1083,6 +1109,7 @@
"notInTheLast": "より前"
},
"visualizer": {
"visualizerType": "ビジュアライザーの種類"
"visualizerType": "ビジュアライザーの種類",
"colors": "色"
}
}
+146 -17
View File
@@ -21,7 +21,23 @@
"removeFromQueue": "fjern fra kø",
"removeFromPlaylist": "fjern fra $t(entity.playlist, {\"count\": 1})",
"viewPlaylists": "vise $t(entity.playlist, {\"count\": 2})",
"toggleSmartPlaylistEditor": "bytt $t(entity.smartPlaylist) editor"
"toggleSmartPlaylistEditor": "bytt $t(entity.smartPlaylist) editor",
"selectAll": "marker alle",
"downloadStarted": "startet nedlasting av {{count}} elementer",
"selectRangeOfItems": "velg en rekke elementer",
"addOrRemoveFromSelection": "legge til eller fjerne fra utvalg",
"moveUp": "flytt opp",
"moveDown": "flytt ned",
"createRadioStation": "opprett $t(entity.radioStation, {\"count\": 1})",
"deleteRadioStation": "slett $t(entity.radioStation, {\"count\": 1})",
"holdToMoveToTop": "hold nede for å gå til toppen",
"holdToMoveToBottom": "hold nede for å gå til bunnen",
"moveItems": "flytt elementer",
"shuffle": "tilfeldig avspilling",
"shuffleAll": "tilfelding avspilling av alt",
"shuffleSelected": "tilfelding avspilling av utvalgte",
"viewMore": "se mer",
"openApplicationDirectory": "åpne applikasjonskatalogen"
},
"common": {
"bpm": "bpm",
@@ -50,8 +66,8 @@
"expand": "utvid",
"favorite": "favoritt",
"filters": "filter",
"forceRestartRequired": "ta omstart for å la endringene trå i kraft... lukk meldingen for å ta omstart",
"forward": "fremover",
"forceRestartRequired": "ta omstart for å aktivere endringene... lukk meldingen for å ta omstart",
"forward": "framover",
"gap": "avstand",
"home": "hjem",
"increase": "øke",
@@ -88,7 +104,7 @@
"quit": "avslutt",
"size": "størrelse",
"setting_one": "innstilling",
"setting_other": "",
"setting_other": "innstillinger",
"trackNumber": "spor",
"title": "tittel",
"channel_one": "kanal",
@@ -119,7 +135,31 @@
"albumGain": "gjennomsnittlig lydnivå for album",
"albumPeak": "høyeste lydnivå for album",
"bitDepth": "bitdybde",
"sampleRate": "samplingsfrekvens"
"sampleRate": "samplingsfrekvens",
"countSelected": "{{count}} valgt",
"doNotShowAgain": "ikke vis dette igjen",
"view": "vis",
"example": "eksempel",
"externalLinks": "eksterne lenker",
"faster": "raskere",
"filter_single": "enkelt",
"filter_multiple": "flerfoldige",
"mood": "humør",
"noFilters": "ingen filtre konfigurert",
"private": "privat",
"public": "offentlig",
"retry": "prøv igjen",
"recordLabel": "plateselskap",
"releaseType": "utgivelsestype",
"rename": "gi nytt navn",
"slower": "saktere",
"sort": "sorter",
"explicit": "grov",
"clean": "ren",
"gridRows": "rutenettrader",
"tableColumns": "tabellkolonner",
"itemsMore": "{{count}} fler",
"explicitStatus": "grovhetsstatus"
},
"entity": {
"smartPlaylist": "smart $t(entity.playlist, {\"count\": 1})",
@@ -156,7 +196,11 @@
"artist_one": "artist",
"artist_other": "artister",
"song_one": "sang",
"song_other": "sanger"
"song_other": "sanger",
"radioStation_one": "radiostasjon",
"radioStation_other": "radiostasjoner",
"radioStationWithCount_one": "{{count}} radiostasjon",
"radioStationWithCount_other": "{{count}} radiostasjoner"
},
"error": {
"apiRouteError": "kan ikke behandle forespørselen",
@@ -166,23 +210,30 @@
"endpointNotImplementedError": "endepunkt {{endpoint}} er ikke implementert for {{serverType}}",
"credentialsRequired": "innloggingsdetaljer er påkrevd",
"genericError": "en feil har oppstått",
"invalidServer": "ugyldig server",
"invalidServer": "ugyldig tjener",
"playbackError": "et problem oppstod ved avspilling av media",
"localFontAccessDenied": "ingen tilgang til lokale skrifttyper",
"loginRateError": "for mange innloggingsforsøk, vennligst prøv igjen om noen få sekunder",
"audioDeviceFetchError": "en feil oppstod ved innhenting av lydenheter",
"networkError": "at nettverksproblem har oppstått",
"networkError": "det har oppstått et nettverksproblem",
"openError": "kunne ikke åpne fil",
"serverNotSelectedError": "ingen server er valgt",
"serverNotSelectedError": "ingen tjener er valgt",
"remotePortError": "et problem oppstod med å sette serverport",
"systemFontError": "et problem oppstod med innlasting av systemskrifttyper",
"serverRequired": "server er påkrevd",
"serverRequired": "tjener er påkrevd",
"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",
"notificationDenied": "tillatelser for varsler ble avvist. Denne innstillingen har ingen effekt",
"badValue": "ugyldig alternativ \"{{value}}\". Denne verdien eksisterer ikke lenger"
"badValue": "ugyldig alternativ \"{{value}}\". Denne verdien eksisterer ikke lenger",
"noNetwork": "tjener utilgjengelig",
"noNetworkDescription": "kunne ikke koble til tjeneren",
"invalidJson": "ugyldig JSON",
"saveQueueFailed": "kunne ikke lagre kø",
"multipleServerSaveQueueError": "Spillekøen har en eller flere sanger som ikke finnes på gjeldene tjener. Dette er ikke støttet",
"serverLockSingleServer": "kun én tjener er tillatt når tjener er låst",
"settingsSyncError": "avvik ble funnet mellom innstillinger i avspilleren og hovedprosessen. ta en omstart av applikasjonen for å aktivere endringene"
},
"filter": {
"bpm": "bpm",
@@ -223,10 +274,14 @@
"isCompilation": "er samling",
"isFavorited": "er merket som favoritt",
"recentlyAdded": "nylig lagt til",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"genre": "$t(entity.genre, {\"count\": 1})",
"trackNumber": "spor",
"albumCount": "$t(entity.album, {\"count\": 2}) opptelling"
"albumCount": "$t(entity.album, {\"count\": 2}) opptelling",
"matchAnd": "og",
"matchOr": "eller",
"sortName": "sorter navn",
"explicitStatus": "$t(common.explicitStatus)"
},
"form": {
"createPlaylist": {
@@ -253,7 +308,12 @@
"input_legacyAuthentication": "aktiver tradisjonell autentisering",
"input_name": "servernavn",
"title": "legg til server",
"input_password": "passord"
"input_password": "passord",
"input_preferInstantMix": "foretrekk øyeblikkelig miks",
"input_preferInstantMixDescription": "bruk bare øyeblikkelig miks for innhenting av lignende sanger. nyttig hvis du har tilleggsmoduler som endrer funksjonaliteten",
"input_preferRemoteUrl": "foretrekk offentlig url",
"input_remoteUrl": "offentlig url",
"input_remoteUrlPlaceholder": "valgfritt: offentlig nettadresse for eksterne funksjoner"
},
"addToPlaylist": {
"success": "la $t(entity.trackWithCount, {\"count\": {{message}} }) til $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -285,7 +345,29 @@
"queryEditor": {
"input_optionMatchAll": "match alle",
"input_optionMatchAny": "matche hvilken som helst",
"title": "redigeringsverktøy for spørringer"
"title": "redigeringsverktøy for spørringer",
"addRuleGroup": "legg til regelgruppe",
"removeRuleGroup": "fjern regelgruppe",
"resetToDefault": "tilbakestill til standard",
"clearFilters": "tøm filter"
},
"largeFetchConfirmation": {
"title": "legg til elementer i køen",
"description": "Denne handlingen vil legge alle elementene til den gjeldende filtervisningen"
},
"createRadioStation": {
"success": "vellykket opprettelse av radiostasjon",
"title": "opprett radiostasjon",
"input_homepageUrl": "hjemmesidelenke",
"input_name": "navn",
"input_streamUrl": "strømmelenke"
},
"saveQueue": {
"success": "lagre spillekø på tjener"
},
"lyricsExport": {
"export": "eksporter sangtekster",
"input_synced": "eksporter sunkroniserte sangtekster"
}
},
"page": {
@@ -501,7 +583,15 @@
"general": {
"autoFitColumns": "automatisk kolonnetilpasning",
"displayType": "visningstype",
"followCurrentSong": "følg gjeldende sang"
"followCurrentSong": "følg gjeldende sang",
"advancedSettings": "avanserte innstillinger",
"moveUp": "flytt opp",
"moveDown": "flytt ned",
"pinToLeft": "fest til venstre",
"pinToRight": "fest til høyre",
"alignLeft": "venstrejuster",
"alignCenter": "midtjuster",
"alignRight": "høyrejuster"
}
},
"column": {
@@ -519,7 +609,46 @@
"rating": "vurdering",
"releaseDate": "utgivelsesdato",
"title": "tittel",
"trackNumber": "spor"
"trackNumber": "spor",
"owner": "eier"
}
},
"filterOperator": {
"after": "er etter",
"afterDate": "er etter (date)",
"before": "er før",
"beforeDate": "er før (date)",
"contains": "inneholder",
"endsWith": "ender med",
"inPlaylist": "er inne i",
"inTheLast": "er inne i de siste",
"inTheRange": "er innenfor området",
"isGreaterThan": "er større enn",
"isLessThan": "er mindre enn",
"matchesRegex": "samsvarer med regex",
"notContains": "inneholder ikke",
"notInPlaylist": "er ikke med i",
"notInTheLast": "er ikke med i de siste",
"startsWith": "starter med",
"inTheRangeDate": "er innenfor området (date)",
"is": "er",
"isNot": "er ikke"
},
"datetime": {
"minuteShort": "m",
"secondShort": "s",
"hourShort": "h",
"dayShort": "d"
},
"visualizer": {
"options": {
"weightingFilter": {
"a": "A",
"b": "B",
"c": "C",
"d": "D",
"z": "Z"
}
}
}
}
+39 -11
View File
@@ -169,7 +169,7 @@
"comment": "commentaar",
"playCount": "aantal keer afgespeeld",
"recentlyUpdated": "recentelijk geüpdate",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"isCompilation": "is compilatie",
"recentlyPlayed": "recentelijk afgespeeld",
"isRated": "is rated",
@@ -205,7 +205,9 @@
"toYear": "tot jaar",
"trackNumber": "track",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "sorteernaam"
"sortName": "sorteernaam",
"matchAnd": "en",
"matchOr": "of"
},
"page": {
"contextMenu": {
@@ -412,6 +414,11 @@
"collections": {
"overrideExisting": "bestaande overschrijven",
"saveAsCollection": "sla op als verzameling"
},
"releasenotes": {
"commitsSinceStable": "commits sinds {{stable}}",
"noNewCommits": "geen nieuwe commits in dit bereik",
"noStableReleaseToCompare": "geen stabiele uitgave beschikbaar om mee te vergelijken"
}
},
"error": {
@@ -443,7 +450,9 @@
"noNetwork": "server niet beschikbaar",
"noNetworkDescription": "kan geen verbinding maken met deze server",
"saveQueueFailed": "kan wachtrij niet opslaan",
"settingsSyncError": "Er zijn verschillen gevonden tussen de instellingen in de renderer en het hoofdproces. Start de applicatie opnieuw op om de wijzigingen toe te passen"
"settingsSyncError": "Er zijn verschillen gevonden tussen de instellingen in de renderer en het hoofdproces. Start de applicatie opnieuw op om de wijzigingen toe te passen",
"invalidJson": "ongeldige JSON",
"serverLockSingleServer": "slechts één server is toegestaan als server op slot is gezet"
},
"entity": {
"genre_one": "genre",
@@ -589,7 +598,8 @@
"view": {
"grid": "grid",
"list": "lijst",
"table": "tabel"
"table": "tabel",
"detail": "details"
}
}
},
@@ -603,7 +613,7 @@
"discordListening_description": "status weergeven als luisterend in plaats van afspelend",
"discordListening": "status weergeven als 'luisterend'",
"discordPausedStatus_description": "wanneer ingeschakeld, wordt de status ook weergegeven als de speler gepauzeerd is",
"discordPausedStatus": "rich presence tonen wanneer gepauseerd",
"discordPausedStatus": "rich presence tonen wanneer gepauzeerd",
"discordRichPresence": "{{discord}} rich presence",
"exitToTray_description": "sluit de applicatie naar het systeemvak",
"exitToTray": "sluit naar systeemvak",
@@ -659,7 +669,7 @@
"artistConfiguration_description": "configureer welke items worden getoond op de albumartiestpagina en in welke volgorde",
"artistReleaseTypeConfiguration": "configuratie artiestuitgavesoorten",
"artistReleaseTypeConfiguration_description": "configureer welke uitgavesoorten worden getoond op de albumartiestpagina en in welke volgorde",
"audioDevice_description": "kies het audioapparaat dat wordt gebruikt om af te spelen (enkel voor de webspeler)",
"audioDevice_description": "kies het audioapparaat dat wordt gebruikt om af te spelen",
"audioDevice": "audioapparaat",
"audioExclusiveMode_description": "schakel exclusieve uitvoermodus in. In deze modus wordt het systeem normaliter uitgesloten en zal enkel mpv audio kunnen uitvoeren",
"audioExclusiveMode": "audio-exclusieve modus",
@@ -688,7 +698,7 @@
"releaseChannel_optionBeta": "beta",
"releaseChannel_optionLatest": "meest recente",
"releaseChannel": "releasekanaal",
"releaseChannel_description": "kies tussen stabiele releases of beta-releases voor automatische updates",
"releaseChannel_description": "kies tussen stabiele, of beta- of alpha-releases (nachtelijk) voor automatische updates",
"disableLibraryUpdateOnStartup": "niet controleren op nieuwe versies bij het opstarten",
"discordApplicationId_description": "de applicatie-id voor {{discord}} rich presence (standaard is {{defaultId}})",
"hotkey_listPlayNow": "nu in lijst spelen",
@@ -728,7 +738,7 @@
"lastfmApiKey": "{{lastfm}}-API-sleutel",
"lyricFetch_description": "bevraag verschillende bronnen op het internet voor songteksten",
"lyricFetch": "haal songteksten op van het internet",
"lyricFetchProvider_description": "kies de diensten die geraadpleegd worden voor songteksten. de volgorde van de diensten is tevens de volgorde waarop deze worden geraadpleegd",
"lyricFetchProvider_description": "kies de diensten die geraadpleegd worden voor songteksten",
"lyricFetchProvider": "diensten voor songteksten",
"lyricOffset_description": "compenseer de songtekst met het gegeven aantal milliseconden",
"lyricOffset": "compensatie songtekst (ms)",
@@ -937,7 +947,14 @@
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"replayGainMode_optionAlbum": "$t(entity.album, {\"count\": 1})",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track, {\"count\": 1})"
"replayGainMode_optionTrack": "$t(entity.track, {\"count\": 1})",
"analyticsEnable": "Verstuur gebruiksstatistieken",
"analyticsEnable_description": "Geanonimiseerde gebruiksgegevens worden naar de ontwikkelaar gestuurd om te helpen de applicatie te verbeteren",
"automaticUpdates": "Automatisch bijwerken",
"automaticUpdates_description": "Zoek en installeer updates automatisch",
"releaseChannel_optionAlpha": "alfa (nachtelijk)",
"discordStateIcon": "toon afspeelicoon",
"discordStateIcon_description": "toon een klein afspeelicoon in de rich-presence-status. het gepauzeerde icoon wordt altijd getoond als \"rich presence tonen wanneer gepauzeerd\" is ingeschakeld"
},
"form": {
"addServer": {
@@ -1009,7 +1026,9 @@
"setExpiration": "stel vervaldatum in",
"success": "deel link gekopiëerd naar klembord (of klik hier om te openen)",
"expireInvalid": "vervaldatum moet in de toekomst zijn",
"createFailed": "kon share niet aanmaken (is delen aangezet?)"
"createFailed": "kon share niet aanmaken (is delen aangezet?)",
"copyToClipboard": "Kopieer naar klembord: Ctrl+C, Enter",
"successMustClick": "gedeelde bron succesvol aangemaakt. klik hier om te openen"
},
"privateMode": {
"enabled": "private modus ingeschakeld, afspeelstatus is nu verborgen voor externe integraties",
@@ -1086,7 +1105,16 @@
"trackRadio": "nummerradio",
"unfavorite": "verwijder favoriet",
"pause": "pauzeren",
"viewQueue": "toon wachtrij"
"viewQueue": "toon wachtrij",
"sleepTimer": "slaaptimer",
"sleepTimer_endOfSong": "einde van huidige nummer",
"sleepTimer_minutes": "{{count}} min",
"sleepTimer_hours": "{{count}} uur",
"sleepTimer_custom": "afwijkend",
"sleepTimer_off": "uit",
"sleepTimer_timeRemaining": "{{time}} resterend",
"sleepTimer_setCustom": "timer instellen",
"sleepTimer_cancel": "timer annuleren"
},
"datetime": {
"minuteShort": "m",
+26 -10
View File
@@ -37,7 +37,8 @@
"addOrRemoveFromSelection": "dodaj lub usuń z wyboru",
"selectRangeOfItems": "wybierz zakres elementów",
"selectAll": "wybierz wszystkie",
"openApplicationDirectory": "otwórz katalog aplikacji"
"openApplicationDirectory": "otwórz katalog aplikacji",
"goToCurrent": "przejdź do aktualnego elementu"
},
"common": {
"increase": "zwiększ",
@@ -254,7 +255,10 @@
"saveQueueFailed": "nie udało się zapisać kolejki",
"settingsSyncError": "zostały znalezione różnice pomiędzy ustawieniami w rendererze a głównym procesem. uruchom aplikację ponownie aby, zastosować zmiany",
"noNetwork": "serwer niedostępny",
"noNetworkDescription": "nie udało się połączyć z tym serwerem"
"noNetworkDescription": "nie udało się połączyć z tym serwerem",
"invalidJson": "nieprawidłowy JSON",
"serverLockSingleServer": "dozwolony jest tylko jeden serwer gdy serwer jest zablokowany",
"playbackPausedDueToError": "odtwarzanie zostało wstrzymane z powodu błędu"
},
"filter": {
"mostPlayed": "najczęściej odtwarzane",
@@ -300,7 +304,9 @@
"isPublic": "jest publiczny",
"album": "$t(entity.album, {\"count\": 1})",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "sortowanie po nazwie"
"sortName": "sortowanie po nazwie",
"matchAnd": "i",
"matchOr": "lub"
},
"form": {
"deletePlaylist": {
@@ -372,7 +378,9 @@
"setExpiration": "ustaw czas wygaśnięcia",
"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"
"expireInvalid": "ustawiony czas wygaśnięcia musi być w przyszłości",
"copyToClipboard": "Skopiuj do schowka: Ctrl+C, Enter",
"successMustClick": "udostępnianie utworzone pomyślnie, kliknij tutaj żeby otworzyć"
},
"privateMode": {
"enabled": "tryb prywatny włączony, status odtwarzania jest ukryty przed usługami zewnętrznymi",
@@ -670,12 +678,13 @@
"sleepTimer_off": "wyłączony",
"sleepTimer_timeRemaining": "pozostało {{time}}",
"sleepTimer_setCustom": "ustaw wyłącznik",
"sleepTimer_cancel": "anuluj wyłączanie"
"sleepTimer_cancel": "anuluj wyłączanie",
"albumRadio": "radio albumu"
},
"setting": {
"crossfadeStyle_description": "wybierz styl przenikania, który ma być używany do odtwarzania dźwięku",
"hotkey_skipBackward": "przeskocz do tyłu",
"audioDevice_description": "wybierz urządzenie dźwiękowe używane do odtwarzania (tylko odtwarzacz przeglądarkowy)",
"audioDevice_description": "wybierz urządzenie dźwiękowe używane do odtwarzania",
"hotkey_playbackPause": "wstrzymaj",
"hotkey_volumeUp": "podgłoś",
"discordIdleStatus_description": "kiedy włączony, aktualizuje stan kiedy odtwarzacz jest bezczynny",
@@ -1020,10 +1029,16 @@
"releaseChannel_optionAlpha": "alpha (nightly)",
"analyticsEnable": "Wysyłaj statystyki na podstawie użytkowania",
"analyticsEnable_description": "Zanonimizowane statystki użytkowania będą wysyłane do twórcy, aby pomóc w poprawie aplikacji",
"automaticUpdates": "Aktualizacje automatyczne",
"automaticUpdates_description": "Sprawdzaj i instaluj aktualizacje automatycznie",
"automaticUpdates": "Automatyczne aktualizacje",
"automaticUpdates_description": "Automatycznie sprawdzaj i instaluj aktualizacje",
"discordStateIcon": "pokaż ikonę odtwarzania",
"discordStateIcon_description": "pokazuje małą ikonę odtwarzania w statusie. ikona pauzy jest zawsze pokazywana gdy \"Pokaż status podczas pauzy\" jest włączone"
"discordStateIcon_description": "pokazuje małą ikonę odtwarzania w statusie. ikona pauzy jest zawsze pokazywana gdy \"Pokaż status podczas pauzy\" jest włączone",
"useThemePrimaryShade": "użyj głównego odcienia motywu",
"useThemePrimaryShade_description": "używaj głównego odcienia zdefiniowanego w wybranym motywie dla głównych wariantów kolorów",
"primaryShade": "główny odcień",
"primaryShade_description": "nadpisz główny odcień (0-9) używany dla przycisków, linków i innych głównie pokolorowanych elementów",
"playerItemConfiguration_description": "skonfiguruj jakie elementy są pokazywane i w jakiej kolejności, w odtwarzaczu pełnoekranowym",
"playerItemConfiguration": "konfiguracja elementów odtwarzacza"
},
"table": {
"config": {
@@ -1100,7 +1115,8 @@
"bitDepth": "$t(common.bitDepth)",
"sampleRate": "$t(common.sampleRate)",
"composer": "kompozytor",
"titleArtist": "$t(common.title) (wykonawca)"
"titleArtist": "$t(common.title) (wykonawca)",
"albumGroup": "grupa albumu"
}
},
"column": {
+53 -12
View File
@@ -329,7 +329,9 @@
"noNetwork": "сервер недоступен",
"noNetworkDescription": "Не удалось подключиться к серверу",
"saveQueueFailed": "Не удалось сохранить очередь",
"settingsSyncError": "обнаружены несоответствия между настройками рендерера и основным процессом. перезапустите приложение, чтобы изменения вступили в силу"
"settingsSyncError": "обнаружены несоответствия между настройками рендерера и основным процессом. перезапустите приложение, чтобы изменения вступили в силу",
"invalidJson": "невалидный JSON",
"serverLockSingleServer": "при заблокированном сервере разрешается использовать только один сервер"
},
"filter": {
"isCompilation": "сборник",
@@ -373,7 +375,11 @@
"lastPlayed": "последний раз проигрывалась",
"toYear": "до года",
"album": "$t(entity.album, {\"count\": 1})",
"trackNumber": "трек"
"trackNumber": "трек",
"matchAnd": "и",
"matchOr": "или",
"sortName": "сортировка по имени",
"explicitStatus": "$t(common.explicitStatus)"
},
"player": {
"repeat_all": "повторять все",
@@ -414,7 +420,15 @@
"lyrics": "тексты песен",
"restoreQueueFromServer": "восстановить очередь с сервера",
"saveQueueToServer": "сохранить очередь на сервер",
"trackRadio": "трек радио"
"trackRadio": "трек радио",
"albumRadio": "Радио по альбому",
"sleepTimer": "таймер сна",
"sleepTimer_endOfSong": "конец текущей песни",
"sleepTimer_minutes": "{{count}} минут",
"sleepTimer_hours": "{{count}} часов",
"sleepTimer_off": "выключено",
"sleepTimer_timeRemaining": "{{time}} осталось",
"sleepTimer_setCustom": "установить таймер"
},
"page": {
"sidebar": {
@@ -431,7 +445,9 @@
"albumArtists": "$t(entity.albumArtist, {\"count\": 2})",
"myLibrary": "Моя библиотека",
"shared": "Публичные плейлисты $t(entity.playlist, {\"count\": 2})",
"collections": "коллекции"
"collections": "коллекции",
"favorites": "$t(entity.favorite, {\"count\": 2})",
"radio": "$t(entity.radioStation, {\"count\": 2})"
},
"fullscreenPlayer": {
"config": {
@@ -504,7 +520,11 @@
"shareItem": "поделиться",
"goToAlbum": "Перейти к $t(entity.album, {\"count\": 1})",
"goToAlbumArtist": "Перейти к $t(entity.albumArtist, {\"count\": 1})",
"goTo": "перейти в"
"goTo": "перейти в",
"moveItems": "$t(action.moveItems)",
"moveToNext": "$t(action.moveToNext)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"playShuffled": "$t(player.shuffle)"
},
"home": {
"mostPlayed": "слушают чаще всего",
@@ -512,7 +532,8 @@
"title": "$t(common.home)",
"explore": "откройте новое",
"recentlyPlayed": "игралось недавно",
"recentlyReleased": "Новинки"
"recentlyReleased": "Новинки",
"genres": "$t(entity.genre, {\"count\": 2})"
},
"albumDetail": {
"moreFromArtist": "больше от $t(entity.artist, {\"count\": 1})",
@@ -540,7 +561,9 @@
"transcoding": "транскодирование",
"scrobble": "скробблер",
"logger": "Отладка",
"playerFilters": "фильтры проигрывателя"
"playerFilters": "фильтры проигрывателя",
"queryBuilder": "конструктор очереди",
"discord": "discord"
},
"albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})"
@@ -585,7 +608,11 @@
"about": "О {{artist}}",
"topSongsFrom": "популярные треки из {{title}}",
"groupingTypeAll": "все типы выпусков",
"groupingTypePrimary": "основные типы выпусков"
"groupingTypePrimary": "основные типы выпусков",
"favoriteSongs": "любимые треки",
"topSongsCommunity": "сообщество",
"topSongsPersonal": "личное",
"favoriteSongsFrom": "любимые треки от {{title}}"
},
"itemDetail": {
"copyPath": "скопировать путь в буфер обмена",
@@ -596,10 +623,21 @@
"title": "радиостанции"
},
"windowBar": {
"privateMode": "(Режим приватности)"
"privateMode": "(Режим приватности)",
"paused": "(Приостановлено) "
},
"collections": {
"saveAsCollection": "сохранить коллекцией"
"saveAsCollection": "сохранить коллекцией",
"overrideExisting": "переопределить существующий"
},
"releasenotes": {
"commitsSinceStable": "коммито после {{stable}}"
},
"favorites": {
"title": "$t(entity.favorite, {\"count\": 2})"
},
"folderList": {
"title": "$t(entity.folder, {\"count\": 2})"
}
},
"form": {
@@ -672,7 +710,9 @@
"createFailed": "не удалось создать ссылку для общего доступа (проверьте, включен ли общий доступ?)",
"allowDownloading": "разрешить скачивание",
"setExpiration": "установить срок действия",
"description": "описание"
"description": "описание",
"copyToClipboard": "Скопировано в буфер обмена: Ctrl+C, Enter",
"successMustClick": "Ссылка создана успешо. Нажимите, чтобы окрыть"
},
"privateMode": {
"enabled": "Приватный режим включен. Статус воспроизведения скрыт от внешних интеграций",
@@ -706,7 +746,8 @@
"input_played": "воспроизвести фильтр",
"input_played_optionAll": "все треки",
"input_played_optionUnplayed": "только не игранные треки",
"input_played_optionPlayed": "только игранные треки"
"input_played_optionPlayed": "только игранные треки",
"input_genre": "$t(entity.genre, {\"count\": 1})"
}
},
"setting": {
+592 -40
View File
@@ -21,7 +21,23 @@
"openIn": {
"lastfm": "Last.fm இல் திறந்திருக்கும்",
"musicbrainz": "மியூசிக் பிரைன்ச் திறந்திருக்கும்"
}
},
"addOrRemoveFromSelection": "தேர்வில் இருந்து சேர்க்கவும் அல்லது நீக்கவும்",
"selectRangeOfItems": "உருப்படிகளின் வரம்பைத் தேர்ந்தெடுக்கவும்",
"createRadioStation": "$t(entity.radioStation, {\"count\": 1}) உருவாக்கவும்",
"deleteRadioStation": "$t(entity.radioStation, {\"count\": 1}) நீக்கு",
"selectAll": "அனைத்தையும் தெரிவுசெய்",
"downloadStarted": "{{count}} உருப்படிகளின் பதிவிறக்கம் தொடங்கியது",
"moveUp": "மேலே செல்ல",
"moveDown": "கீழே நகர",
"holdToMoveToTop": "மேலே செல்ல அழுத்திப் பிடிக்கவும்",
"holdToMoveToBottom": "கீழே நகர்த்த பிடிக்கவும்",
"moveItems": "பொருட்களை நகர்த்த",
"shuffle": "கலக்கு",
"shuffleAll": "அனைத்தையும் கலக்கவும்",
"shuffleSelected": "கலக்கு தேர்ந்தெடுக்கப்பட்டது",
"viewMore": "மேலும் பார்க்க",
"openApplicationDirectory": "பயன்பாட்டு கோப்பகத்தைத் திறக்கவும்"
},
"common": {
"description": "விவரம்",
@@ -67,7 +83,7 @@
"expand": "விரிவாக்கு",
"favorite": "பிடித்த",
"filter_one": "வடிப்பி",
"filter_other": "வடிப்பான்கள்",
"filter_other": "வடிகட்டிகள்",
"filters": "வடிப்பான்கள்",
"forceRestartRequired": "மாற்றங்களைப் பயன்படுத்த மறுதொடக்கம் செய்… மறுதொடக்கம் செய்ய அறிவிப்பை மூடு",
"forward": "முன்னோக்கி",
@@ -101,7 +117,7 @@
"saveAndReplace": "சேமித்து மாற்றவும்",
"search": "தேடல்",
"setting_one": "அமைத்தல்",
"setting_other": "",
"setting_other": "அமைப்புகள்",
"share": "பங்கு",
"size": "அளவு",
"sortOrder": "ஒழுங்கு",
@@ -119,7 +135,31 @@
"viewReleaseNotes": "வெளியீட்டு குறிப்புகளைக் காண்க",
"bitDepth": "பிட் ஆழம்",
"sampleRate": "மாதிரி வீதம்",
"tags": "குறிச்சொற்கள்"
"tags": "குறிச்சொற்கள்",
"countSelected": "{{count}} தேர்ந்தெடுக்கப்பட்டது",
"explicitStatus": "வெளிப்படையான நிலை",
"doNotShowAgain": "இதை மீண்டும் காட்டாதே",
"view": "பார்வை",
"example": "சான்று",
"externalLinks": "வெளிப்புற இணைப்புகள்",
"faster": "வேகமாக",
"filter_single": "ஒற்றை",
"filter_multiple": "பல",
"mood": "மனநிலை",
"noFilters": "வடிப்பான்கள் எதுவும் கட்டமைக்கப்படவில்லை",
"private": "தனிப்பட்ட",
"public": "பொது",
"retry": "மீண்டும் முயற்சிக்கவும்",
"recordLabel": "பதிவு சிட்டை",
"releaseType": "வெளியீட்டு வகை",
"rename": "மறுபெயரிடுங்கள்",
"slower": "மெதுவாக",
"sort": "வரிசைப்படுத்து",
"explicit": "வெளிப்படையான",
"clean": "தூய்மையான",
"gridRows": "கட்டம் வரிசைகள்",
"tableColumns": "அட்டவணை நெடுவரிசைகள்",
"itemsMore": "மேலும் {{count}}"
},
"entity": {
"folderWithCount_one": "{{count}} கோப்புறை",
@@ -128,8 +168,8 @@
"genre_other": "வகைகள்",
"genreWithCount_one": "{{count}} வகை",
"genreWithCount_other": "{{count}} வகைகள்",
"album_one": "செருகேடு",
"album_other": "செருகேடுகள்",
"album_one": "ஆல்பம்",
"album_other": "ஆல்பம்",
"albumArtist_one": "ஆல்பம் கலைஞர்",
"albumArtist_other": "ஆல்பம் கலைஞர்கள்",
"albumArtistCount_one": "{{count}} ஆல்பம் கலைஞர்",
@@ -140,13 +180,13 @@
"artist_other": "கலைஞர்கள்",
"artistWithCount_one": "{{count}} கலைஞர்",
"artistWithCount_other": "{{count}} கலைஞர்கள்",
"favorite_one": "பிடித்த",
"favorite_one": "பிடித்தது",
"favorite_other": "பிடித்தவை",
"folder_one": "கோப்புறை",
"folder_other": "கோப்புறைகள்",
"playlist_one": "பிளேலிச்ட்",
"playlist_other": "பிளேலிச்ட்கள்",
"play_one": "{{count}} நாடகம்",
"play_one": "{{count}} விளையாடு",
"play_other": "{{count}} நாடகங்கள்",
"playlistWithCount_one": "{{count}} பிளேலிச்ட்",
"playlistWithCount_other": "{{count}} பிளேலிச்ட்கள்",
@@ -155,8 +195,12 @@
"track_other": "தடங்கள்",
"song_one": "பாடல்",
"song_other": "பாடல்கள்",
"trackWithCount_one": "{{count}} டிராக்",
"trackWithCount_other": "{{count}} தடங்கள்"
"trackWithCount_one": "{{count}} தடம்",
"trackWithCount_other": "{{count}} தடங்கள்",
"radioStation_one": "வானொலி நிலையம்",
"radioStation_other": "வானொலி நிலையங்கள்",
"radioStationWithCount_one": "{{count}} வானொலி நிலையம்",
"radioStationWithCount_other": "{{count}} வானொலி நிலையங்கள்"
},
"error": {
"mpvRequired": "MPV தேவை",
@@ -182,7 +226,14 @@
"sessionExpiredError": "உங்கள் அமர்வு காலாவதியானது",
"systemFontError": "கணினி எழுத்துருக்களைப் பெற முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"badValue": "தவறான விருப்பம் \"{{value}}\". இந்த மதிப்பு இனி இல்லை",
"notificationDenied": "அறிவிப்புகளுக்கான அனுமதிகள் மறுக்கப்பட்டன. இந்த அமைப்பு எந்த விளைவையும் ஏற்படுத்தாது"
"notificationDenied": "அறிவிப்புகளுக்கான அனுமதிகள் மறுக்கப்பட்டன. இந்த அமைப்பு எந்த விளைவையும் ஏற்படுத்தாது",
"invalidJson": "தவறான சாதொபொகு",
"multipleServerSaveQueueError": "நாடக வரிசையில் ஒன்று அல்லது அதற்கு மேற்பட்ட பாடல்கள் உள்ளன, அவை தற்போதைய சேவையகத்திலிருந்து இல்லை. இது ஆதரிக்கப்படவில்லை",
"noNetwork": "சர்வர் கிடைக்கவில்லை",
"noNetworkDescription": "இந்த சேவையகத்துடன் இணைக்க முடியவில்லை",
"saveQueueFailed": "வரிசையைச் சேமிக்க முடியவில்லை",
"serverLockSingleServer": "சேவையகம் பூட்டப்பட்டிருக்கும் போது ஒரு சேவையகம் மட்டுமே அனுமதிக்கப்படும்",
"settingsSyncError": "ரெண்டரரில் உள்ள அமைப்புகளுக்கும் முக்கிய செயல்முறைக்கும் இடையே முரண்பாடுகள் கண்டறியப்பட்டன. மாற்றங்களைப் பயன்படுத்த பயன்பாட்டை மறுதொடக்கம் செய்யுங்கள்"
},
"filter": {
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
@@ -191,7 +242,7 @@
"biography": "சுயசரிதை",
"bitrate": "பிட்ரேட்",
"bpm": "பிபிஎம்",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"comment": "கருத்து",
"communityRating": "சமூக மதிப்பீடு",
"path": "பாதை",
@@ -226,7 +277,11 @@
"songCount": "பாடல் எண்ணிக்கை",
"title": "தலைப்பு",
"toYear": "ஆண்டு",
"trackNumber": "மின்தடம்"
"trackNumber": "மின்தடம்",
"matchAnd": "மற்றும்",
"matchOr": "அல்லது",
"sortName": "வரிசை பெயர்",
"explicitStatus": "$t(common.explicitStatus)"
},
"form": {
"addServer": {
@@ -240,7 +295,12 @@
"input_url": "முகவரி",
"input_username": "பயனர்பெயர்",
"success": "சேவையகம் வெற்றிகரமாக சேர்க்கப்பட்டது",
"title": "சேவையகத்தைச் சேர்க்கவும்"
"title": "சேவையகத்தைச் சேர்க்கவும்",
"input_preferInstantMix": "உடனடி கலவையை விரும்புகிறது",
"input_preferInstantMixDescription": "ஒரே மாதிரியான பாடல்களைப் பெற உடனடி கலவையை மட்டுமே பயன்படுத்தவும். இந்த நடத்தையை மாற்றும் செருகுநிரல்கள் உங்களிடம் இருந்தால் பயனுள்ளதாக இருக்கும்",
"input_preferRemoteUrl": "பொது முகவரி ஐ விரும்பு",
"input_remoteUrl": "பொது முகவரி",
"input_remoteUrlPlaceholder": "விருப்பத்தேர்வு: வெளிப்புற அம்சங்களுக்கான பொது முகவரி"
},
"deletePlaylist": {
"input_confirm": "உறுதிப்படுத்த $t(entity.playlist, {\"count\": 1}) பெயரைத் தட்டச்சு செய்க",
@@ -250,7 +310,8 @@
"editPlaylist": {
"title": "திருத்து $t(entity.playlist, {\"count\": 1})",
"publicJellyfinNote": "சில காரணங்களால் செல்லிஃபின் ஒரு பிளேலிச்ட் பொதுவில் இல்லையா என்பதை அம்பலப்படுத்தவில்லை. இது பொதுவில் இருக்க விரும்பினால், தயவுசெய்து பின்வரும் உள்ளீட்டைத் தேர்ந்தெடுக்கவும்",
"success": "$t(entity.playlist, {\"count\": 1}) வெற்றிகரமாகப் புதுப்பிக்கப்பட்டது"
"success": "$t(entity.playlist, {\"count\": 1}) வெற்றிகரமாகப் புதுப்பிக்கப்பட்டது",
"editNote": "பெரிய பிளேலிச்ட்களுக்கு கைமுறை திருத்தங்கள் பரிந்துரைக்கப்படவில்லை. ஏற்கனவே உள்ள பிளேலிச்ட்டை மேலெழுதுவதால் ஏற்படும் தரவு இழப்பின் அபாயத்தை நிச்சயமாக ஏற்றுக்கொள்கிறீர்களா?"
},
"lyricSearch": {
"input_artist": "$t(entity.artist, {\"count\": 1})",
@@ -260,7 +321,11 @@
"queryEditor": {
"input_optionMatchAll": "அனைத்தையும் பொருத்துங்கள்",
"input_optionMatchAny": "எந்த பொருத்தவும்",
"title": "வினவல் ஆசிரியர்"
"title": "வினவல் ஆசிரியர்",
"addRuleGroup": "விதி குழுவைச் சேர்க்கவும்",
"removeRuleGroup": "விதி குழுவை அகற்று",
"resetToDefault": "இயல்புநிலைக்கு மீட்டமைக்கவும்",
"clearFilters": "தெளிவான வடிகட்டிகள்"
},
"shareItem": {
"description": "விவரம்",
@@ -268,7 +333,9 @@
"expireInvalid": "காலாவதி எதிர்காலத்தில் இருக்க வேண்டும்",
"allowDownloading": "பதிவிறக்க அனுமதிக்கவும்",
"success": "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்ட இணைப்பைப் பகிரவும் (அல்லது திறக்க இங்கே சொடுக்கு செய்க)",
"createFailed": "பங்கை உருவாக்கத் தவறிவிட்டது (பகிர்வு இயக்கப்பட்டதா?)"
"createFailed": "பங்கை உருவாக்கத் தவறிவிட்டது (பகிர்வு இயக்கப்பட்டதா?)",
"copyToClipboard": "இடைநிலைப்பலகைக்கு நகலெடு: Ctrl+C, உள்ளிடவும்",
"successMustClick": "பகிர்வு வெற்றிகரமாக உருவாக்கப்பட்டது. திறக்க இங்கே சொடுக்கு செய்யவும்"
},
"createPlaylist": {
"success": "$t(entity.playlist, {\"count\": 1}) வெற்றிகரமாக உருவாக்கப்பட்டது",
@@ -282,11 +349,48 @@
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
"input_skipDuplicates": "நகல்களைத் தவிர்க்கவும்",
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) இதற்கு $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} }) சேர்க்கப்பட்டது",
"title": "$t(entity.playlist, {\"count\": 1}) இல் சேர்"
"title": "$t(entity.playlist, {\"count\": 1}) இல் சேர்",
"create": "$t(entity.playlist, {\"count\": 1}) {{playlist}} உருவாக்கு",
"searchOrCreate": "$t(entity.playlist, {\"count\": 2}) தேடவும் அல்லது புதிய ஒன்றை உருவாக்க தட்டச்சு செய்யவும்"
},
"updateServer": {
"success": "சேவையகம் வெற்றிகரமாக புதுப்பிக்கப்பட்டது",
"title": "புதுப்பிப்பு சேவையகம்"
},
"largeFetchConfirmation": {
"title": "பொருட்களை வரிசையில் சேர்க்கவும்",
"description": "இந்தச் செயல் தற்போதைய வடிகட்டப்பட்ட காட்சியில் உள்ள அனைத்து உருப்படிகளையும் சேர்க்கும்"
},
"createRadioStation": {
"success": "வானொலி நிலையம் வெற்றிகரமாக உருவாக்கப்பட்டது",
"title": "வானொலி நிலையத்தை உருவாக்குங்கள்",
"input_homepageUrl": "முகப்பு முகவரி",
"input_name": "பெயர்",
"input_streamUrl": "ச்ட்ரீம் முகவரி"
},
"lyricsExport": {
"export": "ஏற்றுமதி பாடல் வரிகள்",
"input_synced": "ஒத்திசைக்கப்பட்ட பாடல் வரிகளை ஏற்றுமதி செய்யவும்",
"input_offset": "$t(setting.lyricOffset)"
},
"saveQueue": {
"success": "சேவையகத்தில் விளையாடும் வரிசை சேமிக்கப்பட்டது"
},
"shuffleAll": {
"title": "சீரற்ற விளையாட",
"input_genre": "$t(entity.genre, {\"count\": 1})",
"input_limit": "எத்தனை பாடல்கள்?",
"input_minYear": "ஆண்டு முதல்",
"input_maxYear": "ஆண்டுக்கு",
"input_played": "விளையாடு வடிகட்டி",
"input_played_optionAll": "அனைத்து தடங்கள்",
"input_played_optionUnplayed": "இயக்கப்படாத தடங்கள் மட்டுமே",
"input_played_optionPlayed": "டிராக்குகளை மட்டுமே இயக்கியது"
},
"privateMode": {
"enabled": "தனிப்பட்ட பயன்முறை இயக்கப்பட்டது, பின்னணி நிலை இப்போது வெளிப்புற ஒருங்கிணைப்புகளிலிருந்து மறைக்கப்பட்டுள்ளது",
"disabled": "தனிப்பட்ட பயன்முறை முடக்கப்பட்டுள்ளது, இயக்கப்பட்ட வெளிப்புற ஒருங்கிணைப்புகளுக்கு இப்போது பின்னணி நிலை தெரியும்",
"title": "தனிப்பட்ட முறை"
}
},
"page": {
@@ -299,7 +403,13 @@
"viewAllTracks": "அனைத்தையும் காண்க $t(entity.track, {\"count\": 2})",
"relatedArtists": "தொடர்புடைய $t(entity.artist, {\"count\": 2})",
"topSongsFrom": "{{title}} இலிருந்து சிறந்த பாடல்கள்",
"viewAll": "அனைத்தையும் காண்க"
"viewAll": "அனைத்தையும் காண்க",
"favoriteSongs": "பிடித்த பாடல்கள்",
"groupingTypeAll": "அனைத்து வகையான வெளியீடுகள்",
"groupingTypePrimary": "முதன்மை வெளியீட்டு வகைகள்",
"topSongsCommunity": "சமூகம்",
"topSongsPersonal": "தனிப்பட்ட",
"favoriteSongsFrom": "{{title}} இலிருந்து பிடித்த பாடல்கள்"
},
"appMenu": {
"goBack": "திரும்பிச் செல்லுங்கள்",
@@ -311,7 +421,13 @@
"quit": "$t(common.quit)",
"selectServer": "சேவையகத்தைத் தேர்ந்தெடுக்கவும்",
"settings": "$t(common.setting, {\"count\": 2})",
"version": "பதிப்பு {{version}}"
"version": "பதிப்பு {{version}}",
"commandPalette": "கட்டளை தட்டு திறக்க",
"privateModeOff": "தனிப்பட்ட பயன்முறையை அணைக்கவும்",
"privateModeOn": "தனிப்பட்ட பயன்முறையை இயக்கவும்",
"selectMusicFolder": "இசை கோப்புறையைத் தேர்ந்தெடுக்கவும்",
"noMusicFolder": "இசை கோப்புறை எதுவும் தேர்ந்தெடுக்கப்படவில்லை",
"multipleMusicFolders": "{{count}} இசை கோப்புறைகள் தேர்ந்தெடுக்கப்பட்டன"
},
"manageServers": {
"url": "முகவரி",
@@ -343,7 +459,11 @@
"showDetails": "தகவலைப் பெறுங்கள்",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)"
"createPlaylist": "$t(action.createPlaylist)",
"moveItems": "$t(action.moveItems)",
"goTo": "செல்",
"goToAlbum": "$t(entity.album, {\"count\": 1}) க்குச் செல்",
"goToAlbumArtist": "$t(entity.albumArtist, {\"count\": 1}) க்குச் செல்"
},
"fullscreenPlayer": {
"config": {
@@ -386,7 +506,9 @@
"mostPlayed": "அதிகம் விளையாடியது",
"newlyAdded": "புதிதாக சேர்க்கப்பட்ட வெளியீடுகள்",
"recentlyPlayed": "அண்மைக் காலத்தில் விளையாடியது",
"title": "$t(common.home)"
"title": "$t(common.home)",
"genres": "$t(entity.genre, {\"count\": 2})",
"recentlyReleased": "அண்மைக் காலத்தில் வெளியானது"
},
"itemDetail": {
"copyPath": "இடைநிலைப்பலகைக்கு பாதையை நகலெடுக்கவும்",
@@ -404,7 +526,25 @@
"generalTab": "பொது",
"hotkeysTab": "ஆட்கீச்",
"playbackTab": "பின்னணி",
"windowTab": "சாளரம்"
"windowTab": "சாளரம்",
"analytics": "பகுப்பாய்வு",
"updates": "புதுப்பிப்பு",
"cache": "தற்காலிக சேமிப்பு",
"application": "விண்ணப்பம்",
"queryBuilder": "வினவல் கட்டுபவர்",
"theme": "கருப்பொருள்",
"controls": "கட்டுப்பாடுகள்",
"sidebar": "பக்கப்பட்டி",
"remote": "தொலைவில்",
"exportImport": "இறக்குமதி/ஏற்றுமதி",
"scrobble": "சுருள்",
"audio": "ஆடியோ",
"lyrics": "பாடல் வரிகள்",
"lyricsDisplay": "பாடல் வரிகள் காட்சி",
"transcoding": "டிரான்ச்கோடிங்",
"discord": "முரண்பாடு",
"logger": "மரம் வெட்டுபவர்",
"playerFilters": "பிளேயர் வடிப்பான்கள்"
},
"sidebar": {
"folders": "$t(entity.folder, {\"count\": 2})",
@@ -419,7 +559,10 @@
"artists": "$t(entity.artist, {\"count\": 2})",
"shared": "$t(entity.playlist, {\"count\": 2}) பகிரப்பட்டது",
"tracks": "$t(entity.track, {\"count\": 2})",
"myLibrary": "எனது நூலகம்"
"myLibrary": "எனது நூலகம்",
"collections": "சேகரிப்புகள்",
"favorites": "$t(entity.favorite, {\"count\": 2})",
"radio": "$t(entity.radioStation, {\"count\": 2})"
},
"trackList": {
"title": "$t(entity.track, {\"count\": 2})",
@@ -438,11 +581,33 @@
"artistAlbums": "ஆல்பங்கள் {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album, {\"count\": 2})",
"title": "$t(entity.album, {\"count\": 2})"
},
"radioList": {
"title": "வானொலி நிலையங்கள்"
},
"releasenotes": {
"commitsSinceStable": "{{stable}} முதல் உறுதியளிக்கிறது",
"noNewCommits": "இந்த வரம்பில் புதிய பொறுப்புகள் எதுவும் இல்லை",
"noStableReleaseToCompare": "ஒப்பிடுவதற்கு நிலையான வெளியீடு இல்லை"
},
"favorites": {
"title": "$t(entity.favorite, {\"count\": 2})"
},
"windowBar": {
"paused": "(இடைநிறுத்தப்பட்டது) ",
"privateMode": "(தனிப்பட்ட முறை)"
},
"folderList": {
"title": "$t(entity.folder, {\"count\": 2})"
},
"collections": {
"overrideExisting": "ஏற்கனவே உள்ளதை மேலெழுதவும்",
"saveAsCollection": "சேகரிப்பாக சேமிக்கவும்"
}
},
"player": {
"addLast": "கடைசியாக சேர்க்கவும்",
"addNext": "அடுத்து சேர்க்கவும்",
"addLast": "கடைசி",
"addNext": "அடுத்து",
"favorite": "பிடித்த",
"mute": "ஒலிமுடக்கு",
"muted": "முடக்கிய",
@@ -455,7 +620,7 @@
"repeat": "மீண்டும்",
"repeat_all": "அனைத்தையும் மீண்டும் செய்யவும்",
"repeat_off": "முடக்கப்பட்டதை மீண்டும் செய்யவும்",
"shuffle": "விளையாட்டு மாற்றப்பட்டது",
"shuffle": "விளையாடு (குலைக்கப்பட்டது)",
"shuffle_off": "கலக்கு முடக்கப்பட்டது",
"skip": "தவிர்",
"playbackFetchCancel": "இது சிறிது நேரம் ஆகும்… ரத்து செய்ய அறிவிப்பை மூடு",
@@ -471,7 +636,25 @@
"toggleFullscreenPlayer": "முழுத்திரை பிளேயரை மாற்றவும்",
"unfavorite": "மாறாத",
"pause": "இடைநிறுத்தம்",
"viewQueue": "வரிசையைக் காண்க"
"viewQueue": "வரிசையைக் காண்க",
"addLastShuffled": "கடைசியாக (குறைக்கப்பட்டது)",
"addNextShuffled": "அடுத்தது (குலைக்கப்பட்டது)",
"albumRadio": "ஆல்பம் வானொலி",
"artistRadio": "கலைஞர் வானொலி",
"holdToShuffle": "கலக்க பிடி",
"lyrics": "பாடல் வரிகள்",
"restoreQueueFromServer": "சேவையகத்திலிருந்து வரிசையை மீட்டமை",
"saveQueueToServer": "சேவையகத்தில் வரிசையைச் சேமிக்கவும்",
"trackRadio": "டிராக் ரேடியோ",
"sleepTimer": "தூக்க நேரம்",
"sleepTimer_endOfSong": "தற்போதைய பாடலின் முடிவு",
"sleepTimer_minutes": "{{count}} மணித்துளி",
"sleepTimer_hours": "{{count}} மணி",
"sleepTimer_custom": "தனிப்பயன்",
"sleepTimer_off": "அணை",
"sleepTimer_timeRemaining": "{{time}} மீதமுள்ளது",
"sleepTimer_setCustom": "டைமரை அமைக்கவும்",
"sleepTimer_cancel": "நேரங்குறிகருவி ரத்து"
},
"setting": {
"accentColor": "உச்சரிப்பு நிறம்",
@@ -480,7 +663,7 @@
"applicationHotkeys": "பயன்பாட்டு ஆட்கீச்",
"applicationHotkeys_description": "பயன்பாட்டு ஆட்கீசை உள்ளமைக்கவும். உலகளாவிய ஆட்ச்கியாக அமைக்க தேர்வுப்பெட்டியை மாற்றவும் (டெச்க்டாப் மட்டும்)",
"artistConfiguration": "ஆல்பம் கலைஞர் பக்க உள்ளமைவு",
"audioDevice_description": "பிளேபேக்கிற்கு பயன்படுத்த ஆடியோ சாதனத்தைத் தேர்ந்தெடுக்கவும் (வெப் பிளேயர் மட்டும்)",
"audioDevice_description": "பிளேபேக்கிற்குப் பயன்படுத்த ஆடியோ சாதனத்தைத் தேர்ந்தெடுக்கவும்",
"audioExclusiveMode": "ஆடியோ பிரத்தியேக பயன்முறை",
"audioPlayer": "ஆடியோ பிளேயர்",
"audioPlayer_description": "பிளேபேக்கிற்கு பயன்படுத்த ஆடியோ பிளேயரைத் தேர்ந்தெடுக்கவும்",
@@ -531,7 +714,7 @@
"lastfmApiKey": "{{lastfm}} பநிஇ key",
"lastfmApiKey_description": "{{lastfm}} க்கான பநிஇ விசை. கவர் கலைக்குத் தேவை",
"lyricFetch": "இணையத்திலிருந்து வரிகளை பெறுங்கள்",
"lyricFetchProvider_description": "பாடல் பெற வழங்குநர்களைத் தேர்ந்தெடுக்கவும். வழங்குநர்களின் வரிசை அவர்கள் வினவப்படும் ஒழுங்கு",
"lyricFetchProvider_description": "பாடல் வரிகளைப் பெற வழங்குநர்களைத் தேர்ந்தெடுக்கவும்",
"lyricOffset": "பாடல் ஆஃப்செட் (எம்.எச்)",
"minimizeToTray": "தட்டில் குறைக்கவும்",
"minimumScrobblePercentage": "குறைந்தபட்ச துணிச்சல் காலம் (சதவீதம்)",
@@ -687,7 +870,7 @@
"discordPausedStatus": "இடைநிறுத்தப்படும்போது பணக்கார இருப்பைக் காட்டுங்கள்",
"discordPausedStatus_description": "இயக்கப்பட்டால், பிளேயர் இடைநிறுத்தப்படும்போது நிலை காண்பிக்கப்படும்",
"discordServeImage": "சேவையகத்திலிருந்து {{discord}} படங்களை பரிமாறவும்",
"discordServeImage_description": "{{discord}} சேவையகத்திலிருந்தே பணக்கார இருப்புக்கான கவர் கலையைப் பகிரவும், செலிஃபின் மற்றும் நுடிட்ரோம மட்டுமே கிடைக்கும்",
"discordServeImage_description": "சேவையகத்திலிருந்தே {{discord}} சிறந்த இருப்புக்கான கவர் ஆர்ட்டைப் பகிரவும், செல்லிஃபின் மற்றும் நிட்ரோமுக்கு மட்டுமே கிடைக்கும். படங்களைப் பெற {{discord}} ஒரு போட்டைப் பயன்படுத்துகிறது, எனவே உங்கள் சர்வர் பொது இணையத்திலிருந்து அணுகக்கூடியதாக இருக்க வேண்டும்",
"preferLocalLyrics": "உள்ளக பாடல்களை விரும்புங்கள்",
"preferLocalLyrics_description": "கிடைக்கும்போது தொலைநிலை பாடல்களை விட உள்ளக பாடல்களை விரும்புங்கள்",
"lastfm": "last.fm இணைப்புகளைக் காட்டு",
@@ -697,7 +880,134 @@
"neteaseTranslation": "நெட்ச் மொழிபெயர்ப்புகளை இயக்கவும்",
"neteaseTranslation_description": "இயக்கப்பட்டால், கிடைத்தால் நெட்சிலிருந்து மொழிபெயர்க்கப்பட்ட பாடல்களைப் பெறுகிறது மற்றும் காட்சிப்படுத்துகிறது",
"preservePitch": "சுருதியைப் பாதுகாக்கவும்",
"preservePitch_description": "பின்னணி வேகத்தை மாற்றும்போது சுருதியைப் பாதுகாக்கிறது"
"preservePitch_description": "பின்னணி வேகத்தை மாற்றும்போது சுருதியைப் பாதுகாக்கிறது",
"autoDJ": "ஆட்டோ டி.சே",
"autoDJ_description": "தானாக வரிசையில் ஒத்த பாடல்களைச் சேர்க்கவும்",
"autoDJ_itemCount": "பொருள் எண்ணிக்கை",
"autoDJ_itemCount_description": "ஆட்டோ DJ இயக்கப்பட்டிருக்கும் போது, வரிசையில் சேர்க்க முயற்சிக்கும் உருப்படிகளின் எண்ணிக்கை",
"autoDJ_timing": "நேரவிவரம்",
"autoDJ_timing_description": "ஆட்டோ டிசேக்கு முன் வரிசையில் மீதமுள்ள பாடல்களின் எண்ணிக்கை தூண்டப்படுகிறது",
"useThemeAccentColor": "கருப்பொருள் உச்சரிப்பு நிறத்தைப் பயன்படுத்தவும்",
"useThemeAccentColor_description": "தனிப்பயன் உச்சரிப்பு நிறத்திற்குப் பதிலாக தேர்ந்தெடுக்கப்பட்ட தீமில் வரையறுக்கப்பட்ட முதன்மை வண்ணத்தைப் பயன்படுத்தவும்",
"analyticsDisable": "பயன்பாடு அடிப்படையிலான பகுப்பாய்வுகளில் இருந்து விலகுதல்",
"analyticsDisable_description": "பயன்பாட்டை மேம்படுத்த உதவ டெவெலப்பருக்கு அநாமதேய பயன்பாட்டுத் தரவு அனுப்பப்படுகிறது",
"analyticsEnable": "பயன்பாட்டு அடிப்படையிலான பகுப்பாய்வுகளை அனுப்பவும்",
"analyticsEnable_description": "பயன்பாட்டை மேம்படுத்த உதவ டெவெலப்பருக்கு அநாமதேய பயன்பாட்டுத் தரவு அனுப்பப்படுகிறது",
"artistBackground": "கலைஞர் பின்னணி படம்",
"artistBackground_description": "கலைஞர் கலையை உள்ளடக்கிய கலைஞர் பக்கங்களுக்கு பின்னணி படத்தை சேர்க்கிறது",
"artistBackgroundBlur": "கலைஞர் பின்னணி படம் மங்கலான அளவு",
"artistBackgroundBlur_description": "கலைஞரின் பின்னணி படத்தில் பயன்படுத்தப்படும் மங்கலின் அளவை சரிசெய்கிறது",
"artistReleaseTypeConfiguration": "கலைஞர் வெளியீட்டு வகை கட்டமைப்பு",
"artistReleaseTypeConfiguration_description": "ஆல்பம் கலைஞர் பக்கத்தில் என்ன வெளியீட்டு வகைகள் காட்டப்படுகின்றன, எந்த வரிசையில் உள்ளன என்பதை உள்ளமைக்கவும்",
"crossfadeStyle": "குறுக்குவழி பாணி",
"automaticUpdates": "தானியங்கி புதுப்பிப்புகள்",
"automaticUpdates_description": "புதுப்பிப்புகளை தானாக சரிபார்த்து நிறுவவும்",
"releaseChannel_optionAlpha": "ஆல்பா (இரவு)",
"releaseChannel_optionBeta": "பீட்டா",
"releaseChannel_optionLatest": "அண்மைக் கால",
"releaseChannel": "வெளியீடு சேனல்",
"releaseChannel_description": "தானியங்கி புதுப்பிப்புகளுக்கு நிலையான, பீட்டா அல்லது ஆல்பா (இரவு) வெளியீடுகளுக்கு இடையே தேர்வு செய்யவும்",
"discordDisplayType_artistname": "கலைஞர் பெயர்(கள்)",
"discordDisplayType_description": "உங்கள் நிலையில் நீங்கள் கேட்பதை மாற்றுகிறது",
"discordDisplayType_songname": "பாடல் பெயர்",
"discordDisplayType": "{{discord}} இருப்பு காட்சி வகை",
"discordLinkType_description": "{{discord}} சிறந்த முன்னிலையில் பாடல் மற்றும் கலைஞர் புலங்களுக்கு {{lastfm}} அல்லது {{musicbrainz}} வெளிப்புற இணைப்புகளைச் சேர்க்கிறது. {{musicbrainz}} மிகவும் துல்லியமானது ஆனால் குறிச்சொற்கள் தேவை மற்றும் கலைஞர் இணைப்புகளை வழங்காது, {{lastfm}} எப்போதும் இணைப்பை வழங்க வேண்டும். கூடுதல் பிணைய கோரிக்கைகளை செய்யாது",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} உடன் {{lastfm}} ஃபால்பேக்",
"discordLinkType_none": "$t(common.none)",
"discordLinkType": "{{discord}} இருப்பு இணைப்புகள்",
"discordRichPresence": "{{discord}} பணக்கார இருப்பு",
"discordStateIcon": "விளையாடும் ஐகானைக் காட்டு",
"discordStateIcon_description": "பணக்கார இருப்பு நிலையில் சிறிய விளையாடும் ஐகானைக் காட்டு. இடைநிறுத்தப்பட்ட படவுரு எப்போதும் \"இடைநிறுத்தப்பட்ட போது பணக்கார இருப்பைக் காட்டு\" இயக்கப்பட்டிருக்கும் போது காண்பிக்கப்படும்",
"enableAutoTranslation_description": "பாடல் வரிகள் ஏற்றப்படும் போது தானாகவே மொழிபெயர்ப்பை இயக்கவும்",
"enableAutoTranslation": "தானியங்கு மொழிபெயர்ப்பை இயக்கு",
"exportImportSettings_control_description": "சாதொபொகு வழியாக ஏற்றுமதி மற்றும் இறக்குமதி அமைப்புகளை",
"exportImportSettings_control_exportText": "ஏற்றுமதி அமைப்புகள்",
"exportImportSettings_control_importText": "இறக்குமதி அமைப்புகள்",
"exportImportSettings_control_title": "இறக்குமதி / ஏற்றுமதி அமைப்புகள்",
"exportImportSettings_destructiveWarning": "அமைப்புகளை இறக்குமதி செய்வது அழிவுகரமானது, கீழே உள்ள \"இறக்குமதி\" என்பதைக் சொடுக்கு செய்வதற்கு முன் மேலே உள்ளவற்றை மதிப்பாய்வு செய்யவும்!",
"exportImportSettings_importBtn": "இறக்குமதி அமைப்புகள்",
"exportImportSettings_importModalTitle": "feishin அமைப்புகளை இறக்குமதி செய்யவும்",
"exportImportSettings_importSuccess": "அமைப்புகள் வெற்றிகரமாக இறக்குமதி செய்யப்பட்டன!",
"exportImportSettings_notValidJSON": "அனுப்பப்பட்ட கோப்பு சாதொபொகு செல்லுபடியாகாது",
"exportImportSettings_offendingKeyError": "\"{{offendingKey}}\" தவறானது - {{reason}}",
"followCurrentSong_description": "தானாக விளையாடும் வரிசையை தற்போதைய பாடலுக்கு உருட்டும்",
"followCurrentSong": "தற்போதைய பாடலைப் பின்பற்றவும்",
"homeFeatureStyle_description": "வீட்டில் இடம்பெற்றுள்ள கொணர்வியின் பாணியைக் கட்டுப்படுத்துகிறது",
"homeFeatureStyle": "வீட்டில் இடம்பெற்றது கொணர்வி பாணி",
"homeFeatureStyle_optionMultiple": "பல",
"homeFeatureStyle_optionSingle": "ஒற்றை",
"hotkey_listNavigateToPage": "பட்டியல் உருப்படி பக்கத்திற்கு செல்லவும்",
"hotkey_listPlayDefault": "பட்டியல் நாடகம்",
"hotkey_listPlayLast": "பட்டியல் கடைசியாக விளையாடு",
"hotkey_listPlayNext": "பட்டியல் அடுத்து விளையாடு",
"hotkey_listPlayNow": "பட்டியல் விளையாட இப்போது",
"hotkey_navigateHome": "வீட்டிற்கு செல்லவும்",
"language": "மொழி",
"logLevel": "பதிவு நிலை",
"logLevel_description": "காண்பிக்க குறைந்தபட்ச பதிவு அளவை அமைக்கிறது. பிழைத்திருத்தம் அனைத்து பதிவுகளையும் காட்டுகிறது, பிழை பிழைகளை மட்டுமே காட்டுகிறது",
"logLevel_optionDebug": "பிழைத்திருத்தம்",
"logLevel_optionError": "பிழை",
"logLevel_optionInfo": "தகவல்",
"logLevel_optionWarn": "முன்னறிவிப்பு",
"mpvExtraParameters": "mpv கூடுதல் அளவுருக்கள்",
"mpvExtraParameters_description": "mpv க்கு அனுப்ப கூடுதல் வாதங்கள்",
"notify": "பாடல் அறிவிப்புகளை இயக்கவும்",
"notify_description": "தற்போதைய பாடலை மாற்றும்போது அறிவிப்புகளைக் காட்டு",
"pathReplace": "கோப்பு பாதை மாற்று",
"pathReplace_description": "உங்கள் சேவையகத்தின் இயல்புநிலை கோப்பு பாதையை மாற்றவும்",
"pathReplace_optionRemovePrefix": "முன்னொட்டை அகற்று",
"pathReplace_optionAddPrefix": "முன்னொட்டு சேர்க்கவும்",
"playerFilters": "வரிசையில் இருந்து பாடல்களை வடிகட்டவும்",
"playerFilters_description": "பின்வரும் அளவுகோல்களின் அடிப்படையில் பாடல்களை வரிசையில் சேர்க்காமல் தவிர்க்கவும்",
"artistRadioCount_description": "கலைஞர் வானொலி மற்றும் ட்ராக் வானொலிக்கான பாடல்களின் எண்ணிக்கையை அமைக்கிறது",
"artistRadioCount": "கலைஞர்/டிராக் ரேடியோ எண்ணிக்கை",
"imageResolution": "படத்தின் தீர்மானம்",
"imageResolution_description": "பயன்பாட்டைச் சுற்றிப் பயன்படுத்தப்படும் படங்களுக்கான தீர்மானம். 0 இன் மதிப்பைப் பயன்படுத்துவது இயல்பான படத் தீர்மானத்திற்கு இயல்புநிலையாக இருக்கும்",
"imageResolution_optionTable": "அட்டவணை",
"imageResolution_optionItemCard": "பொருள் அட்டை",
"imageResolution_optionSidebar": "பக்கப்பட்டி",
"imageResolution_optionHeader": "தலைப்பி",
"imageResolution_optionFullScreenPlayer": "முழுத்திரை பிளேயர்",
"playerbarSlider": "பிளேயர்பார் ச்லைடர்",
"playerbarSlider_description": "மெதுவான அல்லது மீட்டர் இணைய இணைப்பில் இருந்தால் அலைவடிவம் பரிந்துரைக்கப்படுவதில்லை",
"playerbarSliderType_optionSlider": "ச்லைடர்",
"playerbarSliderType_optionWaveform": "அலைவடிவம், அலைப்படம்",
"playerbarWaveformAlign": "அலை வடிவ சீரமைப்பு",
"playerbarWaveformAlign_optionTop": "மேலே",
"playerbarWaveformAlign_optionCenter": "நடுவண்",
"playerbarWaveformAlign_optionBottom": "கீழே",
"playerbarWaveformBarWidth": "அலைவடிவ பட்டை அகலம்",
"playerbarWaveformGap": "அலைவடிவ இடைவெளி",
"playerbarWaveformRadius": "அலைவடிவ ஆரம்",
"showLyricsInSidebar_description": "பாடல் வரிகளைக் காண்பிக்கும் இணைக்கப்பட்ட நாடக வரிசையில் ஒரு குழு சேர்க்கப்படும்",
"showLyricsInSidebar": "பிளேயர் பக்கப்பட்டியில் பாடல் வரிகளைக் காட்டு",
"showRatings_description": "நட்சத்திர மதிப்பீடு நற்பொருத்தம் இடைமுகத்தில் காட்டப்பட்டால் கட்டுப்படுத்துகிறது",
"showRatings": "நட்சத்திர மதிப்பீடுகளைக் காட்டு",
"blurExplicitImages": "வெளிப்படையான படங்களை மங்கலாக்கும்",
"blurExplicitImages_description": "வெளிப்படையாகக் குறியிடப்பட்ட ஆல்பம் மற்றும் பாடல் கலைப்படைப்புகள் மங்கலாக்கப்படும்",
"enableGridMultiSelect": "கட்டம் பல தேர்வை இயக்கவும்",
"enableGridMultiSelect_description": "இயக்கப்பட்டால், கட்டக் காட்சிகளில் பல உருப்படிகளைத் தேர்ந்தெடுக்க அனுமதிக்கிறது. முடக்கப்பட்டிருக்கும் போது, கிரிட் உருப்படி படங்களைக் சொடுக்கு செய்வதன் மூலம் உருப்படி பக்கத்திற்குச் செல்லும்",
"showVisualizerInSidebar_description": "விசுவலைசரைக் காட்டும் பிளேயர் பக்கப்பட்டியில் ஒரு பேனல் சேர்க்கப்படும்",
"showVisualizerInSidebar": "பிளேயர் பக்கப்பட்டியில் காட்சிப்படுத்தலைக் காட்டு",
"combinedLyricsAndVisualizer_description": "பாடல் வரிகளையும் காட்சிப்படுத்தலையும் ஒரே பேனலில் இணைக்கவும்",
"combinedLyricsAndVisualizer": "பிளேயர் பக்கப்பட்டியில் பாடல் வரிகள் மற்றும் காட்சிப்படுத்தல் ஆகியவற்றை இணைக்கவும்",
"audioFadeOnStatusChange": "நிலை மாறும்போது ஆடியோ மங்குகிறது",
"audioFadeOnStatusChange_description": "ப்ளே/இடைநிறுத்தம் நிலை மாறும்போது ஃபேட் அவுட் மற்றும் ஃபேட் இன் செயல்படுத்துகிறது",
"preventSleepOnPlayback_description": "இசை இயங்கும் போது காட்சி தூங்குவதைத் தடுக்கிறது",
"preventSleepOnPlayback": "பிளேபேக்கில் தூக்கத்தைத் தடுக்கவும்",
"sidebarPlaylistSorting_description": "இயல்புநிலை சர்வர் ஆர்டருக்குப் பதிலாக இழுத்து விடுவதைப் பயன்படுத்தி பக்கப்பட்டியில் கைமுறையாக பிளேலிச்ட்டை வரிசைப்படுத்த அனுமதிக்கிறது",
"sidebarPlaylistSorting": "பக்கப்பட்டி பிளேலிச்ட் வரிசையாக்கம்",
"sidebarPlaylistListFilterRegex_description": "இந்த வழக்கமான வெளிப்பாட்டுடன் பொருந்தக்கூடிய பிளேலிச்ட்களை பக்கப்பட்டியில் மறைக்கவும்",
"sidebarPlaylistListFilterRegex_placeholder": "எ.கா. ^தினசரி கலவை.*",
"sidebarPlaylistListFilterRegex": "பிளேலிச்ட் வடிகட்டி வழக்கவெளி",
"mediaSession_description": "மீடியா அமர்வு ஒருங்கிணைப்பை செயல்படுத்துகிறது, மீடியா கட்டுப்பாடுகள் மற்றும் மெட்டாடேட்டாவை கணினி தொகுதி மேலடுக்கு மற்றும் பூட்டுத் திரையில் காண்பிக்கும்",
"mediaSession": "ஊடக அமர்வை இயக்கவும்",
"transcode": "டிரான்ச்கோடிங்கை இயக்கவும்",
"queryBuilder": "வினவல் கட்டுபவர்",
"queryBuilderCustomFields_inputLabel": "சிட்டை",
"queryBuilderCustomFields_inputTag": "குறிச்சொல்",
"queryBuilderCustomFields": "விருப்ப புலங்கள்",
"queryBuilderCustomFields_description": "வினவல் பில்டர்களில் பயன்படுத்த தனிப்பயன் புலங்களைச் சேர்க்கவும்"
},
"table": {
"config": {
@@ -707,7 +1017,7 @@
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"codec": "$t(common.codec)",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
"rating": "$t(common.rating)",
@@ -719,22 +1029,31 @@
"lastPlayed": "கடைசியாக விளையாடியது",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"actions": "$t(common.action_other)",
"actions": "$t(common.action, {\"count\": 2})",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"discNumber": "வட்டு எண்",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"favorite": "$t(common.favourite)",
"genre": "$t(entity.genre, {\"count\": 1})",
"path": "$t(common.path)",
"playCount": "விளையாட்டு எண்ணிக்கை",
"songCount": "$t(entity.track, {\"count\": 2})",
"title": "$t(common.title)",
"titleCombined": "$t(common.title) (இணைந்தது)"
"titleCombined": "$t(common.title) (இணைந்தது)",
"albumGroup": "ஆல்பம் குழு",
"albumCount": "$t(entity.album, {\"count\": 2})",
"bitDepth": "$t(common.bitDepth)",
"composer": "இசையமைப்பாளர்",
"genreBadge": "$t(entity.genre, {\"count\": 1}) (பேட்ச்கள்)",
"image": "படம்",
"sampleRate": "$t(common.sampleRate)",
"titleArtist": "$t(common.title) (கலைஞர்)"
},
"view": {
"table": "அட்டவணை",
"grid": "வலைவாய்",
"list": "பட்டியல்"
"list": "பட்டியல்",
"detail": "விவரம்"
},
"general": {
"autoFitColumns": "ஆட்டோ பொருத்தம் நெடுவரிசைகள்",
@@ -744,7 +1063,29 @@
"itemGap": "உருப்படி இடைவெளி (பிஎக்ச்)",
"itemSize": "உருப்படி அளவு (பிஎக்ச்)",
"size": "$t(common.size)",
"tableColumns": "அட்டவணை நெடுவரிசைகள்"
"tableColumns": "அட்டவணை நெடுவரிசைகள்",
"advancedSettings": "மேம்பட்ட அமைப்புகள்",
"autosize": "தானியங்கு அளவு",
"moveUp": "மேலே செல்ல",
"moveDown": "கீழே நகர",
"pinToLeft": "இடப்புறம் முள்",
"pinToRight": "வலதுபுறமாக முள்",
"alignLeft": "இடதுபுறம் சீரமைக்கவும்",
"alignCenter": "மையத்தை சீரமைக்கவும்",
"alignRight": "வலது சீரமை",
"itemsPerRow": "ஒரு வரிசைக்கு பொருட்கள்",
"size_default": "இயல்புநிலை",
"size_compact": "கச்சிதமான",
"size_large": "பெரிய",
"pagination": "பேசினேசன்",
"pagination_itemsPerPage": "ஒரு பக்கத்திற்கு உருப்படிகள்",
"pagination_infinite": "கந்தழி, முடிவிலி",
"pagination_paginate": "பக்கமாக",
"alternateRowColors": "மாற்று வரிசை வண்ணங்கள்",
"horizontalBorders": "வரிசை எல்லைகள்",
"rowHoverHighlight": "வரிசை மிதவை ஐலைட்",
"showHeader": "தலைப்பு நிகழ்ச்சி",
"verticalBorders": "நெடுவரிசை எல்லைகள்"
}
},
"column": {
@@ -755,7 +1096,7 @@
"biography": "சுயசரிதை",
"bitrate": "பிட்ரேட்",
"bpm": "பிபிஎம்",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"codec": "$t(common.codec)",
"comment": "கருத்து",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
@@ -771,7 +1112,218 @@
"size": "$t(common.size)",
"songCount": "$t(entity.track, {\"count\": 2})",
"title": "தலைப்பு",
"trackNumber": "மின்தடம்"
"trackNumber": "மின்தடம்",
"bitDepth": "$t(common.bitDepth)",
"sampleRate": "$t(common.sampleRate)",
"owner": "உரிமையாளர்"
}
},
"datetime": {
"minuteShort": "மீ",
"secondShort": "கள்",
"hourShort": "ம",
"dayShort": "டி"
},
"filterOperator": {
"after": "பிறகு உள்ளது",
"afterDate": "பிறகு (தேதி)",
"before": "முன்பு உள்ளது",
"beforeDate": "முன் (தேதி)",
"contains": "கொண்டுள்ளது",
"endsWith": "உடன் முடிகிறது",
"inPlaylist": "உள்ளது",
"inTheLast": "கடைசியில் உள்ளது",
"inTheRange": "வரம்பில் உள்ளது",
"inTheRangeDate": "வரம்பில் உள்ளது (தேதி)",
"is": "உள்ளது",
"isNot": "இல்லை",
"isGreaterThan": "விட அதிகமாக உள்ளது",
"isLessThan": "விட குறைவாக உள்ளது",
"matchesRegex": "ரெசெக்சுடன் பொருந்துகிறது",
"notContains": "கொண்டிருக்கவில்லை",
"notInPlaylist": "உள்ளே இல்லை",
"notInTheLast": "கடைசியில் இல்லை",
"startsWith": "உடன் தொடங்குகிறது"
},
"queryBuilder": {
"standardTags": "நிலையான குறிச்சொற்கள்",
"customTags": "விருப்ப குறிச்சொற்கள்"
},
"releaseType": {
"primary": {
"album": "$t(entity.album, {\"count\": 1})",
"broadcast": "ஒளிபரப்பு",
"ep": "எபி",
"other": "மற்றொன்று",
"single": "ஒற்றை"
},
"secondary": {
"audiobook": "ஒலிப்புத்தகம்",
"audioDrama": "ஆடியோ நாடகம்",
"compilation": "தொகுத்தல்",
"djMix": "dj கலவை",
"demo": "டெமோ",
"fieldRecording": "களப்பதிவு",
"interview": "நேர்காணல்",
"live": "வாழ்க",
"mixtape": "கலவை",
"remix": "ரீமிக்ச்",
"soundtrack": "ஒலிப்பதிவு",
"spokenWord": "பேசப்பட்ட சொல்"
}
},
"dragDropZone": {
"error_oneFileOnly": "1 கோப்பை மட்டும் தேர்ந்தெடுக்கவும்",
"error_readingFile": "கோப்பைப் படிப்பதில் சிக்கல் உள்ளது: {{errorMessage}}",
"mainText": "ஒரு கோப்பை இங்கே விடுங்கள்"
},
"visualizer": {
"visualizerType": "விசுவலைசர் வகை",
"cyclePresets": "சுழற்சி முன்னமைவுகள்",
"cycleTime": "சுழற்சி நேரம் (வினாடிகள்)",
"includeAllPresets": "அனைத்து முன்னமைவுகளையும் சேர்க்கவும்",
"ignoredPresets": "புறக்கணிக்கப்பட்ட முன்னமைவுகள்",
"selectedPresets": "தேர்ந்தெடுக்கப்பட்ட முன்னமைவுகள்",
"randomizeNextPreset": "அடுத்த முன்னமைவை சீரமைக்கவும்",
"blendTime": "கலப்பு நேரம்",
"presets": "முன்னமைவுகள்",
"selectPreset": "முன்னமைவைத் தேர்ந்தெடுக்கவும்",
"applyPreset": "முன்னமைவைப் பயன்படுத்தவும்",
"saveAsPreset": "முன்னமைவாக சேமிக்கவும்",
"updatePreset": "முன்னமைவைப் புதுப்பிக்கவும்",
"copyConfiguration": "நகல் கட்டமைப்பு",
"pasteConfiguration": "ஒட்டு கட்டமைப்பு",
"pasteConfigurationPlaceholder": "சாதொபொகு உள்ளமைவை இங்கே ஒட்டவும்...",
"pasteFromClipboard": "கிளிப்போர்டில் இருந்து ஒட்டவும்",
"applyConfiguration": "உள்ளமைவைப் பயன்படுத்து",
"configCopied": "உள்ளமைவு இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது",
"configCopyFailed": "உள்ளமைவை நகலெடுக்க முடியவில்லை",
"configPasted": "உள்ளமைவு வெற்றிகரமாக பயன்படுத்தப்பட்டது",
"configPasteFailed": "உள்ளமைவைப் பயன்படுத்துவதில் தோல்வி. தயவுசெய்து வடிவமைப்பைச் சரிபார்க்கவும்.",
"configPasteReadFailed": "கிளிப்போர்டில் இருந்து படிக்க முடியவில்லை",
"presetName": "முன்னமைக்கப்பட்ட பெயர்",
"presetNamePlaceholder": "முன்னமைக்கப்பட்ட பெயரை உள்ளிடவும்",
"general": "பொது",
"mode": "பயன்முறை",
"mode1To8": "முறை 1 - 8",
"mode10": "முறை 10",
"barSpace": "பார் இடம்",
"lineWidth": "வரி அகலம்",
"fillAlpha": "ஆல்ஃபாவை நிரப்பவும்",
"channelLayout": "சேனல் தளவமைப்பு",
"maxFPS": "அதிகபட்ச FPS",
"opacity": "ஒளிபுகாநிலை",
"customGradients": "தனிப்பயன் சாய்வு",
"addCustomGradient": "தனிப்பயன் சாய்வு சேர்க்கவும்",
"gradientName": "சாய்வு பெயர்",
"gradientNamePlaceholder": "சாய்வு பெயர்",
"vertical": "செங்குத்து",
"horizontal": "கிடைமட்ட",
"colorStops": "வண்ண நிறுத்தங்கள்",
"addColor": "வண்ணத்தைச் சேர்க்கவும்",
"position": "பதவி",
"level": "நிலை",
"remove": "அகற்று",
"pasteGradient": "சாய்வு ஒட்டவும்",
"pasteGradientPlaceholder": "இங்கே சாய்வு சாதொபொகு ஒட்டவும்...",
"custom": "தனிப்பயன்",
"builtIn": "உள்ளமைக்கப்பட்ட",
"colors": "நிறங்கள்",
"colorMode": "வண்ண முறை",
"gradient": "சரிவு",
"gradientLeft": "கிரேடியன்ட் இடது",
"gradientRight": "கிரேடியன்ட் ரைட்",
"fft": "FFT",
"fftSize": "FFT அளவு",
"smoothing": "மென்மையாக்கும்",
"frequencyRangeAndScaling": "அதிர்வெண் வரம்பு மற்றும் அளவிடுதல்",
"minimumFrequency": "குறைந்தபட்ச அதிர்வெண்",
"maximumFrequency": "அதிகபட்ச அதிர்வெண்",
"frequencyScale": "அதிர்வெண் அளவுகோல்",
"sensitivity": "உணர்திறன்",
"weightingFilter": "எடை வடிகட்டி",
"minimumDecibels": "குறைந்தபட்ச டெசிபல்கள்",
"maximumDecibels": "அதிகபட்ச டெசிபல்கள்",
"linearAmplitude": "நேரியல் அலைவீச்சு",
"linearBoost": "லீனியர் பூச்ட்",
"peakBehavior": "உச்ச நடத்தை",
"showPeaks": "சிகரங்களைக் காட்டு",
"fadePeaks": "மங்கலான சிகரங்கள்",
"peakLine": "உச்சக் கோடு",
"gravity": "புவியீர்ப்பு",
"peakFadeTime": "பீக் ஃபேட் நேரம் (மிவி)",
"peakHoldTime": "பீக் ஓல்ட் நேரம் (மிவி)",
"radialSpectrum": "ரேடியல் ச்பெக்ட்ரம்",
"radial": "ரேடியல்",
"radialInvert": "ரேடியல் தலைகீழ்",
"spinSpeed": "சுழல் விரைவு",
"radius": "ஆரம்",
"reflexMirror": "ரிஃப்ளெக்ச் மிரர்",
"reflexFit": "ரிஃப்ளெக்ச் ஃபிட்",
"reflexRatio": "பிரதிபலிப்பு விகிதம்",
"reflexAlpha": "ரிஃப்ளெக்ச் ஆல்பா",
"reflexBrightness": "ரிஃப்ளெக்ச் ஒளி",
"mirror": "கண்ணாடி",
"miscellaneousSettings": "இதர அமைப்புகள்",
"alphaBars": "ஆல்பா பார்கள்",
"ansiBands": "ANSI பட்டைகள்",
"ledBars": "LED பார்கள்",
"trueLeds": "உண்மையான எல்.ஈ",
"lumiBars": "லுமி பார்கள்",
"outlineBars": "அவுட்லைன் பார்கள்",
"roundBars": "சுற்று பார்கள்",
"lowResolution": "குறைந்த தெளிவுத்திறன்",
"splitGradient": "பிளவு சாய்வு",
"showFPS": "FPS ஐக் காட்டு",
"showScaleX": "ஃச் அளவைக் காட்டு",
"noteLabels": "குறிப்பு லேபிள்கள்",
"showScaleY": "ஒய் அளவைக் காட்டு",
"options": {
"mode": {
"0": "[0] தனித்துவமான அதிர்வெண்கள்",
"1": "[1] 1/24வது ஆக்டேவ் / 240 பட்டைகள்",
"2": "[2] 1/12வது ஆக்டேவ் / 120 பட்டைகள்",
"3": "[3] 1/8 ஆக்டேவ் / 80 பட்டைகள்",
"4": "[4] 1/6வது ஆக்டேவ் / 60 பட்டைகள்",
"5": "[5] 1/4வது ஆக்டேவ் / 40 பட்டைகள்",
"6": "[6] 1/3 ஆக்டேவ் / 30 பட்டைகள்",
"7": "[7] அரை ஆக்டேவ் / 20 பட்டைகள்",
"8": "[8] முழு ஆக்டேவ் / 10 பட்டைகள்",
"10": "[10] கோடு / பகுதி வரைபடம்"
},
"colorMode": {
"gradient": "சரிவு",
"barIndex": "பார்-இண்டெக்ச்",
"barLevel": "பட்டை-நிலை"
},
"gradient": {
"classic": "கிளாசிக்",
"prism": "அரியம், பட்டகம்",
"rainbow": "வானவில்",
"steelblue": "இரும்புநீலம்",
"orangered": "ஆரஞ்சுசிவப்பு"
},
"channelLayout": {
"single": "ஒற்றை",
"dualCombined": "இரட்டை-இணைந்த",
"dualHorizontal": "இரட்டை-கிடைமட்ட",
"dualVertical": "இரட்டை-செங்குத்து"
},
"frequencyScale": {
"none": "எதுவுமில்லை",
"bark": "பட்டை அளவு",
"linear": "நேரியல் அளவுகோல்",
"log": "பதிவு அளவுகோல்",
"mel": "மெல் அளவுகோல்"
},
"weightingFilter": {
"none": "எதுவுமில்லை",
"a": "ஏ",
"b": "பி",
"c": "சி",
"d": "டி",
"z": "சட்"
}
}
}
}
+96 -20
View File
@@ -37,7 +37,8 @@
"selectAll": "全选",
"createRadioStation": "创建$t(entity.radioStation, {\"count\": 1})",
"deleteRadioStation": "删除$t(entity.radioStation, {\"count\": 1})",
"openApplicationDirectory": "打开应用程序目录"
"openApplicationDirectory": "打开应用程序目录",
"goToCurrent": "转到当前项目"
},
"common": {
"increase": "增高",
@@ -74,7 +75,7 @@
"forward": "前进",
"delete": "删除",
"cancel": "取消",
"forceRestartRequired": "重启应用使更改生效…关闭通知即可重启",
"forceRestartRequired": "重启应用使更改生效…关闭通知即可重启",
"setting_other": "设置",
"version": "版本",
"title": "标题",
@@ -107,7 +108,7 @@
"duration": "时长",
"ok": "好",
"no": "否",
"playerMustBePaused": "播放器必须暂停",
"playerMustBePaused": "播放器必须暂停",
"channel_other": "频道",
"none": "无",
"disc": "碟片",
@@ -155,7 +156,8 @@
"filter_single": "单项",
"mood": "氛围",
"rename": "重命名",
"filter_multiple": "多项"
"filter_multiple": "多项",
"newVersionAvailable": "a new version is available"
},
"entity": {
"albumArtist_other": "专辑艺术家",
@@ -218,7 +220,17 @@
"addNextShuffled": "下一个(随机)",
"artistRadio": "艺术家电台",
"holdToShuffle": "按住即可随机",
"trackRadio": "追踪广播"
"trackRadio": "追踪广播",
"sleepTimer": "睡眠定时器",
"sleepTimer_endOfSong": "当前歌曲结束时",
"sleepTimer_minutes": "{{count}} 分钟",
"sleepTimer_hours": "{{count}} 小时",
"sleepTimer_custom": "自定义",
"sleepTimer_off": "关闭",
"sleepTimer_timeRemaining": "剩余时间 {{time}}",
"sleepTimer_setCustom": "设置定时器",
"sleepTimer_cancel": "取消定时器",
"albumRadio": "专辑电台"
},
"setting": {
"crossfadeStyle_description": "选择用于音频播放器的淡入淡出风格",
@@ -249,7 +261,7 @@
"customFontPath_description": "设置应用使用的自定义字体路径",
"gaplessAudio_optionWeak": "弱(推荐)",
"font_description": "设置应用使用的字体",
"audioDevice_description": "选择用于播放的音频设备(仅 web 播放器)",
"audioDevice_description": "选择用于播放的音频设备",
"enableRemote_description": "启用远程控制服务器,以允许其他设备控制此应用",
"remotePort_description": "设置远程服务器端口",
"hotkey_skipBackward": "向后跳过",
@@ -463,7 +475,7 @@
"releaseChannel_optionLatest": "最新的",
"releaseChannel_optionBeta": "测试版",
"releaseChannel": "发布通道",
"releaseChannel_description": "选择稳定版、测试版或 Alpha(夜间构建版)以启用自动更新",
"releaseChannel_description": "选择稳定版、测试版或 Alpha(夜间构建版)以启用自动更新",
"mediaSession": "启用媒体会话",
"mediaSession_description": "启用媒体会话集成,在系统音量叠加层和锁屏界面显示媒体控件和元数据",
"exportImportSettings_control_description": "通过 JSON 导出和导入设置",
@@ -562,7 +574,26 @@
"sidebarPlaylistListFilterRegex_placeholder": "例如:^每日精选*",
"sidebarPlaylistListFilterRegex": "播放列表筛选正则表达式",
"queryBuilder": "查询构建器",
"queryBuilderCustomFields": "自定义字段"
"queryBuilderCustomFields": "自定义字段",
"analyticsEnable": "发送基于使用情况的分析",
"analyticsEnable_description": "发送匿名使用数据帮助开发者改进应用程序",
"automaticUpdates": "自动更新",
"automaticUpdates_description": "自动检查并安装更新",
"releaseChannel_optionAlpha": "alpha(每日构建版)",
"discordStateIcon": "显示播放图标",
"discordStateIcon_description": "在 rich presence 状态中显示一个小的播放图标。启用“暂停时显示 rich presence 在线状态”后,暂停图标始终显示",
"blurExplicitImages": "模糊显式图片",
"blurExplicitImages_description": "专辑和歌曲封面若被标记为不雅内容,将会进行模糊处理",
"autosave": "自动保存播放队列",
"autosave_description": "启用自动将播放队列保存到服务器的功能。此功能仅在使用 Navidrome/Subsonic 时可用,且不能使用混合播放队列。",
"autosaveCount": "自动播放队列保存频率",
"autosaveCount_description": "队列保存前需要更改多少首歌曲?1(最少)表示每次歌曲更改",
"useThemePrimaryShade": "使用主题主色调",
"useThemePrimaryShade_description": "对于主要颜色变体,请使用所选主题中定义的主色调",
"primaryShade": "主色调",
"primaryShade_description": "覆盖按钮、链接和其他主色元素使用的主色调(0-9)",
"playerItemConfiguration_description": "配置全屏播放器上显示的项目及其显示顺序",
"playerItemConfiguration": "播放器项目配置"
},
"error": {
"remotePortWarning": "重启服务器使新端口生效",
@@ -593,7 +624,10 @@
"noNetwork": "服务器不可用",
"noNetworkDescription": "无法连接到该服务器",
"saveQueueFailed": "播放列表保存失败",
"settingsSyncError": "渲染器设置与主进程中存在差异,请重启程序以应用更改"
"settingsSyncError": "渲染器设置与主进程中存在差异,请重启程序以应用更改",
"invalidJson": "无效的 JSON",
"serverLockSingleServer": "服务器锁定时,只允许一台服务器运行",
"playbackPausedDueToError": "发生错误,播放已暂停"
},
"filter": {
"mostPlayed": "最多播放过",
@@ -639,7 +673,9 @@
"duration": "时长",
"album": "$t(entity.album, {\"count\": 1})",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "排序名称"
"sortName": "排序名称",
"matchAnd": "和",
"matchOr": "或"
},
"page": {
"sidebar": {
@@ -808,7 +844,11 @@
"appearsOn": "出现在",
"viewAll": "查看全部",
"groupingTypeAll": "所有发行类型",
"groupingTypePrimary": "首选发布类型"
"groupingTypePrimary": "首选发布类型",
"favoriteSongs": "收藏的歌曲",
"favoriteSongsFrom": "来自 {{title}} 收藏的歌曲",
"topSongsCommunity": "社区",
"topSongsPersonal": "个人"
},
"itemDetail": {
"copyPath": "将路径复制到剪贴板",
@@ -842,6 +882,11 @@
"collections": {
"overrideExisting": "覆盖现有",
"saveAsCollection": "保存为集合"
},
"releasenotes": {
"commitsSinceStable": "自 {{stable}} 以来的提交",
"noNewCommits": "此范围内没有新的提交",
"noStableReleaseToCompare": "目前没有稳定版本可供比较"
}
},
"form": {
@@ -914,7 +959,9 @@
"allowDownloading": "允许下载",
"description": "描述",
"setExpiration": "设置过期时间",
"success": "共享链接已复制到剪贴板(或单击此处打开)"
"success": "共享链接已复制到剪贴板(或单击此处打开)",
"copyToClipboard": "复制到剪贴板:Ctrl+CEnter",
"successMustClick": "分享创建成功。点击此处打开"
},
"privateMode": {
"enabled": "启用私人模式,播放状态现在对外部集成隐藏",
@@ -989,7 +1036,8 @@
"view": {
"table": "表格",
"grid": "网格",
"list": "列表"
"list": "列表",
"detail": "详情"
},
"label": {
"releaseDate": "发布日期",
@@ -1026,7 +1074,8 @@
"sampleRate": "$t(common.sampleRate)",
"genreBadge": "$t(entity.genre, {\"count\": 1})(徽章)",
"composer": "作曲家",
"titleArtist": "$t(common.title) (艺术家)"
"titleArtist": "$t(common.title) (艺术家)",
"albumGroup": "专辑分组"
}
},
"column": {
@@ -1162,21 +1211,41 @@
"miscellaneousSettings": "杂项设置",
"options": {
"channelLayout": {
"single": "单项"
"single": "单项",
"dualCombined": "Dual-Combined",
"dualHorizontal": "Dual-Horizontal",
"dualVertical": "Dual-Vertical"
},
"mode": {
"0": "[0] 离散频率"
"0": "[0] 离散频率",
"1": "[1] 1/24th octave / 240 bands",
"2": "[2] 1/12th octave / 120 bands",
"3": "[3] 1/8th octave / 80 bands",
"4": "[4] 1/6th octave / 60 bands",
"5": "[5] 1/4th octave / 40 bands",
"6": "[6] 1/3rd octave / 30 bands",
"7": "[7] Half octave / 20 bands",
"8": "[8] Full octave / 10 bands",
"10": "[10] Line / Area graph"
},
"colorMode": {
"gradient": "渐变"
"gradient": "渐变",
"barIndex": "Bar-Index",
"barLevel": "Bar-Level"
},
"gradient": {
"classic": "经典",
"prism": "棱镜",
"rainbow": "彩虹"
"rainbow": "彩虹",
"steelblue": "钢蓝色",
"orangered": "橙红色"
},
"frequencyScale": {
"none": "无"
"none": "无",
"bark": "Bark Scale",
"linear": "Linear Scale",
"log": "Log Scale",
"mel": "Mel Scale"
},
"weightingFilter": {
"none": "无",
@@ -1234,7 +1303,14 @@
"splitGradient": "渐变分割",
"showScaleX": "显示比例尺 X",
"noteLabels": "笔记标签",
"showScaleY": "显示比例尺 Y"
"showScaleY": "显示比例尺 Y",
"alphaBars": "Alpha Bars",
"ansiBands": "ANSI Bands",
"ledBars": "LED Bars",
"trueLeds": "True LEDs",
"lumiBars": "Lumi Bars",
"outlineBars": "Outline Bars",
"roundBars": "Round Bars"
},
"queryBuilder": {
"standardTags": "标准标签",
+73 -52
View File
@@ -12,7 +12,7 @@
"delete": "刪除",
"descending": "降冪",
"description": "描述",
"forceRestartRequired": "重新啟動應用程式使更改生效…關閉通知即可重啟",
"forceRestartRequired": "重新啟動應用程式使更改生效…關閉通知即可重啟",
"menu": "選單",
"action_other": "操作",
"add": "新增",
@@ -39,7 +39,7 @@
"ok": "好",
"owner": "所有者",
"path": "路徑",
"playerMustBePaused": "播放器必須暫停",
"playerMustBePaused": "播放器必須暫停",
"previousSong": "上一首$t(entity.track, {\"count\": 1})",
"quit": "退出",
"random": "隨機",
@@ -64,8 +64,8 @@
"cancel": "取消",
"center": "中央",
"channel_other": "聲道",
"configure": "配置",
"create": "建",
"configure": "設定",
"create": "建",
"currentSong": "目前$t(entity.track, {\"count\": 1})",
"minimize": "最小化",
"modified": "已修改",
@@ -78,7 +78,7 @@
"newVersion": "已安裝新版本 ({{version}})",
"viewReleaseNotes": "查看發行註記",
"albumGain": "專輯增益",
"albumPeak": "專輯peak",
"albumPeak": "專輯峰值",
"bitDepth": "位元深度",
"close": "關閉",
"codec": "編碼",
@@ -115,7 +115,8 @@
"rename": "重新命名",
"itemsMore": "{{count}} 更多",
"filter_single": "單選",
"filter_multiple": "複選"
"filter_multiple": "複選",
"newVersionAvailable": "有新的版本可供使用"
},
"error": {
"endpointNotImplementedError": "{{serverType}} 尚未實現端點 {{endpoint}}",
@@ -124,13 +125,13 @@
"authenticationFailed": "驗證失敗",
"credentialsRequired": "需要憑證",
"genericError": "發生了錯誤",
"invalidServer": "無效的服器",
"invalidServer": "無效的服器",
"localFontAccessDenied": "無法取得本地字體",
"loginRateError": "登請求嘗試次數過多,請稍後再試",
"loginRateError": "登請求嘗試次數過多,請稍後再試",
"remoteDisableError": "$t(common.disable)遠端伺服器時出現錯誤",
"remoteEnableError": "$t(common.enable)遠端伺服器時出現錯誤",
"remotePortError": "設定遠端伺服器端口時發生錯誤",
"remotePortWarning": "重啟伺服器使新端口生效",
"remotePortError": "設定遠端伺服器連接埠時發生錯誤",
"remotePortWarning": "重啟伺服器使新連接埠生效",
"serverRequired": "需要伺服器",
"sessionExpiredError": "工作階段已過期",
"systemFontError": "嘗試取得系統字體時出現錯誤",
@@ -138,15 +139,18 @@
"mpvRequired": "需要 MPV",
"playbackError": "無法播放媒體",
"badAlbum": "您看到此頁面是因為這首歌不是專輯的一部分。如果您的音樂資料夾頂層有一首歌,則很可能會看到此問題。 Jellyfin 僅將資料夾中的曲目分組",
"badValue": "無效選項{{value}}。該值不再存在",
"badValue": "無效選項{{value}}。該值不再存在",
"networkError": "發生網路錯誤",
"notificationDenied": "通知權限被拒絕。此設定無效",
"openError": "無法開啟檔案",
"multipleServerSaveQueueError": "播放佇列中包含不是來自目前伺服器的歌曲,此操作不受支援",
"saveQueueFailed": "儲存播放佇列失敗",
"settingsSyncError": "偵測到渲染器與主程之間的設定不一致,請重新啟動應用程式以套用變更",
"settingsSyncError": "偵測到渲染器與主程之間的設定不一致,請重新啟動應用程式以套用變更",
"noNetwork": "伺服器無法連線",
"noNetworkDescription": "無法連接到此伺服器"
"noNetworkDescription": "無法連接到此伺服器",
"invalidJson": "無效的 JSON",
"serverLockSingleServer": "當伺服器鎖定時只允許一個伺服器",
"playbackPausedDueToError": "發生錯誤,已停止播放"
},
"page": {
"contextMenu": {
@@ -195,7 +199,7 @@
"genres": "$t(entity.genre, {\"count\": 2})"
},
"appMenu": {
"openBrowserDevtools": "開瀏覽器開發者工具",
"openBrowserDevtools": "開瀏覽器開發者工具",
"collapseSidebar": "折疊側邊欄",
"expandSidebar": "展開側邊欄",
"goBack": "返回",
@@ -408,7 +412,8 @@
"sleepTimer_off": "關閉",
"sleepTimer_timeRemaining": "剩餘 {{time}}",
"sleepTimer_setCustom": "設定定時器",
"sleepTimer_cancel": "取消定時器"
"sleepTimer_cancel": "取消定時器",
"albumRadio": "專輯電台"
},
"setting": {
"audioPlayer_description": "選擇用於播放的音訊播放器",
@@ -424,9 +429,9 @@
"applicationHotkeys": "應用程式快捷鍵",
"applicationHotkeys_description": "設定應用程式快捷鍵。切換勾選框來設為全域快捷鍵(僅桌面端)",
"audioDevice": "音訊設備",
"audioDevice_description": "選擇用於播放的音訊設備(僅 web 播放器)",
"audioExclusiveMode": "音訊獨模式",
"audioExclusiveMode_description": "啟用獨輸出模式。在此模式下,系統通常被鎖定,只有 mpv 能夠輸出音訊",
"audioDevice_description": "選擇用於播放的音訊設備",
"audioExclusiveMode": "音訊獨模式",
"audioExclusiveMode_description": "啟用獨輸出模式。在此模式下,系統通常被鎖定,只有 mpv 能夠輸出音訊",
"audioPlayer": "音訊播放器",
"crossfadeDuration": "淡入淡出持續時間",
"crossfadeDuration_description": "設定淡入淡出持續時間",
@@ -499,7 +504,7 @@
"playButtonBehavior_description": "設定歌曲新增到佇列時播放按鈕的預設動作",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"remotePort": "遠端控制伺服器端口",
"remotePort": "遠端控制伺服器連接埠",
"remoteUsername": "遠端控制伺服器使用者名稱",
"replayGainClipping": "{{ReplayGain}}削波",
"replayGainFallback": "{{ReplayGain}}後備替代",
@@ -513,7 +518,7 @@
"replayGainPreamp_description": "調整使用在{{ReplayGain}}值上的前置放大增益",
"savePlayQueue": "儲存播放佇列",
"sampleRate_description": "如果選擇的取樣率與目前媒體的取樣率不同,請選擇要使用的輸出取樣率。小於 8000 的值將使用預設頻率",
"savePlayQueue_description": "當應用程式關閉時儲存播放佇列,並在應用程式開時恢複它",
"savePlayQueue_description": "當應用程式關閉時儲存播放佇列,並在應用程式開時恢複它",
"scrobble": "記錄播放資訊(Scrobble",
"scrobble_description": "在你的媒體伺服器中記錄播放資訊",
"showSkipButton": "顯示跳過按鈕",
@@ -525,13 +530,13 @@
"sidebarConfiguration_description": "選擇側邊欄包含的項目與順序",
"sidebarPlaylistList_description": "顯示或隱藏側邊欄歌單清單",
"sidePlayQueueStyle": "側邊播放佇列樣式",
"sidePlayQueueStyle_description": "設側邊播放佇列樣式",
"sidePlayQueueStyle_description": "設側邊播放佇列樣式",
"sidePlayQueueStyle_optionAttached": "吸附",
"sidePlayQueueStyle_optionDetached": "分離",
"skipDuration": "跳過時長",
"skipDuration_description": "設每次按下跳過按鈕將會跳過的時長",
"skipDuration_description": "設每次按下跳過按鈕將會跳過的時長",
"skipPlaylistPage": "跳過播放清單頁面",
"skipPlaylistPage_description": "開播放清單時,直接查看歌曲列表而非查看預設頁面",
"skipPlaylistPage_description": "開播放清單時,直接查看歌曲列表而非查看預設頁面",
"theme": "主題",
"themeDark": "主題(深色)",
"useSystemTheme_description": "使用系統定義的淺色或深色主題",
@@ -559,25 +564,25 @@
"playButtonBehavior_optionPlay": "$t(player.play)",
"remotePassword": "遠端控制伺服器密碼",
"remotePassword_description": "設定遠端控制伺服器的密碼。這些憑證預設以不安全的方式傳輸,因此您應該使用一個您不在意的唯一密碼",
"remotePort_description": "設定遠端控制伺服器的端口",
"remoteUsername_description": "設定遠端控制伺服器的使用者名稱。如果使用者名稱和密碼都為空,則身驗證將被禁用",
"remotePort_description": "設定遠端控制伺服器的連接埠",
"remoteUsername_description": "設定遠端控制伺服器的使用者名稱。如果使用者名稱和密碼都為空,則身驗證將被禁用",
"replayGainClipping_description": "自動降低增益以防止{{ReplayGain}}造成削波",
"showSkipButtons": "顯示跳過按鈕",
"themeDark_description": "應用程式將使用深色主題",
"clearQueryCache_description": "Feishin的軟清除。這將會刷新播放清單、曲目標籤並重置儲存的歌詞。會保留設定、伺服器憑證和暫存圖片",
"clearQueryCache_description": "Feishin的軟清除。這將會刷新播放清單、曲目標籤並重置儲存的歌詞。會保留設定、伺服器憑證和暫存圖片",
"clearCache": "清除瀏覽器快取",
"clearCache_description": "Feishin的硬清除。除了清除Feishin的快取、清除瀏覽器快取(儲存的圖片和其他資源)。會保留伺服器憑證和設定",
"clearCache_description": "Feishin的硬清除。除了清除Feishin的快取、清除瀏覽器快取(儲存的圖片和其他資源)。會保留伺服器憑證和設定",
"clearQueryCache": "清除Feishin快取",
"buttonSize": "播放器欄按鈕大小",
"buttonSize_description": "播放器欄按鈕大小",
"albumBackground": "專輯背景圖片",
"albumBackground_description": "為包含專輯封面的專輯頁面新增背景圖片",
"albumBackgroundBlur": "專輯背景圖片模糊大小",
"albumBackgroundBlur_description": "調整應用於專輯背景圖片的模糊量",
"albumBackgroundBlur_description": "調整應用程式於專輯背景圖片的模糊量",
"artistConfiguration": "專輯藝人頁面設定",
"artistConfiguration_description": "設定專輯藝人頁面中顯示的項目及排序",
"clearCacheSuccess": "成功清除快取",
"contextMenu": "右鍵選單配置",
"contextMenu": "右鍵選單設定",
"contextMenu_description": "允許您隱藏在右鍵選單項目時顯示的項目。未選取的項目將被隱藏",
"customCssEnable": "啟用自訂CSS",
"customCssEnable_description": "允許撰寫自訂CSS",
@@ -594,8 +599,8 @@
"externalLinks_description": "在藝人/專輯頁面顯示外部連結(Last.fm, MusicBrainz)",
"preferLocalLyrics": "偏好本地歌詞",
"preferLocalLyrics_description": "優先選擇本地歌詞,而不是遠端歌詞(如果可用)",
"homeConfiguration": "首頁配置",
"homeConfiguration_description": "配置在首頁上顯示哪些項目以及顯示順序",
"homeConfiguration": "首頁設定",
"homeConfiguration_description": "設定在首頁上顯示哪些項目以及顯示順序",
"homeFeature": "首頁特色輪播",
"homeFeature_description": "控制是否在首頁上顯示大型特色輪播",
"imageAspectRatio": "使用原生封面照長寬比",
@@ -617,14 +622,14 @@
"startMinimized": "啟動時最小化",
"startMinimized_description": "在系統匣中啟動應用程式",
"transcode_description": "啟用轉碼到不同格式",
"transcodeBitrate": "要轉碼的比特率",
"transcodeBitrate_description": "選擇要轉碼的比特率。 0 表示讓伺服器選擇",
"transcodeBitrate": "要轉碼的位元率",
"transcodeBitrate_description": "選擇要轉碼的位元率。 0 表示讓伺服器選擇",
"transcodeFormat": "轉碼的格式",
"transcodeFormat_description": "選擇要轉碼的格式。留空來讓伺服器決定",
"translationApiProvider": "翻譯API提供者",
"translationApiProvider_description": "翻譯API的提供者",
"translationApiKey": "翻譯API金鑰",
"translationApiKey_description": "翻譯的API金鑰(僅限全域服端點)",
"translationApiKey_description": "翻譯的API金鑰(僅限全域服端點)",
"translationTargetLanguage": "目標翻譯語言",
"translationTargetLanguage_description": "翻譯的目標語言",
"trayEnabled": "顯示系統匣",
@@ -761,7 +766,17 @@
"automaticUpdates": "自動更新",
"automaticUpdates_description": "自動檢查並安裝更新",
"discordStateIcon": "顯示播放中圖示",
"discordStateIcon_description": "在 rich presence 狀態中顯示一個小的播放圖示。啟用「暫停時顯示 rich presence」時,會始終顯示暫停的圖示"
"discordStateIcon_description": "在 rich presence 狀態中顯示一個小的播放圖示。啟用「暫停時顯示 rich presence」時,會始終顯示暫停的圖示",
"useThemePrimaryShade": "套用主題主色調",
"useThemePrimaryShade_description": "使用所選主題中定義的主色調作為主色變體",
"primaryShade": "主要色調",
"primaryShade_description": "覆蓋按鈕、連結及其他主色調元素所使用的主色調(0–9)",
"playerItemConfiguration_description": "設定全螢幕播放器顯示的項目及排列順序",
"playerItemConfiguration": "播放器項目設定",
"autosave": "自動儲存播放佇列",
"autosave_description": "啟用自動將播放佇列儲存到您的伺服器。這只有在使用Navidrome/Subsonic時才可使用,並且您不能有混合播放佇列。",
"autosaveCount": "自動播放佇列儲存頻率",
"autosaveCount_description": "在儲存佇列之前,有多少曲目更改。1(最小)表示每次歌曲更改"
},
"table": {
"config": {
@@ -770,7 +785,7 @@
"gap": "$t(common.gap)",
"size": "$t(common.size)",
"tableColumns": "列",
"autoFitColumns": "列寬自適應",
"autoFitColumns": "自動調整列寬",
"followCurrentSong": "跟隨目前歌曲",
"itemGap": "項目間隔 (px)",
"itemSize": "項目大小 (px)",
@@ -832,7 +847,8 @@
"bitDepth": "$t(common.bitDepth)",
"sampleRate": "$t(common.sampleRate)",
"composer": "作曲者",
"titleArtist": "$t(common.title) (藝人)"
"titleArtist": "$t(common.title) (藝人)",
"albumGroup": "專輯分組"
},
"view": {
"table": "表格",
@@ -847,7 +863,7 @@
"albumCount": "$t(entity.album, {\"count\": 2})",
"artist": "$t(entity.artist, {\"count\": 1})",
"biography": "簡介",
"bitrate": "比特率",
"bitrate": "位元率",
"channels": "$t(common.channel, {\"count\": 2})",
"comment": "評論",
"dateAdded": "新增日期",
@@ -874,7 +890,7 @@
"action": {
"addToFavorites": "新增到$t(entity.favorite, {\"count\": 2})",
"clearQueue": "清空播放佇列",
"createPlaylist": "建$t(entity.playlist, {\"count\": 1})",
"createPlaylist": "建$t(entity.playlist, {\"count\": 1})",
"deletePlaylist": "刪除$t(entity.playlist, {\"count\": 1})",
"addToPlaylist": "新增到$t(entity.playlist, {\"count\": 1})",
"deselectAll": "取消全選",
@@ -904,12 +920,13 @@
"moveDown": "向下移動",
"holdToMoveToTop": "按住以移動至頂部",
"holdToMoveToBottom": "按住以移動至底部",
"createRadioStation": "建 $t(entity.radioStation, {\"count\": 1})",
"createRadioStation": "建 $t(entity.radioStation, {\"count\": 1})",
"deleteRadioStation": "刪除 $t(entity.radioStation, {\"count\": 1})",
"openApplicationDirectory": "開啟應用程式目錄",
"addOrRemoveFromSelection": "新增或移除選取項目",
"selectAll": "全選",
"selectRangeOfItems": "批量選取"
"selectRangeOfItems": "批量選取",
"goToCurrent": "前往當前項目"
},
"entity": {
"album_other": "專輯",
@@ -937,7 +954,7 @@
"albumCount": "$t(entity.album, {\"count\": 2})數",
"artist": "$t(entity.artist, {\"count\": 1})",
"biography": "個人簡介",
"bitrate": "比特率",
"bitrate": "位元率",
"bpm": "bpm",
"channels": "$t(common.channel, {\"count\": 2})",
"comment": "評論",
@@ -977,7 +994,9 @@
"toYear": "從年份",
"trackNumber": "曲目",
"explicitStatus": "$t(common.explicitStatus)",
"sortName": "排序名稱"
"sortName": "排序名稱",
"matchAnd": "和",
"matchOr": "或"
},
"form": {
"addServer": {
@@ -993,7 +1012,7 @@
"ignoreCors": "忽略 cors $t(common.restartRequired)",
"ignoreSsl": "忽略 ssl $t(common.restartRequired)",
"input_preferInstantMix": "偏好即時混音",
"input_preferInstantMixDescription": "僅使用即時混音功能來取相似歌曲。若您擁有能修改此行為的外掛,此功能將相當實用",
"input_preferInstantMixDescription": "僅使用即時混音功能來取相似歌曲。若您擁有能修改此行為的外掛,此功能將相當實用",
"input_preferRemoteUrl": "優先使用公開網址",
"input_remoteUrl": "公開網址",
"input_remoteUrlPlaceholder": "選用:對外功能的公開網址"
@@ -1003,7 +1022,7 @@
"input_skipDuplicates": "跳過重複",
"success": "新增 $t(entity.trackWithCount, {\"count\": {{message}} }) 到 $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "新增到$t(entity.playlist, {\"count\": 1})",
"create": "建 $t(entity.playlist, {\"count\": 1}) {{playlist}}",
"create": "建 $t(entity.playlist, {\"count\": 1}) {{playlist}}",
"searchOrCreate": "搜尋$t(entity.playlist, {\"count\": 2}) 或輸入內容以建立新項目"
},
"createPlaylist": {
@@ -1011,8 +1030,8 @@
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "公開",
"success": "已成功建 $t(entity.playlist, {\"count\": 1})",
"title": "建$t(entity.playlist, {\"count\": 1})"
"success": "已成功建 $t(entity.playlist, {\"count\": 1})",
"title": "建$t(entity.playlist, {\"count\": 1})"
},
"lyricSearch": {
"input_name": "$t(common.name)",
@@ -1049,7 +1068,9 @@
"setExpiration": "設定過期時間",
"success": "分享連結已複製到剪貼簿(或點擊此處開啟)",
"expireInvalid": "到期日必須是未來",
"createFailed": "無法建立分享(分享是否啟用?)"
"createFailed": "無法建立分享(分享是否啟用?)",
"copyToClipboard": "複製到剪貼簿:Ctrl+C, Enter",
"successMustClick": "分享建立成功,點擊此處開啟"
},
"privateMode": {
"enabled": "已啟用私人模式,播放狀態將對外部整合隱藏",
@@ -1072,8 +1093,8 @@
"input_played_optionPlayed": "僅播放過的曲目"
},
"createRadioStation": {
"success": "電台建成功",
"title": "建電台",
"success": "電台建成功",
"title": "建電台",
"input_homepageUrl": "首頁連結",
"input_name": "名稱",
"input_streamUrl": "串流網址"
@@ -1182,8 +1203,8 @@
"channelLayout": "聲道佈局",
"maxFPS": "最大幀率",
"opacity": "不透明度",
"customGradients": "自定義漸層",
"addCustomGradient": "新增自定義漸層",
"customGradients": "自漸層",
"addCustomGradient": "新增自漸層",
"gradientName": "漸層名稱",
"gradientNamePlaceholder": "漸層名稱",
"vertical": "垂直",
+5 -1
View File
@@ -116,11 +116,15 @@ const createMpv = async (data: {
mpv.on('status', (status) => {
if (status.property === 'playlist-pos') {
// mpv uses playlist-pos = -1 when nothing is playing (ended, cleared, load failure, etc).
if (status.value === -1) {
mpv?.pause();
return;
}
if (status.value !== 0) {
// In our 2-item queue model, playlist-pos should normally be 0.
// When mpv auto-advances to the next track it becomes > 0 (typically 1).
if (typeof status.value === 'number' && status.value > 0) {
getMainWindow()?.webContents.send('renderer-player-auto-next');
}
}
+54 -10
View File
@@ -68,16 +68,40 @@ class AppUpdater {
const effectiveChannel = store.get('release_channel') as string;
console.log('Effective update channel:', effectiveChannel);
if (effectiveChannel === 'alpha') {
checkAllChannelsAndGetBest().then(({ updater: updaterInstance }) => {
checkAllChannelsAndGetBest().then(({ result, updater: updaterInstance }) => {
updaterInstance.autoInstallOnAppQuit = true;
updaterInstance.autoRunAppAfterInstall = true;
updaterInstance.checkForUpdatesAndNotify();
if (isMacOS()) {
if (result?.isUpdateAvailable) {
getMainWindow()?.webContents.send(
'update-available',
result.updateInfo.version,
);
}
} else {
updaterInstance.checkForUpdatesAndNotify();
}
});
return;
}
configureAndGetUpdater();
autoUpdater.checkForUpdatesAndNotify();
if (isMacOS()) {
autoUpdater.autoDownload = false;
autoUpdater
.checkForUpdates()
.then((result) => {
if (result?.isUpdateAvailable) {
getMainWindow()?.webContents.send(
'update-available',
result.updateInfo.version,
);
}
})
.catch((err) => console.error('Check for updates failed', err));
} else {
autoUpdater.checkForUpdatesAndNotify();
}
}
}
@@ -390,8 +414,11 @@ const createTray = () => {
},
{
click: () => {
mainWindow?.show();
createWinThumbarButtons();
if (mainWindow === null) createWindow(false);
else {
mainWindow.show();
createWinThumbarButtons();
}
},
label: 'Open main window',
},
@@ -405,8 +432,17 @@ const createTray = () => {
]);
tray.on('click', () => {
mainWindow?.show();
createWinThumbarButtons();
if (store.get('window_minimize_to_tray')) {
if (mainWindow?.isVisible()) {
mainWindow?.hide();
} else {
mainWindow?.show();
createWinThumbarButtons();
}
} else {
mainWindow?.show();
createWinThumbarButtons();
}
});
tray.setToolTip('Feishin');
@@ -543,8 +579,15 @@ async function createWindow(first = true): Promise<void> {
const updateAvailable = result?.isUpdateAvailable ?? false;
console.log('Update available:', updateAvailable);
if (updateAvailable && store.get('disable_auto_updates') !== true) {
console.log('Downloading update');
updater.downloadUpdate();
if (isMacOS()) {
getMainWindow()?.webContents.send(
'update-available',
result?.updateInfo?.version,
);
} else {
console.log('Downloading update');
updater.downloadUpdate();
}
}
return {
@@ -618,6 +661,7 @@ async function createWindow(first = true): Promise<void> {
mainWindow.on('closed', () => {
ipcMain.removeHandler('window-clear-cache');
ipcMain.removeHandler('app-check-for-updates');
mainWindow = null;
});
@@ -866,7 +910,7 @@ if (!singleInstance) {
app.whenReady()
.then(() => {
protocol.handle('feishin', async (request) => {
const filePath = `file://${request.url.slice('feishin://'.length)}`;
const filePath = `file:${request.url.slice('feishin:'.length)}`;
const response = await net.fetch(filePath);
const contentType = response.headers.get('content-type');
+1
View File
@@ -8,6 +8,7 @@ declare global {
electron: ElectronAPI;
LEGACY_AUTHENTICATION?: boolean;
queryLocalFonts?: () => Promise<Font[]>;
REMOTE_URL?: string;
SERVER_LOCK?: boolean;
SERVER_NAME?: string;
SERVER_TYPE?: ServerType;
+10
View File
@@ -12,9 +12,19 @@ const invoke = (channel: string, ...args: any[]) => {
return ipcRenderer.invoke(channel, ...args);
};
const on = (channel: string, listener: (event: any, ...args: any[]) => void) => {
ipcRenderer.on(channel, listener);
};
const removeListener = (channel: string, listener: (event: any, ...args: any[]) => void) => {
ipcRenderer.removeListener(channel, listener);
};
export const ipc = {
invoke,
on,
removeAllListeners,
removeListener,
send,
};
+1
View File
@@ -74,6 +74,7 @@ const env = {
SERVER_TYPE !== null
? process.env.LEGACY_AUTHENTICATION?.toLocaleLowerCase() === 'true'
: false,
REMOTE_URL: process.env.REMOTE_URL ?? '',
SERVER_LOCK:
SERVER_TYPE !== null ? process.env.SERVER_LOCK?.toLocaleLowerCase() === 'true' : false,
SERVER_NAME: process.env.SERVER_NAME ?? '',
+45
View File
@@ -200,6 +200,18 @@ export const controller: GeneralController = {
server.type,
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
},
getAlbumArtistInfo(args) {
const server = getServerById(args.apiClientProps.serverId);
if (!server) {
return Promise.resolve(null);
}
const fn = apiController('getAlbumArtistInfo', server.type);
return fn
? fn(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }))
: Promise.resolve(null);
},
getAlbumArtistList(args) {
const server = getServerById(args.apiClientProps.serverId);
@@ -308,6 +320,20 @@ export const controller: GeneralController = {
}),
);
},
getAlbumRadio(args) {
const server = getServerById(args.apiClientProps.serverId);
if (!server) {
throw new Error(
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumRadio`,
);
}
return apiController(
'getAlbumRadio',
server.type,
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
},
getArtistList(args) {
const server = getServerById(args.apiClientProps.serverId);
@@ -416,6 +442,25 @@ export const controller: GeneralController = {
}),
);
},
getImageRequest(args) {
const server = getServerById(args.apiClientProps.serverId);
if (!server) {
return null;
}
return (
apiController(
'getImageRequest',
server.type,
)?.(
addContext({
...args,
apiClientProps: { ...args.apiClientProps, server },
}),
) || null
);
},
getImageUrl(args) {
const server = getServerById(args.apiClientProps.serverId);
+137 -50
View File
@@ -4,7 +4,7 @@ import filter from 'lodash/filter';
import orderBy from 'lodash/orderBy';
import { z } from 'zod';
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
import { createAuthHeader, jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
import { useRadioStore } from '/@/renderer/features/radio/store/radio-store';
import { getServerUrl } from '/@/renderer/utils/normalize-server-url';
import { jfNormalize } from '/@/shared/api/jellyfin/jellyfin-normalize';
@@ -15,10 +15,13 @@ import {
albumListSortMap,
Folder,
genreListSortMap,
ImageArgs,
ImageRequest,
InternalControllerEndpoint,
LibraryItem,
Played,
playlistListSortMap,
ReplaceApiClientProps,
ServerType,
Song,
SongListSort,
@@ -29,6 +32,33 @@ import {
} from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
const getJellyfinImageRequest = ({
apiClientProps: { server },
baseUrl,
query,
}: ReplaceApiClientProps<ImageArgs>): ImageRequest | null => {
const { id, size } = query;
const imageSize = size;
if (!server) {
return null;
}
const url = baseUrl || getServerUrl(server);
if (!url) {
return null;
}
return {
cacheKey: ['jellyfin', server.id, baseUrl || '', id, imageSize || ''].join(':'),
headers: server.credential
? { Authorization: createAuthHeader().concat(`, Token="${server.credential}"`) }
: { Authorization: createAuthHeader() },
url: `${url}/Items/${id}/Images/Primary?quality=96${imageSize ? `&width=${imageSize}` : ''}`,
};
};
const formatCommaDelimitedString = (value: string[]) => {
return value.join(',');
};
@@ -54,15 +84,38 @@ const VERSION_INFO: VersionInfo = [
];
const JF_FIELDS = {
ALBUM_ARTIST_DETAIL: 'Genres, Overview, SortName, ProviderIds',
ALBUM_ARTIST_LIST: 'Genres, DateCreated, ExternalUrls, Overview, SortName, ProviderIds',
ALBUM_DETAIL: 'Genres, DateCreated, ChildCount, People, Tags, ProviderIds',
ALBUM_LIST: 'People, Tags, Studios, SortName, UserData, ProviderIds, ChildCount',
FOLDER: 'Genres, DateCreated, MediaSources, UserData, ParentId',
GENRE: 'ItemCounts',
PLAYLIST_DETAIL: 'Genres, DateCreated, MediaSources, ChildCount, ParentId, SortName',
PLAYLIST_LIST: 'ChildCount, Genres, DateCreated, ParentId, Overview',
SONG: 'Genres, DateCreated, MediaSources, ParentId, People, Tags, SortName, UserData, ProviderIds',
ALBUM_ARTIST_DETAIL: ['Genres', 'Overview', 'SortName', 'ProviderIds'],
ALBUM_ARTIST_LIST: [
'Genres',
'DateCreated',
'ExternalUrls',
'Overview',
'SortName',
'ProviderIds',
],
ALBUM_DETAIL: ['Genres', 'DateCreated', 'ChildCount', 'People', 'Tags', 'ProviderIds'],
ALBUM_LIST: ['People', 'Tags', 'Studios', 'SortName', 'ProviderIds', 'ChildCount'],
FOLDER: ['Genres', 'DateCreated', 'MediaSources', 'ParentId'],
GENRE: ['ItemCounts'],
PLAYLIST_DETAIL: [
'Genres',
'DateCreated',
'MediaSources',
'ChildCount',
'ParentId',
'SortName',
],
PLAYLIST_LIST: ['ChildCount', 'Genres', 'DateCreated', 'ParentId', 'Overview'],
SONG: [
'Genres',
'DateCreated',
'MediaSources',
'ParentId',
'People',
'Tags',
'SortName',
'ProviderIds',
],
} as const;
export const JellyfinController: InternalControllerEndpoint = {
@@ -235,34 +288,54 @@ export const JellyfinController: InternalControllerEndpoint = {
throw new Error('No userId found');
}
const [res, similarArtistsRes] = await Promise.all([
jfApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
userId: apiClientProps.server?.userId,
},
query: {
Fields: JF_FIELDS.ALBUM_ARTIST_DETAIL,
},
}),
jfApiClient(apiClientProps).getSimilarArtistList({
params: {
id: query.id,
},
query: {
Limit: 10,
},
}),
]);
const res = await jfApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
userId: apiClientProps.server?.userId,
},
query: {
Fields: ['Genres', 'Overview', 'SortName'],
},
});
if (res.status !== 200 || similarArtistsRes.status !== 200) {
if (res.status !== 200) {
throw new Error('Failed to get album artist detail');
}
return jfNormalize.albumArtist(
{ ...res.body, similarArtists: similarArtistsRes.body },
apiClientProps.server,
return jfNormalize.albumArtist(res.body, apiClientProps.server);
},
getAlbumArtistInfo: async (args) => {
const { apiClientProps, query } = args;
const similarArtistsRes = await jfApiClient(apiClientProps).getSimilarArtistList({
params: {
id: query.id,
},
query: {
Limit: query.limit ?? 10,
},
});
if (similarArtistsRes.status !== 200) {
return null;
}
const items = similarArtistsRes.body?.Items?.filter(
(entry) => entry.Name !== 'Various Artists',
);
const similarArtists =
items?.map((entry) => ({
id: entry.Id,
imageId: entry.ImageTags?.Primary ? entry.Id : null,
imageUrl: null,
name: entry.Name,
userFavorite: entry.UserData?.IsFavorite || false,
userRating: null,
})) ?? null;
return {
similarArtists,
};
},
getAlbumArtistList: async (args) => {
const { apiClientProps, query } = args;
@@ -271,6 +344,7 @@ export const JellyfinController: InternalControllerEndpoint = {
query: {
Fields: JF_FIELDS.ALBUM_ARTIST_LIST,
ImageTypeLimit: 1,
IsFavorite: query.favorite,
Limit: query.limit,
ParentId: getLibraryId(query.musicFolderId),
Recursive: true,
@@ -410,6 +484,34 @@ export const JellyfinController: InternalControllerEndpoint = {
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
}).then((result) => result!.totalRecordCount!),
getAlbumRadio: async (args) => {
const { apiClientProps, query } = args;
// For Jellyfin, use instant mix for album radio
const res = await jfApiClient(apiClientProps).getInstantMix({
params: {
itemId: query.albumId,
},
query: {
Fields: JF_FIELDS.SONG,
Limit: query.count,
UserId: apiClientProps.server?.userId || undefined,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album radio songs');
}
return res.body.Items.map((song) =>
jfNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
},
getArtistList: async (args) => {
const { apiClientProps, query } = args;
@@ -717,23 +819,8 @@ export const JellyfinController: InternalControllerEndpoint = {
totalRecordCount: res.body?.TotalRecordCount || 0,
};
},
getImageUrl: ({ apiClientProps: { server }, baseUrl, query }) => {
const { id, size } = query;
const imageSize = size;
const url = baseUrl || getServerUrl(server);
if (!url) {
return null;
}
// For Jellyfin, we construct the URL pattern
// The server will return a 404 or placeholder if no image exists
const imageUrl = `${url}/Items/${id}/Images/Primary?quality=96${imageSize ? `&width=${imageSize}` : ''}`;
// For songs, we might want to fall back to album art, but we don't have albumId here
// The caller can handle this if needed
return imageUrl;
},
getImageRequest: getJellyfinImageRequest,
getImageUrl: (args) => getJellyfinImageRequest(args)?.url || null,
getInternetRadioStations: async (args) => {
const { apiClientProps } = args;
@@ -30,6 +30,7 @@ import { ServerFeature } from '/@/shared/types/features-types';
const VERSION_INFO: VersionInfo = [
// Why 2? Subsonic controller will return 1 for its own implementation
// Use 2 to denote that Navidrome's own API has a different endpoint
['0.60.4', { [ServerFeature.TRACK_YES_NO_RATING_FILTER]: [1] }],
['0.57.0', { [ServerFeature.SERVER_PLAY_QUEUE]: [2] }],
['0.56.0', { [ServerFeature.TRACK_ALBUM_ARTIST_SEARCH]: [1] }],
['0.55.0', { [ServerFeature.BFR]: [1], [ServerFeature.TAGS]: [1] }],
@@ -189,19 +190,11 @@ export const NavidromeController: InternalControllerEndpoint = {
getAlbumArtistDetail: async (args) => {
const { apiClientProps, query } = args;
const [res, artistInfoRes] = await Promise.all([
ndApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
},
}),
ssApiClient(apiClientProps).getArtistInfo({
query: {
count: 10,
id: query.id,
},
}),
]);
const res = await ndApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album artist detail');
@@ -211,22 +204,42 @@ export const NavidromeController: InternalControllerEndpoint = {
throw new Error('Server is required');
}
// Prefer images from getArtistInfo first (which should be proxied)
// Prioritize large > medium > small
return ndNormalize.albumArtist(
{
...res.body.data,
...(artistInfoRes.status === 200 && {
largeImageUrl:
artistInfoRes.body.artistInfo.largeImageUrl ||
artistInfoRes.body.artistInfo.mediumImageUrl ||
artistInfoRes.body.artistInfo.smallImageUrl ||
res.body.data.largeImageUrl,
similarArtists: artistInfoRes.body.artistInfo.similarArtist,
}),
return ndNormalize.albumArtist(res.body.data, apiClientProps.server);
},
getAlbumArtistInfo: async (args) => {
const { apiClientProps, query } = args;
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
query: {
id: query.id,
...(query.limit != null && { count: query.limit }),
},
apiClientProps.server,
);
});
if (artistInfoRes.status !== 200) {
return null;
}
const artistInfo = artistInfoRes.body.artistInfo;
const imageUrl =
artistInfo?.largeImageUrl ||
artistInfo?.mediumImageUrl ||
artistInfo?.smallImageUrl ||
null;
return {
biography: artistInfo?.biography || null,
imageUrl,
similarArtists:
artistInfo?.similarArtist?.map((artist) => ({
id: artist.id,
imageId: null,
imageUrl: artist?.artistImageUrl?.replace(/\?size=\d+/, '') ?? null,
name: artist.name,
userFavorite: Boolean(artist.starred) || false,
userRating: artist.userRating ?? null,
})) ?? null,
};
},
getAlbumArtistList: async (args) => {
const { apiClientProps, query } = args;
@@ -376,6 +389,32 @@ export const NavidromeController: InternalControllerEndpoint = {
apiClientProps,
query: { ...query, limit: 1, startIndex: 0 },
}).then((result) => result!.totalRecordCount!),
getAlbumRadio: async (args) => {
const { apiClientProps, query } = args;
// Use getSimilarSongs API for album radio
const res = await ssApiClient({
...apiClientProps,
silent: true,
}).getSimilarSongs({
query: {
count: query.count,
id: query.albumId,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album radio songs');
}
if (!res.body.similarSongs?.song) {
return [];
}
return res.body.similarSongs.song.map((song) =>
ssNormalize.song(song, apiClientProps.server),
);
},
getArtistList: async (args) => {
const { apiClientProps, query } = args;
@@ -506,6 +545,7 @@ export const NavidromeController: InternalControllerEndpoint = {
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
},
getImageRequest: SubsonicController.getImageRequest,
getImageUrl: SubsonicController.getImageUrl,
getInternetRadioStations: SubsonicController.getInternetRadioStations,
getLyrics: SubsonicController.getLyrics,
@@ -643,6 +683,7 @@ export const NavidromeController: InternalControllerEndpoint = {
...subsonicArgs.features,
...navidromeFeatures,
publicPlaylist: [1],
[ServerFeature.ALBUM_YES_NO_RATING_FILTER]: [1],
[ServerFeature.MUSIC_FOLDER_MULTISELECT]: [1],
};
@@ -715,6 +756,10 @@ export const NavidromeController: InternalControllerEndpoint = {
album_id: query.albumIds,
genre_id: query.genreIds,
[getArtistSongKey(apiClientProps.server)]: query.artistIds ?? query.albumArtistIds,
...(hasFeature(apiClientProps.server, ServerFeature.TRACK_YES_NO_RATING_FILTER) &&
query.hasRating !== undefined
? { has_rating: query.hasRating }
: {}),
library_id: getLibraryId(query.musicFolderId),
starred: query.favorite,
title: query.searchTerm,
+13 -8
View File
@@ -1,8 +1,10 @@
import type {
AlbumArtistDetailQuery,
AlbumArtistInfoQuery,
AlbumArtistListQuery,
AlbumDetailQuery,
AlbumListQuery,
AlbumRadioQuery,
ArtistListQuery,
ArtistRadioQuery,
FolderQuery,
@@ -92,6 +94,13 @@ export const queryKeys: Record<
return [serverId, 'albumArtists', 'infiniteList'] as const;
},
info: (serverId: string, query?: AlbumArtistInfoQuery) => {
if (query) {
return [serverId, 'albumArtists', 'info', query] as const;
}
return [serverId, 'albumArtists', 'info'] as const;
},
list: (serverId: string, query?: AlbumArtistListQuery) => {
const { filter, pagination } = splitPaginatedQuery(query);
if (query && pagination) {
@@ -348,6 +357,10 @@ export const queryKeys: Record<
root: (serverId: string) => [serverId] as const,
},
songs: {
albumRadio: (serverId: string, query?: AlbumRadioQuery) => {
if (query) return [serverId, 'songs', 'albumRadio', query] as const;
return [serverId, 'songs', 'albumRadio'] as const;
},
artistRadio: (serverId: string, query?: ArtistRadioQuery) => {
if (query) return [serverId, 'songs', 'artistRadio', query] as const;
return [serverId, 'songs', 'artistRadio'] as const;
@@ -410,15 +423,7 @@ export const queryKeys: Record<
if (query) return [serverId, 'songs', 'randomSongList', query] as const;
return [serverId, 'songs', 'randomSongList'] as const;
},
remoteLyrics: (serverId: string, query?: LyricsQuery) => {
if (query) return [serverId, 'song', 'lyrics', 'remote', query] as const;
return [serverId, 'song', 'lyrics', 'remote'] as const;
},
root: (serverId: string) => [serverId, 'songs'] as const,
serverLyrics: (serverId: string, query?: LyricsQuery) => {
if (query) return [serverId, 'song', 'lyrics', 'server', query] as const;
return [serverId, 'song', 'lyrics', 'server'] as const;
},
similar: (serverId: string, query?: SimilarSongsQuery) => {
if (query) return [serverId, 'song', 'similar', query] as const;
return [serverId, 'song', 'similar'] as const;
+101 -44
View File
@@ -20,9 +20,12 @@ import { hasFeature, sortAlbumArtistList, sortAlbumList, sortSongList } from '/@
import {
AlbumListSort,
GenreListSort,
ImageArgs,
ImageRequest,
InternalControllerEndpoint,
LibraryItem,
PlaylistListSort,
ReplaceApiClientProps,
ServerType,
Song,
SongListSort,
@@ -30,6 +33,36 @@ import {
} from '/@/shared/types/domain-types';
import { ServerFeature, ServerFeatures } from '/@/shared/types/features-types';
const getSubsonicImageRequest = ({
apiClientProps: { server },
baseUrl,
query,
}: ReplaceApiClientProps<ImageArgs>): ImageRequest | null => {
const { id, size } = query;
const imageSize = size;
const url = baseUrl || getServerUrl(server);
if (!url || !server?.credential) {
return null;
}
// Check for default placeholder image ID
if (id.match('2a96cbd8b46e442fc41c2b86b821562f')) {
return null;
}
return {
cacheKey: ['subsonic', server.id, baseUrl || '', id, imageSize || ''].join(':'),
url:
`${url}/rest/getCoverArt.view` +
`?id=${id}` +
`&${server.credential}` +
'&v=1.13.0' +
'&c=Feishin' +
(imageSize ? `&size=${imageSize}` : ''),
};
};
const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefined> = {
[AlbumListSort.ALBUM_ARTIST]: AlbumListSortType.ALPHABETICAL_BY_ARTIST,
[AlbumListSort.ARTIST]: undefined,
@@ -258,18 +291,11 @@ export const SubsonicController: InternalControllerEndpoint = {
getAlbumArtistDetail: async (args) => {
const { apiClientProps, query } = args;
const [artistInfoRes, res] = await Promise.all([
ssApiClient(apiClientProps).getArtistInfo({
query: {
id: query.id,
},
}),
ssApiClient(apiClientProps).getArtist({
query: {
id: query.id,
},
}),
]);
const res = await ssApiClient(apiClientProps).getArtist({
query: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album artist detail');
@@ -277,11 +303,6 @@ export const SubsonicController: InternalControllerEndpoint = {
const artist = res.body.artist;
let artistInfo;
if (artistInfoRes.status === 200) {
artistInfo = artistInfoRes.body.artistInfo;
}
return {
...ssNormalize.albumArtist(artist, apiClientProps.server),
albums: artist.album?.map((album) =>
@@ -292,10 +313,36 @@ export const SubsonicController: InternalControllerEndpoint = {
args.context?.pathReplaceWith,
),
),
similarArtists: null,
};
},
getAlbumArtistInfo: async (args) => {
const { apiClientProps, query } = args;
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
query: {
id: query.id,
...(query.limit != null && { count: query.limit }),
},
});
if (artistInfoRes.status !== 200) {
return null;
}
const artistInfo = artistInfoRes.body.artistInfo;
return {
biography: artistInfo?.biography || null,
similarArtists:
artistInfo?.similarArtist?.map((artist) =>
ssNormalize.albumArtist(artist, apiClientProps.server),
) || null,
artistInfo?.similarArtist?.map((artist) => ({
id: artist.id,
imageId: null,
imageUrl: null,
name: artist.name,
userFavorite: Boolean(artist.starred) || false,
userRating: artist.userRating ?? null,
})) ?? null,
};
},
getAlbumArtistList: async (args) => {
@@ -325,6 +372,10 @@ export const SubsonicController: InternalControllerEndpoint = {
results = searchResults;
}
if (query.favorite) {
results = results.filter((artist) => artist.userFavorite);
}
return sortAndPaginate(results, {
limit: query.limit,
sortBy: query.sortBy,
@@ -676,6 +727,33 @@ export const SubsonicController: InternalControllerEndpoint = {
return totalRecordCount;
},
getAlbumRadio: async (args) => {
const { apiClientProps, context, query } = args;
const res = await ssApiClient(apiClientProps).getSimilarSongs({
query: {
count: query.count,
id: query.albumId,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album radio songs');
}
if (!res.body.similarSongs?.song) {
return [];
}
return res.body.similarSongs.song.map((song) =>
ssNormalize.song(
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
);
},
getArtistList: async (args) => {
const { apiClientProps, query } = args;
@@ -763,7 +841,7 @@ export const SubsonicController: InternalControllerEndpoint = {
getFolder: async ({ apiClientProps, context, query }) => {
const sortOrder = (query.sortOrder?.toLowerCase() ?? 'asc') as 'asc' | 'desc';
const isRootFolderId = /^\d+$/.test(query.id);
const isRootFolderId = query.id === '0';
if (isRootFolderId) {
const res = await ssApiClient(apiClientProps).getIndexes({
@@ -907,29 +985,8 @@ export const SubsonicController: InternalControllerEndpoint = {
startIndex: query.startIndex,
});
},
getImageUrl: ({ apiClientProps: { server }, baseUrl, query }) => {
const { id, size } = query;
const imageSize = size;
const url = baseUrl || getServerUrl(server);
if (!url || !server?.credential) {
return null;
}
// Check for default placeholder image ID
if (id.match('2a96cbd8b46e442fc41c2b86b821562f')) {
return null;
}
return (
`${url}/rest/getCoverArt.view` +
`?id=${id}` +
`&${server.credential}` +
'&v=1.13.0' +
'&c=Feishin' +
(imageSize ? `&size=${imageSize}` : '')
);
},
getImageRequest: getSubsonicImageRequest,
getImageUrl: (args) => getSubsonicImageRequest(args)?.url || null,
getInternetRadioStations: async (args) => {
const { apiClientProps } = args;
+7
View File
@@ -29,6 +29,12 @@ const ReleaseNotesModal = lazy(() =>
})),
);
const UpdateAvailableDialog = lazy(() =>
import('./update-available-dialog').then((module) => ({
default: module.UpdateAvailableDialog,
})),
);
const ipc = isElectron() ? window.api.ipc : null;
export const App = () => {
@@ -118,6 +124,7 @@ export const App = () => {
</WebAudioContext.Provider>
<Suspense fallback={null}>
<ReleaseNotesModal />
<UpdateAvailableDialog />
</Suspense>
</MantineProvider>
);
@@ -365,6 +365,7 @@ interface GridCarouselSkeletonProps {
containerQuery?: ReturnType<typeof useGridCarouselContainerQuery>;
enableRefresh?: boolean;
placeholderItemType: LibraryItem;
placeholderRound?: boolean;
placeholderRows: DataRow[];
rowCount?: number;
title?: ReactNode | string;
@@ -375,12 +376,15 @@ const GridCarouselSkeleton = (props: GridCarouselSkeletonProps) => {
containerQuery: providedContainerQuery,
enableRefresh = false,
placeholderItemType,
placeholderRound = false,
placeholderRows,
rowCount = 1,
title,
} = props;
const { ...cq } = providedContainerQuery;
const defaultContainerQuery = useGridCarouselContainerQuery();
const containerQuery = providedContainerQuery ?? defaultContainerQuery;
const { ...cq } = containerQuery;
const cardsToShow = cq.isCalculated
? getCardsToShow({
@@ -399,6 +403,7 @@ const GridCarouselSkeleton = (props: GridCarouselSkeletonProps) => {
content: (
<MemoizedItemCard
data={undefined}
isRound={placeholderRound}
itemType={placeholderItemType}
rows={placeholderRows}
type="poster"
@@ -406,12 +411,12 @@ const GridCarouselSkeleton = (props: GridCarouselSkeletonProps) => {
),
id: `skeleton-${index}`,
}));
}, [cardsToShow, rowCount, placeholderItemType, placeholderRows]);
}, [cardsToShow, placeholderRound, rowCount, placeholderItemType, placeholderRows]);
return (
<GridCarousel
cards={placeholderCards}
containerQuery={providedContainerQuery}
containerQuery={containerQuery}
enableRefresh={enableRefresh}
hasNextPage={false}
isFetchingNextPage={false}
@@ -12,7 +12,7 @@ import {
useSettingsStore,
} from '/@/renderer/store';
import { BaseImage, ImageProps } from '/@/shared/components/image/image';
import { ExplicitStatus, LibraryItem } from '/@/shared/types/domain-types';
import { ExplicitStatus, ImageRequest, LibraryItem } from '/@/shared/types/domain-types';
const getUnloaderIcon = (itemType: LibraryItem) => {
switch (itemType) {
@@ -54,10 +54,19 @@ const BaseItemImage = (
type: props.type,
});
const imageRequest = useItemImageRequest({
id: props.id,
imageUrl: src,
itemType: props.itemType,
serverId: serverId || undefined,
type: props.type,
});
const isExplicit = blurExplicitImages && explicitStatus === ExplicitStatus.EXPLICIT;
return (
<BaseImage
imageRequest={imageRequest}
isExplicit={isExplicit}
src={imageUrl}
unloaderIcon={getUnloaderIcon(props.itemType)}
@@ -113,6 +122,79 @@ export const useItemImageUrl = (args: UseItemImageUrlProps) => {
}, [args.serverId, id, imageUrl, itemType, serverId, size, sizeByType, useRemoteUrl]);
};
export const useItemImageRequest = (args: UseItemImageUrlProps) => {
const { id, imageUrl, itemType, size, type, useRemoteUrl } = args;
const serverId = useCurrentServerId();
const imageRes = useImageRes();
const sizeByType: number | undefined = type ? imageRes[type] : undefined;
return useMemo(() => {
if (imageUrl) {
return {
cacheKey: imageUrl,
url: imageUrl,
} satisfies ImageRequest;
}
if (!id) {
return undefined;
}
const targetServerId = args.serverId || serverId;
let baseUrl: string | undefined;
if (useRemoteUrl) {
const server = getServerById(targetServerId);
baseUrl = server?.remoteUrl || server?.url;
}
return (
api.controller.getImageRequest({
apiClientProps: { serverId: targetServerId },
baseUrl,
query: { id, itemType, size: size ?? sizeByType },
}) || undefined
);
}, [args.serverId, id, imageUrl, itemType, serverId, size, sizeByType, useRemoteUrl]);
};
export function getItemImageRequest(args: UseItemImageUrlProps) {
const { id, imageUrl, itemType, size, type, useRemoteUrl } = args;
const authStore = useAuthStore.getState();
const currentServerId = authStore.currentServer?.id;
const serverId = (args.serverId || currentServerId) as string;
const imageRes = useSettingsStore.getState().general.imageRes;
const sizeByType: number | undefined = type ? imageRes[type] : undefined;
if (imageUrl) {
return {
cacheKey: imageUrl,
url: imageUrl,
} satisfies ImageRequest;
}
if (!id) {
return undefined;
}
let baseUrl: string | undefined;
if (useRemoteUrl) {
const server = getServerById(serverId);
baseUrl = server?.remoteUrl || server?.url;
}
return (
api.controller.getImageRequest({
apiClientProps: { serverId },
baseUrl,
query: { id, itemType, size: size ?? sizeByType },
}) || undefined
);
}
export function getItemImageUrl(args: UseItemImageUrlProps) {
const { id, imageUrl, itemType, size, type, useRemoteUrl } = args;
const authStore = useAuthStore.getState();
@@ -1,3 +1,3 @@
.container {
height: 500px;
.list-expanded-container {
overflow: auto;
}
@@ -1,32 +1,23 @@
import { motion, Variants } from 'motion/react';
import { ReactNode } from 'react';
import styles from './expanded-list-container.module.css';
const expandedAnimationVariants: Variants = {
hidden: {
height: 0,
minHeight: 0,
},
show: {
minHeight: '300px',
transition: {
duration: 0.3,
ease: 'easeInOut',
},
},
};
const EXPANDED_HEIGHT = 300;
export const ExpandedListContainer = ({ children }: { children: ReactNode }) => {
export interface ExpandedListContainerProps {
children: ReactNode;
}
export const ExpandedListContainer = ({ children }: ExpandedListContainerProps) => {
return (
<motion.div
animate="show"
<div
className={styles.listExpandedContainer}
exit="hidden"
initial="hidden"
variants={expandedAnimationVariants}
style={{
height: EXPANDED_HEIGHT,
overflow: 'auto',
}}
>
{children}
</motion.div>
</div>
);
};
@@ -2,27 +2,18 @@ import { Suspense } from 'react';
import styles from './expanded-list-item.module.css';
import {
ItemListStateActions,
ItemListStateItem,
useItemListStateSubscription,
} from '/@/renderer/components/item-list/helpers/item-list-state';
import { ItemListStateItem } from '/@/renderer/components/item-list/helpers/item-list-state';
import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item';
import { Spinner } from '/@/shared/components/spinner/spinner';
import { LibraryItem } from '/@/shared/types/domain-types';
interface ExpandedListItemProps {
internalState: ItemListStateActions;
item?: ItemListStateItem;
itemType: LibraryItem;
}
export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemProps) => {
const expandedItems = useItemListStateSubscription(internalState, () =>
internalState ? internalState.getExpandedItemsCached() : [],
);
const currentItem = expandedItems[0];
if (!currentItem) {
export const ExpandedListItem = ({ item, itemType }: ExpandedListItemProps) => {
if (!item) {
return null;
}
@@ -30,11 +21,7 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr
<div className={styles.container}>
<div className={styles.inner}>
<Suspense fallback={<Spinner container />}>
<SelectedItem
internalState={internalState}
item={currentItem as ItemListStateItem}
itemType={itemType}
/>
<SelectedItem item={item} itemType={itemType} />
</Suspense>
</div>
</div>
@@ -42,15 +29,14 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr
};
interface SelectedItemProps {
internalState: ItemListStateActions;
item: ItemListStateItem;
itemType: LibraryItem;
}
const SelectedItem = ({ internalState, item, itemType }: SelectedItemProps) => {
const SelectedItem = ({ item, itemType }: SelectedItemProps) => {
switch (itemType) {
case LibraryItem.ALBUM:
return <ExpandedAlbumListItem internalState={internalState} item={item} />;
return <ExpandedAlbumListItem item={item} />;
default:
return null;
}
@@ -8,6 +8,7 @@ import { ContextMenuController } from '/@/renderer/features/context-menu/context
import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite';
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
import { useAppStore } from '/@/renderer/store';
import { LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
import { Play, TableColumn } from '/@/shared/types/types';
@@ -277,19 +278,27 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
}
},
onExpand: ({ internalState, item }: DefaultItemControlProps) => {
if (!item || !internalState) {
return;
}
onExpand: ({ item, itemType }: DefaultItemControlProps) => {
if (!item) return;
// Extract rowId from the item
const rowId = internalState.extractRowId(item);
if (!rowId) return;
// Use the item directly (rowId is separate, used only as key in state)
const itemListItem = item as ItemListStateItemWithRequiredProperties;
const setGlobalExpanded = useAppStore.getState().actions.setGlobalExpanded;
const globalExpanded = useAppStore.getState().globalExpanded;
return internalState?.toggleExpanded(itemListItem);
if (globalExpanded?.item?.id === item.id) {
setGlobalExpanded(null);
} else {
const itemForStore: ItemListStateItemWithRequiredProperties & {
imageId: null | string;
} = {
...itemListItem,
imageId: (itemListItem as { imageId?: null | string }).imageId ?? null,
};
setGlobalExpanded({
item: itemForStore,
itemType,
});
}
},
onFavorite: ({
@@ -49,6 +49,7 @@ const getRowIdFromTableColumn = (tableColumn: TableColumn): null | string => {
[TableColumn.ALBUM]: 'album',
[TableColumn.ALBUM_ARTIST]: 'albumArtists',
[TableColumn.ALBUM_COUNT]: 'albumCount',
[TableColumn.ALBUM_GROUP]: null,
[TableColumn.ARTIST]: 'artists',
[TableColumn.BIOGRAPHY]: null,
[TableColumn.BIT_DEPTH]: 'bitDepth',
@@ -1,6 +1,6 @@
import clsx from 'clsx';
import throttle from 'lodash/throttle';
import { AnimatePresence, motion } from 'motion/react';
import { motion } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import React, {
CSSProperties,
@@ -31,15 +31,12 @@ import {
ItemCard,
ItemCardProps,
} from '/@/renderer/components/item-card/item-card';
import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded-list-container';
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
import {
ItemListStateActions,
ItemListStateItemWithRequiredProperties,
useItemListState,
useItemListStateSubscription,
} from '/@/renderer/components/item-list/helpers/item-list-state';
import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys';
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
@@ -829,10 +826,6 @@ const BaseItemGridList = ({
/>
)}
</AutoSizer>
<AnimatePresence presenceAffectsLayout>
<ExpandedContainer internalState={internalState} itemType={itemType} />
{/* {enableSelectionDialog && <SelectionDialog internalState={internalState} />} */}
</AnimatePresence>
</motion.div>
);
};
@@ -903,25 +896,3 @@ const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
export const ItemGridList = memo(BaseItemGridList);
ItemGridList.displayName = 'ItemGridList';
const ExpandedContainer = ({
internalState,
itemType,
}: {
internalState: ItemListStateActions;
itemType: LibraryItem;
}) => {
const hasExpanded = useItemListStateSubscription(internalState, (state) =>
state ? state.expanded.size > 0 : false,
);
return (
<AnimatePresence initial={false}>
{hasExpanded && (
<ExpandedListContainer>
<ExpandedListItem internalState={internalState} itemType={itemType} />
</ExpandedListContainer>
)}
</AnimatePresence>
);
};
@@ -0,0 +1,33 @@
.container {
display: flex;
gap: var(--theme-spacing-sm);
width: 100%;
height: 100%;
padding: 0 var(--theme-spacing-xs);
}
.image-container {
position: relative;
box-sizing: border-box;
flex-shrink: 0;
height: 100%;
aspect-ratio: 1;
padding-top: calc(var(--theme-spacing-xs) * 0.5);
}
.info {
display: flex;
flex-direction: column;
min-width: 0;
padding: 0;
overflow: hidden;
}
.album-name {
font-size: var(--theme-font-size-sm);
}
.artist-name {
font-size: var(--theme-font-size-xs);
opacity: 0.7;
}
@@ -0,0 +1,79 @@
import { ReactElement, useState } from 'react';
import imageColumnStyles from '../item-detail-list/columns/image-column.module.css';
import styles from './album-group-header.module.css';
import { TableItemSize } from './item-table-list';
import { ItemImage } from '/@/renderer/components/item-image/item-image';
import { PlayButton } from '/@/renderer/features/shared/components/play-button';
import {
LONG_PRESS_PLAY_BEHAVIOR,
PlayTooltip,
} from '/@/renderer/features/shared/components/play-button-group';
import { usePlayButtonBehavior } from '/@/renderer/store';
import { LibraryItem, Song } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types';
interface AlbumGroupHeaderProps {
groupRowCount?: number;
onPlay?: (playType: Play) => void;
size?: 'compact' | 'large' | 'normal';
song: Song | undefined;
}
export const AlbumGroupHeader = ({
groupRowCount,
onPlay,
size = 'normal',
song,
}: AlbumGroupHeaderProps): ReactElement => {
const [isHovered, setIsHovered] = useState(false);
const playButtonBehavior = usePlayButtonBehavior();
const rowHeight = {
compact: TableItemSize.COMPACT,
large: TableItemSize.LARGE,
normal: TableItemSize.DEFAULT,
}[size];
const infoHeight = groupRowCount !== undefined ? groupRowCount * rowHeight : undefined;
return (
<div className={styles.container}>
<div
className={styles.imageContainer}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<ItemImage
className={imageColumnStyles.compactImage}
enableDebounce
enableViewport={false}
id={song?.imageId}
itemType={LibraryItem.SONG}
src={song?.imageUrl}
type="table"
/>
{isHovered && onPlay && (
<div className={imageColumnStyles.playButtonOverlay}>
<PlayTooltip type={playButtonBehavior}>
<PlayButton
fill
onClick={(e) => {
e.stopPropagation();
onPlay(playButtonBehavior);
}}
onLongPress={(e) => {
e.stopPropagation();
onPlay(LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior]);
}}
/>
</PlayTooltip>
</div>
)}
</div>
<div className={styles.info} style={{ height: infoHeight }}>
<div className={styles.albumName}>{song?.album ?? ''}</div>
<div className={styles.artistName}>{song?.albumArtistName ?? ''}</div>
</div>
</div>
);
};
@@ -0,0 +1,90 @@
import { useCallback } from 'react';
import { AlbumGroupHeader } from '/@/renderer/components/item-list/item-table-list/album-group-header';
import {
isLastInAlbumGroup,
ItemTableListInnerColumn,
TableColumnContainer,
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
import { Song } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types';
export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
const firstDataRow = props.enableHeader ? 1 : 0;
const item = props.getRowItem?.(props.rowIndex) as null | Song | undefined;
const handlePlay = useCallback(
(playType: Play) => {
if (!item || !props.controls?.onDoubleClick) return;
const isHeaderEnabled = !!props.enableHeader;
const index = isHeaderEnabled ? props.rowIndex - 1 : props.rowIndex;
props.controls.onDoubleClick({
event: null,
index,
internalState: (props as any).internalState,
item,
itemType: props.itemType,
meta: { playType },
});
},
[item, props],
);
if (!item?.album) {
return <div style={props.style} />;
}
// Check if this is the first row of a new album group (by album name)
let isFirstInGroup = true;
if (props.rowIndex > firstDataRow) {
const prevItem = props.getRowItem?.(props.rowIndex - 1) as null | Song | undefined;
// If prevItem is undefined (not loaded yet), assume same group to avoid duplicates
if (prevItem === undefined || prevItem?.album === item.album) {
isFirstInGroup = false;
}
}
if (!isFirstInGroup) {
// For non-first rows, add border-bottom on the last row of the group
const needsBorder =
props.enableHorizontalBorders &&
isLastInAlbumGroup(
props.rowIndex,
props.getRowItem,
!!props.enableHeader,
props.data.length,
);
return (
<div
style={{
...props.style,
...(needsBorder
? { borderBottom: '1px solid var(--theme-colors-border)' }
: {}),
}}
/>
);
}
let groupRowCount = 1;
const totalDataRows = props.data.length + firstDataRow;
for (let idx = props.rowIndex + 1; idx < totalDataRows; idx++) {
const nextItem = props.getRowItem?.(idx) as null | Song | undefined;
if (!nextItem || nextItem.album !== item.album) break;
groupRowCount++;
}
return (
<TableColumnContainer {...props} enableAlternateRowColors={false}>
<AlbumGroupHeader
groupRowCount={groupRowCount}
onPlay={handlePlay}
size={props.size === 'default' ? 'normal' : props.size}
song={item}
/>
</TableColumnContainer>
);
};
@@ -62,6 +62,7 @@ const DefaultRowIndexColumn = (props: ItemTableListInnerColumn) => {
icon="arrowDownS"
iconProps={{ color: 'muted', size: 'md' }}
onClick={(e) => {
e.stopPropagation();
const item = (props.getRowItem?.(rowIndex) ??
data[rowIndex]) as ItemListItem;
const rowId = internalState.extractRowId(item);
@@ -6,6 +6,10 @@
height: 100%;
}
.title-combined.no-image {
grid-template-columns: 1fr;
}
.text-container {
display: grid;
grid-template-rows: 1fr 1fr;
@@ -81,6 +81,7 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const align = props.columns[props.columnIndex]?.align || 'start';
const hasAlbumGroupColumn = props.hasAlbumGroupColumn ?? false;
const item = rowItem as any;
const titleLinkProps = path
@@ -94,46 +95,53 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
return (
<TableColumnContainer
className={styles.titleCombined}
className={clsx(styles.titleCombined, {
[styles.noImage]: hasAlbumGroupColumn,
})}
containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties}
{...props}
>
<div
className={styles.imageContainer}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<ItemImage
containerClassName={styles.image}
enableDebounce={true}
enableViewport={false}
explicitStatus={item?.explicitStatus}
id={item?.imageId}
itemType={item?._itemType}
src={item?.imageUrl}
type="table"
/>
{isHovered && (
<div
className={clsx(styles.playButtonOverlay, {
[styles.compactPlayButtonOverlay]: props.size === 'compact',
})}
>
<PlayTooltip
disabled={props.itemType === LibraryItem.QUEUE_SONG}
type={playButtonBehavior}
{!hasAlbumGroupColumn && (
<div
className={styles.imageContainer}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<ItemImage
containerClassName={styles.image}
enableDebounce={true}
enableViewport={false}
explicitStatus={item?.explicitStatus}
id={item?.imageId}
itemType={item?._itemType}
src={item?.imageUrl}
type="table"
/>
{isHovered && (
<div
className={clsx(styles.playButtonOverlay, {
[styles.compactPlayButtonOverlay]: props.size === 'compact',
})}
>
<PlayButton
fill
onClick={(e) => handlePlay(playButtonBehavior, e)}
onLongPress={(e) =>
handlePlay(LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior], e)
}
/>
</PlayTooltip>
</div>
)}
</div>
<PlayTooltip
disabled={props.itemType === LibraryItem.QUEUE_SONG}
type={playButtonBehavior}
>
<PlayButton
fill
onClick={(e) => handlePlay(playButtonBehavior, e)}
onLongPress={(e) =>
handlePlay(
LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior],
e,
)
}
/>
</PlayTooltip>
</div>
)}
</div>
)}
<div
className={clsx(styles.textContainer, {
[styles.alignCenter]: align === 'center',
@@ -224,6 +232,7 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const align = props.columns[props.columnIndex]?.align || 'start';
const hasAlbumGroupColumn = props.hasAlbumGroupColumn ?? false;
const item = rowItem as any;
@@ -238,45 +247,52 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
return (
<TableColumnContainer
className={styles.titleCombined}
className={clsx(styles.titleCombined, {
[styles.noImage]: hasAlbumGroupColumn,
})}
containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties}
{...props}
>
<div
className={styles.imageContainer}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<ItemImage
containerClassName={styles.image}
explicitStatus={item?.explicitStatus}
id={item?.imageId}
itemType={item?._itemType}
serverId={item?._serverId}
src={item?.imageUrl}
type="table"
/>
{isHovered && (
<div
className={clsx(styles.playButtonOverlay, {
[styles.compactPlayButtonOverlay]: props.size === 'compact',
})}
>
<PlayTooltip
disabled={props.itemType === LibraryItem.QUEUE_SONG}
type={playButtonBehavior}
{!hasAlbumGroupColumn && (
<div
className={styles.imageContainer}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<ItemImage
containerClassName={styles.image}
explicitStatus={item?.explicitStatus}
id={item?.imageId}
itemType={item?._itemType}
serverId={item?._serverId}
src={item?.imageUrl}
type="table"
/>
{isHovered && (
<div
className={clsx(styles.playButtonOverlay, {
[styles.compactPlayButtonOverlay]: props.size === 'compact',
})}
>
<PlayButton
fill
onClick={(e) => handlePlay(playButtonBehavior, e)}
onLongPress={(e) =>
handlePlay(LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior], e)
}
/>
</PlayTooltip>
</div>
)}
</div>
<PlayTooltip
disabled={props.itemType === LibraryItem.QUEUE_SONG}
type={playButtonBehavior}
>
<PlayButton
fill
onClick={(e) => handlePlay(playButtonBehavior, e)}
onLongPress={(e) =>
handlePlay(
LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior],
e,
)
}
/>
</PlayTooltip>
</div>
)}
</div>
)}
<div
className={clsx(styles.textContainer, {
[styles.active]: isActive,
@@ -13,6 +13,15 @@ export type DefaultTableColumn = {
};
export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
{
align: 'start',
autoSize: false,
isEnabled: false,
label: i18n.t('table.config.label.albumGroup', { postProcess: 'titleCase' }),
pinned: 'left',
value: TableColumn.ALBUM_GROUP,
width: 200,
},
{
align: 'center',
autoSize: false,
@@ -19,24 +19,45 @@ export const useTableColumnModel = ({
const calculatedColumnWidths = useMemo(() => {
const baseWidths = parsedColumns.map((c) => c.width);
// When autoSizeColumns is enabled, treat all widths as proportions and scale to fit container
// When autoSizeColumns is enabled, treat unpinned widths as proportions and scale to fit container.
// Pinned columns keep their base width so they don't get squeezed.
if (autoFitColumns) {
const totalReferenceWidth = baseWidths.reduce((sum, width) => sum + width, 0);
const pinnedWidth = parsedColumns.reduce(
(sum, col, idx) => (col.pinned !== null ? sum + baseWidths[idx] : sum),
0,
);
const unpinnedIndices: number[] = [];
parsedColumns.forEach((col, idx) => {
if (col.pinned === null) {
unpinnedIndices.push(idx);
}
});
if (totalReferenceWidth === 0 || totalContainerWidth === 0) {
const unpinnedReferenceWidth = unpinnedIndices.reduce(
(sum, idx) => sum + baseWidths[idx],
0,
);
const availableForUnpinned = totalContainerWidth - pinnedWidth;
if (unpinnedReferenceWidth === 0 || availableForUnpinned <= 0) {
return baseWidths.map((width) => Math.round(width));
}
const scaleFactor = totalContainerWidth / totalReferenceWidth;
const scaledWidths = baseWidths.map((width) => Math.round(width * scaleFactor));
const scaleFactor = availableForUnpinned / unpinnedReferenceWidth;
const scaledWidths = baseWidths.map((width, idx) => {
if (parsedColumns[idx].pinned !== null) {
return Math.round(width);
}
return Math.round(width * scaleFactor);
});
// Adjust for rounding errors: ensure total equals totalContainerWidth
// Adjust for rounding errors on unpinned columns only
const totalScaled = scaledWidths.reduce((sum, width) => sum + width, 0);
const difference = totalContainerWidth - totalScaled;
if (difference !== 0 && scaledWidths.length > 0) {
const sortedIndices = scaledWidths
.map((width, idx) => ({ idx, width }))
if (difference !== 0 && unpinnedIndices.length > 0) {
const sortedIndices = unpinnedIndices
.map((idx) => ({ idx, width: scaledWidths[idx] }))
.sort((a, b) => b.width - a.width);
const adjustmentPerColumn = Math.sign(difference);
@@ -30,6 +30,7 @@ import { isNoHorizontalPaddingColumn } from '/@/renderer/components/item-list/it
import { ActionsColumn } from '/@/renderer/components/item-list/item-table-list/columns/actions-column';
import { AlbumArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-artists-column';
import { AlbumColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-column';
import { AlbumGroupColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-group-column';
import { ArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/artists-column';
import { ComposerColumn } from '/@/renderer/components/item-list/item-table-list/columns/composer-column';
import { CountColumn } from '/@/renderer/components/item-list/item-table-list/columns/count-column';
@@ -213,6 +214,11 @@ const ItemTableListColumnBase = (props: ItemTableListColumn) => {
case TableColumn.SONG_COUNT:
return <CountColumn {...props} {...dragProps} controls={controls} type={type} />;
case TableColumn.ALBUM_GROUP:
return (
<AlbumGroupColumn {...props} {...dragProps} controls={controls} type={type} />
);
case TableColumn.ARTIST:
return <ArtistsColumn {...props} {...dragProps} controls={controls} type={type} />;
@@ -362,6 +368,27 @@ export const ItemTableListColumn = memo(ItemTableListColumnBase, (prevProps, nex
const NonMutedColumns = [TableColumn.TITLE, TableColumn.TITLE_ARTIST, TableColumn.TITLE_COMBINED];
export function isAlbumGroupingActive(columns: { id: string; isEnabled?: boolean }[]): boolean {
return columns.some((col) => col.id === TableColumn.ALBUM_GROUP && col.isEnabled);
}
export function isLastInAlbumGroup(
rowIndex: number,
getRowItem: ((index: number) => unknown) | undefined,
enableHeader: boolean | undefined,
dataLength: number,
): boolean {
const item = getRowItem?.(rowIndex) as null | undefined | { album?: string };
if (!item?.album) return true;
const nextRowIndex = rowIndex + 1;
const maxRow = enableHeader ? dataLength + 1 : dataLength;
if (nextRowIndex >= maxRow) return true;
const nextItem = getRowItem?.(nextRowIndex) as null | undefined | { album?: string };
return !nextItem || nextItem.album !== item.album;
}
export const TableColumnTextContainer = (
props: ItemTableListColumn & {
children: React.ReactNode;
@@ -493,7 +520,14 @@ export const TableColumnTextContainer = (
props.enableHorizontalBorders &&
props.enableHeader &&
props.rowIndex > 0 &&
(props.rowIndex === 1 || !isLastRow),
(isAlbumGroupingActive(props.columns)
? isLastInAlbumGroup(
props.rowIndex,
props.getRowItem,
!!props.enableHeader,
props.data.length,
)
: props.rowIndex === 1 || !isLastRow),
[styles.withVerticalBorder]: props.enableVerticalBorders && !isLastColumn,
})}
data-row-index={isDataRow ? `${props.tableId}-${props.rowIndex}` : undefined}
@@ -641,13 +675,24 @@ export const TableColumnContainer = (
[styles.paddingXl]: props.cellPadding === 'xl',
[styles.paddingXs]: props.cellPadding === 'xs',
[styles.right]: props.columns[props.columnIndex].align === 'end',
[styles.rowHoverHighlightEnabled]: isDataRow && props.enableRowHoverHighlight,
[styles.rowSelected]: isDataRow && isSelected,
[styles.rowHoverHighlightEnabled]:
isDataRow &&
props.enableRowHoverHighlight &&
props.type !== TableColumn.ALBUM_GROUP,
[styles.rowSelected]:
isDataRow && isSelected && props.type !== TableColumn.ALBUM_GROUP,
[styles.withHorizontalBorder]:
props.enableHorizontalBorders &&
props.enableHeader &&
props.rowIndex > 0 &&
(props.rowIndex === 1 || !isLastRow),
(isAlbumGroupingActive(props.columns)
? isLastInAlbumGroup(
props.rowIndex,
props.getRowItem,
!!props.enableHeader,
props.data.length,
)
: props.rowIndex === 1 || !isLastRow),
[styles.withVerticalBorder]: props.enableVerticalBorders && !isLastColumn,
})}
data-row-index={isDataRow ? `${props.tableId}-${props.rowIndex}` : undefined}
@@ -898,6 +943,9 @@ export const columnLabelMap: Record<TableColumn, ReactNode | string> = {
[TableColumn.ALBUM_COUNT]: i18n.t('table.column.albumCount', {
postProcess: 'upperCase',
}) as string,
[TableColumn.ALBUM_GROUP]: i18n.t('table.config.label.albumGroup', {
postProcess: 'upperCase',
}) as string,
[TableColumn.ARTIST]: i18n.t('table.column.artist', { postProcess: 'upperCase' }) as string,
[TableColumn.BIOGRAPHY]: i18n.t('table.column.biography', {
postProcess: 'upperCase',
@@ -1,7 +1,7 @@
// Component adapted from https://github.com/bvaughn/react-window/issues/826
import clsx from 'clsx';
import { AnimatePresence, motion } from 'motion/react';
import { motion } from 'motion/react';
import React, {
type JSXElementConstructor,
memo,
@@ -18,15 +18,12 @@ import { type CellComponentProps, Grid } from 'react-window-v2';
import styles from './item-table-list.module.css';
import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded-list-container';
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
import {
ItemListStateActions,
ItemListStateItemWithRequiredProperties,
useItemListState,
useItemListStateSubscription,
} from '/@/renderer/components/item-list/helpers/item-list-state';
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys';
@@ -97,7 +94,7 @@ const hasRequiredStateItemProperties = (
);
};
enum TableItemSize {
export enum TableItemSize {
COMPACT = 40,
DEFAULT = 64,
LARGE = 88,
@@ -207,17 +204,6 @@ const VirtualizedTableGrid = ({
[columnWidth, pinnedLeftColumnCount],
);
const rowHeightMemoized = useCallback(
(index: number, cellProps: TableItemProps) =>
getRowHeight(index + pinnedRowCount, cellProps),
[getRowHeight, pinnedRowCount],
);
const pinnedRightColumnWidthMemoized = useCallback(
(index: number) => columnWidth(index + pinnedLeftColumnCount + totalColumnCount),
[columnWidth, pinnedLeftColumnCount, totalColumnCount],
);
const groupHeaderInfoByRowIndex = useMemo(() => {
if (!groups || groups.length === 0) return undefined;
@@ -234,6 +220,19 @@ const VirtualizedTableGrid = ({
return map;
}, [groups, enableHeader]);
const rowHeightMemoized = useCallback(
(index: number, cellProps: TableItemProps) => {
const adjustedIndex = index + pinnedRowCount;
return getRowHeight(adjustedIndex, cellProps);
},
[getRowHeight, pinnedRowCount],
);
const pinnedRightColumnWidthMemoized = useCallback(
(index: number) => columnWidth(index + pinnedLeftColumnCount + totalColumnCount),
[columnWidth, pinnedLeftColumnCount, totalColumnCount],
);
const getGroupRenderData = useCallback(() => data, [data]);
// Calculate pinned column widths for group header positioning
@@ -352,6 +351,7 @@ const VirtualizedTableGrid = ({
controls,
enableHeader,
getRowHeight,
hasAlbumGroupColumn: parsedColumns.some((col) => col.id === TableColumn.ALBUM_GROUP),
internalState,
itemType,
playerContext,
@@ -805,6 +805,7 @@ export interface TableItemProps {
getRowItem?: (rowIndex: number) => null | undefined | unknown;
groupHeaderInfoByRowIndex?: Map<number, { groupIndex: number; startDataIndex: number }>;
groups?: TableGroupHeader[];
hasAlbumGroupColumn?: boolean;
internalState: ItemListStateActions;
itemType: ItemTableListProps['itemType'];
onRowClick?: (item: any, event: React.MouseEvent<HTMLDivElement>) => void;
@@ -1651,8 +1652,6 @@ const BaseItemTableList = ({
totalColumnCount={totalColumnCount}
totalRowCount={totalRowCount}
/>
<ExpandedContainer internalState={internalState} itemType={itemType} />
{/* {enableSelectionDialog && <SelectionDialog internalState={internalState} />} */}
</motion.div>
</ItemTableListConfigProvider>
</ItemTableListStoreProvider>
@@ -1661,26 +1660,4 @@ const BaseItemTableList = ({
export const ItemTableList = memo(BaseItemTableList);
const ExpandedContainer = ({
internalState,
itemType,
}: {
internalState: ItemListStateActions;
itemType: LibraryItem;
}) => {
const hasExpanded = useItemListStateSubscription(internalState, (state) =>
state ? state.expanded.size > 0 : false,
);
return (
<AnimatePresence initial={false}>
{hasExpanded && (
<ExpandedListContainer>
<ExpandedListItem internalState={internalState} itemType={itemType} />
</ExpandedListContainer>
)}
</AnimatePresence>
);
};
ItemTableList.displayName = 'ItemTableList';
@@ -196,7 +196,7 @@ export const QueryBuilder = ({
filters={filters}
groupIndex={groupIndex || []}
level={level}
noRemove={data?.rules?.length === 1}
noRemove={false}
onChangeField={onChangeField}
onChangeOperator={onChangeOperator}
onChangeValue={onChangeValue}
@@ -139,7 +139,7 @@ const QueryValueInput = ({
case 'dateRange':
if (operator === 'inTheRangeDate') {
return (
<Group gap="sm" wrap="nowrap">
<Group gap="sm" grow wrap="nowrap">
<DateInput
clearable
defaultLevel="year"
@@ -21,7 +21,10 @@ import { ItemControls } from '/@/renderer/components/item-list/types';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
import {
ListConfigMenu,
SONG_DISPLAY_TYPES,
} from '/@/renderer/features/shared/components/list-config-menu';
import {
CLIENT_SIDE_SONG_FILTERS,
ListSortByDropdownControlled,
@@ -755,7 +758,10 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
sortOrder={sortOrder}
/>
<ListConfigMenu
displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]}
displayTypes={[
{ hidden: true, value: ListDisplayType.GRID },
...SONG_DISPLAY_TYPES,
]}
listKey={ItemListKey.ALBUM_DETAIL}
optionsConfig={{
table: {
@@ -1,10 +1,11 @@
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { forwardRef, Fragment, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useParams } from 'react-router';
import styles from './album-detail-header.module.css';
import { queryKeys } from '/@/renderer/api/query-keys';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists';
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
@@ -15,10 +16,11 @@ import {
} from '/@/renderer/features/shared/components/library-header';
import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite';
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useShowRatings } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { formatDateAbsoluteUTC, formatDurationString } from '/@/renderer/utils';
import { useArtistRadioCount, usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { formatDateAbsoluteUTC, formatDurationString, formatSizeString } from '/@/renderer/utils';
import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types';
import { Group } from '/@/shared/components/group/group';
import { Separator } from '/@/shared/components/separator/separator';
@@ -32,6 +34,8 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
const { t } = useTranslation();
const server = useCurrentServer();
const showRatings = useShowRatings();
const queryClient = useQueryClient();
const albumRadioCount = useArtistRadioCount();
const detailQuery = useQuery(
albumQueries.detail({ query: { id: albumId }, serverId: server?.id }),
);
@@ -41,7 +45,7 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
(detailQuery?.data?._serverType === ServerType.NAVIDROME ||
detailQuery?.data?._serverType === ServerType.SUBSONIC);
const { addToQueueByFetch } = usePlayer();
const { addToQueueByData, addToQueueByFetch } = usePlayer();
const playButtonBehavior = usePlayButtonBehavior();
const setRating = useSetRating();
@@ -92,6 +96,28 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
});
};
const handleAlbumRadio = async () => {
if (!server?.id || !albumId) return;
try {
const albumRadioSongs = await queryClient.fetchQuery({
...songsQueries.albumRadio({
query: {
albumId: albumId,
count: albumRadioCount,
},
serverId: server.id,
}),
queryKey: queryKeys.player.fetch({ albumId: albumId }),
});
if (albumRadioSongs && albumRadioSongs.length > 0) {
addToQueueByData(albumRadioSongs, Play.NOW);
}
} catch (error) {
console.error('Failed to load album radio:', error);
}
};
const releaseYear = detailQuery?.data?.releaseYear;
const releaseDate = detailQuery?.data?.releaseDate;
@@ -166,6 +192,12 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
id: 'explicitStatus',
value: detailQuery?.data?.explicitStatus,
},
{
id: 'size',
value: detailQuery?.data?.size
? formatSizeString(detailQuery?.data?.size)
: undefined,
},
{
id: 'playCount',
value: playCount ? t('entity.play', { count: playCount }) : undefined,
@@ -249,6 +281,7 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
</Group>
<LibraryHeaderMenu
favorite={detailQuery?.data?.userFavorite}
onAlbumRadio={handleAlbumRadio}
onFavorite={handleFavorite}
onMore={handleMoreOptions}
onPlay={(type) => handlePlay(type)}
@@ -20,7 +20,6 @@ export function AlbumGridCarousel(props: AlbumGridCarouselProps) {
const controls = useDefaultItemListControls();
const cards = useMemo(() => {
// Filter out excluded IDs if provided
const filteredItems = excludeIds
? data.filter((album) => !excludeIds.includes(album.id))
: data;
@@ -31,6 +30,7 @@ export function AlbumGridCarousel(props: AlbumGridCarouselProps) {
controls={controls}
data={album}
enableDrag
enableExpansion
itemType={LibraryItem.ALBUM}
rows={rows}
type="poster"
@@ -58,7 +58,6 @@ const BaseAlbumInfiniteCarousel = (props: AlbumCarouselProps & { rows: DataRow[]
const controls = useDefaultItemListControls();
const cards = useMemo(() => {
// Flatten all pages and filter excluded IDs
const allItems = albums?.pages.flatMap((page: AlbumListResponse) => page.items) || [];
const filteredItems = excludeIds
? allItems.filter((album) => !excludeIds.includes(album.id))
@@ -70,6 +69,7 @@ const BaseAlbumInfiniteCarousel = (props: AlbumCarouselProps & { rows: DataRow[]
controls={controls}
data={album}
enableDrag
enableExpansion
itemType={LibraryItem.ALBUM}
rows={rows}
type="poster"
@@ -6,6 +6,7 @@ import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpe
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
import { ItemDetailList } from '/@/renderer/components/item-list/item-detail-list/item-detail-list';
import { ItemListComponentProps } from '/@/renderer/components/item-list/types';
import { useListContext } from '/@/renderer/context/list-context';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import {
AlbumListQuery,
@@ -34,6 +35,7 @@ export const AlbumListInfiniteDetail = ({
}) as UseSuspenseQueryOptions<number, Error, number, readonly unknown[]>;
const listQueryFn = api.controller.getAlbumList;
const { pageKey } = useListContext();
const { handleColumnReordered } = useItemListColumnReorder({
itemListKey: ItemListKey.ALBUM,
@@ -46,7 +48,7 @@ export const AlbumListInfiniteDetail = ({
});
const { getItem, itemCount, loadedItems, onRangeChanged } = useItemListInfiniteLoader({
eventKey: ItemListKey.ALBUM,
eventKey: pageKey || ItemListKey.ALBUM,
itemsPerPage,
itemType: LibraryItem.ALBUM,
listCountQuery,
@@ -6,6 +6,7 @@ import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-r
import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist';
import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list';
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
import { useListContext } from '/@/renderer/context/list-context';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { useGeneralSettings } from '/@/renderer/store';
import {
@@ -37,9 +38,11 @@ export const AlbumListInfiniteGrid = ({
const listQueryFn = api.controller.getAlbumList;
const { pageKey } = useListContext();
const { dataVersion, getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ALBUM,
eventKey: pageKey || ItemListKey.ALBUM,
itemsPerPage,
itemType: LibraryItem.ALBUM,
listCountQuery,
@@ -8,6 +8,7 @@ import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpe
import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list';
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
import { ItemListTableComponentProps } from '/@/renderer/components/item-list/types';
import { useListContext } from '/@/renderer/context/list-context';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import {
AlbumListQuery,
@@ -43,10 +44,11 @@ export const AlbumListInfiniteTable = ({
}) as UseSuspenseQueryOptions<number, Error, number, readonly unknown[]>;
const listQueryFn = api.controller.getAlbumList;
const { pageKey } = useListContext();
const { getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ALBUM,
eventKey: pageKey || ItemListKey.ALBUM,
itemsPerPage,
itemType: LibraryItem.ALBUM,
listCountQuery,
@@ -8,6 +8,7 @@ import { ItemDetailList } from '/@/renderer/components/item-list/item-detail-lis
import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
import { useItemListPagination } from '/@/renderer/components/item-list/item-list-pagination/use-item-list-pagination';
import { ItemListComponentProps } from '/@/renderer/components/item-list/types';
import { useListContext } from '/@/renderer/context/list-context';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import {
AlbumListQuery,
@@ -36,6 +37,7 @@ export const AlbumListPaginatedDetail = ({
}) as UseSuspenseQueryOptions<number, Error, number, readonly unknown[]>;
const listQueryFn = api.controller.getAlbumList;
const { pageKey } = useListContext();
const { handleColumnReordered } = useItemListColumnReorder({
itemListKey: ItemListKey.ALBUM,
@@ -51,7 +53,7 @@ export const AlbumListPaginatedDetail = ({
const { data, pageCount, totalItemCount } = useItemListPaginatedLoader({
currentPage,
eventKey: ItemListKey.ALBUM,
eventKey: pageKey || ItemListKey.ALBUM,
itemsPerPage,
itemType: LibraryItem.ALBUM,
listCountQuery,
@@ -8,6 +8,7 @@ import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/it
import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
import { useItemListPagination } from '/@/renderer/components/item-list/item-list-pagination/use-item-list-pagination';
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
import { useListContext } from '/@/renderer/context/list-context';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { useGeneralSettings } from '/@/renderer/store';
import {
@@ -32,6 +33,7 @@ export const AlbumListPaginatedGrid = ({
serverId,
size,
}: AlbumListPaginatedGridProps) => {
const { pageKey } = useListContext();
const { currentPage, onChange } = useItemListPagination();
const listCountQuery = albumQueries.listCount({
@@ -43,7 +45,7 @@ export const AlbumListPaginatedGrid = ({
const { data, pageCount, totalItemCount } = useItemListPaginatedLoader({
currentPage,
eventKey: ItemListKey.ALBUM,
eventKey: pageKey || ItemListKey.ALBUM,
itemsPerPage,
itemType: LibraryItem.ALBUM,
listCountQuery,
@@ -10,6 +10,7 @@ import { useItemListPagination } from '/@/renderer/components/item-list/item-lis
import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list';
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
import { ItemListTableComponentProps } from '/@/renderer/components/item-list/types';
import { useListContext } from '/@/renderer/context/list-context';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import {
AlbumListQuery,
@@ -39,6 +40,7 @@ export const AlbumListPaginatedTable = ({
serverId,
size = 'default',
}: AlbumListPaginatedTableProps) => {
const { pageKey } = useListContext();
const { currentPage, onChange } = useItemListPagination();
const listCountQuery = albumQueries.listCount({
@@ -50,7 +52,7 @@ export const AlbumListPaginatedTable = ({
const { data, pageCount, totalItemCount } = useItemListPaginatedLoader({
currentPage,
eventKey: ItemListKey.ALBUM,
eventKey: pageKey || ItemListKey.ALBUM,
itemsPerPage,
itemType: LibraryItem.ALBUM,
listCountQuery,
@@ -22,6 +22,7 @@ import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { PlayButtonGroup } from '/@/renderer/features/shared/components/play-button-group';
import { useFastAverageColor } from '/@/renderer/hooks';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import { useSetGlobalExpanded } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Group } from '/@/shared/components/group/group';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
@@ -30,10 +31,24 @@ import { Spinner } from '/@/shared/components/spinner/spinner';
import { TextTitle } from '/@/shared/components/text-title/text-title';
import { Text } from '/@/shared/components/text/text';
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
import { LibraryItem, Song } from '/@/shared/types/domain-types';
import { LibraryItem, RelatedArtist, Song } from '/@/shared/types/domain-types';
import { DragOperation, DragTarget, DragTargetMap } from '/@/shared/types/drag-and-drop';
import { Play } from '/@/shared/types/types';
export interface ExpandedAlbumData {
_serverId: string;
albumArtists: RelatedArtist[];
id: string;
imageId: null | string;
name: string;
songs?: null | Song[];
}
export interface ExpandedAlbumListItemProps {
album?: ExpandedAlbumData;
item?: ItemListStateItem;
}
interface AlbumTracksTableProps {
isDark?: boolean;
serverId: string;
@@ -46,11 +61,6 @@ interface AlbumTracksTableProps {
}>;
}
interface ExpandedAlbumListItemProps {
internalState?: ItemListStateActions;
item: ItemListStateItem;
}
interface TrackRowProps {
controls: ReturnType<typeof useDefaultItemListControls>;
internalState: ItemListStateActions;
@@ -60,6 +70,23 @@ interface TrackRowProps {
songs: Song[];
}
const CloseExpandedButton = () => {
const setGlobalExpanded = useSetGlobalExpanded();
return (
<ActionIcon
className={clsx(styles.closeButton)}
icon="x"
iconProps={{
size: 'xl',
}}
onClick={() => setGlobalExpanded(null)}
radius="50%"
size="sm"
variant="default"
/>
);
};
const TrackRow = ({ controls, internalState, player, serverId, song, songs }: TrackRowProps) => {
const rowId = internalState.extractRowId(song);
const isSelected = useItemSelectionState(internalState, rowId);
@@ -188,136 +215,165 @@ const AlbumTracksTable = ({ isDark, serverId, songs }: AlbumTracksTableProps) =>
);
};
export const ExpandedAlbumListItem = ({ internalState, item }: ExpandedAlbumListItemProps) => {
const { data, isLoading } = useSuspenseQuery(
albumQueries.detail({
query: { id: item.id },
serverId: item._serverId,
}),
);
interface ExpandedAlbumListItemContentProps {
albumData: ExpandedAlbumData;
}
const ExpandedAlbumListItemContent = ({ albumData }: ExpandedAlbumListItemContentProps) => {
const player = usePlayer();
const imageUrl = useItemImageUrl({
id: item.imageId || undefined,
id: albumData.imageId || undefined,
itemType: LibraryItem.ALBUM,
type: 'itemCard',
});
const color = useFastAverageColor({
algorithm: 'sqrt',
id: item.id,
id: albumData.id,
src: imageUrl,
srcLoaded: true,
});
const handlePlay = useCallback(
(playType: Play) => {
if (!data) {
return;
}
if (data.songs) {
player.addToQueueByData(data.songs, playType);
if (albumData.songs?.length) {
player.addToQueueByData(albumData.songs, playType);
}
},
[data, player],
[albumData.songs, player],
);
if (color.isLoading) {
return null;
return <Spinner container />;
}
const songs = albumData.songs ?? null;
return (
<motion.div
animate={{
opacity: 1,
}}
animate={{ opacity: 1 }}
className={styles.container}
exit={{ opacity: 0 }}
initial={{ opacity: 0 }}
style={{ backgroundColor: color.background }}
>
{isLoading && (
<div className={styles.loading}>
<Spinner />
</div>
)}
<Suspense>
<div className={styles.expanded}>
<div className={styles.content}>
<div className={styles.header}>
<div className={styles.headerTitle}>
<TextTitle
className={clsx(styles.itemTitle, {
[styles.dark]: color.isDark,
})}
fw={700}
order={4}
>
{data?.name}
</TextTitle>
{internalState && (
<ActionIcon
className={clsx(styles.closeButton)}
icon="x"
iconProps={{
size: 'xl',
}}
onClick={() => {
const rowId = internalState.extractRowId(item);
if (rowId) {
internalState.clearExpanded();
}
}}
radius="50%"
size="sm"
variant="default"
/>
)}
</div>
<Group
className={clsx(styles.itemSubtitle, {
[styles.dark]: color.isDark,
})}
gap="xs"
<div className={styles.expanded}>
<div className={styles.content}>
<div className={styles.header}>
<div className={styles.headerTitle}>
<TextTitle
className={clsx(styles.itemTitle, { [styles.dark]: color.isDark })}
fw={700}
order={4}
>
{data?.albumArtists.map((artist, index) => (
<Fragment key={artist.id}>
<Text
className={clsx(styles.itemSubtitle, {
[styles.dark]: color.isDark,
})}
>
{artist.name}
</Text>
{index < data?.albumArtists.length - 1 && <Separator />}
</Fragment>
))}
</Group>
{albumData.name}
</TextTitle>
<CloseExpandedButton />
</div>
<AlbumTracksTable
isDark={color.isDark}
serverId={item._serverId}
songs={data?.songs}
/>
</div>
<div className={styles.imageContainer}>
<div
className={styles.backgroundImage}
style={{
['--bg-color' as string]: color?.background,
backgroundImage: `url(${imageUrl})`,
}}
/>
{data?.songs && data.songs.length > 0 && (
<div className={styles.playButtonGroup}>
<PlayButtonGroup onPlay={handlePlay} />
</div>
)}
<Group
className={clsx(styles.itemSubtitle, { [styles.dark]: color.isDark })}
gap="xs"
>
{albumData.albumArtists?.map((artist, index) => (
<Fragment key={artist.id}>
<Text
className={clsx(styles.itemSubtitle, {
[styles.dark]: color.isDark,
})}
>
{artist.name}
</Text>
{index < (albumData.albumArtists?.length ?? 0) - 1 && (
<Separator />
)}
</Fragment>
))}
</Group>
</div>
<AlbumTracksTable
isDark={color.isDark}
serverId={albumData._serverId}
songs={songs ?? undefined}
/>
</div>
</Suspense>
<div className={styles.imageContainer}>
<div
className={styles.backgroundImage}
style={{
['--bg-color' as string]: color?.background,
backgroundImage: `url(${imageUrl})`,
}}
/>
{songs && songs.length > 0 && (
<div className={styles.playButtonGroup}>
<PlayButtonGroup onPlay={handlePlay} />
</div>
)}
</div>
</div>
</motion.div>
);
};
const ExpandedAlbumListItemWithFetch = ({ item }: { item: ItemListStateItem }) => {
const { data } = useSuspenseQuery(
albumQueries.detail({
query: { id: item.id },
serverId: item._serverId,
}),
);
const albumData: ExpandedAlbumData = {
_serverId: item._serverId,
albumArtists: data?.albumArtists ?? [],
id: item.id,
imageId: item.imageId ?? data?.imageId ?? null,
name: data?.name ?? '',
songs: data?.songs ?? null,
};
return <ExpandedAlbumListItemContent albumData={albumData} />;
};
function itemToExpandedAlbumData(
item: ItemListStateItem & {
_playlistSongs?: Song[];
albumArtists?: RelatedArtist[];
name?: string;
},
): ExpandedAlbumData | null {
const songs =
(item as { songs?: Song[] }).songs ?? (item as { _playlistSongs?: Song[] })._playlistSongs;
if (songs == null) return null;
return {
_serverId: item._serverId,
albumArtists: item.albumArtists ?? [],
id: item.id,
imageId: (item as { imageId?: null | string }).imageId ?? null,
name: (item as { name?: string }).name ?? '',
songs,
};
}
export const ExpandedAlbumListItem = (props: ExpandedAlbumListItemProps) => {
if (props.album != null) {
return <ExpandedAlbumListItemContent albumData={props.album} />;
}
if (props.item != null) {
const albumData = itemToExpandedAlbumData(props.item);
if (albumData != null) {
return <ExpandedAlbumListItemContent albumData={albumData} />;
}
return (
<Suspense fallback={<Spinner container />}>
<ExpandedAlbumListItemWithFetch item={props.item} />
</Suspense>
);
}
return null;
};

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