Compare commits

...

37 Commits

Author SHA1 Message Date
jeffvli 5bab80b89b convert remaining locales to sentence case 2026-05-01 20:50:58 -07:00
jeffvli 066e5188f2 refactor some strings to sentence case 2026-05-01 20:50:57 -07:00
jeffvli 456f4d7f65 remove sentence case override configuration 2026-05-01 20:50:57 -07:00
jeffvli 02f5a1bd94 convert locales to new casing 2026-05-01 20:50:57 -07:00
jeffvli 4424e9ae33 convert EN localization to use proper casing, remove postprocessing from renderer 2026-05-01 20:47:24 -07:00
Hosted Weblate bc7ef0624b Translated using Weblate
Currently translated at 100.0% (1205 of 1205 strings) (Dutch)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nl/

Translated using Weblate

Currently translated at 100.0% (1205 of 1205 strings) (Dutch)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nl/

Co-authored-by: Joren Vansteenkiste <vansteenkiste.joren@telenet.be>
Co-authored-by: bokse <weblate@bokse.nl>
2026-05-01 21:32:24 +02:00
Pedro Daniel Reis 304ce8b881 [UI] Made sidebar image just use flex (#1975)
* made sidebar image just use flex

* force aspect ratio to be square

* prevent image container from expanding

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
2026-05-01 12:32:18 -07:00
Jonne Saloranta 01011a49a2 Replace success toast with info when no songs are added (#1994) 2026-05-01 11:44:15 -07:00
York d24ca04878 fix: detect Homebrew mpv on macOS (#1989) 2026-05-01 11:43:07 -07:00
Hosted Weblate 640d38e5a9 Translated using Weblate
Currently translated at 100.0% (1205 of 1205 strings) (Catalan)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/

Co-authored-by: Ondo <SparkyOndo@proton.me>
2026-04-30 15:09:57 +00:00
jeffvli ac0c074d4b fix undefined / null parameter string for Subsonic (#1978) 2026-04-28 21:17:44 -07:00
jeffvli 6be5818493 migrate to mantine v9 2026-04-28 21:02:27 -07:00
Hosted Weblate 03edd5a639 Translated using Weblate
Currently translated at 100.0% (1205 of 1205 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

Currently translated at 100.0% (1205 of 1205 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% (1205 of 1205 strings) (Czech)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/

Translated using Weblate

Currently translated at 99.8% (1201 of 1203 strings) (French)
Translation: feishin/Translation
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>
Co-authored-by: York <goog10216922@gmail.com>
2026-04-29 04:45:44 +02:00
jeffvli f5eb3f1488 wrap useHotkeys to disable on command palette open (#1925) 2026-04-28 19:31:41 -07:00
jeffvli 8eab9edb15 fix performance issue related to blurred library header 2026-04-28 19:05:43 -07:00
Mitch Ray fcc69980e4 Stretch the wavesurfer waveform to the full height (#1962)
* Stretch the wavesurfer waveform to the full height

* Add waveform stretch setting
2026-04-27 20:28:03 -07:00
Hosted Weblate 053b78a3fd Translated using Weblate
Currently translated at 100.0% (1203 of 1203 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

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

Translated using Weblate

Currently translated at 100.0% (1203 of 1203 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% (1203 of 1203 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: linger <linger0517@gmail.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
2026-04-22 11:09:49 +02:00
mathew4 42ded966e4 fix: proper selection of next song when using shuffle and repeat-one (#1932) 2026-04-21 00:10:21 -07:00
Kendall Garner ea9119431c use urlsearchparams instead of qs (#1970) 2026-04-21 00:09:23 -07:00
vimae add0345f10 feat(lyrics): non-active lyric settings (#1954)
* feat: non-active lyric settings
2026-04-21 00:09:03 -07:00
Hosted Weblate e5a8324a79 Translated using Weblate
Currently translated at 99.3% (1193 of 1201 strings) (Chinese (Simplified Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/

Co-authored-by: yidaduizuoye <yidaduizuoye@outlook.com>
2026-04-19 06:09:51 +02:00
jeffvli cc4e933c07 fix missing path replacement transformations 2026-04-16 00:29:13 -07:00
Hosted Weblate 382d279dad Translated using Weblate
Currently translated at 100.0% (1201 of 1201 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

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

Translated using Weblate

Currently translated at 100.0% (1201 of 1201 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% (1201 of 1201 strings) (Polish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/

Translated using Weblate

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

Translated using Weblate

Currently translated at 79.9% (959 of 1200 strings) (Russian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ru/

Co-authored-by: Dylan MONTIGAUD <dylanmontigaud17@gmail.com>
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: Nick <n.grakhov08@gmail.com>
Co-authored-by: York <goog10216922@gmail.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
2026-04-15 06:10:05 +02:00
York b99899f128 fix MPV visualizer on macOS and handle exclusive mode UX (#1930) 2026-04-13 20:47:03 -07:00
korpseluv f5839bf39c normalize album release types and improve grouping logic (#1892) 2026-04-13 20:40:11 -07:00
Damien Erambert 914ed5b8f3 macOS 26-friendly icon (#1941) 2026-04-13 20:32:21 -07:00
Ross ca0a1569f8 Add everfrost dark and light themes (#1934)
Co-authored-by: Ross <ro@noirlab.edu>
2026-04-13 20:22:37 -07:00
Hosted Weblate 9f10fe398a Translated using Weblate
Currently translated at 58.1% (698 of 1200 strings) (Portuguese (Brazil))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pt_BR/

Translated using Weblate

Currently translated at 38.8% (466 of 1200 strings) (Ukrainian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/uk/

Co-authored-by: Yuri Shumyatsky <shumy260404@gmail.com>
Co-authored-by: albatrays <weblate.duct925@passmail.net>
2026-04-12 12:09:54 +00:00
Kendall Garner 8869278898 make theme selector serachable 2026-04-10 20:03:50 -07:00
Jeff 16c9e6cc1b Fix various build issues (#1942)
* remove dynamic import for platform features

* increase node memory limit on macOS build

* fix invalid dynamic imports in renderer

* remove discord-rpc import in renderer
2026-04-10 01:54:11 -07:00
Kendall Garner 2a6e9b6ad3 add extendInfo to alpha/beta builders 2026-04-08 07:28:34 -07:00
Kendall Garner 167b42df2b Merge pull request #1926 from noctuum/fix/wayland-screen-share-dialog
fix(linux): remove unnecessary screen capture from audio loopback handler
2026-04-08 02:11:36 +00:00
jeffvli e6a2bc3acf disable useTransition in router again 2026-04-07 18:21:32 -07:00
jeffvli ca3c7015c6 add fallback to direct streamURL if getTranscodeDecision fails 2026-04-07 18:14:47 -07:00
jeffvli c7c15d917a isolate item card control renders 2026-04-07 18:14:47 -07:00
noctuum 6adb29bc38 fix(linux): remove unnecessary desktopCapturer call from display media handler
The setDisplayMediaRequestHandler was calling desktopCapturer.getSources()
to provide a video source that the renderer never uses (it requests
video: false and only consumes audio tracks). On Wayland, this created a
new xdg-desktop-portal ScreenCast session on every launch, showing an
unavoidable screen share dialog because Electron does not persist
PipeWire restore tokens across desktopCapturer sessions.

Simplified the handler to return only { audio: 'loopback' }, which
captures system audio via PipeWire/PulseAudio monitor source without
any portal interaction.
2026-04-08 04:56:05 +07:00
Hosted Weblate 2c3cd7af24 Translated using Weblate
Currently translated at 94.6% (1136 of 1200 strings) (German)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/

Translated using Weblate

Currently translated at 100.0% (1200 of 1200 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% (1200 of 1200 strings) (Japanese)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/

Translated using Weblate

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

Translated using Weblate

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

Translated using Weblate

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

Translated using Weblate

Currently translated at 100.0% (1200 of 1200 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: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Co-authored-by: York <goog10216922@gmail.com>
Co-authored-by: Zarakkas <kaz@users.noreply.hosted.weblate.org>
Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
2026-04-07 08:10:05 +02:00
277 changed files with 18541 additions and 18945 deletions
+2 -2
View File
@@ -121,7 +121,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [windows-latest, macos-latest, ubuntu-latest] os: [windows-latest, macos-26, ubuntu-latest]
steps: steps:
- name: Checkout git repo - name: Checkout git repo
@@ -156,7 +156,7 @@ jobs:
on_retry_command: pnpm cache delete on_retry_command: pnpm cache delete
- name: Build and Publish to R2 (macOS) - name: Build and Publish to R2 (macOS)
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-26'
uses: nick-invision/retry@v3.0.2 uses: nick-invision/retry@v3.0.2
with: with:
timeout_minutes: 30 timeout_minutes: 30
+2 -2
View File
@@ -115,7 +115,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [windows-latest, macos-latest, ubuntu-latest] os: [windows-latest, macos-26, ubuntu-latest]
steps: steps:
- name: Checkout git repo - name: Checkout git repo
@@ -156,7 +156,7 @@ jobs:
on_retry_command: pnpm cache delete on_retry_command: pnpm cache delete
- name: Build and Publish releases (macOS) - name: Build and Publish releases (macOS)
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-26'
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v3.0.2 uses: nick-invision/retry@v3.0.2
+2 -1
View File
@@ -8,7 +8,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos-latest] os: [macos-26]
steps: steps:
- name: Checkout git repo - name: Checkout git repo
@@ -24,6 +24,7 @@ jobs:
- name: Build and Publish releases - name: Build and Publish releases
env: env:
NODE_OPTIONS: --max-old-space-size=4096
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v3.0.2 uses: nick-invision/retry@v3.0.2
with: with:
+4 -4
View File
@@ -30,7 +30,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos-latest, ubuntu-latest, windows-latest] os: [macos-26, ubuntu-latest, windows-latest]
steps: steps:
- name: Checkout git repo - name: Checkout git repo
@@ -65,7 +65,7 @@ jobs:
pnpm run package:linux:pr pnpm run package:linux:pr
- name: Build for MacOS - name: Build for MacOS
if: ${{ matrix.os == 'macos-latest' }} if: ${{ matrix.os == 'macos-26' }}
uses: nick-invision/retry@v3.0.2 uses: nick-invision/retry@v3.0.2
with: with:
timeout_minutes: 30 timeout_minutes: 30
@@ -86,7 +86,7 @@ jobs:
zip -r dist/linux-binaries.zip dist/*.{AppImage,deb,rpm} zip -r dist/linux-binaries.zip dist/*.{AppImage,deb,rpm}
- name: Zip MacOS Binaries - name: Zip MacOS Binaries
if: ${{ matrix.os == 'macos-latest' }} if: ${{ matrix.os == 'macos-26' }}
run: | run: |
zip -r dist/macos-binaries.zip dist/*.dmg zip -r dist/macos-binaries.zip dist/*.dmg
@@ -105,7 +105,7 @@ jobs:
path: dist/linux-binaries.zip path: dist/linux-binaries.zip
- name: Upload MacOS Binaries - name: Upload MacOS Binaries
if: ${{ matrix.os == 'macos-latest' }} if: ${{ matrix.os == 'macos-26' }}
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: macos-binaries name: macos-binaries
+2 -2
View File
@@ -8,7 +8,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [windows-latest, macos-latest, ubuntu-latest] os: [windows-latest, macos-26, ubuntu-latest]
steps: steps:
- name: Checkout git repo - name: Checkout git repo
@@ -36,7 +36,7 @@ jobs:
on_retry_command: pnpm cache delete on_retry_command: pnpm cache delete
- name: Build and Publish releases (macOS) - name: Build and Publish releases (macOS)
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-26'
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v3.0.2 uses: nick-invision/retry@v3.0.2
Binary file not shown.
+6 -4
View File
@@ -40,13 +40,15 @@ mac:
arch: arch:
- arm64 - arm64
- x64 - x64
icon: assets/icons/icon.icns icon: media/feishin.icon
type: distribution type: distribution
hardenedRuntime: false hardenedRuntime: false
identity: "-" identity: '-'
gatekeeperAssess: false gatekeeperAssess: false
notarize: false notarize: false
extendInfo:
NSAudioCaptureUsageDescription: "System audio access is required for mpv visualizer capture in Feishin"
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
dmg: dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }] contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
@@ -61,7 +63,7 @@ linux:
artifactName: ${productName}-${os}-${arch}.${ext} artifactName: ${productName}-${os}-${arch}.${ext}
toolsets: toolsets:
appimage: "1.0.2" appimage: '1.0.2'
npmRebuild: false npmRebuild: false
+6 -3
View File
@@ -40,12 +40,15 @@ mac:
arch: arch:
- arm64 - arm64
- x64 - x64
icon: assets/icons/icon.icns icon: media/feishin.icon
type: distribution type: distribution
hardenedRuntime: false hardenedRuntime: false
identity: "-" identity: '-'
gatekeeperAssess: false gatekeeperAssess: false
notarize: false notarize: false
extendInfo:
NSAudioCaptureUsageDescription: "System audio access is required for mpv visualizer capture in Feishin"
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
dmg: dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }] contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
@@ -60,7 +63,7 @@ linux:
artifactName: ${productName}-${os}-${arch}.${ext} artifactName: ${productName}-${os}-${arch}.${ext}
toolsets: toolsets:
appimage: "1.0.2" appimage: '1.0.2'
npmRebuild: false npmRebuild: false
publish: publish:
+2 -1
View File
@@ -40,13 +40,14 @@ mac:
arch: arch:
- arm64 - arm64
- x64 - x64
icon: assets/icons/icon.icns icon: media/feishin.icon
type: distribution type: distribution
hardenedRuntime: false hardenedRuntime: false
identity: '-' identity: '-'
gatekeeperAssess: false gatekeeperAssess: false
notarize: false notarize: false
extendInfo: extendInfo:
NSAudioCaptureUsageDescription: 'System audio access is required for mpv visualizer capture in Feishin'
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin' NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
dmg: dmg:
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512"><g style="display:inline" transform="translate(-53.452 -43.352)scale(1.11813)"><circle cx="256" cy="240.312" r="21.5" style="opacity:1;fill:#000;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)"/><path d="M220.85 277.951 183.5 315.6l36 36.1 20-19.7s5.856-6.2 16.5-6.2 16.5 6.2 16.5 6.2l20 19.7 36-36.1-37.35-37.649A51.5 51.5 0 0 1 256 291.812a51.5 51.5 0 0 1-35.15-13.86" style="opacity:1;fill:#000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter249)"/><path d="M256 145.4a25.7 25.7 0 0 0-18.229 7.551L66.97 323.47A25.42 25.42 0 0 0 59.5 341.5c0 14.083 11.417 25.5 25.5 25.5a25.42 25.42 0 0 0 18.031-7.469l103.895-103.597a51.5 51.5 0 0 1-2.426-15.621 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.426 15.62L408.97 359.532A25.42 25.42 0 0 0 427 367c14.083 0 25.5-11.417 25.5-25.5a25.42 25.42 0 0 0-7.469-18.031L274.23 152.95a25.7 25.7 0 0 0-18.229-7.55" style="display:inline;opacity:1;fill:#000;fill-opacity:1;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;filter:url(#filter249)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+202
View File
@@ -0,0 +1,202 @@
{
"fill-specializations" : [
{
"value" : {
"linear-gradient" : [
"display-p3:0.87416,0.87416,0.87416,1.00000",
"display-p3:0.99575,0.99575,0.99575,1.00000"
],
"orientation" : {
"start" : {
"x" : 0.5,
"y" : 1
},
"stop" : {
"x" : 0.5,
"y" : 0.3
}
}
}
},
{
"appearance" : "dark",
"value" : "system-dark"
}
],
"groups" : [
{
"blend-mode-specializations" : [
{
"appearance" : "tinted",
"value" : "normal"
}
],
"blur-material-specializations" : [
{
"value" : 0.7
},
{
"appearance" : "dark",
"value" : 0.7
},
{
"appearance" : "tinted",
"value" : null
}
],
"hidden" : false,
"layers" : [
{
"blend-mode-specializations" : [
{
"appearance" : "tinted",
"value" : "normal"
}
],
"fill-specializations" : [
{
"value" : {
"solid" : "extended-gray:0.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : {
"linear-gradient" : [
"display-p3:0.78674,0.78674,0.78674,1.00000",
"display-p3:0.87416,0.87416,0.87416,1.00000"
],
"orientation" : {
"start" : {
"x" : 0.5,
"y" : 1
},
"stop" : {
"x" : 0.5,
"y" : 0
}
}
}
},
{
"appearance" : "tinted",
"value" : {
"solid" : "gray:1.00000,1.00000"
}
}
],
"glass-specializations" : [
{
"value" : true
},
{
"appearance" : "dark",
"value" : true
},
{
"appearance" : "tinted",
"value" : true
}
],
"hidden" : false,
"image-name" : "feishin.svg",
"name" : "feishin",
"opacity-specializations" : [
{
"value" : 1
},
{
"appearance" : "tinted",
"value" : 1
}
],
"position" : {
"scale" : 0.79,
"translation-in-points" : [
18,
-2
]
}
}
],
"lighting-specializations" : [
{
"value" : "individual"
},
{
"appearance" : "tinted",
"value" : "combined"
}
],
"position" : {
"scale" : 2.2,
"translation-in-points" : [
0,
0
]
},
"shadow-specializations" : [
{
"value" : {
"kind" : "neutral",
"opacity" : 1
}
},
{
"appearance" : "dark",
"value" : {
"kind" : "layer-color",
"opacity" : 0.5
}
},
{
"appearance" : "tinted",
"value" : {
"kind" : "neutral",
"opacity" : 1
}
}
],
"specular-specializations" : [
{
"value" : false
},
{
"appearance" : "dark",
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"translucency-specializations" : [
{
"value" : {
"enabled" : true,
"value" : 0.29
}
},
{
"appearance" : "dark",
"value" : {
"enabled" : false,
"value" : 0.29
}
},
{
"appearance" : "tinted",
"value" : {
"enabled" : true,
"value" : 0.5
}
}
]
}
],
"supported-platforms" : {
"squares" : [
"macOS"
]
}
}
+7 -7
View File
@@ -78,13 +78,13 @@
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0", "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@mantine/colors-generator": "^8.3.18", "@mantine/colors-generator": "^9.1.1",
"@mantine/core": "^8.3.18", "@mantine/core": "^9.1.1",
"@mantine/dates": "^8.3.18", "@mantine/dates": "^9.1.1",
"@mantine/form": "^8.3.18", "@mantine/form": "^9.1.1",
"@mantine/hooks": "^8.3.18", "@mantine/hooks": "^9.1.1",
"@mantine/modals": "^8.3.18", "@mantine/modals": "^9.1.1",
"@mantine/notifications": "^8.3.18", "@mantine/notifications": "^9.1.1",
"@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-context-menu": "^2.2.16",
"@tanstack/react-query": "^5.96.2", "@tanstack/react-query": "^5.96.2",
"@tanstack/react-query-devtools": "^5.96.2", "@tanstack/react-query-devtools": "^5.96.2",
+96 -131
View File
@@ -28,26 +28,26 @@ importers:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0(electron@39.8.6) version: 4.0.0(electron@39.8.6)
'@mantine/colors-generator': '@mantine/colors-generator':
specifier: ^8.3.18 specifier: ^9.1.1
version: 8.3.18(chroma-js@3.1.2) version: 9.1.1(chroma-js@3.1.2)
'@mantine/core': '@mantine/core':
specifier: ^8.3.18 specifier: ^9.1.1
version: 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/dates': '@mantine/dates':
specifier: ^8.3.18 specifier: ^9.1.1
version: 8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/form': '@mantine/form':
specifier: ^8.3.18 specifier: ^9.1.1
version: 8.3.18(react@19.2.4) version: 9.1.1(react@19.2.4)
'@mantine/hooks': '@mantine/hooks':
specifier: ^8.3.18 specifier: ^9.1.1
version: 8.3.18(react@19.2.4) version: 9.1.1(react@19.2.4)
'@mantine/modals': '@mantine/modals':
specifier: ^8.3.18 specifier: ^9.1.1
version: 8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/notifications': '@mantine/notifications':
specifier: ^8.3.18 specifier: ^9.1.1
version: 8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@radix-ui/react-context-menu': '@radix-ui/react-context-menu':
specifier: ^2.2.16 specifier: ^2.2.16
version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -1441,57 +1441,57 @@ packages:
resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
'@mantine/colors-generator@8.3.18': '@mantine/colors-generator@9.1.1':
resolution: {integrity: sha512-u7gNAuVD/WPvB49uNszfkn7lQr85OXTI0Ijkbutcymhv0/utqlapQvZlQAYHwdrrWpQgqPNLsDBt3VSheVe9jw==} resolution: {integrity: sha512-mzEkW9oBrtvnq3zHW1jISa5hNWGi2wRgo4/N0W96DKxTqS0E1IMQ13KWJM0I9er8MK9xZo69jlpR7SyztRYFaw==}
peerDependencies: peerDependencies:
chroma-js: '>=2.4.2' chroma-js: '>=2.4.2'
'@mantine/core@8.3.18': '@mantine/core@9.1.1':
resolution: {integrity: sha512-9tph1lTVogKPjTx02eUxDUOdXacPzK62UuSqb4TdGliI54/Xgxftq0Dfqu6XuhCxn9J5MDJaNiLDvL/1KRkYqA==} resolution: {integrity: sha512-vClOZdCeZ4oLYuA/3jAOgKGQ6dXbF6ZkzpYz09Gied9nZpB7HcQeb3dcMh8UPBE4f+EM7KlYWk6dch7GoASeaA==}
peerDependencies: peerDependencies:
'@mantine/hooks': 8.3.18 '@mantine/hooks': 9.1.1
react: ^18.x || ^19.x react: ^19.2.0
react-dom: ^18.x || ^19.x react-dom: ^19.2.0
'@mantine/dates@8.3.18': '@mantine/dates@9.1.1':
resolution: {integrity: sha512-FHx5teJOhupI0gO2o5evtVYQEdqOjayOkLRhEQfB5Nc5DvcysfPfmNILGkc1Nrp9ZQeQWKLT9qr+CkcCXwHOaw==} resolution: {integrity: sha512-P1tr/Hr+EVxppbOVpTLvaZZnM1W/r0TNpqNNMeM81xfyuKYzd7zt2/SQYb6BuudgEQfRJnAee+7bIJLEsrb0uA==}
peerDependencies: peerDependencies:
'@mantine/core': 8.3.18 '@mantine/core': 9.1.1
'@mantine/hooks': 8.3.18 '@mantine/hooks': 9.1.1
dayjs: '>=1.0.0' dayjs: '>=1.0.0'
react: ^18.x || ^19.x react: ^19.2.0
react-dom: ^18.x || ^19.x react-dom: ^19.2.0
'@mantine/form@8.3.18': '@mantine/form@9.1.1':
resolution: {integrity: sha512-r5OGLJWTkmIruFjRZRZy9oA7maNYlyt50jB4Pmd2X5360WOmJLd4KH8MFhHZQC7vN+z8/rmBl3t3XGAR2I8xig==} resolution: {integrity: sha512-xmebZ3s8GGMrCOPOaOwA+gQkdgNVfT2F9kBtkjAbRoZrMoY+vYFbiPWbIvWFl8pU1jBslYZrj+M0PIawJmFOdQ==}
peerDependencies: peerDependencies:
react: ^18.x || ^19.x react: ^19.2.0
'@mantine/hooks@8.3.18': '@mantine/hooks@9.1.1':
resolution: {integrity: sha512-QoWr9+S8gg5050TQ06aTSxtlpGjYOpIllRbjYYXlRvZeTsUqiTbVfvQROLexu4rEaK+yy9Wwriwl9PMRgbLqPw==} resolution: {integrity: sha512-tTJK73nGFyy1v214TLdvBq0be7QCoc6osfbXVuJgOH3YG85lWk9Mvvor6k+w6hC6HXSqKMqLKePyiGm83xGcMg==}
peerDependencies: peerDependencies:
react: ^18.x || ^19.x react: ^19.2.0
'@mantine/modals@8.3.18': '@mantine/modals@9.1.1':
resolution: {integrity: sha512-JfPDS4549L314SxFPC1x6CbKwzh82OdnIzwgMxPCVNsWLKV2vEHHUH/fzUYj4Wli6IBrsW4cufjMj9BTj3hm3Q==} resolution: {integrity: sha512-SjJ2kIheJaWoKMsSuYQlLFvuJTxCQTOl3gr+wDj/bLmGBgfUykLStRNm9s1H7vFxMIWtN20N8mwtcZV2dGeYBg==}
peerDependencies: peerDependencies:
'@mantine/core': 8.3.18 '@mantine/core': 9.1.1
'@mantine/hooks': 8.3.18 '@mantine/hooks': 9.1.1
react: ^18.x || ^19.x react: ^19.2.0
react-dom: ^18.x || ^19.x react-dom: ^19.2.0
'@mantine/notifications@8.3.18': '@mantine/notifications@9.1.1':
resolution: {integrity: sha512-IpQ0lmwbigTBbZCR6iSYWqIOKEx1tlcd7PcEJ5M5X1qeVSY/N3mmDQt1eJmObvcyDeL5cTJMbSA9UPqhRqo9jw==} resolution: {integrity: sha512-ZfcEMMDp0BQ+yKmVp8ifPXLKej8pv9TcaRnmy2CZ07USD61E9LH5ClRAP/hxQuCyf/qLb5BPHsI7+f3K8uhj4Q==}
peerDependencies: peerDependencies:
'@mantine/core': 8.3.18 '@mantine/core': 9.1.1
'@mantine/hooks': 8.3.18 '@mantine/hooks': 9.1.1
react: ^18.x || ^19.x react: ^19.2.0
react-dom: ^18.x || ^19.x react-dom: ^19.2.0
'@mantine/store@8.3.18': '@mantine/store@9.1.1':
resolution: {integrity: sha512-i+QRTLmZzLldea0egtUVnGALd6UMIu8jd44nrNWBSNIXJU/8B6rMlC6gyX+l4szopZSuOaaNJIXkqRdC1gQsVg==} resolution: {integrity: sha512-kbxEU8wVGbobHlmQmk0lu9M+xCILKjuAPcMAshgzPznGLfXeE9zrB0gNT2cbk11Ik8dlV9J6Vsn9cuACyOSpfQ==}
peerDependencies: peerDependencies:
react: ^18.x || ^19.x react: ^19.2.0
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -1912,66 +1912,79 @@ packages:
resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.1': '@rollup/rollup-linux-arm-musleabihf@4.60.1':
resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.1': '@rollup/rollup-linux-arm64-gnu@4.60.1':
resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.1': '@rollup/rollup-linux-arm64-musl@4.60.1':
resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.1': '@rollup/rollup-linux-loong64-gnu@4.60.1':
resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.1': '@rollup/rollup-linux-loong64-musl@4.60.1':
resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.1': '@rollup/rollup-linux-ppc64-gnu@4.60.1':
resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.1': '@rollup/rollup-linux-ppc64-musl@4.60.1':
resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.1': '@rollup/rollup-linux-riscv64-gnu@4.60.1':
resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.1': '@rollup/rollup-linux-riscv64-musl@4.60.1':
resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.1': '@rollup/rollup-linux-s390x-gnu@4.60.1':
resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.1': '@rollup/rollup-linux-x64-gnu@4.60.1':
resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.1': '@rollup/rollup-linux-x64-musl@4.60.1':
resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.1': '@rollup/rollup-openbsd-x64@4.60.1':
resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==}
@@ -2015,6 +2028,9 @@ packages:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@surma/rollup-plugin-off-main-thread@2.2.3': '@surma/rollup-plugin-off-main-thread@2.2.3':
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
@@ -4710,12 +4726,6 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
react-textarea-autosize@8.5.9:
resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
engines: {node: '>=10'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-transition-group@4.4.5: react-transition-group@4.4.5:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies: peerDependencies:
@@ -5358,6 +5368,10 @@ packages:
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
tagged-tag@1.0.0:
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
engines: {node: '>=20'}
tar@7.5.13: tar@7.5.13:
resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -5454,9 +5468,9 @@ packages:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'} engines: {node: '>=12.20'}
type-fest@4.41.0: type-fest@5.6.0:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==}
engines: {node: '>=16'} engines: {node: '>=20'}
typed-array-buffer@1.0.3: typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
@@ -5569,33 +5583,6 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
use-composed-ref@1.4.0:
resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
use-isomorphic-layout-effect@1.2.1:
resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
use-latest@1.3.0:
resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
use-sidecar@1.1.3: use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -7111,60 +7098,60 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@mantine/colors-generator@8.3.18(chroma-js@3.1.2)': '@mantine/colors-generator@9.1.1(chroma-js@3.1.2)':
dependencies: dependencies:
chroma-js: 3.1.2 chroma-js: 3.1.2
'@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': '@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@floating-ui/react': 0.27.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@floating-ui/react': 0.27.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/hooks': 8.3.18(react@19.2.4) '@mantine/hooks': 9.1.1(react@19.2.4)
clsx: 2.1.1 clsx: 2.1.1
react: 19.2.4 react: 19.2.4
react-dom: 19.2.4(react@19.2.4) react-dom: 19.2.4(react@19.2.4)
react-number-format: 5.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-number-format: 5.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
react-textarea-autosize: 8.5.9(@types/react@19.2.14)(react@19.2.4) type-fest: 5.6.0
type-fest: 4.41.0
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
'@mantine/dates@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': '@mantine/dates@9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@mantine/core': 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@mantine/core': 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/hooks': 8.3.18(react@19.2.4) '@mantine/hooks': 9.1.1(react@19.2.4)
clsx: 2.1.1 clsx: 2.1.1
dayjs: 1.11.20 dayjs: 1.11.20
react: 19.2.4 react: 19.2.4
react-dom: 19.2.4(react@19.2.4) react-dom: 19.2.4(react@19.2.4)
'@mantine/form@8.3.18(react@19.2.4)': '@mantine/form@9.1.1(react@19.2.4)':
dependencies: dependencies:
'@standard-schema/spec': 1.1.0
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
klona: 2.0.6 klona: 2.0.6
react: 19.2.4 react: 19.2.4
'@mantine/hooks@8.3.18(react@19.2.4)': '@mantine/hooks@9.1.1(react@19.2.4)':
dependencies: dependencies:
react: 19.2.4 react: 19.2.4
'@mantine/modals@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': '@mantine/modals@9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@mantine/core': 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@mantine/core': 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/hooks': 8.3.18(react@19.2.4) '@mantine/hooks': 9.1.1(react@19.2.4)
react: 19.2.4 react: 19.2.4
react-dom: 19.2.4(react@19.2.4) react-dom: 19.2.4(react@19.2.4)
'@mantine/notifications@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': '@mantine/notifications@9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@mantine/core': 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@mantine/core': 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/hooks': 8.3.18(react@19.2.4) '@mantine/hooks': 9.1.1(react@19.2.4)
'@mantine/store': 8.3.18(react@19.2.4) '@mantine/store': 9.1.1(react@19.2.4)
react: 19.2.4 react: 19.2.4
react-dom: 19.2.4(react@19.2.4) react-dom: 19.2.4(react@19.2.4)
react-transition-group: 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-transition-group: 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mantine/store@8.3.18(react@19.2.4)': '@mantine/store@9.1.1(react@19.2.4)':
dependencies: dependencies:
react: 19.2.4 react: 19.2.4
@@ -7611,6 +7598,8 @@ snapshots:
'@sindresorhus/is@4.6.0': {} '@sindresorhus/is@4.6.0': {}
'@standard-schema/spec@1.1.0': {}
'@surma/rollup-plugin-off-main-thread@2.2.3': '@surma/rollup-plugin-off-main-thread@2.2.3':
dependencies: dependencies:
ejs: 3.1.10 ejs: 3.1.10
@@ -10672,15 +10661,6 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
react-textarea-autosize@8.5.9(@types/react@19.2.14)(react@19.2.4):
dependencies:
'@babel/runtime': 7.29.2
react: 19.2.4
use-composed-ref: 1.4.0(@types/react@19.2.14)(react@19.2.4)
use-latest: 1.3.0(@types/react@19.2.14)(react@19.2.4)
transitivePeerDependencies:
- '@types/react'
react-transition-group@4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4): react-transition-group@4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
'@babel/runtime': 7.29.2 '@babel/runtime': 7.29.2
@@ -11435,6 +11415,8 @@ snapshots:
string-width: 4.2.3 string-width: 4.2.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
tagged-tag@1.0.0: {}
tar@7.5.13: tar@7.5.13:
dependencies: dependencies:
'@isaacs/fs-minipass': 4.0.1 '@isaacs/fs-minipass': 4.0.1
@@ -11544,7 +11526,9 @@ snapshots:
type-fest@2.19.0: {} type-fest@2.19.0: {}
type-fest@4.41.0: {} type-fest@5.6.0:
dependencies:
tagged-tag: 1.0.0
typed-array-buffer@1.0.3: typed-array-buffer@1.0.3:
dependencies: dependencies:
@@ -11664,25 +11648,6 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
use-composed-ref@1.4.0(@types/react@19.2.14)(react@19.2.4):
dependencies:
react: 19.2.4
optionalDependencies:
'@types/react': 19.2.14
use-isomorphic-layout-effect@1.2.1(@types/react@19.2.14)(react@19.2.4):
dependencies:
react: 19.2.4
optionalDependencies:
'@types/react': 19.2.14
use-latest@1.3.0(@types/react@19.2.14)(react@19.2.4):
dependencies:
react: 19.2.4
use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.14)(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4): use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4):
dependencies: dependencies:
detect-node-es: 1.1.0 detect-node-es: 1.1.0
+4 -12
View File
@@ -1,4 +1,4 @@
import { PostProcessorModule, TOptions } from 'i18next'; import { PostProcessorModule } from 'i18next';
import i18n from 'i18next'; import i18n from 'i18next';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
@@ -203,25 +203,17 @@ const titleCasePostProcessor: PostProcessorModule = {
type: 'postProcessor', type: 'postProcessor',
}; };
const ignoreSentenceCaseLanguages = ['de']; // const ignoreSentenceCaseLanguages = ['de'];
const sentenceCasePostProcessor: PostProcessorModule = { const sentenceCasePostProcessor: PostProcessorModule = {
name: 'sentenceCase', name: 'sentenceCase',
process: ( process: (value: string) => {
value: string,
_key: string,
_options: TOptions<Record<string, string>>,
translator: any,
) => {
const sentences = value.split('. '); const sentences = value.split('. ');
return sentences return sentences
.map((sentence) => { .map((sentence) => {
return ( return (
sentence.charAt(0).toLocaleUpperCase() + sentence.charAt(0).toLocaleUpperCase() + sentence.slice(1).toLocaleLowerCase()
(!ignoreSentenceCaseLanguages.includes(translator.language)
? sentence.slice(1).toLocaleLowerCase()
: sentence.slice(1))
); );
}) })
.join('. '); .join('. ');
+957 -925
View File
File diff suppressed because it is too large Load Diff
+937 -926
View File
File diff suppressed because it is too large Load Diff
+871 -871
View File
File diff suppressed because it is too large Load Diff
+434 -421
View File
File diff suppressed because it is too large Load Diff
+924 -918
View File
File diff suppressed because it is too large Load Diff
+514 -500
View File
File diff suppressed because it is too large Load Diff
+742 -742
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -12,7 +12,7 @@
"unfavorite": "حذف از موردعلاقه‌ها", "unfavorite": "حذف از موردعلاقه‌ها",
"shuffle_off": "پخش تصادفی غیر فعال", "shuffle_off": "پخش تصادفی غیر فعال",
"skip_forward": "برو جلو", "skip_forward": "برو جلو",
"queue_moveToTop": "جابجا کردن انتخاب شده به پایین", "queue_moveToTop": "جابجا کردن انتخاب شده به بالا",
"queue_clear": "خالی کردن صف", "queue_clear": "خالی کردن صف",
"queue_remove": "حذف انتخاب شده", "queue_remove": "حذف انتخاب شده",
"addLast": "افزودن به پایان", "addLast": "افزودن به پایان",
@@ -24,7 +24,7 @@
"mute": "بی‌صدا کردن", "mute": "بی‌صدا کردن",
"playbackFetchCancel": "دارد طول می‌کشد... برای لفو کردن اعلان را ببندید", "playbackFetchCancel": "دارد طول می‌کشد... برای لفو کردن اعلان را ببندید",
"playbackFetchInProgress": "بارگذاری قطعه‌ها…", "playbackFetchInProgress": "بارگذاری قطعه‌ها…",
"queue_moveToBottom": "جابجا کردن انتخاب شده به بالا", "queue_moveToBottom": "جابجا کردن انتخاب شده به پایین",
"addNext": "افزودن به پسین", "addNext": "افزودن به پسین",
"favorite": "مورد علاقه", "favorite": "مورد علاقه",
"playSimilarSongs": "پخش آهنگ‌های همگون", "playSimilarSongs": "پخش آهنگ‌های همگون",
@@ -70,7 +70,7 @@
"hotkey_rate1": "امتیاز ۱ ستاره", "hotkey_rate1": "امتیاز ۱ ستاره",
"hotkey_skipForward": "برو جلو", "hotkey_skipForward": "برو جلو",
"disableLibraryUpdateOnStartup": "غیرفعال کردن بررسی آخرین نسخه در آغاز به کار برنامه", "disableLibraryUpdateOnStartup": "غیرفعال کردن بررسی آخرین نسخه در آغاز به کار برنامه",
"discordApplicationId_description": "the application id for {{discord}} rich presence (defaults to {{defaultId}})", "discordApplicationId_description": "the application ID for {{discord}} Rich Presence (defaults to {{defaultId}})",
"playButtonBehavior_optionAddLast": "$t(player.addLast)", "playButtonBehavior_optionAddLast": "$t(player.addLast)",
"hotkey_playbackPlay": "پخش", "hotkey_playbackPlay": "پخش",
"hotkey_volumeDown": "کم کردن صدا", "hotkey_volumeDown": "کم کردن صدا",
@@ -109,7 +109,7 @@
"customFontPath": "مسیر قلم سفارشی", "customFontPath": "مسیر قلم سفارشی",
"audioPlayer": "پخش‌کنندهٔ صدا", "audioPlayer": "پخش‌کنندهٔ صدا",
"hotkey_rate0": "حذف امتیاز", "hotkey_rate0": "حذف امتیاز",
"discordApplicationId": "{{discord}} application id", "discordApplicationId": "{{discord}} application ID",
"hotkey_volumeMute": "بستن صدا", "hotkey_volumeMute": "بستن صدا",
"showSkipButton": "نمایش دکمهٔ رد کردن", "showSkipButton": "نمایش دکمهٔ رد کردن",
"customFontPath_description": "مسیر قلم سفارشی را برای استفاده در اپلیکیشن مشخص کنید", "customFontPath_description": "مسیر قلم سفارشی را برای استفاده در اپلیکیشن مشخص کنید",
@@ -132,7 +132,7 @@
"buttonSize": "اندازه‌ی دکمه‌ی پخش نوار", "buttonSize": "اندازه‌ی دکمه‌ی پخش نوار",
"contextMenu": "پیکربندی فهرست زمینه (کلیک راست)", "contextMenu": "پیکربندی فهرست زمینه (کلیک راست)",
"buttonSize_description": "اندازه‌ی دکمه‌های پخش نوار", "buttonSize_description": "اندازه‌ی دکمه‌های پخش نوار",
"audioExclusiveMode_description": "حالت اختصاصی خروجی را فعال می‌کند. در این حالت، سامانه معمولاً قفل است و فقط mpv می‌تواند خروجی صدا دهد", "audioExclusiveMode_description": "حالت اختصاصی خروجی را فعال می‌کند. در این حالت، سامانه معمولاً قفل است و فقط MPV می‌تواند خروجی صدا دهد",
"clearQueryCache_description": "یک 'پاک‌سازی نرم' از فیشین. این فهرست‌های پخش و فراداده‌ی قطعه‌ها را تازه می‌کند و متن شعرهای ذخیره شده را بازنشانی می‌کند. پیکربندی‌ها، اعتبارنامه‌های سرویس‌دهنده و نگاره‌های کَش شده حفظ می‌شوند", "clearQueryCache_description": "یک 'پاک‌سازی نرم' از فیشین. این فهرست‌های پخش و فراداده‌ی قطعه‌ها را تازه می‌کند و متن شعرهای ذخیره شده را بازنشانی می‌کند. پیکربندی‌ها، اعتبارنامه‌های سرویس‌دهنده و نگاره‌های کَش شده حفظ می‌شوند",
"clearCache_description": "یک 'پاک‌سازی سخت' فیشین. افزون بر پاک‌سازی کَش فیشین، کَش مرورگر هم تهی می‌شود (نگاره‌های ذخیره شده و باقی دارایی‌ها). اعتبارنامه‌ها و پیکربندی‌ها حفظ می‌شوند", "clearCache_description": "یک 'پاک‌سازی سخت' فیشین. افزون بر پاک‌سازی کَش فیشین، کَش مرورگر هم تهی می‌شود (نگاره‌های ذخیره شده و باقی دارایی‌ها). اعتبارنامه‌ها و پیکربندی‌ها حفظ می‌شوند",
"contextMenu_description": "به شما اجازه می‌دهد که آیتم‌های نمایش داده شده در فهرستی که وقتی روی یک آیتم کلیک راست می‌کنید پدیدار می‌شود، را پنهان کنید. آیتم‌هایی که منتخب نیستند پنهان می‌شوند", "contextMenu_description": "به شما اجازه می‌دهد که آیتم‌های نمایش داده شده در فهرستی که وقتی روی یک آیتم کلیک راست می‌کنید پدیدار می‌شود، را پنهان کنید. آیتم‌هایی که منتخب نیستند پنهان می‌شوند",
@@ -176,7 +176,7 @@
"backward": "به عقب", "backward": "به عقب",
"increase": "افزایش", "increase": "افزایش",
"rating": "امتیاز", "rating": "امتیاز",
"bpm": "bpm", "bpm": "BPM",
"refresh": "تازه‌سازی", "refresh": "تازه‌سازی",
"unknown": "ناشناخته", "unknown": "ناشناخته",
"areYouSure": "مطمئنید؟", "areYouSure": "مطمئنید؟",
@@ -313,9 +313,9 @@
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})", "albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"isRecentlyPlayed": "به تازگی پخش شده است", "isRecentlyPlayed": "به تازگی پخش شده است",
"isFavorited": "موردعلاقه است", "isFavorited": "موردعلاقه است",
"bpm": "bpm", "bpm": "BPM",
"releaseYear": "سال انتشار", "releaseYear": "سال انتشار",
"id": "id", "id": "ID",
"disc": "دیسک", "disc": "دیسک",
"biography": "زندگی‌نامه", "biography": "زندگی‌نامه",
"songCount": "تعداد ترانه", "songCount": "تعداد ترانه",
+753 -753
View File
File diff suppressed because it is too large Load Diff
+987 -975
View File
File diff suppressed because it is too large Load Diff
+728 -728
View File
File diff suppressed because it is too large Load Diff
+821 -821
View File
File diff suppressed because it is too large Load Diff
+734 -734
View File
File diff suppressed because it is too large Load Diff
+16 -7
View File
@@ -22,8 +22,8 @@
"queue_clear": "キューをクリア", "queue_clear": "キューをクリア",
"muted": "ミュート中", "muted": "ミュート中",
"unfavorite": "お気に入り解除", "unfavorite": "お気に入り解除",
"queue_moveToTop": "選択項目を一番下に移動", "queue_moveToTop": "選択項目を先頭に移動",
"queue_moveToBottom": "選択項目を先頭に移動", "queue_moveToBottom": "選択項目を一番下に移動",
"shuffle_off": "シャッフル無効", "shuffle_off": "シャッフル無効",
"addLast": "最後", "addLast": "最後",
"mute": "ミュート", "mute": "ミュート",
@@ -271,7 +271,7 @@
"customCss": "カスタム CSS", "customCss": "カスタム CSS",
"customCssEnable_description": "カスタム CSS の記述を許可します", "customCssEnable_description": "カスタム CSS の記述を許可します",
"customCssEnable": "カスタム CSS を有効にする", "customCssEnable": "カスタム CSS を有効にする",
"customCssNotice": "警告: ある程度のサニタイズ (url() と content: の禁止) はありますが、カスタム CSS を使用するとインターフェースの変更によりリスクが生じる可能性があります", "customCssNotice": "警告: ある程度のサニタイズ (URL() と content: の禁止) はありますが、カスタム CSS を使用するとインターフェースの変更によりリスクが生じる可能性があります",
"releaseChannel_optionBeta": "ベータ", "releaseChannel_optionBeta": "ベータ",
"releaseChannel_optionLatest": "最新", "releaseChannel_optionLatest": "最新",
"releaseChannel": "リリースチャンネル", "releaseChannel": "リリースチャンネル",
@@ -501,7 +501,7 @@
"name": "名前", "name": "名前",
"maximize": "最大化", "maximize": "最大化",
"decrease": "減少", "decrease": "減少",
"ok": "OK", "ok": "Ok",
"description": "説明", "description": "説明",
"configure": "設定", "configure": "設定",
"path": "パス", "path": "パス",
@@ -639,7 +639,7 @@
"titleCombined": "$t(common.title) (結合)", "titleCombined": "$t(common.title) (結合)",
"dateAdded": "追加日", "dateAdded": "追加日",
"size": "$t(common.size)", "size": "$t(common.size)",
"bpm": "$t(common.bpm)", "bpm": "$t(common.BPM)",
"lastPlayed": "最後に再生", "lastPlayed": "最後に再生",
"trackNumber": "トラック番号", "trackNumber": "トラック番号",
"rowIndex": "行インデックス", "rowIndex": "行インデックス",
@@ -1037,7 +1037,7 @@
}, },
"updateServer": { "updateServer": {
"title": "サーバーをアップデート", "title": "サーバーをアップデート",
"success": "サーバーがアップデートされました" "success": "サーバーの更新に成功しました"
}, },
"queryEditor": { "queryEditor": {
"input_optionMatchAll": "すべて一致", "input_optionMatchAll": "すべて一致",
@@ -1102,6 +1102,9 @@
}, },
"saveQueue": { "saveQueue": {
"success": "プレイキューをサーバーに保存しました" "success": "プレイキューをサーバーに保存しました"
},
"editRadioStation": {
"success": "ラジオ局の更新に成功しました"
} }
}, },
"entity": { "entity": {
@@ -1332,6 +1335,12 @@
"d": "D", "d": "D",
"z": "Z" "z": "Z"
} }
} },
"systemAudioConsentAllow": "許可",
"systemAudioConsentBody": "ビジュアライザーを機能させるためには、システムオーディオへのアクセスが必要です",
"systemAudioConsentDecline": "拒否",
"systemAudioConsentTitle": "システムオーディオへのアクセスを許可しますか?",
"systemAudioCaptureFailed": "キャプチャを開始できませんでした: {{message}}",
"systemAudioNoAudioTrack": "音声トラックが返されませんでした。プロンプトが表示されたら、音声キャプチャが有効になっていることを確認してください。"
} }
} }
+6 -6
View File
@@ -106,7 +106,7 @@
"ascending": "오름차순", "ascending": "오름차순",
"areYouSure": "확실한가요?", "areYouSure": "확실한가요?",
"bitrate": "비트 전송률", "bitrate": "비트 전송률",
"bpm": "bpm", "bpm": "BPM",
"biography": "바이오그래피", "biography": "바이오그래피",
"center": "중앙", "center": "중앙",
"channel_other": "채널", "channel_other": "채널",
@@ -127,7 +127,7 @@
"filters": "필터", "filters": "필터",
"noResultsFromQuery": "쿼리 결과가 없습니다", "noResultsFromQuery": "쿼리 결과가 없습니다",
"note": "노트", "note": "노트",
"ok": "OK", "ok": "Ok",
"owner": "소유자", "owner": "소유자",
"sampleRate": "샘플레이트", "sampleRate": "샘플레이트",
"tags": "태그", "tags": "태그",
@@ -224,7 +224,7 @@
"biography": "바이오그래피", "biography": "바이오그래피",
"channels": "$t(common.channel_other)", "channels": "$t(common.channel_other)",
"duration": "길이", "duration": "길이",
"bpm": "bpm", "bpm": "BPM",
"albumCount": "$t(entity.album, {\"count\": 2}) 앨범수", "albumCount": "$t(entity.album, {\"count\": 2}) 앨범수",
"comment": "코멘트", "comment": "코멘트",
"favorited": "즐겨찾기", "favorited": "즐겨찾기",
@@ -251,7 +251,7 @@
"input_name": "서버 이름", "input_name": "서버 이름",
"input_password": "비밀번호", "input_password": "비밀번호",
"input_savePassword": "비밀번호 저장하기", "input_savePassword": "비밀번호 저장하기",
"input_url": "url", "input_url": "URL",
"error_savePassword": "비밀번호를 저장하는 도중 오류가 발생했습니다", "error_savePassword": "비밀번호를 저장하는 도중 오류가 발생했습니다",
"ignoreCors": "CORS 무시 ($t(common.restartRequired))", "ignoreCors": "CORS 무시 ($t(common.restartRequired))",
"ignoreSsl": "SSL 무시 ($t(common.restartRequired))", "ignoreSsl": "SSL 무시 ($t(common.restartRequired))",
@@ -458,8 +458,8 @@
"playSimilarSongs": "비슷한 곡 재생", "playSimilarSongs": "비슷한 곡 재생",
"previous": "이전", "previous": "이전",
"queue_clear": "재생 대기열 지우기", "queue_clear": "재생 대기열 지우기",
"queue_moveToBottom": "선택한 곡을 가장 로 이동", "queue_moveToBottom": "선택한 곡을 가장 아래로 이동",
"queue_moveToTop": "선택한 곡을 가장 아래로 이동", "queue_moveToTop": "선택한 곡을 가장 로 이동",
"queue_remove": "선택한 항목 삭제", "queue_remove": "선택한 항목 삭제",
"repeat": "반복", "repeat": "반복",
"repeat_all": "모두 반복하기", "repeat_all": "모두 반복하기",
File diff suppressed because it is too large Load Diff
+913 -903
View File
File diff suppressed because it is too large Load Diff
+919 -910
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+352 -352
View File
@@ -1,166 +1,166 @@
{ {
"action": { "action": {
"addToFavorites": "adicionar a $t(entity.favorite, {\"count\": 2})", "addToFavorites": "Adicionar a $t(entity.favorite, {\"count\": 2})",
"addToPlaylist": "adicionar a $t(entity.playlist, {\"count\": 1})", "addToPlaylist": "Adicionar a $t(entity.playlist, {\"count\": 1})",
"clearQueue": "limpar fila", "clearQueue": "Limpar fila",
"createPlaylist": "criar $t(entity.playlist, {\"count\": 1})", "createPlaylist": "Criar $t(entity.playlist, {\"count\": 1})",
"deletePlaylist": "apagar $t(entity.playlist, {\"count\": 1})", "deletePlaylist": "Apagar $t(entity.playlist, {\"count\": 1})",
"deselectAll": "desmarcar todos", "deselectAll": "Desmarcar todos",
"editPlaylist": "editar $t(entity.playlist, {\"count\": 1})", "editPlaylist": "Editar $t(entity.playlist, {\"count\": 1})",
"goToPage": "vá para página", "goToPage": "Vá para página",
"moveToNext": "mover para o próximo", "moveToNext": "Mover para o próximo",
"moveToBottom": "mover para baixo", "moveToBottom": "Mover para baixo",
"moveToTop": "mover para o topo", "moveToTop": "Mover para o topo",
"refresh": "$t(common.refresh)", "refresh": "$t(common.refresh)",
"removeFromFavorites": "remover de $t(entity.favorite, {\"count\": 2})", "removeFromFavorites": "Remover de $t(entity.favorite, {\"count\": 2})",
"removeFromPlaylist": "remover da $t(entity.playlist, {\"count\": 1})", "removeFromPlaylist": "Remover da $t(entity.playlist, {\"count\": 1})",
"removeFromQueue": "remover da fila", "removeFromQueue": "Remover da fila",
"setRating": "definir classificação", "setRating": "Definir classificação",
"toggleSmartPlaylistEditor": "alternar editor $t(entity.smartPlaylist)", "toggleSmartPlaylistEditor": "Alternar editor $t(entity.smartPlaylist)",
"viewPlaylists": "ver $t(entity.playlist, {\"count\": 2})", "viewPlaylists": "Ver $t(entity.playlist, {\"count\": 2})",
"openIn": { "openIn": {
"lastfm": "Abrir em Last.fm", "lastfm": "Abrir em Last.fm",
"musicbrainz": "Abrir em MusicBrainz" "musicbrainz": "Abrir em MusicBrainz"
} }
}, },
"common": { "common": {
"action_one": "ação", "action_one": "Ação",
"action_many": "ações", "action_many": "ações",
"action_other": "ações", "action_other": "Ações",
"add": "adicionar", "add": "Adicionar",
"additionalParticipants": "participantes adicionais", "additionalParticipants": "Participantes adicionais",
"newVersion": "uma nova versão foi instalada ({{version}})", "newVersion": "Uma nova versão foi instalada ({{version}})",
"viewReleaseNotes": "ver notas de lançamento", "viewReleaseNotes": "Ver notas de lançamento",
"albumGain": "ganho do álbum", "albumGain": "Ganho do álbum",
"albumPeak": "pico do álbum", "albumPeak": "Pico do álbum",
"areYouSure": "tem certeza?", "areYouSure": "Tem certeza?",
"ascending": "ascendente", "ascending": "Ascendente",
"backward": "para trás", "backward": "Para trás",
"biography": "biografia", "biography": "Biografia",
"bitrate": "taxa de bits", "bitrate": "Taxa de bits",
"bpm": "bpm", "bpm": "Bpm",
"cancel": "cancelar", "cancel": "Cancelar",
"center": "centro", "center": "Centro",
"channel_one": "canal", "channel_one": "Canal",
"channel_many": "canais", "channel_many": "canais",
"channel_other": "canais", "channel_other": "Canais",
"clear": "limpar", "clear": "Limpar",
"close": "fechar", "close": "Fechar",
"codec": "codec", "codec": "Codec",
"collapse": "minimizar", "collapse": "Minimizar",
"comingSoon": "em breve…", "comingSoon": "Em breve…",
"configure": "configurar", "configure": "Configurar",
"confirm": "confirmar", "confirm": "Confirmar",
"create": "criar", "create": "Criar",
"currentSong": "$t(entity.track, {\"count\": 1}) atual", "currentSong": "$t(entity.track, {\"count\": 1}) atual",
"decrease": "diminuir", "decrease": "Diminuir",
"delete": "apagar", "delete": "Apagar",
"descending": "abaixar", "descending": "Abaixar",
"description": "descrição", "description": "Descrição",
"disable": "desativar", "disable": "Desativar",
"disc": "disco", "disc": "Disco",
"dismiss": "liberar", "dismiss": "Liberar",
"duration": "duração", "duration": "Duração",
"edit": "editar", "edit": "Editar",
"enable": "ativar", "enable": "Ativar",
"expand": "expandir", "expand": "Expandir",
"favorite": "favorito", "favorite": "Favorito",
"filter_one": "filtro", "filter_one": "Filtro",
"filter_many": "filtros", "filter_many": "filtros",
"filter_other": "filtros", "filter_other": "Filtros",
"filters": "filtros", "filters": "Filtros",
"forceRestartRequired": "reinicie para aplicar as alterações… feche a notificação para reiniciar", "forceRestartRequired": "Reinicie para aplicar as alterações… feche a notificação para reiniciar",
"forward": "para frente", "forward": "Para frente",
"gap": "intervalo", "gap": "Intervalo",
"grouping": "agrupamento", "grouping": "Agrupamento",
"home": "início", "home": "Início",
"increase": "incrementar", "increase": "Incrementar",
"left": "esquerda", "left": "Esquerda",
"limit": "limite", "limit": "Limite",
"manage": "gerir", "manage": "Gerir",
"maximize": "maximizar", "maximize": "Maximizar",
"menu": "menu", "menu": "Menu",
"minimize": "minimizar", "minimize": "Minimizar",
"modified": "modificado", "modified": "Modificado",
"mbid": "ID no MusicBrainz", "mbid": "ID no MusicBrainz",
"name": "nome", "name": "Nome",
"no": "não", "no": "Não",
"none": "nenhum", "none": "Nenhum",
"noResultsFromQuery": "a consulta não retornou resultados", "noResultsFromQuery": "A consulta não retornou resultados",
"note": "observação", "note": "Observação",
"ok": "ok", "ok": "Ok",
"owner": "dono", "owner": "Dono",
"path": "caminho", "path": "Caminho",
"playerMustBePaused": "o player deve estar pausado", "playerMustBePaused": "O player deve estar pausado",
"preview": "pré-visualizar", "preview": "Pré-visualizar",
"previousSong": "anterior $t(entity.track, {\"count\": 1})", "previousSong": "Anterior $t(entity.track, {\"count\": 1})",
"quit": "sair", "quit": "Sair",
"random": "aleatório", "random": "Aleatório",
"rating": "classificação", "rating": "Classificação",
"refresh": "atualizar", "refresh": "Atualizar",
"reload": "recarregar", "reload": "Recarregar",
"reset": "reiniciar", "reset": "Reiniciar",
"resetToDefault": "restaurar ao padrão", "resetToDefault": "Restaurar ao padrão",
"restartRequired": "é necessário reiniciar", "restartRequired": "É necessário reiniciar",
"right": "direita", "right": "Direita",
"save": "gravar", "save": "Gravar",
"saveAndReplace": "gravar e substituir", "saveAndReplace": "Gravar e substituir",
"saveAs": "gravar como", "saveAs": "Gravar como",
"search": "procurar", "search": "Procurar",
"setting_one": "configuração", "setting_one": "Configuração",
"setting_many": "", "setting_many": "",
"setting_other": "", "setting_other": "",
"share": "partilhar", "share": "Partilhar",
"size": "tamanho", "size": "Tamanho",
"sortOrder": "ordem", "sortOrder": "Ordem",
"tags": "tags", "tags": "Tags",
"title": "titulo", "title": "Titulo",
"trackNumber": "faixa", "trackNumber": "Faixa",
"trackGain": "ganho da faixa", "trackGain": "Ganho da faixa",
"trackPeak": "pico da faixa", "trackPeak": "Pico da faixa",
"translation": "tradução", "translation": "Tradução",
"unknown": "desconhecido", "unknown": "Desconhecido",
"version": "versão", "version": "Versão",
"year": "ano", "year": "Ano",
"yes": "sim" "yes": "Sim"
}, },
"entity": { "entity": {
"album_one": "álbum", "album_one": "Álbum",
"album_many": "álbuns", "album_many": "álbuns",
"album_other": "álbuns", "album_other": "Álbuns",
"albumArtist_one": "artista do álbum", "albumArtist_one": "Artista do álbum",
"albumArtist_many": "artistas do álbum", "albumArtist_many": "artistas do álbum",
"albumArtist_other": "artistas do álbum", "albumArtist_other": "Artistas do álbum",
"albumArtistCount_one": "{{count}} artista do álbum", "albumArtistCount_one": "{{count}} artista do álbum",
"albumArtistCount_many": "{{count}} artistas do álbum", "albumArtistCount_many": "{{count}} artistas do álbum",
"albumArtistCount_other": "{{count}} artistas do álbum", "albumArtistCount_other": "{{count}} artistas do álbum",
"albumWithCount_one": "{{count}} álbum", "albumWithCount_one": "{{count}} álbum",
"albumWithCount_many": "{{count}} álbuns", "albumWithCount_many": "{{count}} álbuns",
"albumWithCount_other": "{{count}} álbuns", "albumWithCount_other": "{{count}} álbuns",
"artist_one": "artista", "artist_one": "Artista",
"artist_many": "artistas", "artist_many": "artistas",
"artist_other": "artistas", "artist_other": "Artistas",
"artistWithCount_one": "{{count}} artista", "artistWithCount_one": "{{count}} artista",
"artistWithCount_many": "{{count}} artistas", "artistWithCount_many": "{{count}} artistas",
"artistWithCount_other": "{{count}} artistas", "artistWithCount_other": "{{count}} artistas",
"favorite_one": "favorito", "favorite_one": "Favorito",
"favorite_many": "favoritos", "favorite_many": "favoritos",
"favorite_other": "favoritos", "favorite_other": "Favoritos",
"folder_one": "pasta", "folder_one": "Pasta",
"folder_many": "pastas", "folder_many": "pastas",
"folder_other": "pastas", "folder_other": "Pastas",
"folderWithCount_one": "{{count}} pasta", "folderWithCount_one": "{{count}} pasta",
"folderWithCount_many": "{{count}} pastas", "folderWithCount_many": "{{count}} pastas",
"folderWithCount_other": "{{count}} pastas", "folderWithCount_other": "{{count}} pastas",
"genre_one": "gênero", "genre_one": "Gênero",
"genre_many": "gêneros", "genre_many": "gêneros",
"genre_other": "gêneros", "genre_other": "Gêneros",
"genreWithCount_one": "{{count}} gênero", "genreWithCount_one": "{{count}} gênero",
"genreWithCount_many": "{{count}} gêneros", "genreWithCount_many": "{{count}} gêneros",
"genreWithCount_other": "{{count}} gêneros", "genreWithCount_other": "{{count}} gêneros",
"playlist_one": "playlist", "playlist_one": "Playlist",
"playlist_many": "playlists", "playlist_many": "playlists",
"playlist_other": "playlists", "playlist_other": "Playlists",
"play_one": "{{count}} reprodução", "play_one": "{{count}} reprodução",
"play_many": "{{count}} reproduções", "play_many": "{{count}} reproduções",
"play_other": "{{count}} reproduções", "play_other": "{{count}} reproduções",
@@ -168,189 +168,189 @@
"playlistWithCount_many": "{{count}} playlists", "playlistWithCount_many": "{{count}} playlists",
"playlistWithCount_other": "{{count}} playlists", "playlistWithCount_other": "{{count}} playlists",
"smartPlaylist": "$t(entity.playlist, {\"count\": 1}) inteligente", "smartPlaylist": "$t(entity.playlist, {\"count\": 1}) inteligente",
"track_one": "faixa", "track_one": "Faixa",
"track_many": "faixas", "track_many": "faixas",
"track_other": "faixas", "track_other": "Faixas",
"song_one": "música", "song_one": "Música",
"song_many": "músicas", "song_many": "músicas",
"song_other": "músicas", "song_other": "Músicas",
"trackWithCount_one": "{{count}} faixa", "trackWithCount_one": "{{count}} faixa",
"trackWithCount_many": "{{count}} faixas", "trackWithCount_many": "{{count}} faixas",
"trackWithCount_other": "{{count}} faixas" "trackWithCount_other": "{{count}} faixas"
}, },
"error": { "error": {
"apiRouteError": "não é possível encaminhar a solicitação", "apiRouteError": "Não é possível encaminhar a solicitação",
"audioDeviceFetchError": "ocorreu um erro ao tentar obter dispositivos de áudio", "audioDeviceFetchError": "Ocorreu um erro ao tentar obter dispositivos de áudio",
"authenticationFailed": "falha na autenticação", "authenticationFailed": "Falha na autenticação",
"badAlbum": "está a ver este erro por que está música não é parte de algum album. um motivo comum para si estar a ver este erro é se a sua música estiver na raiz da sua pasta de músicas. o Jellyfin apenas agrupa as músicas se elas estiveram na mesma pasta", "badAlbum": "Está a ver este erro por que está música não é parte de algum album. um motivo comum para si estar a ver este erro é se a sua música estiver na raiz da sua pasta de músicas. o Jellyfin apenas agrupa as músicas se elas estiveram na mesma pasta",
"badValue": "opção inválida \"{{value}}\". este valor não existe no momento", "badValue": "Opção inválida \"{{value}}\". este valor não existe no momento",
"credentialsRequired": "credenciais necessárias", "credentialsRequired": "Credenciais necessárias",
"endpointNotImplementedError": "endpoint {{endpoint}} não está implementado para {{serverType}}", "endpointNotImplementedError": "Endpoint {{endpoint}} não está implementado para {{serverType}}",
"genericError": "um erro ocorreu", "genericError": "Um erro ocorreu",
"invalidServer": "servidor inválido", "invalidServer": "Servidor inválido",
"localFontAccessDenied": "acesso a fontes locais rejeitado", "localFontAccessDenied": "Acesso a fontes locais rejeitado",
"loginRateError": "muitas tentativas de login, tente novamente em alguns segundos", "loginRateError": "Muitas tentativas de login, tente novamente em alguns segundos",
"mpvRequired": "MPV necessário", "mpvRequired": "MPV necessário",
"networkError": "ocorreu um erro na internet", "networkError": "Ocorreu um erro na internet",
"openError": "não foi possível abrir o ficheiro", "openError": "Não foi possível abrir o ficheiro",
"playbackError": "ocorreu um erro ao tentar reproduzir a média", "playbackError": "Ocorreu um erro ao tentar reproduzir a média",
"remoteDisableError": "ocorreu um erro ao tentar $t(common.disable) o servidor remoto", "remoteDisableError": "Ocorreu um erro ao tentar $t(common.disable) o servidor remoto",
"remoteEnableError": "ocorreu um erro ao tentar $t(common.enable) o servidor remoto", "remoteEnableError": "Ocorreu um erro ao tentar $t(common.enable) o servidor remoto",
"remotePortError": "ocorreu um erro ao tentar definir a porta do servidor remoto", "remotePortError": "Ocorreu um erro ao tentar definir a porta do servidor remoto",
"remotePortWarning": "reinicie o servidor para aplicar a nova porta", "remotePortWarning": "Reinicie o servidor para aplicar a nova porta",
"serverNotSelectedError": "nenhum servidor selecionado", "serverNotSelectedError": "Nenhum servidor selecionado",
"serverRequired": "servidor necessário", "serverRequired": "Servidor necessário",
"sessionExpiredError": "a sua sessão expirou", "sessionExpiredError": "A sua sessão expirou",
"systemFontError": "ocorreu um erro ao tentar obter fontes do sistema" "systemFontError": "Ocorreu um erro ao tentar obter fontes do sistema"
}, },
"filter": { "filter": {
"album": "$t(entity.album, {\"count\": 1})", "album": "$t(entity.album, {\"count\": 1})",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})", "albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"albumCount": "número de $t(entity.album, {\"count\": 2})", "albumCount": "Número de $t(entity.album, {\"count\": 2})",
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"biography": "bibliografia", "biography": "Bibliografia",
"bitrate": "bitrate", "bitrate": "Bitrate",
"bpm": "bpm", "bpm": "Bpm",
"channels": "$t(common.channel_other)", "channels": "$t(common.channel_other)",
"comment": "comentário", "comment": "Comentário",
"communityRating": "Nota da comunidade", "communityRating": "Nota da comunidade",
"criticRating": "avaliação da crítica", "criticRating": "Avaliação da crítica",
"dateAdded": "data de adição", "dateAdded": "Data de adição",
"disc": "disco", "disc": "Disco",
"duration": "duração", "duration": "Duração",
"favorited": "favoritado", "favorited": "Favoritado",
"fromYear": "a partir do ano", "fromYear": "A partir do ano",
"genre": "$t(entity.genre, {\"count\": 1})", "genre": "$t(entity.genre, {\"count\": 1})",
"id": "id", "id": "Id",
"isCompilation": "é compilação", "isCompilation": "É compilação",
"isFavorited": "é favoritado", "isFavorited": "É favoritado",
"isPublic": "é público", "isPublic": "É público",
"isRated": "possui avaliação", "isRated": "Possui avaliação",
"isRecentlyPlayed": "foi tocado recentemente", "isRecentlyPlayed": "Foi tocado recentemente",
"lastPlayed": "última tocada", "lastPlayed": "Última tocada",
"mostPlayed": "mais tocado", "mostPlayed": "Mais tocado",
"name": "nome", "name": "Nome",
"note": "nota", "note": "Nota",
"owner": "$t(common.owner)", "owner": "$t(common.owner)",
"path": "caminho", "path": "Caminho",
"playCount": "contador de reproduções", "playCount": "Contador de reproduções",
"random": "aleatório", "random": "Aleatório",
"rating": "avaliação", "rating": "Avaliação",
"recentlyAdded": "adicionado recentemente", "recentlyAdded": "Adicionado recentemente",
"recentlyPlayed": "tocado recentemente", "recentlyPlayed": "Tocado recentemente",
"recentlyUpdated": "atualizado recentemente", "recentlyUpdated": "Atualizado recentemente",
"releaseDate": "data de lançamento", "releaseDate": "Data de lançamento",
"releaseYear": "ano de lançamento", "releaseYear": "Ano de lançamento",
"search": "buscar", "search": "Buscar",
"songCount": "contador de músicas", "songCount": "Contador de músicas",
"title": "titulo", "title": "Titulo",
"toYear": "até o ano", "toYear": "Até o ano",
"trackNumber": "faixa" "trackNumber": "Faixa"
}, },
"form": { "form": {
"addServer": { "addServer": {
"error_savePassword": "um erro ocorreu ao tentar gravar a palavra-passe", "error_savePassword": "Um erro ocorreu ao tentar gravar a palavra-passe",
"ignoreCors": "ignorar CORS ($t(common.restartRequired))", "ignoreCors": "Ignorar CORS ($t(common.restartRequired))",
"ignoreSsl": "ignorar ssl ($t(common.restartRequired))", "ignoreSsl": "Ignorar ssl ($t(common.restartRequired))",
"input_legacyAuthentication": "ativar autenticação legada", "input_legacyAuthentication": "Ativar autenticação legada",
"input_name": "nome do servidor", "input_name": "Nome do servidor",
"input_password": "palavra-passe", "input_password": "Palavra-passe",
"input_savePassword": "gravar palavra-passe", "input_savePassword": "Gravar palavra-passe",
"input_url": "url", "input_url": "Url",
"input_username": "nome de utilizador", "input_username": "Nome de utilizador",
"success": "servidor adicionado com sucesso", "success": "Servidor adicionado com sucesso",
"title": "adicionar servidor" "title": "Adicionar servidor"
}, },
"addToPlaylist": { "addToPlaylist": {
"input_playlists": "$t(entity.playlist, {\"count\": 2})", "input_playlists": "$t(entity.playlist, {\"count\": 2})",
"input_skipDuplicates": "pular duplicadas", "input_skipDuplicates": "Pular duplicadas",
"success": "adicionado $t(entity.trackWithCount, {\"count\": {{message}} }) para $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })", "success": "Adicionado $t(entity.trackWithCount, {\"count\": {{message}} }) para $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "adicionar à $t(entity.playlist, {\"count\": 1})" "title": "Adicionar à $t(entity.playlist, {\"count\": 1})"
}, },
"createPlaylist": { "createPlaylist": {
"input_description": "$t(common.description)", "input_description": "$t(common.description)",
"input_name": "$t(common.name)", "input_name": "$t(common.name)",
"input_owner": "$t(common.owner)", "input_owner": "$t(common.owner)",
"input_public": "público", "input_public": "Público",
"success": "$t(entity.playlist, {\"count\": 1}) criada com sucesso", "success": "$t(entity.playlist, {\"count\": 1}) criada com sucesso",
"title": "criar $t(entity.playlist, {\"count\": 1})" "title": "Criar $t(entity.playlist, {\"count\": 1})"
}, },
"deletePlaylist": { "deletePlaylist": {
"input_confirm": "escreva o nome da $t(entity.playlist, {\"count\": 1}) para confirmar", "input_confirm": "Escreva o nome da $t(entity.playlist, {\"count\": 1}) para confirmar",
"success": "$t(entity.playlist, {\"count\": 1}) apagada com sucesso", "success": "$t(entity.playlist, {\"count\": 1}) apagada com sucesso",
"title": "apagar $t(entity.playlist, {\"count\": 1})" "title": "Apagar $t(entity.playlist, {\"count\": 1})"
}, },
"editPlaylist": { "editPlaylist": {
"publicJellyfinNote": "O Jellyfin por algum motivo não expõe se uma playlist é pública ou não. Se deseja que ela permaneça pública, por favor selecione a seguinte entrada", "publicJellyfinNote": "O Jellyfin por algum motivo não expõe se uma playlist é pública ou não. Se deseja que ela permaneça pública, por favor selecione a seguinte entrada",
"success": "$t(entity.playlist, {\"count\": 1}) atualizada com sucesso", "success": "$t(entity.playlist, {\"count\": 1}) atualizada com sucesso",
"title": "editar $t(entity.playlist, {\"count\": 1})" "title": "Editar $t(entity.playlist, {\"count\": 1})"
}, },
"lyricSearch": { "lyricSearch": {
"input_artist": "$t(entity.artist, {\"count\": 1})", "input_artist": "$t(entity.artist, {\"count\": 1})",
"input_name": "$t(common.name)", "input_name": "$t(common.name)",
"title": "pesquisa de letras" "title": "Pesquisa de letras"
}, },
"queryEditor": { "queryEditor": {
"input_optionMatchAll": "corresponder todos", "input_optionMatchAll": "Corresponder todos",
"input_optionMatchAny": "corresponder qualquer um" "input_optionMatchAny": "Corresponder qualquer um"
}, },
"shareItem": { "shareItem": {
"allowDownloading": "permitir descargas", "allowDownloading": "Permitir descargas",
"description": "descrição", "description": "Descrição",
"setExpiration": "definir expiração", "setExpiration": "Definir expiração",
"success": "ligação de compartilhamento copiado para a área de transferência (ou clique aqui para abrir)", "success": "Ligação de compartilhamento copiado para a área de transferência (ou clique aqui para abrir)",
"expireInvalid": "a expiração deve ser uma data futura", "expireInvalid": "A expiração deve ser uma data futura",
"createFailed": "falha ao criar compartilhamento (o compartilhamento está ativado?)" "createFailed": "Falha ao criar compartilhamento (o compartilhamento está ativado?)"
}, },
"updateServer": { "updateServer": {
"success": "servidor atualizado com sucesso", "success": "Servidor atualizado com sucesso",
"title": "atualizar servidor" "title": "Atualizar servidor"
} }
}, },
"page": { "page": {
"albumArtistDetail": { "albumArtistDetail": {
"about": "Sobre {{artist}}", "about": "Sobre {{artist}}",
"appearsOn": "aparece em", "appearsOn": "Aparece em",
"recentReleases": "lançamentos recentes", "recentReleases": "Lançamentos recentes",
"viewDiscography": "ver discografia", "viewDiscography": "Ver discografia",
"relatedArtists": "$t(entity.artist, {\"count\": 2}) relacionados", "relatedArtists": "$t(entity.artist, {\"count\": 2}) relacionados",
"topSongs": "músicas mais tocadas", "topSongs": "Músicas mais tocadas",
"topSongsFrom": "músicas mais tocadas de {{title}}", "topSongsFrom": "Músicas mais tocadas de {{title}}",
"viewAll": "ver tudo", "viewAll": "Ver tudo",
"viewAllTracks": "ver todas as $t(entity.track, {\"count\": 2})" "viewAllTracks": "Ver todas as $t(entity.track, {\"count\": 2})"
}, },
"albumArtistList": { "albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})" "title": "$t(entity.albumArtist, {\"count\": 2})"
}, },
"albumDetail": { "albumDetail": {
"moreFromArtist": "mais deste $t(entity.artist, {\"count\": 1})", "moreFromArtist": "Mais deste $t(entity.artist, {\"count\": 1})",
"moreFromGeneric": "mais que {{item}}", "moreFromGeneric": "Mais que {{item}}",
"released": "lançado" "released": "Lançado"
}, },
"albumList": { "albumList": {
"artistAlbums": "álbuns de {{artist}}", "artistAlbums": "Álbuns de {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album, {\"count\": 2})", "genreAlbums": "\"{{genre}}\" $t(entity.album, {\"count\": 2})",
"title": "$t(entity.album, {\"count\": 2})" "title": "$t(entity.album, {\"count\": 2})"
}, },
"appMenu": { "appMenu": {
"collapseSidebar": "recolher barra lateral", "collapseSidebar": "Recolher barra lateral",
"expandSidebar": "expandir barra lateral", "expandSidebar": "Expandir barra lateral",
"goBack": "voltar", "goBack": "Voltar",
"goForward": "avançar", "goForward": "Avançar",
"manageServers": "gerir servidores", "manageServers": "Gerir servidores",
"openBrowserDevtools": "abrir ferramentas do programador", "openBrowserDevtools": "Abrir ferramentas do programador",
"quit": "$t(common.quit)", "quit": "$t(common.quit)",
"selectServer": "selecionar servidor", "selectServer": "Selecionar servidor",
"settings": "$t(common.setting, {\"count\": 2})", "settings": "$t(common.setting, {\"count\": 2})",
"version": "versão {{version}}" "version": "Versão {{version}}"
}, },
"manageServers": { "manageServers": {
"title": "gerir servidores", "title": "Gerir servidores",
"serverDetails": "pormenores do servidor", "serverDetails": "Pormenores do servidor",
"url": "URL", "url": "URL",
"username": "nome de utilizador", "username": "Nome de utilizador",
"editServerDetailsTooltip": "editar pormenores do servidor", "editServerDetailsTooltip": "Editar pormenores do servidor",
"removeServer": "remover servidor" "removeServer": "Remover servidor"
}, },
"contextMenu": { "contextMenu": {
"addFavorite": "$t(action.addToFavorites)", "addFavorite": "$t(action.addToFavorites)",
@@ -361,7 +361,7 @@
"createPlaylist": "$t(action.createPlaylist)", "createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)", "deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)", "deselectAll": "$t(action.deselectAll)",
"download": "descarregar", "download": "Descarregar",
"moveToNext": "$t(action.moveToNext)", "moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)", "moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)", "moveToTop": "$t(action.moveToTop)",
@@ -373,69 +373,69 @@
"removeFromQueue": "$t(action.removeFromQueue)", "removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)", "setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)", "playShuffled": "$t(player.shuffle)",
"shareItem": "partilhar elemento", "shareItem": "Partilhar elemento",
"showDetails": "obter informações" "showDetails": "Obter informações"
}, },
"fullscreenPlayer": { "fullscreenPlayer": {
"config": { "config": {
"dynamicBackground": "fundo dinâmico", "dynamicBackground": "Fundo dinâmico",
"dynamicImageBlur": "tamanho do desfoque da imagem", "dynamicImageBlur": "Tamanho do desfoque da imagem",
"dynamicIsImage": "ativar imagem de fundo", "dynamicIsImage": "Ativar imagem de fundo",
"followCurrentLyric": "acompanhar letra", "followCurrentLyric": "Acompanhar letra",
"lyricAlignment": "alinhamento da letra", "lyricAlignment": "Alinhamento da letra",
"lyricOffset": "deslocamento da letra (ms)", "lyricOffset": "Deslocamento da letra (ms)",
"lyricGap": "espaçamento da letra", "lyricGap": "Espaçamento da letra",
"lyricSize": "tamanho da letra", "lyricSize": "Tamanho da letra",
"opacity": "opacidade", "opacity": "Opacidade",
"showLyricMatch": "exibir correspondência da letra", "showLyricMatch": "Exibir correspondência da letra",
"showLyricProvider": "exibir origem da letra", "showLyricProvider": "Exibir origem da letra",
"synchronized": "sincronizado", "synchronized": "Sincronizado",
"unsynchronized": "não sincronizado", "unsynchronized": "Não sincronizado",
"useImageAspectRatio": "usar proporção da imagem" "useImageAspectRatio": "Usar proporção da imagem"
}, },
"lyrics": "letra", "lyrics": "Letra",
"related": "relacionado", "related": "Relacionado",
"upNext": "a seguir", "upNext": "A seguir",
"visualizer": "visualizador", "visualizer": "Visualizador",
"noLyrics": "nenhuma letra encontrada" "noLyrics": "Nenhuma letra encontrada"
}, },
"genreList": { "genreList": {
"showAlbums": "mostrar $t(entity.genre, {\"count\": 1}) $t(entity.album, {\"count\": 2})", "showAlbums": "Mostrar $t(entity.genre, {\"count\": 1}) $t(entity.album, {\"count\": 2})",
"showTracks": "mostrar $t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2})", "showTracks": "Mostrar $t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2})",
"title": "$t(entity.genre, {\"count\": 2})" "title": "$t(entity.genre, {\"count\": 2})"
}, },
"globalSearch": { "globalSearch": {
"commands": { "commands": {
"goToPage": "ir à página", "goToPage": "Ir à página",
"searchFor": "procurar {{query}}", "searchFor": "Procurar {{query}}",
"serverCommands": "comandos do servidor" "serverCommands": "Comandos do servidor"
}, },
"title": "comandos" "title": "Comandos"
}, },
"home": { "home": {
"explore": "explore a sua biblioteca", "explore": "Explore a sua biblioteca",
"mostPlayed": "mais tocado", "mostPlayed": "Mais tocado",
"newlyAdded": "lançamentos recém-adicionados", "newlyAdded": "Lançamentos recém-adicionados",
"recentlyPlayed": "tocado recentemente", "recentlyPlayed": "Tocado recentemente",
"title": "$t(common.home)" "title": "$t(common.home)"
}, },
"itemDetail": { "itemDetail": {
"copyPath": "copiar caminho para a área de transferência", "copyPath": "Copiar caminho para a área de transferência",
"copiedPath": "caminho copiado com sucesso", "copiedPath": "Caminho copiado com sucesso",
"openFile": "mostrar faixa no gestor de ficheiros" "openFile": "Mostrar faixa no gestor de ficheiros"
}, },
"playlist": { "playlist": {
"reorder": "reordenar apenas disponível quando ordenado pelo id" "reorder": "Reordenar apenas disponível quando ordenado pelo ID"
}, },
"playlistList": { "playlistList": {
"title": "$t(entity.playlist, {\"count\": 2})" "title": "$t(entity.playlist, {\"count\": 2})"
}, },
"setting": { "setting": {
"advanced": "avançado", "advanced": "Avançado",
"generalTab": "geral", "generalTab": "Geral",
"hotkeysTab": "teclas de atalho", "hotkeysTab": "Teclas de atalho",
"playbackTab": "reprodução", "playbackTab": "Reprodução",
"windowTab": "janela" "windowTab": "Janela"
}, },
"sidebar": { "sidebar": {
"albumArtists": "$t(entity.albumArtist, {\"count\": 2})", "albumArtists": "$t(entity.albumArtist, {\"count\": 2})",
@@ -444,8 +444,8 @@
"folders": "$t(entity.folder, {\"count\": 2})", "folders": "$t(entity.folder, {\"count\": 2})",
"genres": "$t(entity.genre, {\"count\": 2})", "genres": "$t(entity.genre, {\"count\": 2})",
"home": "$t(common.home)", "home": "$t(common.home)",
"myLibrary": "a minha biblioteca", "myLibrary": "A minha biblioteca",
"nowPlaying": "agora a tocar", "nowPlaying": "Agora a tocar",
"playlists": "$t(entity.playlist, {\"count\": 2})", "playlists": "$t(entity.playlist, {\"count\": 2})",
"search": "$t(common.search)", "search": "$t(common.search)",
"settings": "$t(common.setting, {\"count\": 2})", "settings": "$t(common.setting, {\"count\": 2})",
@@ -453,92 +453,92 @@
"tracks": "$t(entity.track, {\"count\": 2})" "tracks": "$t(entity.track, {\"count\": 2})"
}, },
"trackList": { "trackList": {
"artistTracks": "faixas de {{artist}}", "artistTracks": "Faixas de {{artist}}",
"genreTracks": "\"{{genre}}\" $t(entity.track, {\"count\": 2})", "genreTracks": "\"{{genre}}\" $t(entity.track, {\"count\": 2})",
"title": "$t(entity.track, {\"count\": 2})" "title": "$t(entity.track, {\"count\": 2})"
} }
}, },
"player": { "player": {
"addLast": "adicionar no final", "addLast": "Adicionar no final",
"addNext": "adicionar a seguir", "addNext": "Adicionar a seguir",
"favorite": "favorito", "favorite": "Favorito",
"mute": "mudo", "mute": "Mudo",
"muted": "mudo", "muted": "Mudo",
"next": "próximo", "next": "Próximo",
"play": "tocar", "play": "Tocar",
"playbackFetchCancel": "isto demora um pouco... feche a notificação para cancelar", "playbackFetchCancel": "Isto demora um pouco... feche a notificação para cancelar",
"playbackFetchInProgress": "a carregar músicas…", "playbackFetchInProgress": "A carregar músicas…",
"playbackFetchNoResults": "nenhuma música encontrada", "playbackFetchNoResults": "Nenhuma música encontrada",
"playbackSpeed": "velocidade de reprodução", "playbackSpeed": "Velocidade de reprodução",
"playRandom": "tocar aleatório", "playRandom": "Tocar aleatório",
"playSimilarSongs": "tocar músicas similares", "playSimilarSongs": "Tocar músicas similares",
"previous": "anterior", "previous": "Anterior",
"queue_clear": "limpar fila", "queue_clear": "Limpar fila",
"queue_moveToBottom": "mover selecionados para o topo", "queue_moveToBottom": "Mover selecionados para o fim",
"queue_moveToTop": "mover selecionados para o fim", "queue_moveToTop": "Mover selecionados para o topo",
"queue_remove": "remover selecionados", "queue_remove": "Remover selecionados",
"repeat": "repetir", "repeat": "Repetir",
"repeat_all": "repetir tudo", "repeat_all": "Repetir tudo",
"repeat_off": "repetição desativada", "repeat_off": "Repetição desativada",
"shuffle": "tocar aleatório", "shuffle": "Tocar aleatório",
"shuffle_off": "aleatório desativado", "shuffle_off": "Aleatório desativado",
"skip": "pular", "skip": "Pular",
"skip_back": "retroceder", "skip_back": "Retroceder",
"skip_forward": "avançar", "skip_forward": "Avançar",
"stop": "parar", "stop": "Parar",
"toggleFullscreenPlayer": "alternar player de ecrã cheio", "toggleFullscreenPlayer": "Alternar player de ecrã cheio",
"unfavorite": "remover favorito", "unfavorite": "Remover favorito",
"pause": "pausar", "pause": "Pausar",
"viewQueue": "ver fila" "viewQueue": "Ver fila"
}, },
"setting": { "setting": {
"accentColor": "cor de realce", "accentColor": "Cor de realce",
"accentColor_description": "define a cor de realce para a aplicação", "accentColor_description": "Define a cor de realce para a aplicação",
"albumBackground": "imagem de fundo do álbum", "albumBackground": "Imagem de fundo do álbum",
"albumBackground_description": "adiciona uma imagem de fundo contendo a arte do álbum para a página de álbum", "albumBackground_description": "Adiciona uma imagem de fundo contendo a arte do álbum para a página de álbum",
"albumBackgroundBlur": "tamanho de desfoque da imagem de fundo do álbum", "albumBackgroundBlur": "Tamanho de desfoque da imagem de fundo do álbum",
"albumBackgroundBlur_description": "ajusta a quantidade de desfoque aplicada para a imagem de fundo do álbum", "albumBackgroundBlur_description": "Ajusta a quantidade de desfoque aplicada para a imagem de fundo do álbum",
"applicationHotkeys": "teclas de atalho da aplicação", "applicationHotkeys": "Teclas de atalho da aplicação",
"applicationHotkeys_description": "configure as teclas de atalho da aplicação. clique na caixa de seleção para definir como tecla de atalho global (somente desktop)", "applicationHotkeys_description": "Configure as teclas de atalho da aplicação. clique na caixa de seleção para definir como tecla de atalho global (somente desktop)",
"artistConfiguration": "configuração da página de artista de álbum", "artistConfiguration": "Configuração da página de artista de álbum",
"artistConfiguration_description": "configure quais elementos serão mostrados, e em qual ordem, na página de artista de álbum", "artistConfiguration_description": "Configure quais elementos serão mostrados, e em qual ordem, na página de artista de álbum",
"audioDevice": "dispositivo de áudio", "audioDevice": "Dispositivo de áudio",
"audioDevice_description": "selecione o dispositivo de áudio usado para reprodução (somente player web)", "audioDevice_description": "Selecione o dispositivo de áudio usado para reprodução (somente player web)",
"audioExclusiveMode": "modo de áudio exclusivo", "audioExclusiveMode": "Modo de áudio exclusivo",
"audioExclusiveMode_description": "ativar modo de saída exclusiva. Neste modo, o sistema é geralmente bloqueado, e apenas mpv terá saída de áudio", "audioExclusiveMode_description": "Ativar modo de saída exclusiva. Neste modo, o sistema é geralmente bloqueado, e apenas mpv terá saída de áudio",
"audioPlayer": "player de áudio", "audioPlayer": "Player de áudio",
"audioPlayer_description": "selecione o player de áudio usado para reprodução", "audioPlayer_description": "Selecione o player de áudio usado para reprodução",
"buttonSize": "tamanho do botão da barra de reprodução", "buttonSize": "Tamanho do botão da barra de reprodução",
"buttonSize_description": "o tamanho dos botões da barra de reprodução", "buttonSize_description": "O tamanho dos botões da barra de reprodução",
"clearCache": "limpar cache do navegador", "clearCache": "Limpar cache do navegador",
"clearCache_description": "uma 'limpeza geral' do feishin. em adição a limpar o cache do feishin, limpa o cache do navegador (imagens gravadas e outros recursos). as credenciais de servidor e as configurações serão mantidas", "clearCache_description": "Uma 'limpeza geral' do Feishin. Em adição a limpar o cache do Feishin, limpa o cache do navegador (imagens gravadas e outros recursos). As credenciais de servidor e as configurações serão mantidas",
"clearQueryCache": "limpar cache do feishin", "clearQueryCache": "Limpar cache do Feishin",
"clearQueryCache_description": "uma 'limpeza leve' do feishin. isto irá renovar playlists, metadados de faixas, e resetar letras gravadas. as configurações, as credenciais de servidor e o cache de imagens serão mantidos", "clearQueryCache_description": "Uma 'limpeza leve' do Feishin. Isto irá renovar playlists, metadados de faixas, e resetar letras gravadas. As configurações, as credenciais de servidor e o cache de imagens serão mantidos",
"clearCacheSuccess": "cache limpo com sucesso", "clearCacheSuccess": "Cache limpo com sucesso",
"contextMenu": "configuração do menu de contexto (clique do botão direito do rato)", "contextMenu": "Configuração do menu de contexto (clique do botão direito do rato)",
"contextMenu_description": "permite esconder elementos exibidos no menu quando clica num elemento com o botão direito. elementos não selecionados serão escondidos", "contextMenu_description": "Permite esconder elementos exibidos no menu quando clica num elemento com o botão direito. elementos não selecionados serão escondidos",
"crossfadeDuration": "duraçao de crossfade", "crossfadeDuration": "Duraçao de crossfade",
"crossfadeDuration_description": "define a duração do efeito crossfade", "crossfadeDuration_description": "Define a duração do efeito crossfade",
"crossfadeStyle_description": "seleciona qual estilo de crossfade usado no player de áudio", "crossfadeStyle_description": "Seleciona qual estilo de crossfade usado no player de áudio",
"customCssEnable": "ativar css customizado", "customCssEnable": "Ativar CSS customizado",
"customCssEnable_description": "permite escrever css customizado", "customCssEnable_description": "Permite escrever CSS customizado",
"customCssNotice": "Aviso: apesar de existir alguma higienização (url() e content: não são permitidas), o uso de css personalizado ainda pode representar riscos ao alterar a interface", "customCssNotice": "Aviso: apesar de existir alguma higienização (URL() e content: não são permitidas), o uso de CSS personalizado ainda pode representar riscos ao alterar a interface",
"customCss": "css customizado", "customCss": "Css customizado",
"disableLibraryUpdateOnStartup": "desativar a verificação de novas versões na inicialização", "disableLibraryUpdateOnStartup": "Desativar a verificação de novas versões na inicialização",
"discordApplicationId": "{{discord}} ID da aplicação", "discordApplicationId": "{{discord}} ID da aplicação",
"discordIdleStatus_description": "quando ativado, atualiza o estado enquanto o player está ocioso", "discordIdleStatus_description": "Quando ativado, atualiza o estado enquanto o player está ocioso",
"discordUpdateInterval_description": "o tempo em segundos entre cada atualização (mínimo 15 segundos)", "discordUpdateInterval_description": "O tempo em segundos entre cada atualização (mínimo 15 segundos)",
"playButtonBehavior_description": "define o comportamento padrão do botão play ao adicionar músicas à fila" "playButtonBehavior_description": "Define o comportamento padrão do botão play ao adicionar músicas à fila"
}, },
"table": { "table": {
"column": { "column": {
"discNumber": "disco", "discNumber": "Disco",
"size": "$t(common.size)", "size": "$t(common.size)",
"title": "titulo" "title": "Titulo"
}, },
"config": { "config": {
"label": { "label": {
"discNumber": "numero do disco", "discNumber": "Numero do disco",
"titleCombined": "$t(common.title) (combinado)" "titleCombined": "$t(common.title) (combinado)"
} }
} }
+13 -13
View File
@@ -1,19 +1,19 @@
{ {
"common": { "common": {
"confirm": "confirmă", "confirm": "Confirmă",
"create": "creează", "create": "Creează",
"biography": "biografie", "biography": "Biografie",
"areYouSure": "ești sigur?", "areYouSure": "Ești sigur?",
"no": "nu", "no": "Nu",
"name": "nume", "name": "Nume",
"ok": "ok", "ok": "Ok",
"note": "notă", "note": "Notă",
"yes": "da", "yes": "Da",
"explicit": "explicit", "explicit": "Explicit",
"year": "an", "year": "An",
"menu": "meniu" "menu": "Meniu"
}, },
"filter": { "filter": {
"biography": "biografie" "biography": "Biografie"
} }
} }
+781 -774
View File
File diff suppressed because it is too large Load Diff
+579 -579
View File
File diff suppressed because it is too large Load Diff
+438 -438
View File
File diff suppressed because it is too large Load Diff
+421 -421
View File
File diff suppressed because it is too large Load Diff
+341 -341
View File
@@ -1,323 +1,323 @@
{ {
"action": { "action": {
"editPlaylist": "redigera $t(entity.playlist, {\"count\": 1})", "editPlaylist": "Redigera $t(entity.playlist, {\"count\": 1})",
"goToPage": "gå till sida", "goToPage": "Gå till sida",
"moveToTop": "flytta till toppen", "moveToTop": "Flytta till toppen",
"clearQueue": "rensa kö", "clearQueue": "Rensa kö",
"addToFavorites": "lägg till $t(entity.favorite, {\"count\": 2})", "addToFavorites": "Lägg till $t(entity.favorite, {\"count\": 2})",
"addToPlaylist": "lägg till $t(entity.playlist, {\"count\": 1})", "addToPlaylist": "Lägg till $t(entity.playlist, {\"count\": 1})",
"createPlaylist": "skapa $t(entity.playlist, {\"count\": 1})", "createPlaylist": "Skapa $t(entity.playlist, {\"count\": 1})",
"removeFromPlaylist": "ta bort från $t(entity.playlist, {\"count\": 1})", "removeFromPlaylist": "Ta bort från $t(entity.playlist, {\"count\": 1})",
"viewPlaylists": "visa $t(entity.playlist, {\"count\": 2})", "viewPlaylists": "Visa $t(entity.playlist, {\"count\": 2})",
"refresh": "$t(common.refresh)", "refresh": "$t(common.refresh)",
"deletePlaylist": "ta bort $t(entity.playlist, {\"count\": 1})", "deletePlaylist": "Ta bort $t(entity.playlist, {\"count\": 1})",
"removeFromQueue": "ta bort från kö", "removeFromQueue": "Ta bort från kö",
"deselectAll": "avmarkera alla", "deselectAll": "Avmarkera alla",
"moveToBottom": "flytta till botten", "moveToBottom": "Flytta till botten",
"setRating": "sätt betyg", "setRating": "Sätt betyg",
"toggleSmartPlaylistEditor": "växla $t(entity.smartPlaylist) redigerare", "toggleSmartPlaylistEditor": "Växla $t(entity.smartPlaylist) redigerare",
"removeFromFavorites": "ta bort från $t(entity.favorite, {\"count\": 2})", "removeFromFavorites": "Ta bort från $t(entity.favorite, {\"count\": 2})",
"downloadStarted": "startade nedladdning av {{count}} objekt", "downloadStarted": "Startade nedladdning av {{count}} objekt",
"moveToNext": "flytta till nästa", "moveToNext": "Flytta till nästa",
"moveUp": "flytta upp", "moveUp": "Flytta upp",
"moveDown": "flytta ner", "moveDown": "Flytta ner",
"holdToMoveToTop": "håll för att flytta till toppen", "holdToMoveToTop": "Håll för att flytta till toppen",
"holdToMoveToBottom": "håll för att flytta till botten", "holdToMoveToBottom": "Håll för att flytta till botten",
"moveItems": "flytta objekt", "moveItems": "Flytta objekt",
"shuffle": "slumpa", "shuffle": "Slumpa",
"shuffleAll": "slumpa alla", "shuffleAll": "Slumpa alla",
"shuffleSelected": "slumpa valda", "shuffleSelected": "Slumpa valda",
"viewMore": "visa mer", "viewMore": "Visa mer",
"openIn": { "openIn": {
"lastfm": "Öppna i Last.fm", "lastfm": "Öppna i Last.fm",
"musicbrainz": "Öppna i MusicBrainz" "musicbrainz": "Öppna i MusicBrainz"
}, },
"createRadioStation": "skapa $t(entity.radioStation, {\"count\": 1})", "createRadioStation": "Skapa $t(entity.radioStation, {\"count\": 1})",
"deleteRadioStation": "ta bort $t(entity.radioStation, {\"count\": 1})", "deleteRadioStation": "Ta bort $t(entity.radioStation, {\"count\": 1})",
"addOrRemoveFromSelection": "lägg till eller ta bort från markerade", "addOrRemoveFromSelection": "Lägg till eller ta bort från markerade",
"selectRangeOfItems": "välj en mängd objekt", "selectRangeOfItems": "Välj en mängd objekt",
"selectAll": "markera alla", "selectAll": "Markera alla",
"openApplicationDirectory": "öppna applikationskatalog" "openApplicationDirectory": "Öppna applikationskatalog"
}, },
"common": { "common": {
"backward": "bakåt", "backward": "Bakåt",
"increase": "öka", "increase": "Öka",
"rating": "betyg", "rating": "Betyg",
"bpm": "bpm", "bpm": "Bpm",
"refresh": "laddaom", "refresh": "Laddaom",
"unknown": "okänd", "unknown": "Okänd",
"areYouSure": "är du säker?", "areYouSure": "Är du säker?",
"edit": "redigera", "edit": "Redigera",
"favorite": "favorit", "favorite": "Favorit",
"left": "vänster", "left": "Vänster",
"save": "spara", "save": "Spara",
"right": "höger", "right": "Höger",
"currentSong": "aktuell $t(entity.track, {\"count\": 1})", "currentSong": "Aktuell $t(entity.track, {\"count\": 1})",
"collapse": "kollaps", "collapse": "Kollaps",
"trackNumber": "spår", "trackNumber": "Spår",
"descending": "fallande", "descending": "Fallande",
"add": "lägg till", "add": "Lägg till",
"gap": "avstånd", "gap": "Avstånd",
"ascending": "stigande", "ascending": "Stigande",
"dismiss": "avskeda", "dismiss": "Avskeda",
"year": "år", "year": "År",
"manage": "hantera", "manage": "Hantera",
"limit": "gräns", "limit": "Gräns",
"minimize": "minimera", "minimize": "Minimera",
"modified": "modifierad", "modified": "Modifierad",
"duration": "längd", "duration": "Längd",
"name": "namn", "name": "Namn",
"maximize": "maximera", "maximize": "Maximera",
"decrease": "minska", "decrease": "Minska",
"ok": "ok", "ok": "Ok",
"description": "beskrivning", "description": "Beskrivning",
"configure": "konfigurera", "configure": "Konfigurera",
"path": "sökväg", "path": "Sökväg",
"no": "nej", "no": "Nej",
"owner": "ägare", "owner": "Ägare",
"enable": "aktivera", "enable": "Aktivera",
"clear": "töm", "clear": "Töm",
"forward": "framåt", "forward": "Framåt",
"delete": "ta bort", "delete": "Ta bort",
"cancel": "avbryt", "cancel": "Avbryt",
"forceRestartRequired": "starta om för att tillämpa ändringar... Stäng meddelandet för att starta om", "forceRestartRequired": "Starta om för att tillämpa ändringar... Stäng meddelandet för att starta om",
"setting_one": "inställning", "setting_one": "Inställning",
"setting_other": "", "setting_other": "",
"version": "version", "version": "Version",
"title": "titel", "title": "Titel",
"filter_one": "filter", "filter_one": "Filter",
"filter_other": "filter", "filter_other": "Filter",
"filters": "filter", "filters": "Filter",
"create": "skapa", "create": "Skapa",
"bitrate": "bithastighet", "bitrate": "Bithastighet",
"saveAndReplace": "spara och skrivöver", "saveAndReplace": "Spara och skrivöver",
"action_one": "handling", "action_one": "Handling",
"action_other": "handlingar", "action_other": "Handlingar",
"playerMustBePaused": "spelaren måste pausas", "playerMustBePaused": "Spelaren måste pausas",
"confirm": "bekräfta", "confirm": "Bekräfta",
"resetToDefault": "återställ till standard", "resetToDefault": "Återställ till standard",
"home": "hem", "home": "Hem",
"comingSoon": "kommer snart…", "comingSoon": "Kommer snart…",
"reset": "nollställ", "reset": "Nollställ",
"channel_one": "kanal", "channel_one": "Kanal",
"channel_other": "kanaler", "channel_other": "Kanaler",
"disable": "inaktivera", "disable": "Inaktivera",
"sortOrder": "ordning", "sortOrder": "Ordning",
"none": "ingen", "none": "Ingen",
"menu": "meny", "menu": "Meny",
"restartRequired": "omstart krävs", "restartRequired": "Omstart krävs",
"previousSong": "föregående $t(entity.track, {\"count\": 1})", "previousSong": "Föregående $t(entity.track, {\"count\": 1})",
"noResultsFromQuery": "frågan returnerade inga resultat", "noResultsFromQuery": "Frågan returnerade inga resultat",
"quit": "avsluta", "quit": "Avsluta",
"expand": "expandera", "expand": "Expandera",
"search": "sök", "search": "Sök",
"saveAs": "spara som", "saveAs": "Spara som",
"disc": "skiva", "disc": "Skiva",
"yes": "ja", "yes": "Ja",
"random": "slumpmässig", "random": "Slumpmässig",
"size": "storlek", "size": "Storlek",
"biography": "biografi", "biography": "Biografi",
"note": "anteckning", "note": "Anteckning",
"center": "center", "center": "Center",
"explicitStatus": "olämplig status", "explicitStatus": "Olämplig status",
"additionalParticipants": "ytterligare medverkare", "additionalParticipants": "Ytterligare medverkare",
"newVersion": "en ny version har installerats {{version}}", "newVersion": "En ny version har installerats {{version}}",
"viewReleaseNotes": "se utgåveinformation", "viewReleaseNotes": "Se utgåveinformation",
"bitDepth": "bitdjup", "bitDepth": "Bitdjup",
"close": "stäng", "close": "Stäng",
"codec": "kodek", "codec": "Kodek",
"doNotShowAgain": "visa inte detta igen", "doNotShowAgain": "Visa inte detta igen",
"view": "visa", "view": "Visa",
"externalLinks": "externa länkar", "externalLinks": "Externa länkar",
"faster": "snabbare", "faster": "Snabbare",
"mbid": "MusicBrainz ID", "mbid": "MusicBrainz ID",
"noFilters": "inga filter konfigurerade", "noFilters": "Inga filter konfigurerade",
"preview": "förhandsvisa", "preview": "Förhandsvisa",
"private": "privat", "private": "Privat",
"public": "allmän", "public": "Allmän",
"recordLabel": "skivbolag", "recordLabel": "Skivbolag",
"releaseType": "utgåvetyp", "releaseType": "Utgåvetyp",
"reload": "ladda om", "reload": "Ladda om",
"sampleRate": "samplingstakt", "sampleRate": "Samplingstakt",
"slower": "långsammare", "slower": "Långsammare",
"share": "dela", "share": "Dela",
"sort": "sortera", "sort": "Sortera",
"tags": "taggar", "tags": "Taggar",
"translation": "översättning", "translation": "Översättning",
"explicit": "olämplig", "explicit": "Olämplig",
"clean": "städad", "clean": "Städad",
"gridRows": "rutnätsrader", "gridRows": "Rutnätsrader",
"tableColumns": "tabellkolumner", "tableColumns": "Tabellkolumner",
"itemsMore": "{{count}} fler", "itemsMore": "{{count}} fler",
"countSelected": "{{count}} markerade" "countSelected": "{{count}} markerade"
}, },
"error": { "error": {
"remotePortWarning": "starta om servern för att tillämpa den nya porten", "remotePortWarning": "Starta om servern för att tillämpa den nya porten",
"systemFontError": "ett fel uppstod vid försök att hämta systemteckensnitt", "systemFontError": "Ett fel uppstod vid försök att hämta systemteckensnitt",
"playbackError": "ett fel uppstod vid försök att spela upp media", "playbackError": "Ett fel uppstod vid försök att spela upp media",
"endpointNotImplementedError": "endpoint {{endpoint}} är inte implementerad för {{serverType}}", "endpointNotImplementedError": "Endpoint {{endpoint}} är inte implementerad för {{serverType}}",
"remotePortError": "ett fel uppstod vid försök att ange serverporten", "remotePortError": "Ett fel uppstod vid försök att ange serverporten",
"serverRequired": "server krävs", "serverRequired": "Server krävs",
"authenticationFailed": "autentiseringen misslyckades", "authenticationFailed": "Autentiseringen misslyckades",
"apiRouteError": "det går inte att dirigera begäran", "apiRouteError": "Det går inte att dirigera begäran",
"genericError": "ett fel uppstod", "genericError": "Ett fel uppstod",
"credentialsRequired": "autentiseringsuppgifter som krävs", "credentialsRequired": "Autentiseringsuppgifter som krävs",
"sessionExpiredError": "din session har löpt ut", "sessionExpiredError": "Din session har löpt ut",
"remoteEnableError": "Ett fel uppstod vid försök att $t(common.enable) servern", "remoteEnableError": "Ett fel uppstod vid försök att $t(common.enable) servern",
"localFontAccessDenied": "åtkomst nekad till lokala teckensnitt", "localFontAccessDenied": "Åtkomst nekad till lokala teckensnitt",
"serverNotSelectedError": "ingen server vald", "serverNotSelectedError": "Ingen server vald",
"remoteDisableError": "ett fel uppstod vid försök av $t(common.disable) servern", "remoteDisableError": "Ett fel uppstod vid försök av $t(common.disable) servern",
"mpvRequired": "MPV krävs", "mpvRequired": "MPV krävs",
"audioDeviceFetchError": "ett fel uppstod vid hämtning av ljudenheter", "audioDeviceFetchError": "Ett fel uppstod vid hämtning av ljudenheter",
"invalidServer": "ogiltig server", "invalidServer": "Ogiltig server",
"loginRateError": "för många inloggningsförsök, försök igen om några sekunder", "loginRateError": "För många inloggningsförsök, försök igen om några sekunder",
"badAlbum": "du ser denna sidan eftersom denna låten inte är en del av ett album. du ser troligtvis detta problemet för att du har en låt på toppnivån i din musikmapp. Jellyfin grupperar bara låtar om de finns i en mapp", "badAlbum": "Du ser denna sidan eftersom denna låten inte är en del av ett album. du ser troligtvis detta problemet för att du har en låt på toppnivån i din musikmapp. Jellyfin grupperar bara låtar om de finns i en mapp",
"badValue": "felaktigt alternativ \"{{value}}\". detta värde existerar inte längre", "badValue": "Felaktigt alternativ \"{{value}}\". detta värde existerar inte längre",
"multipleServerSaveQueueError": "spelningskön har en eller flera låtar som inte är från den nuvarande valda servern. detta är inte stöttat", "multipleServerSaveQueueError": "Spelningskön har en eller flera låtar som inte är från den nuvarande valda servern. detta är inte stöttat",
"networkError": "en nätverksfel uppstod", "networkError": "En nätverksfel uppstod",
"notificationDenied": "åtkomst till notifieringarna var nekad. inställningen har ingen verkan", "notificationDenied": "Åtkomst till notifieringarna var nekad. inställningen har ingen verkan",
"openError": "kunde inte öppna filen", "openError": "Kunde inte öppna filen",
"settingsSyncError": "diskrepans hittades mellan inställningarna för renderingsprocessen och huvudprocessen. starta om applikationen för att ändringarna ska tillämpas" "settingsSyncError": "Diskrepans hittades mellan inställningarna för renderingsprocessen och huvudprocessen. starta om applikationen för att ändringarna ska tillämpas"
}, },
"filter": { "filter": {
"mostPlayed": "mest spelade", "mostPlayed": "Mest spelade",
"comment": "kommentar", "comment": "Kommentar",
"playCount": "antal spelningar", "playCount": "Antal spelningar",
"recentlyUpdated": "nyligen uppdaterad", "recentlyUpdated": "Nyligen uppdaterad",
"channels": "$t(common.channel_other)", "channels": "$t(common.channel_other)",
"isCompilation": "är kompilering", "isCompilation": "Är kompilering",
"recentlyPlayed": "nyligen spelad", "recentlyPlayed": "Nyligen spelad",
"isRated": "är betygsatt", "isRated": "Är betygsatt",
"owner": "$t(common.owner)", "owner": "$t(common.owner)",
"title": "titel", "title": "Titel",
"rating": "betyg", "rating": "Betyg",
"search": "sök", "search": "Sök",
"bitrate": "bithastighet", "bitrate": "Bithastighet",
"genre": "$t(entity.genre, {\"count\": 1})", "genre": "$t(entity.genre, {\"count\": 1})",
"recentlyAdded": "nyligen tillagda", "recentlyAdded": "Nyligen tillagda",
"note": "anteckning", "note": "Anteckning",
"name": "namn", "name": "Namn",
"dateAdded": "datum tillagt", "dateAdded": "Datum tillagt",
"releaseDate": "utgivningsdag", "releaseDate": "Utgivningsdag",
"communityRating": "betyg från communityn", "communityRating": "Betyg från communityn",
"path": "sökväg", "path": "Sökväg",
"favorited": "favoritmärkt", "favorited": "Favoritmärkt",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})", "albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"isRecentlyPlayed": "spelas nyligen", "isRecentlyPlayed": "Spelas nyligen",
"isFavorited": "är favoritmärkt", "isFavorited": "Är favoritmärkt",
"bpm": "bpm", "bpm": "Bpm",
"releaseYear": "utgivningsår", "releaseYear": "Utgivningsår",
"id": "id", "id": "Id",
"disc": "skiva", "disc": "Skiva",
"biography": "biografi", "biography": "Biografi",
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"duration": "längd", "duration": "Längd",
"isPublic": "är offentlig", "isPublic": "Är offentlig",
"random": "slumpmässig", "random": "Slumpmässig",
"lastPlayed": "senast spelad", "lastPlayed": "Senast spelad",
"toYear": "till år", "toYear": "Till år",
"fromYear": "från år", "fromYear": "Från år",
"album": "$t(entity.album, {\"count\": 1})", "album": "$t(entity.album, {\"count\": 1})",
"trackNumber": "spår", "trackNumber": "Spår",
"songCount": "sångräkning", "songCount": "Sångräkning",
"criticRating": "kritikerbetyg", "criticRating": "Kritikerbetyg",
"albumCount": "$t(entity.album, {\"count\": 2}) antal", "albumCount": "$t(entity.album, {\"count\": 2}) antal",
"explicitStatus": "$t(common.explicitStatus)" "explicitStatus": "$t(common.explicitStatus)"
}, },
"form": { "form": {
"deletePlaylist": { "deletePlaylist": {
"title": "ta bort $t(entity.playlist, {\"count\": 1})", "title": "Ta bort $t(entity.playlist, {\"count\": 1})",
"success": "$t(entity.playlist, {\"count\": 1}) har tagits bort", "success": "$t(entity.playlist, {\"count\": 1}) har tagits bort",
"input_confirm": "Skriv namnet på $t(entity.playlist, {\"count\": 1}) för att bekräfta" "input_confirm": "Skriv namnet på $t(entity.playlist, {\"count\": 1}) för att bekräfta"
}, },
"createPlaylist": { "createPlaylist": {
"input_description": "$t(common.description)", "input_description": "$t(common.description)",
"title": "skapa $t(entity.playlist, {\"count\": 1})", "title": "Skapa $t(entity.playlist, {\"count\": 1})",
"input_public": "offentlig", "input_public": "Offentlig",
"input_name": "$t(common.name)", "input_name": "$t(common.name)",
"success": "$t(entity.playlist, {\"count\": 1}) skapad", "success": "$t(entity.playlist, {\"count\": 1}) skapad",
"input_owner": "$t(common.owner)" "input_owner": "$t(common.owner)"
}, },
"addServer": { "addServer": {
"title": "lägg till server", "title": "Lägg till server",
"input_username": "användarnamn", "input_username": "Användarnamn",
"input_url": "länk", "input_url": "Länk",
"input_password": "lösenord", "input_password": "Lösenord",
"input_legacyAuthentication": "aktivera äldre autentisering", "input_legacyAuthentication": "Aktivera äldre autentisering",
"input_name": "server namn", "input_name": "Server namn",
"success": "servern har lagts till", "success": "Servern har lagts till",
"input_savePassword": "spara lösenord", "input_savePassword": "Spara lösenord",
"ignoreSsl": "ignorera ssl ($t(common.restartRequired))", "ignoreSsl": "Ignorera ssl ($t(common.restartRequired))",
"ignoreCors": "ignorera cors ($t(common.restartRequired))", "ignoreCors": "Ignorera cors ($t(common.restartRequired))",
"error_savePassword": "ett fel uppstod när lösenordet skulle sparas", "error_savePassword": "Ett fel uppstod när lösenordet skulle sparas",
"input_preferInstantMix": "föredra instant mixning", "input_preferInstantMix": "Föredra instant mixning",
"input_preferInstantMixDescription": "använd bara instant mixning för att få liknande låtar. användbar om du har plugin för att förändra detta beteendet" "input_preferInstantMixDescription": "Använd bara instant mixning för att få liknande låtar. användbar om du har plugin för att förändra detta beteendet"
}, },
"addToPlaylist": { "addToPlaylist": {
"success": "lade till $t(entity.trackWithCount, {\"count\": {{message}} }) till $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })", "success": "Lade till $t(entity.trackWithCount, {\"count\": {{message}} }) till $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "lägg till i $t(entity.playlist, {\"count\": 1})", "title": "Lägg till i $t(entity.playlist, {\"count\": 1})",
"input_skipDuplicates": "hoppa över dubbletter", "input_skipDuplicates": "Hoppa över dubbletter",
"input_playlists": "$t(entity.playlist, {\"count\": 2})", "input_playlists": "$t(entity.playlist, {\"count\": 2})",
"create": "skapa $t(entity.playlist, {\"count\": 1}) {{playlist}}", "create": "Skapa $t(entity.playlist, {\"count\": 1}) {{playlist}}",
"searchOrCreate": "sök $t(entity.playlist, {\"count\": 2}) eller skriv för att skapa en ny" "searchOrCreate": "Sök $t(entity.playlist, {\"count\": 2}) eller skriv för att skapa en ny"
}, },
"updateServer": { "updateServer": {
"title": "uppdatera server", "title": "Uppdatera server",
"success": "servern har uppdaterats" "success": "Servern har uppdaterats"
}, },
"queryEditor": { "queryEditor": {
"input_optionMatchAll": "matcha alla", "input_optionMatchAll": "Matcha alla",
"input_optionMatchAny": "matcha något" "input_optionMatchAny": "Matcha något"
}, },
"lyricSearch": { "lyricSearch": {
"input_name": "$t(common.name)", "input_name": "$t(common.name)",
"input_artist": "$t(entity.artist, {\"count\": 1})", "input_artist": "$t(entity.artist, {\"count\": 1})",
"title": "sångtext sök" "title": "Sångtext sök"
}, },
"editPlaylist": { "editPlaylist": {
"title": "redigera $t(entity.playlist, {\"count\": 1})", "title": "Redigera $t(entity.playlist, {\"count\": 1})",
"publicJellyfinNote": "Jellyfin visar av någon anledning inte om en spellista är publik eller inte. Om du önskar att denna ska förbli publik, så får du ha följande indata markerade" "publicJellyfinNote": "Jellyfin visar av någon anledning inte om en spellista är publik eller inte. Om du önskar att denna ska förbli publik, så får du ha följande indata markerade"
}, },
"largeFetchConfirmation": { "largeFetchConfirmation": {
"title": "lägg till objekt till kön", "title": "Lägg till objekt till kön",
"description": "Åtgärden kommer att lägga till alla objekt till den nuvarande filtrerade vyn" "description": "Åtgärden kommer att lägga till alla objekt till den nuvarande filtrerade vyn"
}, },
"createRadioStation": { "createRadioStation": {
"success": "radiostation skapades", "success": "Radiostation skapades",
"title": "skapa radiostation", "title": "Skapa radiostation",
"input_homepageUrl": "hemside-URL", "input_homepageUrl": "Hemside-URL",
"input_name": "namn", "input_name": "Namn",
"input_streamUrl": "stream url" "input_streamUrl": "Stream url"
} }
}, },
"page": { "page": {
"fullscreenPlayer": { "fullscreenPlayer": {
"config": { "config": {
"showLyricMatch": "Visa låttext matchning", "showLyricMatch": "Visa låttext matchning",
"dynamicBackground": "dynamisk bakgrund", "dynamicBackground": "Dynamisk bakgrund",
"followCurrentLyric": "följ aktuell låttext", "followCurrentLyric": "Följ aktuell låttext",
"opacity": "ogenomskinlighet", "opacity": "Ogenomskinlighet",
"lyricSize": "låttext storlek", "lyricSize": "Låttext storlek",
"lyricAlignment": "låttext justering", "lyricAlignment": "Låttext justering",
"lyricGap": "låttext mellanrum", "lyricGap": "Låttext mellanrum",
"synchronized": "synkroniserad", "synchronized": "Synkroniserad",
"showLyricProvider": "visa sångtextleverantör", "showLyricProvider": "Visa sångtextleverantör",
"unsynchronized": "osynkroniserad" "unsynchronized": "Osynkroniserad"
}, },
"lyrics": "sångtext", "lyrics": "Sångtext",
"related": "relaterad" "related": "Relaterad"
}, },
"appMenu": { "appMenu": {
"selectServer": "välj server", "selectServer": "Välj server",
"version": "version {{version}}", "version": "Version {{version}}",
"settings": "$t(common.setting, {\"count\": 2})", "settings": "$t(common.setting, {\"count\": 2})",
"manageServers": "hantera servrar", "manageServers": "Hantera servrar",
"expandSidebar": "expandera sidofältet", "expandSidebar": "Expandera sidofältet",
"openBrowserDevtools": "öppna webbläsarens utvecklingsverktyg", "openBrowserDevtools": "Öppna webbläsarens utvecklingsverktyg",
"quit": "$t(common.quit)", "quit": "$t(common.quit)",
"goBack": "gå tillbaka", "goBack": "Gå tillbaka",
"goForward": "gå framåt", "goForward": "Gå framåt",
"collapseSidebar": "växla sidofältet" "collapseSidebar": "Växla sidofältet"
}, },
"contextMenu": { "contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)", "addToPlaylist": "$t(action.addToPlaylist)",
@@ -336,20 +336,20 @@
"play": "$t(player.play)", "play": "$t(player.play)",
"numberSelected": "{{count}} vald", "numberSelected": "{{count}} vald",
"removeFromQueue": "$t(action.removeFromQueue)", "removeFromQueue": "$t(action.removeFromQueue)",
"download": "ladda ner", "download": "Ladda ner",
"moveItems": "$t(action.moveItems)", "moveItems": "$t(action.moveItems)",
"moveToNext": "$t(action.moveToNext)", "moveToNext": "$t(action.moveToNext)",
"playSimilarSongs": "$t(player.playSimilarSongs)", "playSimilarSongs": "$t(player.playSimilarSongs)",
"playShuffled": "$t(player.shuffle)", "playShuffled": "$t(player.shuffle)",
"shareItem": "dela objekt", "shareItem": "Dela objekt",
"goTo": "gå till", "goTo": "Gå till",
"goToAlbum": "gå till $t(entity.album, {\"count\": 1})", "goToAlbum": "Gå till $t(entity.album, {\"count\": 1})",
"goToAlbumArtist": "gå till $t(entity.albumArtist, {\"count\": 1})", "goToAlbumArtist": "Gå till $t(entity.albumArtist, {\"count\": 1})",
"showDetails": "hämta information" "showDetails": "Hämta information"
}, },
"albumDetail": { "albumDetail": {
"moreFromArtist": "mer från $t(entity.artist, {\"count\": 1})", "moreFromArtist": "Mer från $t(entity.artist, {\"count\": 1})",
"moreFromGeneric": "mer från {{item}}" "moreFromGeneric": "Mer från {{item}}"
}, },
"albumArtistList": { "albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})" "title": "$t(entity.albumArtist, {\"count\": 2})"
@@ -358,124 +358,124 @@
"title": "$t(entity.album, {\"count\": 2})" "title": "$t(entity.album, {\"count\": 2})"
}, },
"sidebar": { "sidebar": {
"nowPlaying": "nu spelas" "nowPlaying": "Nu spelas"
}, },
"home": { "home": {
"mostPlayed": "mest spelade", "mostPlayed": "Mest spelade",
"newlyAdded": "nytillkomna utgåvor", "newlyAdded": "Nytillkomna utgåvor",
"explore": "utforska från ditt bibliotek", "explore": "Utforska från ditt bibliotek",
"recentlyPlayed": "nyligen spelat" "recentlyPlayed": "Nyligen spelat"
}, },
"setting": { "setting": {
"playbackTab": "uppspelning", "playbackTab": "Uppspelning",
"generalTab": "allmänt", "generalTab": "Allmänt",
"hotkeysTab": "snabbtangenter", "hotkeysTab": "Snabbtangenter",
"windowTab": "fönster" "windowTab": "Fönster"
}, },
"globalSearch": { "globalSearch": {
"commands": { "commands": {
"serverCommands": "serverkommandon", "serverCommands": "Serverkommandon",
"goToPage": "gå till sidan", "goToPage": "Gå till sidan",
"searchFor": "sök efter {{query}}" "searchFor": "Sök efter {{query}}"
}, },
"title": "kommandon" "title": "Kommandon"
}, },
"manageServers": { "manageServers": {
"url": "URL", "url": "URL",
"username": "användarnamn", "username": "Användarnamn",
"editServerDetailsTooltip": "redigera serverinställningar", "editServerDetailsTooltip": "Redigera serverinställningar",
"removeServer": "ta bort server" "removeServer": "Ta bort server"
} }
}, },
"entity": { "entity": {
"playlist_one": "spellista", "playlist_one": "Spellista",
"playlist_other": "spellistor", "playlist_other": "Spellistor",
"artist_one": "artist", "artist_one": "Artist",
"artist_other": "artister", "artist_other": "Artister",
"albumArtist_one": "albumartist", "albumArtist_one": "Albumartist",
"albumArtist_other": "albumartister", "albumArtist_other": "Albumartister",
"albumArtistCount_one": "{{count}} Albumartist", "albumArtistCount_one": "{{count}} albumartist",
"albumArtistCount_other": "{{count}} Albumartister", "albumArtistCount_other": "{{count}} albumartister",
"albumWithCount_one": "{{count}} album", "albumWithCount_one": "{{count}} album",
"albumWithCount_other": "{{count}} album", "albumWithCount_other": "{{count}} album",
"favorite_one": "favorit", "favorite_one": "Favorit",
"favorite_other": "favoriter", "favorite_other": "Favoriter",
"folder_one": "mapp", "folder_one": "Mapp",
"folder_other": "mappar", "folder_other": "Mappar",
"album_one": "album", "album_one": "Album",
"album_other": "album", "album_other": "Album",
"playlistWithCount_one": "{{count}} spellista", "playlistWithCount_one": "{{count}} spellista",
"playlistWithCount_other": "{{count}} spellistor", "playlistWithCount_other": "{{count}} spellistor",
"folderWithCount_one": "{{count}} mapp", "folderWithCount_one": "{{count}} mapp",
"folderWithCount_other": "{{count}} mappar", "folderWithCount_other": "{{count}} mappar",
"track_one": "spår", "track_one": "Spår",
"track_other": "spår", "track_other": "Spår",
"trackWithCount_one": "{{count}} spår", "trackWithCount_one": "{{count}} spår",
"trackWithCount_other": "{{count}} spår", "trackWithCount_other": "{{count}} spår",
"artistWithCount_one": "{{count}} artist", "artistWithCount_one": "{{count}} artist",
"artistWithCount_other": "{{count}} artister", "artistWithCount_other": "{{count}} artister",
"genre_one": "genre", "genre_one": "Genre",
"genre_other": "genrer", "genre_other": "Genrer",
"genreWithCount_one": "{{count}} genre", "genreWithCount_one": "{{count}} genre",
"genreWithCount_other": "{{count}} genrer", "genreWithCount_other": "{{count}} genrer",
"play_one": "{{count}} spelning", "play_one": "{{count}} spelning",
"play_other": "{{count}} spelningar", "play_other": "{{count}} spelningar",
"smartPlaylist": "smart $t(entity.playlist, {\"count\": 1})", "smartPlaylist": "Smart $t(entity.playlist, {\"count\": 1})",
"song_one": "låt", "song_one": "Låt",
"song_other": "låtar", "song_other": "Låtar",
"radioStation_one": "radiostation", "radioStation_one": "Radiostation",
"radioStation_other": "radiostationer", "radioStation_other": "Radiostationer",
"radioStationWithCount_one": "{{count}} radiostation", "radioStationWithCount_one": "{{count}} radiostation",
"radioStationWithCount_other": "{{count}} radiostationer" "radioStationWithCount_other": "{{count}} radiostationer"
}, },
"player": { "player": {
"repeat_all": "repetera alla", "repeat_all": "Repetera alla",
"repeat": "repetera", "repeat": "Repetera",
"queue_remove": "ta bort markerad", "queue_remove": "Ta bort markerad",
"playRandom": "spela slumpmässigt", "playRandom": "Spela slumpmässigt",
"previous": "föregående", "previous": "Föregående",
"favorite": "favorit", "favorite": "Favorit",
"next": "nästa", "next": "Nästa",
"shuffle": "blanda", "shuffle": "Blanda",
"playbackFetchNoResults": "inga låtar hittades", "playbackFetchNoResults": "Inga låtar hittades",
"playbackFetchInProgress": "laddar låtar…", "playbackFetchInProgress": "Laddar låtar…",
"addNext": "lägg till nästa", "addNext": "Lägg till nästa",
"playbackSpeed": "uppspelningshastighet", "playbackSpeed": "Uppspelningshastighet",
"playbackFetchCancel": "det här tar ett tag... stäng aviseringen för att avbryta", "playbackFetchCancel": "Det här tar ett tag... stäng aviseringen för att avbryta",
"play": "spela", "play": "Spela",
"repeat_off": "repetera inaktiverad", "repeat_off": "Repetera inaktiverad",
"queue_clear": "rensa kö", "queue_clear": "Rensa kö",
"muted": "mutad", "muted": "Mutad",
"queue_moveToTop": "flytta markerad till botten", "queue_moveToTop": "Flytta markerad till toppen",
"queue_moveToBottom": "flytta markerad till toppen", "queue_moveToBottom": "Flytta markerad till botten",
"addLast": "lägg till sist", "addLast": "Lägg till sist",
"mute": "muta" "mute": "Muta"
}, },
"datetime": { "datetime": {
"minuteShort": "min", "minuteShort": "Min",
"secondShort": "sek", "secondShort": "Sek",
"hourShort": "h", "hourShort": "H",
"dayShort": "dag" "dayShort": "Dag"
}, },
"filterOperator": { "filterOperator": {
"after": "är efter", "after": "Är efter",
"afterDate": "är efter (datum)", "afterDate": "Är efter (datum)",
"before": "är före", "before": "Är före",
"beforeDate": "är före (datum)", "beforeDate": "Är före (datum)",
"contains": "innehåller", "contains": "Innehåller",
"endsWith": "slutar med", "endsWith": "Slutar med",
"inPlaylist": "är inom", "inPlaylist": "Är inom",
"inTheLast": "är i den sista", "inTheLast": "Är i den sista",
"inTheRange": "är i spannet", "inTheRange": "Är i spannet",
"inTheRangeDate": "är i spannet (datum)", "inTheRangeDate": "Är i spannet (datum)",
"is": "är", "is": "Är",
"isNot": "är inte", "isNot": "Är inte",
"isGreaterThan": "är större än", "isGreaterThan": "Är större än",
"isLessThan": "är mindre än", "isLessThan": "Är mindre än",
"matchesRegex": "matchar regex", "matchesRegex": "Matchar regex",
"notContains": "innehåller inte", "notContains": "Innehåller inte",
"notInPlaylist": "är inte inom", "notInPlaylist": "Är inte inom",
"notInTheLast": "är inte inom den sista", "notInTheLast": "Är inte inom den sista",
"startsWith": "startar med" "startsWith": "Startar med"
} }
} }
+8 -8
View File
@@ -627,8 +627,8 @@
"playbackFetchNoResults": "பாடல்கள் எதுவும் கிடைக்கவில்லை", "playbackFetchNoResults": "பாடல்கள் எதுவும் கிடைக்கவில்லை",
"playbackSpeed": "பிளேபேக் விரைவு", "playbackSpeed": "பிளேபேக் விரைவு",
"playRandom": "சீரற்ற முறையில் விளையாடுங்கள்", "playRandom": "சீரற்ற முறையில் விளையாடுங்கள்",
"queue_moveToBottom": "மேலே தேர்ந்தெடுக்கப்பட்ட நகர்த்த", "queue_moveToBottom": "தேர்ந்தெடுக்கப்பட்டதை கீழே நகர்த்தவும்",
"queue_moveToTop": "தேர்ந்தெடுக்கப்பட்டதை கீழே நகர்த்தவும்", "queue_moveToTop": "மேலே தேர்ந்தெடுக்கப்பட்ட நகர்த்த",
"skip_back": "பின்னோக்கி தவிர்க்கவும்", "skip_back": "பின்னோக்கி தவிர்க்கவும்",
"skip_forward": "முன்னோக்கி தவிர்க்கவும்", "skip_forward": "முன்னோக்கி தவிர்க்கவும்",
"stop": "நிறுத்து", "stop": "நிறுத்து",
@@ -800,7 +800,7 @@
"enableRemote": "ரிமோட் கண்ட்ரோல் சேவையகத்தை இயக்கவும்", "enableRemote": "ரிமோட் கண்ட்ரோல் சேவையகத்தை இயக்கவும்",
"enableRemote_description": "பயன்பாட்டைக் கட்டுப்படுத்த மற்ற சாதனங்களை அனுமதிக்க ரிமோட் கண்ட்ரோல் சேவையகத்தை இயக்குகிறது", "enableRemote_description": "பயன்பாட்டைக் கட்டுப்படுத்த மற்ற சாதனங்களை அனுமதிக்க ரிமோட் கண்ட்ரோல் சேவையகத்தை இயக்குகிறது",
"externalLinks": "வெளிப்புற இணைப்புகளைக் காட்டு", "externalLinks": "வெளிப்புற இணைப்புகளைக் காட்டு",
"externalLinks_description": "கலைஞர்/ஆல்பம் பக்கங்களில் வெளிப்புற இணைப்புகளை (last.fm, மியூசிக் ப்ரெய்ன்ச்) காண்பிக்க உதவுகிறது", "externalLinks_description": "கலைஞர்/ஆல்பம் பக்கங்களில் வெளிப்புற இணைப்புகளை (Last.fm, மியூசிக் ப்ரெய்ன்ச்) காண்பிக்க உதவுகிறது",
"exitToTray": "தட்டில் வெளியேறவும்", "exitToTray": "தட்டில் வெளியேறவும்",
"globalMediaHotkeys": "உலகளாவிய மீடியா ஆட்கீச்", "globalMediaHotkeys": "உலகளாவிய மீடியா ஆட்கீச்",
"discordUpdateInterval": "{{discord}} பணக்கார இருப்பு புதுப்பிப்பு இடைவெளி", "discordUpdateInterval": "{{discord}} பணக்கார இருப்பு புதுப்பிப்பு இடைவெளி",
@@ -872,7 +872,7 @@
"discordServeImage_description": "சேவையகத்திலிருந்தே {{discord}} சிறந்த இருப்புக்கான கவர் ஆர்ட்டைப் பகிரவும், செல்லிஃபின் மற்றும் நவிட்ரோமுக்கு மட்டுமே கிடைக்கும். படங்களைப் பெற {{discord}} ஒரு போட்டைப் பயன்படுத்துகிறது, எனவே உங்கள் சர்வர் பொது இணையத்திலிருந்து அணுகக்கூடியதாக இருக்க வேண்டும்", "discordServeImage_description": "சேவையகத்திலிருந்தே {{discord}} சிறந்த இருப்புக்கான கவர் ஆர்ட்டைப் பகிரவும், செல்லிஃபின் மற்றும் நவிட்ரோமுக்கு மட்டுமே கிடைக்கும். படங்களைப் பெற {{discord}} ஒரு போட்டைப் பயன்படுத்துகிறது, எனவே உங்கள் சர்வர் பொது இணையத்திலிருந்து அணுகக்கூடியதாக இருக்க வேண்டும்",
"preferLocalLyrics": "உள்ளக பாடல்களை விரும்புங்கள்", "preferLocalLyrics": "உள்ளக பாடல்களை விரும்புங்கள்",
"preferLocalLyrics_description": "கிடைக்கும்போது தொலைநிலை பாடல்களை விட உள்ளக பாடல்களை விரும்புங்கள்", "preferLocalLyrics_description": "கிடைக்கும்போது தொலைநிலை பாடல்களை விட உள்ளக பாடல்களை விரும்புங்கள்",
"lastfm": "last.fm இணைப்புகளைக் காட்டு", "lastfm": "Last.fm இணைப்புகளைக் காட்டு",
"lastfm_description": "கலைஞர்/ஆல்பம் பக்கங்களில் Last.fm க்கான இணைப்புகளைக் காட்டு", "lastfm_description": "கலைஞர்/ஆல்பம் பக்கங்களில் Last.fm க்கான இணைப்புகளைக் காட்டு",
"musicbrainz": "மியூசிக் பிரேன்ச் இணைப்புகளைக் காட்டு", "musicbrainz": "மியூசிக் பிரேன்ச் இணைப்புகளைக் காட்டு",
"musicbrainz_description": "கலைஞர்/ஆல்பம் பக்கங்களில் மியூசிக் பிரைன்ச் இணைப்புகளைக் காட்டு, அங்கு மியூசிக் பிரைன்ச் ID உள்ளது", "musicbrainz_description": "கலைஞர்/ஆல்பம் பக்கங்களில் மியூசிக் பிரைன்ச் இணைப்புகளைக் காட்டு, அங்கு மியூசிக் பிரைன்ச் ID உள்ளது",
@@ -925,7 +925,7 @@
"exportImportSettings_control_title": "இறக்குமதி / ஏற்றுமதி அமைப்புகள்", "exportImportSettings_control_title": "இறக்குமதி / ஏற்றுமதி அமைப்புகள்",
"exportImportSettings_destructiveWarning": "அமைப்புகளை இறக்குமதி செய்வது அழிவுகரமானது, கீழே உள்ள \"இறக்குமதி\" என்பதைக் சொடுக்கு செய்வதற்கு முன் மேலே உள்ளவற்றை மதிப்பாய்வு செய்யவும்!", "exportImportSettings_destructiveWarning": "அமைப்புகளை இறக்குமதி செய்வது அழிவுகரமானது, கீழே உள்ள \"இறக்குமதி\" என்பதைக் சொடுக்கு செய்வதற்கு முன் மேலே உள்ளவற்றை மதிப்பாய்வு செய்யவும்!",
"exportImportSettings_importBtn": "இறக்குமதி அமைப்புகள்", "exportImportSettings_importBtn": "இறக்குமதி அமைப்புகள்",
"exportImportSettings_importModalTitle": "feishin அமைப்புகளை இறக்குமதி செய்யவும்", "exportImportSettings_importModalTitle": "Feishin அமைப்புகளை இறக்குமதி செய்யவும்",
"exportImportSettings_importSuccess": "அமைப்புகள் வெற்றிகரமாக இறக்குமதி செய்யப்பட்டன!", "exportImportSettings_importSuccess": "அமைப்புகள் வெற்றிகரமாக இறக்குமதி செய்யப்பட்டன!",
"exportImportSettings_notValidJSON": "அனுப்பப்பட்ட கோப்பு சாதொபொகு செல்லுபடியாகாது", "exportImportSettings_notValidJSON": "அனுப்பப்பட்ட கோப்பு சாதொபொகு செல்லுபடியாகாது",
"exportImportSettings_offendingKeyError": "\"{{offendingKey}}\" தவறானது - {{reason}}", "exportImportSettings_offendingKeyError": "\"{{offendingKey}}\" தவறானது - {{reason}}",
@@ -948,8 +948,8 @@
"logLevel_optionError": "பிழை", "logLevel_optionError": "பிழை",
"logLevel_optionInfo": "தகவல்", "logLevel_optionInfo": "தகவல்",
"logLevel_optionWarn": "முன்னறிவிப்பு", "logLevel_optionWarn": "முன்னறிவிப்பு",
"mpvExtraParameters": "mpv கூடுதல் அளவுருக்கள்", "mpvExtraParameters": "MPV கூடுதல் அளவுருக்கள்",
"mpvExtraParameters_description": "mpv க்கு அனுப்ப கூடுதல் வாதங்கள்", "mpvExtraParameters_description": "MPV க்கு அனுப்ப கூடுதல் வாதங்கள்",
"notify": "பாடல் அறிவிப்புகளை இயக்கவும்", "notify": "பாடல் அறிவிப்புகளை இயக்கவும்",
"notify_description": "தற்போதைய பாடலை மாற்றும்போது அறிவிப்புகளைக் காட்டு", "notify_description": "தற்போதைய பாடலை மாற்றும்போது அறிவிப்புகளைக் காட்டு",
"pathReplace": "கோப்பு பாதை மாற்று", "pathReplace": "கோப்பு பாதை மாற்று",
@@ -1015,7 +1015,7 @@
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"biography": "$t(common.biography)", "biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)", "bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)", "bpm": "$t(common.BPM)",
"channels": "$t(common.channel, {\"count\": 2})", "channels": "$t(common.channel, {\"count\": 2})",
"codec": "$t(common.codec)", "codec": "$t(common.codec)",
"dateAdded": "தேதி சேர்க்கப்பட்டது", "dateAdded": "தேதி சேர்க்கப்பட்டது",
+554 -554
View File
File diff suppressed because it is too large Load Diff
+415 -355
View File
@@ -1,176 +1,181 @@
{ {
"action": { "action": {
"addToFavorites": "додати до $t(entity.favorite, {\"count\": 2})", "addToFavorites": "Додати до $t(entity.favorite, {\"count\": 2})",
"addOrRemoveFromSelection": "додати або видалити з вибору", "addOrRemoveFromSelection": "Додати або видалити з вибору",
"selectRangeOfItems": "вибрати діапазон елементів", "selectRangeOfItems": "Вибрати діапазон елементів",
"addToPlaylist": "додати до $t(entity.playlist, {\"count\": 1})", "addToPlaylist": "Додати до $t(entity.playlist, {\"count\": 1})",
"clearQueue": "очистити чергу", "clearQueue": "Очистити чергу",
"createPlaylist": "створити $t(entity.playlist, {\"count\": 1})", "createPlaylist": "Створити $t(entity.playlist, {\"count\": 1})",
"createRadioStation": "створити $t(entity.radioStation, {\"count\": 1})", "createRadioStation": "Створити $t(entity.radioStation, {\"count\": 1})",
"deletePlaylist": "видалити $t(entity.playlist, {\"count\": 1})", "deletePlaylist": "Видалити $t(entity.playlist, {\"count\": 1})",
"deleteRadioStation": "видалити $t(entity.radioStation, {\"count\": 1})", "deleteRadioStation": "Видалити $t(entity.radioStation, {\"count\": 1})",
"selectAll": "вибрати все", "selectAll": "Вибрати все",
"deselectAll": "скасувати вибір усього", "deselectAll": "Скасувати вибір усього",
"downloadStarted": "почато завантаження {{count}} елементів", "downloadStarted": "Почато завантаження {{count}} елементів",
"editPlaylist": "редагувати $t(entity.playlist, {\"count\": 1})", "editPlaylist": "Редагувати $t(entity.playlist, {\"count\": 1})",
"goToPage": "перейти на сторінку", "goToPage": "Перейти на сторінку",
"moveToNext": "перейти до наступного", "moveToNext": "Перейти до наступного",
"moveToBottom": "перемістити вниз", "moveToBottom": "Перемістити вниз",
"moveToTop": "перемістити вгору", "moveToTop": "Перемістити вгору",
"moveUp": "перемістити вище", "moveUp": "Перемістити вище",
"moveDown": "перемістити нижче", "moveDown": "Перемістити нижче",
"holdToMoveToTop": "утримуйте, щоб перемістити вгору", "holdToMoveToTop": "Утримуйте, щоб перемістити вгору",
"holdToMoveToBottom": "утримувати, щоб перемістити вниз", "holdToMoveToBottom": "Утримувати, щоб перемістити вниз",
"moveItems": "перемістити елементи", "moveItems": "Перемістити елементи",
"shuffle": "перемішати", "shuffle": "Перемішати",
"shuffleAll": "все випадково", "shuffleAll": "Все випадково",
"shuffleSelected": "вибране випадково", "shuffleSelected": "Вибране випадково",
"refresh": "$t(common.refresh)", "refresh": "$t(common.refresh)",
"removeFromFavorites": "видалити з $t(entity.favorite, {\"count\": 2})", "removeFromFavorites": "Видалити з $t(entity.favorite, {\"count\": 2})",
"removeFromPlaylist": "видалити з $t(entity.playlist, {\"count\": 1})", "removeFromPlaylist": "Видалити з $t(entity.playlist, {\"count\": 1})",
"removeFromQueue": "видалити з черги", "removeFromQueue": "Видалити з черги",
"setRating": "встановити рейтинг", "setRating": "Встановити рейтинг",
"toggleSmartPlaylistEditor": "перемикати редактор $t(entity.smartPlaylist)", "toggleSmartPlaylistEditor": "Перемикати редактор $t(entity.smartPlaylist)",
"viewPlaylists": "показати $t(entity.playlist, {\"count\": 2})", "viewPlaylists": "Показати $t(entity.playlist, {\"count\": 2})",
"viewMore": "переглянути більше", "viewMore": "Переглянути більше",
"openApplicationDirectory": "відкрити каталог додатків", "openApplicationDirectory": "Відкрити каталог додатків",
"openIn": { "openIn": {
"lastfm": "Відкрити в Last.fm", "lastfm": "Відкрити в Last.fm",
"musicbrainz": "Відкрити в MusicBrainz" "musicbrainz": "Відкрити в MusicBrainz",
"listenbrainz": "Відкрити у ListenBrainz",
"qobuz": "Відкрити у Qobuz",
"spotify": "Відкрити у Spotify"
} }
}, },
"common": { "common": {
"countSelected": "вибрано {{count}}", "countSelected": "Вибрано {{count}}",
"explicitStatus": "явний статус", "explicitStatus": "Явний статус",
"action_one": "дія", "action_one": "Дія",
"action_few": "дії", "action_few": "дії",
"action_many": "дій", "action_many": "дій",
"add": "додати", "add": "Додати",
"additionalParticipants": "додаткові учасники", "additionalParticipants": "Додаткові учасники",
"newVersion": "встановлено нову версію ({{version}})", "newVersion": "Встановлено нову версію ({{version}})",
"viewReleaseNotes": "переглянути список змін", "viewReleaseNotes": "Переглянути список змін",
"albumGain": "підсилення альбому", "albumGain": "Підсилення альбому",
"albumPeak": "піковий рівень альбому", "albumPeak": "Піковий рівень альбому",
"areYouSure": "ви впевнені?", "areYouSure": "Ви впевнені?",
"ascending": "зростаючи", "ascending": "Зростаючи",
"backward": "назад", "backward": "Назад",
"biography": "біографія", "biography": "Біографія",
"bitDepth": "розрядність", "bitDepth": "Розрядність",
"bitrate": "бітрейт", "bitrate": "Бітрейт",
"bpm": "уд/хв", "bpm": "Уд/хв",
"cancel": "скасувати", "cancel": "Скасувати",
"center": "посередині", "center": "Посередині",
"channel_one": "канал", "channel_one": "Канал",
"channel_few": "канали", "channel_few": "канали",
"channel_many": "каналів", "channel_many": "каналів",
"clear": "очистити", "clear": "Очистити",
"close": "закрити", "close": "Закрити",
"codec": "кодек", "codec": "Кодек",
"collapse": "згорнути", "collapse": "Згорнути",
"comingSoon": "скоро…", "comingSoon": "Скоро…",
"configure": "налаштувати", "configure": "Налаштувати",
"confirm": "підтвердити", "confirm": "Підтвердити",
"create": "створити", "create": "Створити",
"currentSong": "поточний $t(entity.track, {\"count\": 1})", "currentSong": "Поточний $t(entity.track, {\"count\": 1})",
"decrease": "знизити", "decrease": "Знизити",
"delete": "видалити", "delete": "Видалити",
"descending": "за спаданням", "descending": "За спаданням",
"description": "опис", "description": "Опис",
"disable": "вимкнути", "disable": "Вимкнути",
"disc": "диск", "disc": "Диск",
"dismiss": "відхилити", "dismiss": "Відхилити",
"doNotShowAgain": "не показувати це знову", "doNotShowAgain": "Не показувати це знову",
"duration": "тривалість", "duration": "Тривалість",
"view": "показати", "view": "Показати",
"edit": "змінити", "edit": "Змінити",
"enable": "увімкнути", "enable": "Увімкнути",
"expand": "розширити", "expand": "Розширити",
"example": "приклад", "example": "Приклад",
"externalLinks": "зовнішні посилання", "externalLinks": "Зовнішні посилання",
"faster": "швидше", "faster": "Швидше",
"favorite": "улюблений", "favorite": "Улюблений",
"filter_one": "фільтр", "filter_one": "Фільтр",
"filter_few": "фільтри", "filter_few": "фільтри",
"filter_many": "фільтрів", "filter_many": "фільтрів",
"filters": "фільтри", "filters": "Фільтри",
"filter_single": "одиночний", "filter_single": "Одиночний",
"filter_multiple": "кілька", "filter_multiple": "Кілька",
"forceRestartRequired": "перезапустіть, щоб застосувати зміни… закрийте повідомлення, щоб перезапустити", "forceRestartRequired": "Перезапустіть, щоб застосувати зміни… закрийте повідомлення, щоб перезапустити",
"forward": "уперед", "forward": "Уперед",
"gap": "прогалина", "gap": "Прогалина",
"grouping": "групування", "grouping": "Групування",
"home": "додому", "home": "Додому",
"increase": "збільшити", "increase": "Збільшити",
"left": "ліво", "left": "Ліво",
"limit": "ліміт", "limit": "Ліміт",
"manage": "управління", "manage": "Управління",
"maximize": "максимізувати", "maximize": "Максимізувати",
"menu": "меню", "menu": "Меню",
"minimize": "мінімізувати", "minimize": "Мінімізувати",
"modified": "відредаговано", "modified": "Відредаговано",
"mbid": "MusicBrainz ID", "mbid": "MusicBrainz ID",
"mood": "настрій", "mood": "Настрій",
"name": "назва", "name": "Назва",
"no": "ні", "no": "Ні",
"none": "жоден", "none": "Жоден",
"noResultsFromQuery": "запит не дав результатів", "noResultsFromQuery": "Запит не дав результатів",
"noFilters": "фільтри не налаштовані", "noFilters": "Фільтри не налаштовані",
"note": "примітка", "note": "Примітка",
"ok": "ок", "ok": "Ок",
"owner": "власник", "owner": "Власник",
"path": "шлях", "path": "Шлях",
"playerMustBePaused": "плеєр повинен бути призупинений", "playerMustBePaused": "Плеєр повинен бути призупинений",
"preview": "перегляд", "preview": "Перегляд",
"previousSong": "минулий $t(entity.track, {\"count\": 1})", "previousSong": "Минулий $t(entity.track, {\"count\": 1})",
"private": "приватний", "private": "Приватний",
"public": "публічний", "public": "Публічний",
"quit": "вийти", "quit": "Вийти",
"random": "випадково", "random": "Випадково",
"rating": "рейтинг", "rating": "Рейтинг",
"retry": "повторити спробу", "retry": "Повторити спробу",
"recordLabel": "лейбл звукозапису", "recordLabel": "Лейбл звукозапису",
"releaseType": "тип випуску", "releaseType": "Тип випуску",
"refresh": "оновити", "refresh": "Оновити",
"reload": "перезавантажити", "reload": "Перезавантажити",
"rename": "перейменувати", "rename": "Перейменувати",
"reset": "скинути", "reset": "Скинути",
"resetToDefault": "скинути до заводських налаштувань", "resetToDefault": "Скинути до заводських налаштувань",
"restartRequired": "необхідний перезапуск", "restartRequired": "Необхідний перезапуск",
"right": "право", "right": "Право",
"clean": "чистo", "clean": "Чистo",
"sampleRate": "частота дискретизації", "sampleRate": "Частота дискретизації",
"save": "зберегти", "save": "Зберегти",
"saveAndReplace": "зберегти та замінити", "saveAndReplace": "Зберегти та замінити",
"saveAs": "зберегти як", "saveAs": "Зберегти як",
"search": "пошук", "search": "Пошук",
"setting_one": "налаштування", "setting_one": "Налаштування",
"setting_few": "налаштування", "setting_few": "налаштування",
"setting_many": "налаштувань", "setting_many": "налаштувань",
"slower": "повільніше", "slower": "Повільніше",
"share": "поділитися", "share": "Поділитися",
"size": "розмір", "size": "Розмір",
"sort": "впорядкувати", "sort": "Впорядкувати",
"sortOrder": "порядок", "sortOrder": "Порядок",
"tags": "теги", "tags": "Теги",
"title": "назва", "title": "Назва",
"trackNumber": "трек", "trackNumber": "Трек",
"trackGain": "підсилення треку", "trackGain": "Підсилення треку",
"trackPeak": "піковий рівень треку", "trackPeak": "Піковий рівень треку",
"translation": "переклад", "translation": "Переклад",
"unknown": "невідомий", "unknown": "Невідомий",
"version": "версія", "version": "Версія",
"year": "рік", "year": "Рік",
"yes": "так", "yes": "Так",
"explicit": "Експліцитний зміст", "explicit": "Експліцитний зміст",
"gridRows": "рядки сітки", "gridRows": "Рядки сітки",
"tableColumns": "стовпці таблиці", "tableColumns": "Стовпці таблиці",
"itemsMore": "{{count}} більше" "itemsMore": "{{count}} більше",
"numberOfResults": "{{numberOfResults}} результатів",
"newVersionAvailable": "Доступна нова версія"
}, },
"entity": { "entity": {
"album_one": "альбом", "album_one": "Альбом",
"album_few": "альбоми", "album_few": "альбоми",
"album_many": "альбомів", "album_many": "альбомів",
"albumArtist_one": "виконавець альбому", "albumArtist_one": "Виконавець альбому",
"albumArtist_few": "виконавці альбому", "albumArtist_few": "виконавці альбому",
"albumArtist_many": "виконавців альбому", "albumArtist_many": "виконавців альбому",
"albumArtistCount_one": "{{count}} виконавець альбому", "albumArtistCount_one": "{{count}} виконавець альбому",
@@ -179,34 +184,34 @@
"albumWithCount_one": "{{count}} альбом", "albumWithCount_one": "{{count}} альбом",
"albumWithCount_few": "{{count}} альбоми", "albumWithCount_few": "{{count}} альбоми",
"albumWithCount_many": "{{count}} альбомів", "albumWithCount_many": "{{count}} альбомів",
"radioStation_one": "радіостанція", "radioStation_one": "Радіостанція",
"radioStation_few": "радіостанції", "radioStation_few": "радіостанції",
"radioStation_many": "радіостанцій", "radioStation_many": "радіостанцій",
"radioStationWithCount_one": "{{count}} радіостанція", "radioStationWithCount_one": "{{count}} радіостанція",
"radioStationWithCount_few": "{{count}} радіостанції", "radioStationWithCount_few": "{{count}} радіостанції",
"radioStationWithCount_many": "{{count}} радіостанцій", "radioStationWithCount_many": "{{count}} радіостанцій",
"artist_one": "виконавець", "artist_one": "Виконавець",
"artist_few": "виконавці", "artist_few": "виконавці",
"artist_many": "виконавців", "artist_many": "виконавців",
"artistWithCount_one": "{{count}} виконавець", "artistWithCount_one": "{{count}} виконавець",
"artistWithCount_few": "{{count}} виконавці", "artistWithCount_few": "{{count}} виконавці",
"artistWithCount_many": "{{count}} виконавців", "artistWithCount_many": "{{count}} виконавців",
"favorite_one": "улюблений", "favorite_one": "Улюблений",
"favorite_few": "улюблені", "favorite_few": "улюблені",
"favorite_many": "улюблених", "favorite_many": "улюблених",
"folder_one": "папка", "folder_one": "Папка",
"folder_few": "папки", "folder_few": "папки",
"folder_many": "папок", "folder_many": "папок",
"folderWithCount_one": "{{count}} папка", "folderWithCount_one": "{{count}} папка",
"folderWithCount_few": "{{count}} папки", "folderWithCount_few": "{{count}} папки",
"folderWithCount_many": "{{count}} папок", "folderWithCount_many": "{{count}} папок",
"genre_one": "жанр", "genre_one": "Жанр",
"genre_few": "жанри", "genre_few": "жанри",
"genre_many": "жанрів", "genre_many": "жанрів",
"genreWithCount_one": "{{count}} жанр", "genreWithCount_one": "{{count}} жанр",
"genreWithCount_few": "{{count}} жанри", "genreWithCount_few": "{{count}} жанри",
"genreWithCount_many": "{{count}} жанрів", "genreWithCount_many": "{{count}} жанрів",
"playlist_one": "плейлист", "playlist_one": "Плейлист",
"playlist_few": "плейлисти", "playlist_few": "плейлисти",
"playlist_many": "плейлистів", "playlist_many": "плейлистів",
"play_one": "{{count}} відтворення", "play_one": "{{count}} відтворення",
@@ -215,11 +220,11 @@
"playlistWithCount_one": "{{count}} плейлист", "playlistWithCount_one": "{{count}} плейлист",
"playlistWithCount_few": "{{count}} плейлисти", "playlistWithCount_few": "{{count}} плейлисти",
"playlistWithCount_many": "{{count}} плейлистів", "playlistWithCount_many": "{{count}} плейлистів",
"smartPlaylist": "розумний $t(entity.playlist, {\"count\": 1})", "smartPlaylist": "Розумний $t(entity.playlist, {\"count\": 1})",
"track_one": "трек", "track_one": "Трек",
"track_few": "треки", "track_few": "треки",
"track_many": "треків", "track_many": "треків",
"song_one": "пісня", "song_one": "Пісня",
"song_few": "пісні", "song_few": "пісні",
"song_many": "пісень", "song_many": "пісень",
"trackWithCount_one": "{{count}} трек", "trackWithCount_one": "{{count}} трек",
@@ -227,257 +232,266 @@
"trackWithCount_many": "{{count}} треків" "trackWithCount_many": "{{count}} треків"
}, },
"error": { "error": {
"apiRouteError": "неможливо виконати запит", "apiRouteError": "Неможливо виконати запит",
"audioDeviceFetchError": "сталася помилка під час спроби отримати аудіопристрої", "audioDeviceFetchError": "Сталася помилка під час спроби отримати аудіопристрої",
"authenticationFailed": "аутентифікація не вдалася", "authenticationFailed": "Аутентифікація не вдалася",
"badAlbum": "ви бачите цю сторінку, тому що ця пісня не входить до альбому. найімовірніше, ця проблема виникає, якщо у верхньому рівні вашої музичної папки знаходиться пісня. Jellyfin групує треки тільки в тому випадку, якщо вони знаходяться в папці", "badAlbum": "Ви бачите цю сторінку, тому що ця пісня не входить до альбому. найімовірніше, ця проблема виникає, якщо у верхньому рівні вашої музичної папки знаходиться пісня. Jellyfin групує треки тільки в тому випадку, якщо вони знаходяться в папці",
"badValue": "недійсний параметр \"{{value}}\". це значення більше не існує", "badValue": "Недійсний параметр \"{{value}}\". це значення більше не існує",
"credentialsRequired": "необхідні дані для входу", "credentialsRequired": "Необхідні дані для входу",
"endpointNotImplementedError": "кінцева точка {{endpoint}} не реалізована для {{serverType}}", "endpointNotImplementedError": "Кінцева точка {{endpoint}} не реалізована для {{serverType}}",
"genericError": "сталася помилка", "genericError": "Сталася помилка",
"invalidServer": "недійсний сервер", "invalidServer": "Недійсний сервер",
"localFontAccessDenied": "відмова в доступі до локальних шрифтів", "localFontAccessDenied": "Відмова в доступі до локальних шрифтів",
"loginRateError": "занадто багато спроб входу, спробуйте ще раз через кілька секунд", "loginRateError": "Занадто багато спроб входу, спробуйте ще раз через кілька секунд",
"mpvRequired": "необхідний MPV", "mpvRequired": "Необхідний MPV",
"multipleServerSaveQueueError": "у черзі відтворення є одна або кілька пісень, які не належать до поточного сервера. це не підтримується", "multipleServerSaveQueueError": "У черзі відтворення є одна або кілька пісень, які не належать до поточного сервера. це не підтримується",
"networkError": "сталася мережева помилка", "networkError": "Сталася мережева помилка",
"noNetwork": "сервер недоступний", "noNetwork": "Сервер недоступний",
"noNetworkDescription": "не вдалося підключитися до цього сервера", "noNetworkDescription": "Не вдалося підключитися до цього сервера",
"notificationDenied": "дозвіл на сповіщення було відхилено. це налаштування не має впливу", "notificationDenied": "Дозвіл на сповіщення було відхилено. це налаштування не має впливу",
"openError": "не вдалося відкрити файл", "openError": "Не вдалося відкрити файл",
"playbackError": "сталася помилка під час спроби відтворити медіафайл", "playbackError": "Сталася помилка під час спроби відтворити медіафайл",
"remoteDisableError": "сталася помилка під час спроби $t(common.disable) віддаленого сервера", "remoteDisableError": "Сталася помилка під час спроби $t(common.disable) віддаленого сервера",
"remoteEnableError": "сталася помилка під час спроби $t(common.enable) віддаленого сервера", "remoteEnableError": "Сталася помилка під час спроби $t(common.enable) віддаленого сервера",
"remotePortError": "сталася помилка під час спроби налаштувати порт віддаленого сервера", "remotePortError": "Сталася помилка під час спроби налаштувати порт віддаленого сервера",
"remotePortWarning": "перезапустіть сервер щоб застосувати новий порт", "remotePortWarning": "Перезапустіть сервер щоб застосувати новий порт",
"saveQueueFailed": "не вдалося зберегти чергу", "saveQueueFailed": "Не вдалося зберегти чергу",
"serverNotSelectedError": "не вибрано жодного сервера", "serverNotSelectedError": "Не вибрано жодного сервера",
"serverRequired": "потрібен сервер", "serverRequired": "Потрібен сервер",
"sessionExpiredError": "ваша сесія закінчилася", "sessionExpiredError": "Ваша сесія закінчилася",
"systemFontError": "сталася помилка під час спроби отримати системні шрифти", "systemFontError": "Сталася помилка під час спроби отримати системні шрифти",
"settingsSyncError": "виявлено розбіжності між налаштуваннями в рендерері та основним процесом. перезапустіть програму, щоб застосувати зміни" "settingsSyncError": "Виявлено розбіжності між налаштуваннями в рендерері та основним процесом. перезапустіть програму, щоб застосувати зміни",
"invalidJson": "Недійсний JSON",
"playbackPausedDueToError": "Відтворення було призупинено через помилку"
}, },
"filter": { "filter": {
"album": "$t(entity.album, {\"count\": 1})", "album": "$t(entity.album, {\"count\": 1})",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})", "albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"albumCount": "кількість $t(entity.album, {\"count\": 2})", "albumCount": "Кількість $t(entity.album, {\"count\": 2})",
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"biography": "біографія", "biography": "Біографія",
"bitrate": "бітрейт", "bitrate": "Бітрейт",
"bpm": "уд/хв", "bpm": "Уд/хв",
"channels": "$t(common.channel, {\"count\": 2})", "channels": "$t(common.channel, {\"count\": 2})",
"comment": "коментар", "comment": "Коментар",
"communityRating": "рейтинг спільноти", "communityRating": "Рейтинг спільноти",
"criticRating": "рейтинг критиків", "criticRating": "Рейтинг критиків",
"dateAdded": "дата додавання", "dateAdded": "Дата додавання",
"disc": "диск", "disc": "Диск",
"duration": "тривалість", "duration": "Тривалість",
"favorited": "улюблене", "favorited": "Улюблене",
"fromYear": "з року", "fromYear": "З року",
"genre": "$t(entity.genre, {\"count\": 1})", "genre": "$t(entity.genre, {\"count\": 1})",
"id": "id", "id": "Id",
"isCompilation": "є компіляцією", "isCompilation": "Є компіляцією",
"isFavorited": "є улюбленим", "isFavorited": "Є улюбленим",
"isPublic": "є публічним", "isPublic": "Є публічним",
"isRated": "є оціненим", "isRated": "Є оціненим",
"isRecentlyPlayed": "нещодавно відтворено", "isRecentlyPlayed": "Нещодавно відтворено",
"lastPlayed": "нещодавно відтворені", "lastPlayed": "Останнє відтворене",
"mostPlayed": "найбільш відтворювані", "mostPlayed": "Найбільш відтворювані",
"name": "назва", "name": "Назва",
"note": "примітка", "note": "Примітка",
"owner": "$t(common.owner)", "owner": "$t(common.owner)",
"path": "шлях", "path": "Шлях",
"playCount": "кількість відтворень", "playCount": "Кількість відтворень",
"random": "випадково", "random": "Випадково",
"rating": "рейтинг", "rating": "Рейтинг",
"recentlyAdded": "нещодавно додано", "recentlyAdded": "Нещодавно додано",
"recentlyPlayed": "нещодавно відтворено", "recentlyPlayed": "Нещодавно відтворено",
"recentlyUpdated": "нещодавно оновлено", "recentlyUpdated": "Нещодавно оновлено",
"releaseDate": "дата випуску", "releaseDate": "Дата випуску",
"releaseYear": "рік випуску", "releaseYear": "Рік випуску",
"search": "шукати", "search": "Шукати",
"songCount": "кількість пісень", "songCount": "Кількість пісень",
"sortName": "сортування за назвою", "sortName": "Сортування за назвою",
"title": "назва", "title": "Назва",
"toYear": "до року", "toYear": "До року",
"trackNumber": "трек", "trackNumber": "Трек",
"explicitStatus": "$t(common.explicitStatus)" "explicitStatus": "$t(common.explicitStatus)",
"matchAnd": "І",
"matchOr": "Або"
}, },
"datetime": { "datetime": {
"minuteShort": "хв.", "minuteShort": "Хв.",
"secondShort": "сек.", "secondShort": "Сек.",
"hourShort": "год", "hourShort": "Год",
"dayShort": "дн." "dayShort": "Дн."
}, },
"filterOperator": { "filterOperator": {
"after": "є після", "after": "Є після",
"afterDate": "після (дата)", "afterDate": "Після (дата)",
"before": "є перед", "before": "Є перед",
"beforeDate": "є перед (дата)", "beforeDate": "Є перед (дата)",
"contains": "містить", "contains": "Містить",
"endsWith": "закінчується на", "endsWith": "Закінчується на",
"inPlaylist": "є в", "inPlaylist": "Є в",
"inTheLast": "є в останньому", "inTheLast": "Є в останньому",
"inTheRange": "є в межах", "inTheRange": "Є в межах",
"inTheRangeDate": "є в межах (дата)", "inTheRangeDate": "Є в межах (дата)",
"is": "є", "is": "Є",
"isNot": "не є", "isNot": "Не є",
"isGreaterThan": "більше ніж", "isGreaterThan": "Більше ніж",
"isLessThan": "менше ніж", "isLessThan": "Менше ніж",
"matchesRegex": "відповідає регулярному виразу", "matchesRegex": "Відповідає регулярному виразу",
"notContains": "не містить", "notContains": "Не містить",
"notInPlaylist": "немає в", "notInPlaylist": "Немає в",
"notInTheLast": "не є в останньому", "notInTheLast": "Не є в останньому",
"startsWith": "починається з" "startsWith": "Починається з"
}, },
"form": { "form": {
"addServer": { "addServer": {
"error_savePassword": "сталася помилка під час спроби зберегти пароль", "error_savePassword": "Сталася помилка під час спроби зберегти пароль",
"ignoreCors": "ігнорувати cors ($t(common.restartRequired))", "ignoreCors": "Ігнорувати cors ($t(common.restartRequired))",
"ignoreSsl": "ігнорувати ssl ($t(common.restartRequired)}", "ignoreSsl": "Ігнорувати ssl ($t(common.restartRequired)}",
"input_legacyAuthentication": "увімкнути застарілу автентифікацію", "input_legacyAuthentication": "Увімкнути застарілу автентифікацію",
"input_name": "назва сервера", "input_name": "Назва сервера",
"input_password": "пароль", "input_password": "Пароль",
"input_preferInstantMix": "віддавати перевагу миттєвому міксу", "input_preferInstantMix": "Віддавати перевагу миттєвому міксу",
"input_preferInstantMixDescription": "використовувати тільки миттєвий мікс щоб отримати подібні пісні. корисно, коли у вас є плагіни, які змінюють цю поведінку", "input_preferInstantMixDescription": "Використовувати тільки миттєвий мікс щоб отримати подібні пісні. корисно, коли у вас є плагіни, які змінюють цю поведінку",
"input_preferRemoteUrl": "віддавати перевагу публічній URL-адресі", "input_preferRemoteUrl": "Віддавати перевагу публічній URL-адресі",
"input_remoteUrl": "публічна URL-адреса", "input_remoteUrl": "Публічна URL-адреса",
"input_remoteUrlPlaceholder": "опціонально: публічна URL-адреса для зовнішніх функцій", "input_remoteUrlPlaceholder": "Опціонально: публічна URL-адреса для зовнішніх функцій",
"input_savePassword": "зберегти пароль", "input_savePassword": "Зберегти пароль",
"input_url": "URL-адреса", "input_url": "URL-адреса",
"input_username": "Ім'я користувача", "input_username": "Ім'я користувача",
"success": "сервер додано успішно", "success": "Сервер додано успішно",
"title": "додати сервер" "title": "Додати сервер"
}, },
"largeFetchConfirmation": { "largeFetchConfirmation": {
"title": "додати елементи до черги", "title": "Додати елементи до черги",
"description": "Ця дія додасть усі елементи в поточний відфільтрований перегляд" "description": "Ця дія додасть усі елементи в поточний відфільтрований перегляд"
}, },
"addToPlaylist": { "addToPlaylist": {
"create": "створити $t(entity.playlist, {\"count\": 1}) {{playlist}}", "create": "Створити $t(entity.playlist, {\"count\": 1}) {{playlist}}",
"input_playlists": "$t(entity.playlist, {\"count\": 2})", "input_playlists": "$t(entity.playlist, {\"count\": 2})",
"input_skipDuplicates": "пропустити дублікати", "input_skipDuplicates": "Пропустити дублікати",
"searchOrCreate": "шукайте $t(entity.playlist, {\"count\": 2}) або пишіть, щоб створити новий", "searchOrCreate": "Шукайте $t(entity.playlist, {\"count\": 2}) або пишіть, щоб створити новий",
"success": "додано $t(entity.trackWithCount, {\"count\": {{message}} }) до $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })", "success": "Додано $t(entity.trackWithCount, {\"count\": {{message}} }) до $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "додати до $t(entity.playlist, {\"count\": 1})" "title": "Додати до $t(entity.playlist, {\"count\": 1})"
}, },
"createPlaylist": { "createPlaylist": {
"input_description": "$t(common.description)", "input_description": "$t(common.description)",
"input_name": "$t(common.name)", "input_name": "$t(common.name)",
"input_owner": "$t(common.owner)", "input_owner": "$t(common.owner)",
"input_public": "публічний", "input_public": "Публічний",
"success": "$t(entity.playlist, {\"count\": 1}) стрворено успішно", "success": "$t(entity.playlist, {\"count\": 1}) стрворено успішно",
"title": "створити $t(entity.playlist, {\"count\": 1})" "title": "Створити $t(entity.playlist, {\"count\": 1})"
}, },
"createRadioStation": { "createRadioStation": {
"success": "радіостанція створена успішно", "success": "Радіостанція створена успішно",
"title": "створити радіостанцію", "title": "Створити радіостанцію",
"input_homepageUrl": "адреса домашньої сторінки", "input_homepageUrl": "Адреса домашньої сторінки",
"input_name": "назва", "input_name": "Назва",
"input_streamUrl": "URL-адреса потоку" "input_streamUrl": "URL-адреса потоку"
}, },
"deletePlaylist": { "deletePlaylist": {
"input_confirm": "введіть ім'я $t(entity.playlist, {\"count\": 1}) для підтвердження", "input_confirm": "Введіть ім'я $t(entity.playlist, {\"count\": 1}) для підтвердження",
"success": "$t(entity.playlist, {\"count\": 1}) успішно видалено", "success": "$t(entity.playlist, {\"count\": 1}) успішно видалено",
"title": "видалити $t(entity.playlist, {\"count\": 1})" "title": "Видалити $t(entity.playlist, {\"count\": 1})"
}, },
"editPlaylist": { "editPlaylist": {
"publicJellyfinNote": "Jellyfin з якоїсь причини не показує, чи є плейлист публічним чи ні. Якщо ви хочете, щоб він залишався публічним, виберіть варіант нижче", "publicJellyfinNote": "Jellyfin з якоїсь причини не показує, чи є плейлист публічним чи ні. Якщо ви хочете, щоб він залишався публічним, виберіть варіант нижче",
"success": "$t(entity.playlist, {\"count\": 1}) успішно оновлено", "success": "$t(entity.playlist, {\"count\": 1}) успішно оновлено",
"title": "змінити $t(entity.playlist, {\"count\": 1})" "title": "Змінити $t(entity.playlist, {\"count\": 1})"
}, },
"lyricsExport": { "lyricsExport": {
"export": "експортувати тексти пісень", "export": "Експортувати тексти пісень",
"input_synced": "експортувати синхронізовані тексти пісень", "input_synced": "Експортувати синхронізовані тексти пісень",
"input_offset": "$t(setting.lyricOffset)" "input_offset": "$t(setting.lyricOffset)"
}, },
"lyricSearch": { "lyricSearch": {
"input_artist": "$t(entity.artist, {\"count\": 1})", "input_artist": "$t(entity.artist, {\"count\": 1})",
"input_name": "$t(common.name)", "input_name": "$t(common.name)",
"title": "шукати тексти пісень" "title": "Шукати тексти пісень"
}, },
"queryEditor": { "queryEditor": {
"title": "редактор запитів", "title": "Редактор запитів",
"input_optionMatchAll": "збіг за всіма", "input_optionMatchAll": "Збіг за всіма",
"input_optionMatchAny": "збіг за будь-яким", "input_optionMatchAny": "Збіг за будь-яким",
"addRuleGroup": "додати групу правил", "addRuleGroup": "Додати групу правил",
"removeRuleGroup": "видалити групу правил", "removeRuleGroup": "Видалити групу правил",
"resetToDefault": "скинути до заводських налаштувань", "resetToDefault": "Скинути до заводських налаштувань",
"clearFilters": "очистити фільтри" "clearFilters": "Очистити фільтри"
}, },
"saveQueue": { "saveQueue": {
"success": "черга відтворення збережена на сервері" "success": "Черга відтворення збережена на сервері"
}, },
"shareItem": { "shareItem": {
"allowDownloading": "дозволити завантаження", "allowDownloading": "Дозволити завантаження",
"description": "опис", "description": "Опис",
"setExpiration": "встановити термін дії", "setExpiration": "Встановити термін дії",
"success": "посилання для спільного використання скопійовано в буфер обміну (натисніть тут, щоб відкрити)", "success": "Посилання для спільного використання скопійовано в буфер обміну (натисніть тут, щоб відкрити)",
"expireInvalid": "термін дії повинен бути в майбутньому", "expireInvalid": "Термін дії повинен бути в майбутньому",
"createFailed": "не вдалося створити спільний доступ (чи ввімкнено спільний доступ?)" "createFailed": "Не вдалося створити спільний доступ (чи ввімкнено спільний доступ?)",
"copyToClipboard": "Скопіювати до буфера обміну: Ctrl+C, enter",
"successMustClick": "Посилання успішно створено, натисніть сюди, щоб відкрити"
}, },
"shuffleAll": { "shuffleAll": {
"title": "відтворити випадково", "title": "Відтворити випадково",
"input_genre": "$t(entity.genre, {\"count\": 1})", "input_genre": "$t(entity.genre, {\"count\": 1})",
"input_limit": "скільки пісень?", "input_limit": "Скільки пісень?",
"input_minYear": "від року", "input_minYear": "Від року",
"input_maxYear": "до року", "input_maxYear": "До року",
"input_played": "відтворити фільтр", "input_played": "Відтворити фільтр",
"input_played_optionAll": "всі треки", "input_played_optionAll": "Всі треки",
"input_played_optionUnplayed": "тільки не відтворені треки", "input_played_optionUnplayed": "Тільки не відтворені треки",
"input_played_optionPlayed": "тільки відтворені треки" "input_played_optionPlayed": "Тільки відтворені треки"
}, },
"updateServer": { "updateServer": {
"success": "сервер успішно оновлено", "success": "Сервер успішно оновлено",
"title": "оновити сервер" "title": "Оновити сервер"
}, },
"privateMode": { "privateMode": {
"enabled": "приватний режим увімкнено, стан відтворення тепер приховано від зовнішніх інтеграцій", "enabled": "Приватний режим увімкнено, стан відтворення тепер приховано від зовнішніх інтеграцій",
"disabled": "приватний режим вимкнено, стан відтворення тепер видно для увімкнених зовнішніх інтеграцій", "disabled": "Приватний режим вимкнено, стан відтворення тепер видно для увімкнених зовнішніх інтеграцій",
"title": "приватний режим" "title": "Приватний режим"
},
"editRadioStation": {
"success": "Радіо станція успішно оновлена"
} }
}, },
"player": { "player": {
"skip": "пропустити" "skip": "Пропустити"
}, },
"page": { "page": {
"albumArtistDetail": { "albumArtistDetail": {
"about": "Про {{artist}}", "about": "Про {{artist}}",
"appearsOn": "з'являється на", "appearsOn": "З'являється на",
"favoriteSongs": "улюблені пісні", "favoriteSongs": "Улюблені пісні",
"groupingTypeAll": "всі типи випуску", "groupingTypeAll": "Всі типи випуску",
"groupingTypePrimary": "основні типи випуску", "groupingTypePrimary": "Основні типи випуску",
"recentReleases": "останні випуски", "recentReleases": "Останні випуски",
"viewDiscography": "переглянути дискографію", "viewDiscography": "Переглянути дискографію",
"relatedArtists": "подібні $t(entity.artist, {\"count\": 2})", "relatedArtists": "Подібні $t(entity.artist, {\"count\": 2})",
"topSongs": "найкращі пісні", "topSongs": "Найкращі пісні",
"topSongsCommunity": "спільнота", "topSongsCommunity": "Спільнота",
"topSongsFrom": "найкращі пісні від {{title}}", "topSongsFrom": "Найкращі пісні від {{title}}",
"topSongsPersonal": "особисте", "topSongsPersonal": "Особисте",
"favoriteSongsFrom": "улюблені пісні від {{title}}", "favoriteSongsFrom": "Улюблені пісні від {{title}}",
"viewAll": "показати все", "viewAll": "Показати все",
"viewAllTracks": "показати усі $t(entity.track, {\"count\": 2})" "viewAllTracks": "Показати усі $t(entity.track, {\"count\": 2})"
}, },
"albumArtistList": { "albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})" "title": "$t(entity.albumArtist, {\"count\": 2})"
}, },
"albumDetail": { "albumDetail": {
"moreFromArtist": "більше від цього $t(entity.artist, {\"count\": 1})", "moreFromArtist": "Більше від цього $t(entity.artist, {\"count\": 1})",
"moreFromGeneric": "більше від {{item}}", "moreFromGeneric": "Більше від {{item}}",
"released": "видано" "released": "Видано"
}, },
"albumList": { "albumList": {
"artistAlbums": "альбоми виконавця {{artist}}", "artistAlbums": "Альбоми виконавця {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album, {\"count\": 2})", "genreAlbums": "\"{{genre}}\" $t(entity.album, {\"count\": 2})",
"title": "$t(entity.album, {\"count\": 2})" "title": "$t(entity.album, {\"count\": 2})"
}, },
"radioList": { "radioList": {
"title": "радіостанції" "title": "Радіостанції"
}, },
"releasenotes": { "releasenotes": {
"commitsSinceStable": "комміти від {{stable}}", "commitsSinceStable": "Комміти від {{stable}}",
"noNewCommits": "немає нових коммітів у цьому періоді", "noNewCommits": "Немає нових коммітів у цьому періоді",
"noStableReleaseToCompare": "немає доступної стабільної версії для порівняння" "noStableReleaseToCompare": "Немає доступної стабільної версії для порівняння"
}, },
"favorites": { "favorites": {
"title": "$t(entity.favorite, {\"count\": 2})" "title": "$t(entity.favorite, {\"count\": 2})"
@@ -487,30 +501,30 @@
"privateMode": "(Приватний режим)" "privateMode": "(Приватний режим)"
}, },
"appMenu": { "appMenu": {
"collapseSidebar": "згорнути бічну панель", "collapseSidebar": "Згорнути бічну панель",
"commandPalette": "відкрити палітру команд", "commandPalette": "Відкрити палітру команд",
"expandSidebar": "розгорнути бічну панель", "expandSidebar": "Розгорнути бічну панель",
"goBack": "повернутися назад", "goBack": "Повернутися назад",
"goForward": "перейти вперед", "goForward": "Перейти вперед",
"manageServers": "управління серверами", "manageServers": "Управління серверами",
"privateModeOff": "вимкнути приватний режим", "privateModeOff": "Вимкнути приватний режим",
"privateModeOn": "увімкнути приватний режим", "privateModeOn": "Увімкнути приватний режим",
"openBrowserDevtools": "відкрити інструменти розробника", "openBrowserDevtools": "Відкрити інструменти розробника",
"quit": "$t(common.quit)", "quit": "$t(common.quit)",
"selectServer": "вибрати сервер", "selectServer": "Вибрати сервер",
"selectMusicFolder": "вибрати папку з музикою", "selectMusicFolder": "Вибрати папку з музикою",
"noMusicFolder": "не вибрано папку з музикою", "noMusicFolder": "Не вибрано папку з музикою",
"multipleMusicFolders": "Вибрано {{count}} папок з музикою", "multipleMusicFolders": "Вибрано {{count}} папок з музикою",
"settings": "$t(common.setting, {\"count\": 2})", "settings": "$t(common.setting, {\"count\": 2})",
"version": "версія {{version}}" "version": "Версія {{version}}"
}, },
"manageServers": { "manageServers": {
"title": "управління серверами", "title": "Управління серверами",
"serverDetails": "інформація про сервер", "serverDetails": "Інформація про сервер",
"url": "URL-адреса", "url": "URL-адреса",
"username": "Ім'я користувача", "username": "Ім'я користувача",
"editServerDetailsTooltip": "редагувати дані сервера", "editServerDetailsTooltip": "Редагувати дані сервера",
"removeServer": "видалити сервер" "removeServer": "Видалити сервер"
}, },
"contextMenu": { "contextMenu": {
"addFavorite": "$t(action.addToFavorites)", "addFavorite": "$t(action.addToFavorites)",
@@ -521,7 +535,7 @@
"createPlaylist": "$t(action.createPlaylist)", "createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)", "deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)", "deselectAll": "$t(action.deselectAll)",
"download": "завантажити", "download": "Завантажити",
"moveItems": "$t(action.moveItems)", "moveItems": "$t(action.moveItems)",
"moveToNext": "$t(action.moveToNext)", "moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)", "moveToBottom": "$t(action.moveToBottom)",
@@ -534,11 +548,57 @@
"removeFromQueue": "$t(action.removeFromQueue)", "removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)", "setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)", "playShuffled": "$t(player.shuffle)",
"shareItem": "поділитися елементом", "shareItem": "Поділитися елементом",
"goTo": "перейти до", "goTo": "Перейти до",
"goToAlbum": "перейти до $t(entity.album, {\"count\": 1})", "goToAlbum": "Перейти до $t(entity.album, {\"count\": 1})",
"goToAlbumArtist": "перейти до $t(entity.albumArtist, {\"count\": 1})", "goToAlbumArtist": "Перейти до $t(entity.albumArtist, {\"count\": 1})",
"showDetails": "отримати інформацію" "showDetails": "Отримати інформацію"
},
"fullscreenPlayer": {
"config": {
"dynamicBackground": "Динамічний фон",
"dynamicImageBlur": "Розмір розмиття зображення",
"dynamicIsImage": "Включити фонове зображення",
"followCurrentLyric": "Слідкувати за поточним рядком",
"lyricAlignment": "Вирівнювання тексту",
"lyricOffset": "Затримка тексту (мс)",
"lyricGap": "Розмір між рядками",
"lyricSize": "Розмір тексту",
"opacity": "Непрозорість",
"showLyricMatch": "Показувати збіг тексту пісень",
"showLyricProvider": "Показувати джерело тексту пісень",
"synchronized": "Синхронізовано",
"unsynchronized": "Несинхронізовано",
"useImageAspectRatio": "Використовувати співвідношення сторін зображення"
},
"lyrics": "Текст пісні",
"related": "Пов'язані",
"upNext": "Далі",
"visualizer": "Візуалізатор",
"noLyrics": "Текст пісні не знайдено"
},
"genreList": {
"showAlbums": "Показати $t(entity.genre, {\"count\": 1}) $t(entity.album, {\"count\": 2})",
"showTracks": "Показати $t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2})",
"title": "$t(entity.genre, {\"count\": 2})"
},
"folderList": {
"title": "$t(entity.folder, {\"count\": 2})"
},
"globalSearch": {
"commands": {
"goToPage": "Перейти до сторінки",
"searchFor": "Шукати на {{query}}",
"serverCommands": "Команди сервера"
},
"title": "Команди"
},
"home": {
"explore": "Дослідити з вашої бібліотеки",
"genres": "$t(entity.genre, {\"count\": 2})",
"mostPlayed": "Найбільш відтворені",
"newlyAdded": "Нещодавно додані релізи",
"recentlyPlayed": "Нещодавно відтворені"
} }
} }
} }
+37 -34
View File
@@ -46,7 +46,7 @@
"common": { "common": {
"increase": "增高", "increase": "增高",
"rating": "评分", "rating": "评分",
"bpm": "bpm", "bpm": "BPM",
"refresh": "刷新", "refresh": "刷新",
"unknown": "未知", "unknown": "未知",
"edit": "编辑", "edit": "编辑",
@@ -208,8 +208,8 @@
"queue_clear": "清空播放队列", "queue_clear": "清空播放队列",
"muted": "已静音", "muted": "已静音",
"unfavorite": "取消收藏", "unfavorite": "取消收藏",
"queue_moveToTop": "将所选项移至部", "queue_moveToTop": "将所选项移至部",
"queue_moveToBottom": "将所选项移至部", "queue_moveToBottom": "将所选项移至部",
"shuffle_off": "禁用随机播放", "shuffle_off": "禁用随机播放",
"addLast": "最后", "addLast": "最后",
"mute": "静音", "mute": "静音",
@@ -240,12 +240,12 @@
"setting": { "setting": {
"crossfadeStyle_description": "选择用于音频播放器的淡入淡出风格", "crossfadeStyle_description": "选择用于音频播放器的淡入淡出风格",
"hotkey_favoriteCurrentSong": "收藏$t(common.currentSong)", "hotkey_favoriteCurrentSong": "收藏$t(common.currentSong)",
"audioExclusiveMode_description": "启用独占输出模式。在此模式下,系统通常被锁定为只有 mpv 能够输出音频", "audioExclusiveMode_description": "启用独占输出模式。在此模式下,系统通常被锁定为只有 MPV 能够输出音频",
"disableLibraryUpdateOnStartup": "禁用启动时查询新版本", "disableLibraryUpdateOnStartup": "禁用启动时查询新版本",
"gaplessAudio": "无缝音频", "gaplessAudio": "无缝音频",
"audioPlayer_description": "选择用于播放的音频播放器", "audioPlayer_description": "选择用于播放的音频播放器",
"globalMediaHotkeys": "全局媒体快捷键", "globalMediaHotkeys": "全局媒体快捷键",
"gaplessAudio_description": "调整 mpv 无缝音频设置", "gaplessAudio_description": "调整 MPV 无缝音频设置",
"followLyric_description": "滚动歌词到当前播放位置", "followLyric_description": "滚动歌词到当前播放位置",
"audioExclusiveMode": "音频独占模式", "audioExclusiveMode": "音频独占模式",
"font": "字体", "font": "字体",
@@ -261,7 +261,7 @@
"followLyric": "跟随当前歌词", "followLyric": "跟随当前歌词",
"crossfadeDuration": "淡入淡出持续时间", "crossfadeDuration": "淡入淡出持续时间",
"audioPlayer": "音频播放器", "audioPlayer": "音频播放器",
"discordApplicationId": "{{discord}} 应用 id", "discordApplicationId": "{{discord}} 应用 ID",
"applicationHotkeys_description": "配置应用快捷键。勾选设为全局快捷键(仅桌面端)", "applicationHotkeys_description": "配置应用快捷键。勾选设为全局快捷键(仅桌面端)",
"customFontPath_description": "设置应用使用的自定义字体路径", "customFontPath_description": "设置应用使用的自定义字体路径",
"gaplessAudio_optionWeak": "弱(推荐)", "gaplessAudio_optionWeak": "弱(推荐)",
@@ -285,7 +285,7 @@
"scrobble": "记录播放信息", "scrobble": "记录播放信息",
"skipDuration_description": "设置每次按下跳过按钮将会跳过的时长", "skipDuration_description": "设置每次按下跳过按钮将会跳过的时长",
"fontType_optionSystem": "系统字体", "fontType_optionSystem": "系统字体",
"mpvExecutablePath_description": "设置 mpv 可执行文件的路径。如果留空,则使用默认路径", "mpvExecutablePath_description": "设置 MPV 可执行文件的路径。如果留空,则使用默认路径",
"sampleRate": "采样率", "sampleRate": "采样率",
"sidePlayQueueStyle_optionAttached": "吸附", "sidePlayQueueStyle_optionAttached": "吸附",
"sidebarConfiguration": "侧边栏设定", "sidebarConfiguration": "侧边栏设定",
@@ -334,7 +334,7 @@
"hotkey_toggleShuffle": "切换随机", "hotkey_toggleShuffle": "切换随机",
"theme": "主题", "theme": "主题",
"playbackStyle_description": "选择音频播放器的播放风格", "playbackStyle_description": "选择音频播放器的播放风格",
"mpvExecutablePath": "mpv 可执行文件路径", "mpvExecutablePath": "MPV 可执行文件路径",
"hotkey_rate2": "评为 2 星", "hotkey_rate2": "评为 2 星",
"playButtonBehavior_description": "设置将歌曲添加到播放队列时播放按钮的默认行为", "playButtonBehavior_description": "设置将歌曲添加到播放队列时播放按钮的默认行为",
"minimumScrobblePercentage_description": "歌曲被记录为已播放所需的最小播放百分比", "minimumScrobblePercentage_description": "歌曲被记录为已播放所需的最小播放百分比",
@@ -344,7 +344,7 @@
"savePlayQueue": "保存播放队列", "savePlayQueue": "保存播放队列",
"minimumScrobbleSeconds_description": "歌曲被记录为已播放所需的最小播放时间", "minimumScrobbleSeconds_description": "歌曲被记录为已播放所需的最小播放时间",
"skipPlaylistPage_description": "打开歌单时,直接查看歌曲列表而非查看默认页面", "skipPlaylistPage_description": "打开歌单时,直接查看歌曲列表而非查看默认页面",
"fontType_description": "内置字体可以选择 feishin 提供的字体之一。系统字体允许您选择操作系统提供的任何字体。自定义选项允许您使用自己的字体", "fontType_description": "内置字体可以选择 Feishin 提供的字体之一。系统字体允许您选择操作系统提供的任何字体。自定义选项允许您使用自己的字体",
"playButtonBehavior": "播放按钮行为", "playButtonBehavior": "播放按钮行为",
"volumeWheelStep": "音量滚轮分度", "volumeWheelStep": "音量滚轮分度",
"sidebarPlaylistList_description": "显示或隐藏侧边栏歌单列表", "sidebarPlaylistList_description": "显示或隐藏侧边栏歌单列表",
@@ -385,20 +385,20 @@
"replayGainClipping_description": "自动降低增益以防止{{ReplayGain}}造成削波", "replayGainClipping_description": "自动降低增益以防止{{ReplayGain}}造成削波",
"replayGainPreamp": "{{ReplayGain}}前置放大(分贝)", "replayGainPreamp": "{{ReplayGain}}前置放大(分贝)",
"replayGainClipping": "{{ReplayGain}}削波", "replayGainClipping": "{{ReplayGain}}削波",
"discordUpdateInterval": "{{discord}} rich presence 更新间隔", "discordUpdateInterval": "{{discord}} Rich Presence 更新间隔",
"discordApplicationId_description": "{{discord}} rich presence 应用 id(默认为 {{defaultId}}", "discordApplicationId_description": "{{discord}} Rich Presence 应用 ID(默认为 {{defaultId}}",
"discordUpdateInterval_description": "更新间隔秒数(至少 15 秒)", "discordUpdateInterval_description": "更新间隔秒数(至少 15 秒)",
"discordRichPresence_description": "在 {{discord}} rich presence 中显示播放状态。图片键为:{{icon}}、{{playing}} 和 {{paused}}", "discordRichPresence_description": "在 {{discord}} Rich Presence 中显示播放状态。图片键为:{{icon}}、{{playing}} 和 {{paused}}",
"accentColor": "强调色", "accentColor": "强调色",
"accentColor_description": "设置应用的强调色", "accentColor_description": "设置应用的强调色",
"replayGainPreamp_description": "调整应用在{{ReplayGain}}值上的前置放大增益", "replayGainPreamp_description": "调整应用在{{ReplayGain}}值上的前置放大增益",
"discordIdleStatus": "显示 rich presence 闲置状态", "discordIdleStatus": "显示 Rich Presence 闲置状态",
"clearCache": "清除浏览器缓存", "clearCache": "清除浏览器缓存",
"buttonSize": "播放器栏按钮大小", "buttonSize": "播放器栏按钮大小",
"buttonSize_description": "播放器栏按钮大小", "buttonSize_description": "播放器栏按钮大小",
"clearCache_description": "feishin的“硬清除”。除了清除feishin的缓存,清空浏览器缓存(保存的图像和其他资源)。服务器凭据和设置会被保留", "clearCache_description": "Feishin的“硬清除”。除了清除Feishin的缓存,清空浏览器缓存(保存的图像和其他资源)。服务器凭据和设置会被保留",
"clearQueryCache_description": "feishin的“软清除”。这将会刷新播放列表、元数据并重置保存的歌词。设置、服务器凭据和缓存图像会被保留", "clearQueryCache_description": "Feishin的“软清除”。这将会刷新播放列表、元数据并重置保存的歌词。设置、服务器凭据和缓存图像会被保留",
"clearQueryCache": "清除feishin缓存", "clearQueryCache": "清除Feishin缓存",
"externalLinks": "显示外部链接", "externalLinks": "显示外部链接",
"externalLinks_description": "允许在艺术家/专辑页面上显示外部链接(Last.fm、MusicBrainz", "externalLinks_description": "允许在艺术家/专辑页面上显示外部链接(Last.fm、MusicBrainz",
"mpvExtraParameters_help": "每行一个", "mpvExtraParameters_help": "每行一个",
@@ -420,10 +420,10 @@
"contextMenu_description": "允许您隐藏右键单击项目时显示在菜单中的项目。未选中的项目将被隐藏", "contextMenu_description": "允许您隐藏右键单击项目时显示在菜单中的项目。未选中的项目将被隐藏",
"customCssEnable_description": "允许编写自定义 css", "customCssEnable_description": "允许编写自定义 css",
"customCss": "自定义css", "customCss": "自定义css",
"customCss_description": "自定义css内容。注意:内容和远程url是不允许的属性。内容预览展示如下。出于安全考虑,您未设置的其它字段也会显示", "customCss_description": "自定义css内容。注意:内容和远程URL是不允许的属性。内容预览展示如下。出于安全考虑,您未设置的其它字段也会显示",
"contextMenu": "上下文菜单(右键单击)配置", "contextMenu": "上下文菜单(右键单击)配置",
"customCssEnable": "启用自定义 css", "customCssEnable": "启用自定义 css",
"customCssNotice": "警告:虽然预设了一些安全限制(不允许 url() 和 content:),但使用自定义 css 仍然会因更改界面而带来风险", "customCssNotice": "警告:虽然预设了一些安全限制(不允许 URL() 和 content:),但使用自定义 css 仍然会因更改界面而带来风险",
"transcode_description": "可以转码为不同的格式", "transcode_description": "可以转码为不同的格式",
"transcodeBitrate": "转码比特率", "transcodeBitrate": "转码比特率",
"albumBackground": "专辑背景图片", "albumBackground": "专辑背景图片",
@@ -451,14 +451,14 @@
"lastfmApiKey": "{{lastfm}} API 密钥", "lastfmApiKey": "{{lastfm}} API 密钥",
"lastfmApiKey_description": "{{lastfm}} 的 API 密钥。封面艺术图所需", "lastfmApiKey_description": "{{lastfm}} 的 API 密钥。封面艺术图所需",
"discordServeImage": "从服务器提供 {{discord}} 图像", "discordServeImage": "从服务器提供 {{discord}} 图像",
"discordServeImage_description": "从服务器本身分享 {{discord}} rich presence 的封面艺术,仅适用于 Jellyfin 和 Navidrome。 {{discord}} 使用机器人来获取图像,因此您的服务器必须可通过公共互联网访问", "discordServeImage_description": "从服务器本身分享 {{discord}} Rich Presence 的封面艺术,仅适用于 Jellyfin 和 Navidrome。 {{discord}} 使用机器人来获取图像,因此您的服务器必须可通过公共互联网访问",
"musicbrainz": "显示 MusicBrainz 链接", "musicbrainz": "显示 MusicBrainz 链接",
"musicbrainz_description": "在艺术家/专辑页面上显示 MusicBrainz 链接(如果存在 MusicBrainz ID", "musicbrainz_description": "在艺术家/专辑页面上显示 MusicBrainz 链接(如果存在 MusicBrainz ID",
"lastfm": "显示 last.fm 链接", "lastfm": "显示 Last.fm 链接",
"lastfm_description": "在艺术家/专辑页面上显示 Last.fm 的链接", "lastfm_description": "在艺术家/专辑页面上显示 Last.fm 的链接",
"preferLocalLyrics_description": "优先选择本地歌词(如有),而不是远程歌词", "preferLocalLyrics_description": "优先选择本地歌词(如有),而不是远程歌词",
"preferLocalLyrics": "首选本地歌词", "preferLocalLyrics": "首选本地歌词",
"discordPausedStatus": "暂停时显示rich presence", "discordPausedStatus": "暂停时显示Rich Presence",
"discordPausedStatus_description": "启用后将在播放器暂停时显示状态", "discordPausedStatus_description": "启用后将在播放器暂停时显示状态",
"preservePitch": "保持音高", "preservePitch": "保持音高",
"preservePitch_description": "在调整播放速度时保持音高", "preservePitch_description": "在调整播放速度时保持音高",
@@ -489,7 +489,7 @@
"exportImportSettings_control_title": "导入/导出设置", "exportImportSettings_control_title": "导入/导出设置",
"exportImportSettings_destructiveWarning": "导入设置会破坏现有设置,请在点击下方“导入”按钮前仔细阅读以上内容!", "exportImportSettings_destructiveWarning": "导入设置会破坏现有设置,请在点击下方“导入”按钮前仔细阅读以上内容!",
"exportImportSettings_importBtn": "导入设置", "exportImportSettings_importBtn": "导入设置",
"exportImportSettings_importModalTitle": "导入 feishin 设置", "exportImportSettings_importModalTitle": "导入 Feishin 设置",
"exportImportSettings_importSuccess": "设置已成功导入!", "exportImportSettings_importSuccess": "设置已成功导入!",
"exportImportSettings_notValidJSON": "传递的文件不是有效的 JSON 文件", "exportImportSettings_notValidJSON": "传递的文件不是有效的 JSON 文件",
"exportImportSettings_offendingKeyError": "\"{{offendingKey}}\" 不正确 - {{reason}}", "exportImportSettings_offendingKeyError": "\"{{offendingKey}}\" 不正确 - {{reason}}",
@@ -510,14 +510,14 @@
"combinedLyricsAndVisualizer": "在播放器侧边栏合并歌词和可视化界面", "combinedLyricsAndVisualizer": "在播放器侧边栏合并歌词和可视化界面",
"autoDJ_description": "自动添加相似歌曲到队列中", "autoDJ_description": "自动添加相似歌曲到队列中",
"notify_description": "歌曲变更时显示通知", "notify_description": "歌曲变更时显示通知",
"mpvExtraParameters_description": "向mpv传递额外参数", "mpvExtraParameters_description": "向MPV传递额外参数",
"audioFadeOnStatusChange": "音频改变时淡入淡出", "audioFadeOnStatusChange": "音频改变时淡入淡出",
"showVisualizerInSidebar": "在播放器侧边栏显示可视化效果", "showVisualizerInSidebar": "在播放器侧边栏显示可视化效果",
"showLyricsInSidebar": "在播放器侧边栏显示歌词", "showLyricsInSidebar": "在播放器侧边栏显示歌词",
"analyticsDisable": "退出使用情况的分析", "analyticsDisable": "退出使用情况的分析",
"artistReleaseTypeConfiguration": "艺术家发行类型设置", "artistReleaseTypeConfiguration": "艺术家发行类型设置",
"useThemeAccentColor": "使用主题强调色", "useThemeAccentColor": "使用主题强调色",
"mpvExtraParameters": "mpv额外参数", "mpvExtraParameters": "MPV额外参数",
"showRatings": "显示星级评分", "showRatings": "显示星级评分",
"followCurrentSong": "跟随当前歌曲", "followCurrentSong": "跟随当前歌曲",
"logLevel": "日志等级", "logLevel": "日志等级",
@@ -552,7 +552,7 @@
"autoDJ_timing": "定时", "autoDJ_timing": "定时",
"autoDJ_timing_description": "自动 DJ 触发前队列中剩余的歌曲数量", "autoDJ_timing_description": "自动 DJ 触发前队列中剩余的歌曲数量",
"crossfadeStyle": "交叉渐变风格", "crossfadeStyle": "交叉渐变风格",
"discordRichPresence": "{{discord}} rich presence", "discordRichPresence": "{{discord}} Rich Presence",
"homeFeatureStyle_description": "控制首页特色轮播图的样式", "homeFeatureStyle_description": "控制首页特色轮播图的样式",
"homeFeatureStyle": "首页特色旋转样式", "homeFeatureStyle": "首页特色旋转样式",
"homeFeatureStyle_optionMultiple": "多样", "homeFeatureStyle_optionMultiple": "多样",
@@ -586,7 +586,7 @@
"automaticUpdates_description": "自动检查并安装更新", "automaticUpdates_description": "自动检查并安装更新",
"releaseChannel_optionAlpha": "alpha(每日构建版)", "releaseChannel_optionAlpha": "alpha(每日构建版)",
"discordStateIcon": "显示播放图标", "discordStateIcon": "显示播放图标",
"discordStateIcon_description": "在 rich presence 状态中显示一个小的播放图标。启用“暂停时显示 rich presence 在线状态”后,暂停图标始终显示", "discordStateIcon_description": "在 Rich Presence 状态中显示一个小的播放图标。启用“暂停时显示 Rich Presence 在线状态”后,暂停图标始终显示",
"blurExplicitImages": "模糊显式图片", "blurExplicitImages": "模糊显式图片",
"blurExplicitImages_description": "专辑和歌曲封面若被标记为不雅内容,将会进行模糊处理", "blurExplicitImages_description": "专辑和歌曲封面若被标记为不雅内容,将会进行模糊处理",
"autosave": "自动保存播放队列", "autosave": "自动保存播放队列",
@@ -673,7 +673,7 @@
"fromYear": "起始年份", "fromYear": "起始年份",
"criticRating": "评论家评分", "criticRating": "评论家评分",
"trackNumber": "曲目", "trackNumber": "曲目",
"bpm": "bpm", "bpm": "BPM",
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"comment": "评论", "comment": "评论",
"isCompilation": "为合辑", "isCompilation": "为合辑",
@@ -687,7 +687,7 @@
"genre": "$t(entity.genre, {\"count\": 1})", "genre": "$t(entity.genre, {\"count\": 1})",
"note": "注释", "note": "注释",
"albumCount": "$t(entity.album, {\"count\": 2})数", "albumCount": "$t(entity.album, {\"count\": 2})数",
"id": "id", "id": "ID",
"disc": "碟片", "disc": "碟片",
"duration": "时长", "duration": "时长",
"album": "$t(entity.album, {\"count\": 1})", "album": "$t(entity.album, {\"count\": 1})",
@@ -925,12 +925,12 @@
"ignoreSsl": "忽略 ssl $t(common.restartRequired)", "ignoreSsl": "忽略 ssl $t(common.restartRequired)",
"ignoreCors": "忽略 cors $t(common.restartRequired)", "ignoreCors": "忽略 cors $t(common.restartRequired)",
"error_savePassword": "保存密码时出现错误", "error_savePassword": "保存密码时出现错误",
"input_url": "url", "input_url": "URL",
"input_preferInstantMixDescription": "仅使用即时混音来获取类似的歌曲。如果您有修改此行为的插件,则很有用", "input_preferInstantMixDescription": "仅使用即时混音来获取类似的歌曲。如果您有修改此行为的插件,则很有用",
"input_preferInstantMix": "首选即时混音", "input_preferInstantMix": "首选即时混音",
"input_preferRemoteUrl": "首选公共 url", "input_preferRemoteUrl": "首选公共 URL",
"input_remoteUrl": "公共 url", "input_remoteUrl": "公共 URL",
"input_remoteUrlPlaceholder": "可选:对外功能的公共 url" "input_remoteUrlPlaceholder": "可选:对外功能的公共 URL"
}, },
"addToPlaylist": { "addToPlaylist": {
"success": "添加$t(entity.trackWithCount, {\"count\": {{message}} })到$t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })", "success": "添加$t(entity.trackWithCount, {\"count\": {{message}} })到$t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -1015,6 +1015,9 @@
"input_played_optionPlayed": "仅已播放的曲目", "input_played_optionPlayed": "仅已播放的曲目",
"input_limit": "有多少首歌?", "input_limit": "有多少首歌?",
"input_played": "播放筛选器" "input_played": "播放筛选器"
},
"editRadioStation": {
"success": "电台更新成功"
} }
}, },
"table": { "table": {
@@ -1063,7 +1066,7 @@
"duration": "$t(common.duration)", "duration": "$t(common.duration)",
"dateAdded": "添加日期", "dateAdded": "添加日期",
"size": "$t(common.size)", "size": "$t(common.size)",
"bpm": "$t(common.bpm)", "bpm": "$t(common.BPM)",
"lastPlayed": "最后播放", "lastPlayed": "最后播放",
"trackNumber": "音轨编号", "trackNumber": "音轨编号",
"rowIndex": "行索引", "rowIndex": "行索引",
@@ -1109,7 +1112,7 @@
"releaseDate": "发布日期", "releaseDate": "发布日期",
"bitrate": "比特率", "bitrate": "比特率",
"title": "标题", "title": "标题",
"bpm": "bpm", "bpm": "BPM",
"dateAdded": "添加日期", "dateAdded": "添加日期",
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"songCount": "$t(entity.track, {\"count\": 2})", "songCount": "$t(entity.track, {\"count\": 2})",
+36 -27
View File
@@ -3,7 +3,7 @@
"backward": "返回", "backward": "返回",
"biography": "簡介", "biography": "簡介",
"bitrate": "位元率", "bitrate": "位元率",
"bpm": "bpm", "bpm": "BPM",
"clear": "清空", "clear": "清空",
"collapse": "折疊", "collapse": "折疊",
"comingSoon": "即將推出…", "comingSoon": "即將推出…",
@@ -233,7 +233,9 @@
"showLyricMatch": "顯示匹配的歌詞", "showLyricMatch": "顯示匹配的歌詞",
"dynamicImageBlur": "圖片模糊大小", "dynamicImageBlur": "圖片模糊大小",
"dynamicIsImage": "啟用背景圖片", "dynamicIsImage": "啟用背景圖片",
"lyricOffset": "歌詞偏移時間 (ms)" "lyricOffset": "歌詞偏移時間 (ms)",
"lyricOpacityNonActive": "非活躍歌詞的不透明度",
"lyricScaleNonActive": "非活躍歌詞的比例"
}, },
"lyrics": "歌詞", "lyrics": "歌詞",
"related": "相關", "related": "相關",
@@ -394,8 +396,8 @@
"next": "下一首", "next": "下一首",
"play": "播放", "play": "播放",
"playbackFetchCancel": "請稍等…關閉通知以取消", "playbackFetchCancel": "請稍等…關閉通知以取消",
"queue_moveToBottom": "使所選置", "queue_moveToBottom": "使所選置",
"queue_moveToTop": "使所選置", "queue_moveToTop": "使所選置",
"playSimilarSongs": "播放相似歌曲", "playSimilarSongs": "播放相似歌曲",
"viewQueue": "檢視佇列", "viewQueue": "檢視佇列",
"addLastShuffled": "新增至尾端 (隨機)", "addLastShuffled": "新增至尾端 (隨機)",
@@ -424,7 +426,7 @@
"hotkey_volumeDown": "音量降低", "hotkey_volumeDown": "音量降低",
"hotkey_volumeMute": "靜音", "hotkey_volumeMute": "靜音",
"minimumScrobblePercentage": "最小紀錄時長(百分比)", "minimumScrobblePercentage": "最小紀錄時長(百分比)",
"minimumScrobblePercentage_description": "歌曲被記錄為已播放(scrobble)所需的最小播放百分比", "minimumScrobblePercentage_description": "歌曲被記錄為已播放(Scrobble)所需的最小播放百分比",
"theme_description": "設定應用程式的主題", "theme_description": "設定應用程式的主題",
"accentColor": "強調色", "accentColor": "強調色",
"accentColor_description": "設定應用程式的強調色", "accentColor_description": "設定應用程式的強調色",
@@ -433,7 +435,7 @@
"audioDevice": "音訊設備", "audioDevice": "音訊設備",
"audioDevice_description": "選擇用於播放的音訊設備", "audioDevice_description": "選擇用於播放的音訊設備",
"audioExclusiveMode": "音訊獨佔模式", "audioExclusiveMode": "音訊獨佔模式",
"audioExclusiveMode_description": "啟用獨佔輸出模式。在此模式下,系統通常被鎖定,只有 mpv 能夠輸出音訊", "audioExclusiveMode_description": "啟用獨佔輸出模式。在此模式下,系統通常被鎖定,只有 MPV 能夠輸出音訊。視覺化音訊截取在此選項啟用時不會作用",
"audioPlayer": "音訊播放器", "audioPlayer": "音訊播放器",
"crossfadeDuration": "淡入淡出持續時間", "crossfadeDuration": "淡入淡出持續時間",
"crossfadeDuration_description": "設定淡入淡出持續時間", "crossfadeDuration_description": "設定淡入淡出持續時間",
@@ -441,12 +443,12 @@
"customFontPath": "自定字體路徑", "customFontPath": "自定字體路徑",
"customFontPath_description": "設定應用程式使用的自定字體路徑", "customFontPath_description": "設定應用程式使用的自定字體路徑",
"disableLibraryUpdateOnStartup": "禁用啟動時檢查新版本", "disableLibraryUpdateOnStartup": "禁用啟動時檢查新版本",
"discordApplicationId": "{{discord}} 應用程式 id", "discordApplicationId": "{{discord}} 應用程式 ID",
"discordApplicationId_description": "{{discord}} rich presence 應用程式 id(預設為 {{defaultId}}", "discordApplicationId_description": "{{discord}} Rich Presence 應用程式 ID(預設為 {{defaultId}}",
"discordIdleStatus": "顯示 rich presence 閒置狀態", "discordIdleStatus": "顯示 Rich Presence 閒置狀態",
"discordIdleStatus_description": "啟用後將會在播放器閒置時更新狀態", "discordIdleStatus_description": "啟用後將會在播放器閒置時更新狀態",
"discordRichPresence_description": "在 {{discord}} rich presence 中顯示播放狀態。圖片鍵為:{{icon}}、{{playing}} 和 {{paused}}", "discordRichPresence_description": "在 {{discord}} Rich Presence 中顯示播放狀態。圖片鍵為:{{icon}}、{{playing}} 和 {{paused}}",
"discordUpdateInterval": "{{discord}} rich presence 更新間隔", "discordUpdateInterval": "{{discord}} Rich Presence 更新間隔",
"discordUpdateInterval_description": "更新間隔秒數(至少 15 秒)", "discordUpdateInterval_description": "更新間隔秒數(至少 15 秒)",
"enableRemote": "啟用遠端控制伺服器", "enableRemote": "啟用遠端控制伺服器",
"enableRemote_description": "啟用遠端控制伺服器,以允許其他設備控制此應用程式", "enableRemote_description": "啟用遠端控制伺服器,以允許其他設備控制此應用程式",
@@ -454,12 +456,12 @@
"followLyric": "跟隨目前歌詞", "followLyric": "跟隨目前歌詞",
"font_description": "設定應用程式使用的字體", "font_description": "設定應用程式使用的字體",
"fontType": "字體類型", "fontType": "字體類型",
"fontType_description": "內建字體可以選擇 feishin 提供的字體之一。系統字體允許您選擇作業系統提供的任何字體。自定選項允許您使用自己的字體", "fontType_description": "內建字體可以選擇 Feishin 提供的字體之一。系統字體允許您選擇作業系統提供的任何字體。自定選項允許您使用自己的字體",
"fontType_optionBuiltIn": "內建字體", "fontType_optionBuiltIn": "內建字體",
"fontType_optionCustom": "自定字體", "fontType_optionCustom": "自定字體",
"fontType_optionSystem": "系統字體", "fontType_optionSystem": "系統字體",
"gaplessAudio": "無間隔音訊", "gaplessAudio": "無間隔音訊",
"gaplessAudio_description": "調整 mpv 無間隔音訊設定", "gaplessAudio_description": "調整 MPV 無間隔音訊設定",
"gaplessAudio_optionWeak": "弱(建議)", "gaplessAudio_optionWeak": "弱(建議)",
"globalMediaHotkeys": "全域媒體快捷鍵", "globalMediaHotkeys": "全域媒體快捷鍵",
"hotkey_browserForward": "瀏覽器往前", "hotkey_browserForward": "瀏覽器往前",
@@ -498,8 +500,8 @@
"minimizeToTray": "最小化到系統匣", "minimizeToTray": "最小化到系統匣",
"minimizeToTray_description": "將應用程式最小化到系統匣", "minimizeToTray_description": "將應用程式最小化到系統匣",
"minimumScrobbleSeconds": "最小紀錄時間(秒)", "minimumScrobbleSeconds": "最小紀錄時間(秒)",
"minimumScrobbleSeconds_description": "歌曲被記錄為已播放(scrobble)所需的最小播放時間", "minimumScrobbleSeconds_description": "歌曲被記錄為已播放(Scrobble)所需的最小播放時間",
"mpvExecutablePath": "mpv 執行檔路徑", "mpvExecutablePath": "MPV 執行檔路徑",
"playbackStyle_optionCrossFade": "淡入淡出", "playbackStyle_optionCrossFade": "淡入淡出",
"playbackStyle_optionNormal": "一般", "playbackStyle_optionNormal": "一般",
"playButtonBehavior": "播放按鈕動作", "playButtonBehavior": "播放按鈕動作",
@@ -561,7 +563,7 @@
"hotkey_favoriteCurrentSong": "收藏 $t(common.currentSong)", "hotkey_favoriteCurrentSong": "收藏 $t(common.currentSong)",
"hotkey_playbackStop": "停止", "hotkey_playbackStop": "停止",
"hotkey_rate0": "清除評分", "hotkey_rate0": "清除評分",
"mpvExecutablePath_description": "設定 mpv 執行檔的路徑。如果留空,則使用預設路徑", "mpvExecutablePath_description": "設定 MPV 執行檔的路徑。如果留空,則使用預設路徑",
"playbackStyle_description": "選擇播放器的播放風格", "playbackStyle_description": "選擇播放器的播放風格",
"playButtonBehavior_optionPlay": "$t(player.play)", "playButtonBehavior_optionPlay": "$t(player.play)",
"remotePassword": "遠端控制伺服器密碼", "remotePassword": "遠端控制伺服器密碼",
@@ -588,10 +590,10 @@
"contextMenu_description": "允許您隱藏在右鍵選單項目時顯示的項目。未選取的項目將被隱藏", "contextMenu_description": "允許您隱藏在右鍵選單項目時顯示的項目。未選取的項目將被隱藏",
"customCssEnable": "啟用自訂CSS", "customCssEnable": "啟用自訂CSS",
"customCssEnable_description": "允許撰寫自訂CSS", "customCssEnable_description": "允許撰寫自訂CSS",
"customCssNotice": "警告:即使已限制某些用法(不允許 url() 和 content:),但使用自訂 CSS 仍然會透過更改介面帶來風險", "customCssNotice": "警告:即使已限制某些用法(不允許 URL() 和 content:),但使用自訂 CSS 仍然會透過更改介面帶來風險",
"customCss": "自訂CSS", "customCss": "自訂CSS",
"customCss_description": "自訂 CSS 內容。注意:內容和遠端 URL 是不允許使用的屬性。您的內容預覽如下所示。由於需要進行清理,因此存在一些您未設定的其他欄位", "customCss_description": "自訂 CSS 內容。注意:內容和遠端 URL 是不允許使用的屬性。您的內容預覽如下所示。由於需要進行清理,因此存在一些您未設定的其他欄位",
"discordPausedStatus": "暫停時顯示 rich presence", "discordPausedStatus": "暫停時顯示 Rich Presence",
"discordPausedStatus_description": "啟用後,播放器暫停時將顯示狀態", "discordPausedStatus_description": "啟用後,播放器暫停時將顯示狀態",
"discordListening": "將狀態設為\"正在聽\"", "discordListening": "將狀態設為\"正在聽\"",
"discordListening_description": "將狀態顯示為\"正在聽\"而不是\"正在玩\"", "discordListening_description": "將狀態顯示為\"正在聽\"而不是\"正在玩\"",
@@ -607,7 +609,7 @@
"homeFeature_description": "控制是否在首頁上顯示大型特色輪播", "homeFeature_description": "控制是否在首頁上顯示大型特色輪播",
"imageAspectRatio": "使用原生封面照長寬比", "imageAspectRatio": "使用原生封面照長寬比",
"imageAspectRatio_description": "如果啟用,封面照將使用其原始長寬比顯示。對於非 1:1 的封面,剩餘空間將為空", "imageAspectRatio_description": "如果啟用,封面照將使用其原始長寬比顯示。對於非 1:1 的封面,剩餘空間將為空",
"lastfm": "顯示 last.fm 連結", "lastfm": "顯示 Last.fm 連結",
"lastfm_description": "在藝人/專輯頁面顯示 Last.fm 連結", "lastfm_description": "在藝人/專輯頁面顯示 Last.fm 連結",
"lastfmApiKey": "{{lastfm}} API金鑰", "lastfmApiKey": "{{lastfm}} API金鑰",
"lastfmApiKey_description": "{{lastfm}}的API金鑰。用於封面照", "lastfmApiKey_description": "{{lastfm}}的API金鑰。用於封面照",
@@ -768,7 +770,7 @@
"automaticUpdates": "自動更新", "automaticUpdates": "自動更新",
"automaticUpdates_description": "自動檢查並安裝更新", "automaticUpdates_description": "自動檢查並安裝更新",
"discordStateIcon": "顯示播放中圖示", "discordStateIcon": "顯示播放中圖示",
"discordStateIcon_description": "在 rich presence 狀態中顯示一個小的播放圖示。啟用「暫停時顯示 rich presence」時,會始終顯示暫停的圖示", "discordStateIcon_description": "在 Rich Presence 狀態中顯示一個小的播放圖示。啟用「暫停時顯示 Rich Presence」時,會始終顯示暫停的圖示",
"useThemePrimaryShade": "套用主題主色調", "useThemePrimaryShade": "套用主題主色調",
"useThemePrimaryShade_description": "使用所選主題中定義的主色調作為主色變體", "useThemePrimaryShade_description": "使用所選主題中定義的主色調作為主色變體",
"primaryShade": "主要色調", "primaryShade": "主要色調",
@@ -792,7 +794,9 @@
"qobuz_description": "在藝術家/專輯頁面上顯示 Qobuz 的連結", "qobuz_description": "在藝術家/專輯頁面上顯示 Qobuz 的連結",
"qobuz": "顯示 Qobuz 連結", "qobuz": "顯示 Qobuz 連結",
"waveformLoadingDelay": "波形載入延遲", "waveformLoadingDelay": "波形載入延遲",
"waveformLoadingDelay_description": "載入波形前的延遲(以秒為單位)。如果您在使用網頁播放器時遇到卡頓,請增加此值。" "waveformLoadingDelay_description": "載入波形前的延遲(以秒為單位)。如果您在使用網頁播放器時遇到卡頓,請增加此值。",
"playerbarWaveformStretch": "波形拉伸",
"playerbarWaveformStretch_description": "拉伸波形來填補可用空間"
}, },
"table": { "table": {
"config": { "config": {
@@ -833,7 +837,7 @@
"album": "$t(entity.album, {\"count\": 1})", "album": "$t(entity.album, {\"count\": 1})",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})", "albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"bpm": "$t(common.bpm)", "bpm": "$t(common.BPM)",
"biography": "$t(common.biography)", "biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)", "bitrate": "$t(common.bitrate)",
"channels": "$t(common.channel, {\"count\": 2})", "channels": "$t(common.channel, {\"count\": 2})",
@@ -892,7 +896,7 @@
"releaseDate": "發布日期", "releaseDate": "發布日期",
"releaseYear": "年份", "releaseYear": "年份",
"genre": "$t(entity.genre, {\"count\": 1})", "genre": "$t(entity.genre, {\"count\": 1})",
"bpm": "bpm", "bpm": "BPM",
"songCount": "$t(entity.track, {\"count\": 2})", "songCount": "$t(entity.track, {\"count\": 2})",
"title": "標題", "title": "標題",
"trackNumber": "曲目編號", "trackNumber": "曲目編號",
@@ -974,7 +978,7 @@
"artist": "$t(entity.artist, {\"count\": 1})", "artist": "$t(entity.artist, {\"count\": 1})",
"biography": "個人簡介", "biography": "個人簡介",
"bitrate": "位元率", "bitrate": "位元率",
"bpm": "bpm", "bpm": "BPM",
"channels": "$t(common.channel, {\"count\": 2})", "channels": "$t(common.channel, {\"count\": 2})",
"comment": "評論", "comment": "評論",
"communityRating": "社群評分", "communityRating": "社群評分",
@@ -982,7 +986,7 @@
"dateAdded": "已新增日期", "dateAdded": "已新增日期",
"disc": "光碟", "disc": "光碟",
"duration": "時長", "duration": "時長",
"id": "id", "id": "ID",
"fromYear": "從年份", "fromYear": "從年份",
"genre": "$t(entity.genre, {\"count\": 1})", "genre": "$t(entity.genre, {\"count\": 1})",
"isCompilation": "為合輯", "isCompilation": "為合輯",
@@ -1023,7 +1027,7 @@
"input_name": "伺服器名稱", "input_name": "伺服器名稱",
"input_password": "密碼", "input_password": "密碼",
"input_savePassword": "儲存密碼", "input_savePassword": "儲存密碼",
"input_url": "url", "input_url": "URL",
"input_username": "使用者名稱", "input_username": "使用者名稱",
"success": "伺服器新增成功", "success": "伺服器新增成功",
"title": "新增伺服器", "title": "新增伺服器",
@@ -1337,6 +1341,11 @@
} }
}, },
"systemAudioCaptureFailed": "無法開始擷取:{{message}}", "systemAudioCaptureFailed": "無法開始擷取:{{message}}",
"systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。" "systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。",
"systemAudioConsentAllow": "允許",
"systemAudioConsentBody": "此視覺化器需要存取系統音訊才能運作",
"systemAudioConsentDecline": "拒絕",
"systemAudioConsentTitle": "允許存取系統音訊?",
"systemAudioExclusiveModeNotSupported": "啟用音訊獨佔模式時,視覺化不可用。 在MPV設定中停用音訊獨佔模式,然後再試一次。"
} }
} }
+30 -3
View File
@@ -1,13 +1,13 @@
import console from 'console'; import console from 'console';
import { app, ipcMain } from 'electron'; import { app, ipcMain } from 'electron';
import { rm } from 'fs/promises'; import { access, rm } from 'fs/promises';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import MpvAPI from 'node-mpv'; import MpvAPI from 'node-mpv';
import { pid } from 'node:process'; import { pid } from 'node:process';
import process from 'process'; import process from 'process';
import { getMainWindow, sendToastToRenderer } from '../../../index'; import { getMainWindow, sendToastToRenderer } from '../../../index';
import { createLog, isWindows } from '../../../utils'; import { createLog, isMacOS, isWindows } from '../../../utils';
import { store } from '../settings'; import { store } from '../settings';
import { PlayerData } from '/@/shared/types/domain-types'; import { PlayerData } from '/@/shared/types/domain-types';
@@ -69,6 +69,7 @@ const mpvLog = (
}; };
const MPV_BINARY_PATH = store.get('mpv_path') as string | undefined; const MPV_BINARY_PATH = store.get('mpv_path') as string | undefined;
const MACOS_MPV_BINARY_PATHS = ['/opt/homebrew/bin/mpv', '/usr/local/bin/mpv'];
const prefetchPlaylistParams = [ const prefetchPlaylistParams = [
'--prefetch-playlist=no', '--prefetch-playlist=no',
@@ -86,12 +87,38 @@ const DEFAULT_MPV_PARAMETERS = (extraParameters?: string[]) => {
return parameters; return parameters;
}; };
const resolveMpvBinaryPath = async (binaryPath?: string) => {
if (binaryPath) {
return binaryPath;
}
if (MPV_BINARY_PATH) {
return MPV_BINARY_PATH;
}
if (!isMacOS()) {
return undefined;
}
for (const candidate of MACOS_MPV_BINARY_PATHS) {
try {
await access(candidate);
return candidate;
} catch {
// Try the next common Homebrew location.
}
}
return undefined;
};
const createMpv = async (data: { const createMpv = async (data: {
binaryPath?: string; binaryPath?: string;
extraParameters?: string[]; extraParameters?: string[];
properties?: Record<string, any>; properties?: Record<string, any>;
}): Promise<MpvAPI> => { }): Promise<MpvAPI> => {
const { binaryPath, extraParameters, properties } = data; const { binaryPath, extraParameters, properties } = data;
const resolvedBinaryPath = await resolveMpvBinaryPath(binaryPath);
const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]); const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]);
@@ -99,7 +126,7 @@ const createMpv = async (data: {
{ {
audio_only: true, audio_only: true,
auto_restart: false, auto_restart: false,
binary: binaryPath || MPV_BINARY_PATH || undefined, binary: resolvedBinaryPath,
socket: socketPath, socket: socketPath,
time_update: 1, time_update: 1,
}, },
+8 -1
View File
@@ -1,2 +1,9 @@
import './core'; import './core';
import(`./${process.platform}`);
if (process.platform === 'linux') {
import('./linux');
} else if (process.platform === 'darwin') {
import('./darwin');
} else if (process.platform === 'win32') {
import('./win32');
}
+1
View File
@@ -0,0 +1 @@
export {};
+11 -4
View File
@@ -734,14 +734,21 @@ async function createWindow(first = true): Promise<void> {
}); });
mainWindow.webContents.session.setDisplayMediaRequestHandler((_request, callback) => { mainWindow.webContents.session.setDisplayMediaRequestHandler((_request, callback) => {
if (!isMacOS()) {
callback({ audio: 'loopback' });
return;
}
desktopCapturer desktopCapturer
.getSources({ types: ['screen'] }) .getSources({ thumbnailSize: { height: 0, width: 0 }, types: ['screen'] })
.then((sources) => { .then((sources) => {
if (sources.length > 0) { const source = sources[0];
callback({ audio: 'loopback', video: sources[0] }); if (!source) {
} else {
callback({}); callback({});
return;
} }
callback({ audio: 'loopback', video: source });
}) })
.catch((err) => { .catch((err) => {
log.warn('desktopCapturer.getSources failed', err); log.warn('desktopCapturer.getSources failed', err);
+2 -1
View File
@@ -1,4 +1,5 @@
import { SetActivity } from '@xhayper/discord-rpc'; import type { SetActivity } from '@xhayper/discord-rpc';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
const initialize = (clientId: string) => { const initialize = (clientId: string) => {
+63 -183
View File
@@ -34,10 +34,8 @@ const apiController = <K extends keyof ControllerEndpoint>(
if (!serverType) { if (!serverType) {
toast.error({ toast.error({
message: i18n.t('error.serverNotSelectedError', { message: i18n.t('error.serverNotSelectedError') as string,
postProcess: 'sentenceCase', title: i18n.t('error.apiRouteError') as string,
}) as string,
title: i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' }) as string,
}); });
throw new Error(`No server selected`); throw new Error(`No server selected`);
} }
@@ -47,13 +45,13 @@ const apiController = <K extends keyof ControllerEndpoint>(
if (typeof controllerFn !== 'function') { if (typeof controllerFn !== 'function') {
toast.error({ toast.error({
message: `Endpoint ${endpoint} is not implemented for ${serverType}`, message: `Endpoint ${endpoint} is not implemented for ${serverType}`,
title: i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' }) as string, title: i18n.t('error.apiRouteError') as string,
}); });
throw new Error( throw new Error(
i18n.t('error.endpointNotImplementedError', { i18n.t('error.endpointNotImplementedError', {
endpoint, endpoint,
postProcess: 'sentenceCase',
serverType, serverType,
}) as string, }) as string,
); );
@@ -92,9 +90,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: addToPlaylist`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: addToPlaylist`,
);
} }
return apiController( return apiController(
@@ -109,9 +105,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: createFavorite`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: createFavorite`,
);
} }
return apiController( return apiController(
@@ -123,9 +117,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: createInternetRadioStation`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: createInternetRadioStation`,
);
} }
return apiController( return apiController(
@@ -137,9 +129,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: createPlaylist`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: createPlaylist`,
);
} }
return apiController( return apiController(
@@ -151,9 +141,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: deleteArtistImage`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deleteArtistImage`,
);
} }
return apiController( return apiController(
@@ -165,9 +153,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: deleteFavorite`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deleteFavorite`,
);
} }
return apiController( return apiController(
@@ -179,9 +165,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: deleteInternetRadioStation`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deleteInternetRadioStation`,
);
} }
return apiController( return apiController(
@@ -193,9 +177,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: deleteInternetRadioStationImage`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deleteInternetRadioStationImage`,
);
} }
return apiController( return apiController(
@@ -207,9 +189,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: deletePlaylist`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deletePlaylist`,
);
} }
return apiController( return apiController(
@@ -221,9 +201,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: deletePlaylistImage`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: deletePlaylistImage`,
);
} }
return apiController( return apiController(
@@ -235,9 +213,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumArtistDetail`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumArtistDetail`,
);
} }
return apiController( return apiController(
@@ -261,9 +237,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumArtistList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumArtistList`,
);
} }
return apiController( return apiController(
@@ -281,9 +255,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumArtistListCount`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumArtistListCount`,
);
} }
return apiController( return apiController(
@@ -301,9 +273,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumDetail`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumDetail`,
);
} }
return apiController( return apiController(
@@ -315,9 +285,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumInfo`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumInfo`,
);
} }
return apiController( return apiController(
@@ -329,9 +297,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumList`,
);
} }
return apiController( return apiController(
@@ -349,9 +315,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumListCount`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumListCount`,
);
} }
return apiController( return apiController(
@@ -369,9 +333,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getAlbumRadio`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getAlbumRadio`,
);
} }
return apiController( return apiController(
@@ -383,9 +345,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getArtistList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getArtistList`,
);
} }
return apiController( return apiController(
@@ -403,9 +363,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getArtistListCount`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getArtistListCount`,
);
} }
return apiController( return apiController(
@@ -423,9 +381,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getArtistRadio`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getArtistRadio`,
);
} }
return apiController( return apiController(
@@ -437,9 +393,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getDownloadUrl`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getDownloadUrl`,
);
} }
return apiController( return apiController(
@@ -451,9 +405,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getFolder`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getFolder`,
);
} }
return apiController( return apiController(
@@ -471,9 +423,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getGenreList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getGenreList`,
);
} }
return apiController( return apiController(
@@ -529,9 +479,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getInternetRadioStations`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getInternetRadioStations`,
);
} }
return apiController( return apiController(
'getInternetRadioStations', 'getInternetRadioStations',
@@ -542,9 +490,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getLyrics`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getLyrics`,
);
} }
return apiController( return apiController(
@@ -556,9 +502,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getMusicFolderList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getMusicFolderList`,
);
} }
return apiController( return apiController(
@@ -570,9 +514,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getPlaylistDetail`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistDetail`,
);
} }
return apiController( return apiController(
@@ -584,9 +526,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getPlaylistList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistList`,
);
} }
return apiController( return apiController(
@@ -598,9 +538,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getPlaylistListCount`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistListCount`,
);
} }
return apiController( return apiController(
@@ -612,9 +550,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getPlaylistSongList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlaylistSongList`,
);
} }
return apiController( return apiController(
@@ -626,9 +562,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getPlayQueue`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getPlayQueue`,
);
} }
return apiController( return apiController(
@@ -640,9 +574,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getRandomSongList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getRandomSongList`,
);
} }
return apiController( return apiController(
@@ -660,9 +592,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getRoles`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getRoles`,
);
} }
return apiController( return apiController(
@@ -674,9 +604,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getServerInfo`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getServerInfo`,
);
} }
return apiController( return apiController(
@@ -688,9 +616,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getSimilarSongs`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSimilarSongs`,
);
} }
return apiController( return apiController(
@@ -708,9 +634,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getSongDetail`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSongDetail`,
);
} }
return apiController( return apiController(
@@ -722,9 +646,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getSongList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSongList`,
);
} }
return apiController( return apiController(
@@ -742,9 +664,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getSongListCount`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getSongListCount`,
);
} }
return apiController( return apiController(
@@ -762,9 +682,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getStreamUrl`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getStreamUrl`,
);
} }
return apiController( return apiController(
@@ -776,9 +694,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getStructuredLyrics`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getStructuredLyrics`,
);
} }
return apiController( return apiController(
@@ -790,9 +706,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getTags`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getTags`,
);
} }
return apiController( return apiController(
@@ -804,9 +718,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getTopSongs`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getTopSongs`,
);
} }
return apiController( return apiController(
@@ -818,9 +730,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getUserInfo`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getUserInfo`,
);
} }
return apiController( return apiController(
@@ -832,9 +742,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: getUserList`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getUserList`,
);
} }
return apiController( return apiController(
@@ -846,9 +754,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: movePlaylistItem`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: movePlaylistItem`,
);
} }
return apiController( return apiController(
@@ -860,9 +766,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: removeFromPlaylist`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: removeFromPlaylist`,
);
} }
return apiController( return apiController(
@@ -874,9 +778,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: replacePlaylist`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: replacePlaylist`,
);
} }
return apiController( return apiController(
@@ -888,9 +790,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: savePlayQueue`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: savePlayQueue`,
);
} }
return apiController( return apiController(
@@ -902,9 +802,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: scrobble`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: scrobble`,
);
} }
return apiController( return apiController(
@@ -916,9 +814,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: search`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: search`,
);
} }
return apiController( return apiController(
@@ -936,9 +832,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: setPlaylistSongs`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: setPlaylistSongs`,
);
} }
return apiController( return apiController(
@@ -950,9 +844,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: setRating`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: setRating`,
);
} }
return apiController( return apiController(
@@ -964,9 +856,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: shareItem`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: shareItem`,
);
} }
return apiController( return apiController(
@@ -978,9 +868,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: updateInternetRadioStation`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: updateInternetRadioStation`,
);
} }
return apiController( return apiController(
@@ -992,9 +880,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: updatePlaylist`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: updatePlaylist`,
);
} }
return apiController( return apiController(
@@ -1006,9 +892,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: uploadArtistImage`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: uploadArtistImage`,
);
} }
return apiController( return apiController(
@@ -1020,9 +904,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: uploadInternetRadioStationImage`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: uploadInternetRadioStationImage`,
);
} }
return apiController( return apiController(
@@ -1034,9 +916,7 @@ export const controller: GeneralController = {
const server = getServerById(args.apiClientProps.serverId); const server = getServerById(args.apiClientProps.serverId);
if (!server) { if (!server) {
throw new Error( throw new Error(`${i18n.t('error.apiRouteError')}: uploadPlaylistImage`);
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: uploadPlaylistImage`,
);
} }
return apiController( return apiController(
+1 -5
View File
@@ -447,11 +447,7 @@ export const jfApiClient = (args: {
} catch (e: any | AxiosError | Error) { } catch (e: any | AxiosError | Error) {
if (isAxiosError(e)) { if (isAxiosError(e)) {
if (e.code === 'ERR_NETWORK') { if (e.code === 'ERR_NETWORK') {
throw new Error( throw new Error(i18n.t('error.networkError') as string);
i18n.t('error.networkError', {
postProcess: 'sentenceCase',
}) as string,
);
} }
const error = e as AxiosError; const error = e as AxiosError;
@@ -409,6 +409,8 @@ export const JellyfinController: InternalControllerEndpoint = {
return jfNormalize.album( return jfNormalize.album(
{ ...res.body, Songs: songsRes.body.Items }, { ...res.body, Songs: songsRes.body.Items },
apiClientProps.server, apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
); );
}, },
getAlbumList: async (args) => { getAlbumList: async (args) => {
@@ -580,7 +582,8 @@ export const JellyfinController: InternalControllerEndpoint = {
return `${apiClientProps.server?.url}/items/${query.id}/download?apiKey=${apiClientProps.server?.credential}`; return `${apiClientProps.server?.url}/items/${query.id}/download?apiKey=${apiClientProps.server?.credential}`;
}, },
getFolder: async ({ apiClientProps, query }) => { getFolder: async (args) => {
const { apiClientProps, query } = args;
const userId = apiClientProps.server?.userId; const userId = apiClientProps.server?.userId;
if (!userId) throw new Error('No userId found'); if (!userId) throw new Error('No userId found');
@@ -742,6 +745,8 @@ export const JellyfinController: InternalControllerEndpoint = {
jfNormalize.song( jfNormalize.song(
item as unknown as z.infer<typeof jfType._response.song>, item as unknown as z.infer<typeof jfType._response.song>,
apiClientProps.server, apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
), ),
); );
+4 -16
View File
@@ -405,12 +405,8 @@ axiosClient.interceptors.response.use(
if (res.status === 429) { if (res.status === 429) {
toast.error({ toast.error({
message: i18n.t('error.loginRateError', { message: i18n.t('error.loginRateError') as string,
postProcess: 'sentenceCase', title: i18n.t('error.sessionExpiredError') as string,
}) as string,
title: i18n.t('error.sessionExpiredError', {
postProcess: 'sentenceCase',
}) as string,
}); });
const serverId = currentServer.id; const serverId = currentServer.id;
@@ -425,11 +421,7 @@ axiosClient.interceptors.response.use(
throw TIMEOUT_ERROR; throw TIMEOUT_ERROR;
} }
if (res.status !== 200) { if (res.status !== 200) {
throw new Error( throw new Error(i18n.t('error.authenticatedFailed') as string);
i18n.t('error.authenticatedFailed', {
postProcess: 'sentenceCase',
}) as string,
);
} }
const newCredential = res.data.token; const newCredential = res.data.token;
@@ -522,11 +514,7 @@ export const ndApiClient = (args: {
} catch (e: any | AxiosError | Error) { } catch (e: any | AxiosError | Error) {
if (isAxiosError(e)) { if (isAxiosError(e)) {
if (e.code === 'ERR_NETWORK') { if (e.code === 'ERR_NETWORK') {
throw new Error( throw new Error(i18n.t('error.networkError') as string);
i18n.t('error.networkError', {
postProcess: 'sentenceCase',
}) as string,
);
} }
const error = e as AxiosError; const error = e as AxiosError;
@@ -496,7 +496,12 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
return res.body.similarSongs.song.map((song) => return res.body.similarSongs.song.map((song) =>
ssNormalize.song(song, apiClientProps.server), ssNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
); );
}, },
getArtistList: async (args) => { getArtistList: async (args) => {
@@ -566,7 +571,12 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
return res.body.similarSongs2.song.map((song) => return res.body.similarSongs2.song.map((song) =>
ssNormalize.song(song, apiClientProps.server), ssNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
); );
}, },
getDownloadUrl: SubsonicController.getDownloadUrl, getDownloadUrl: SubsonicController.getDownloadUrl,
@@ -823,7 +833,14 @@ export const NavidromeController: InternalControllerEndpoint = {
return ( return (
(res.body.similarSongs?.song || []) (res.body.similarSongs?.song || [])
.filter((song) => song.id !== query.songId) .filter((song) => song.id !== query.songId)
.map((song) => ssNormalize.song(song, apiClientProps.server)) || [] .map((song) =>
ssNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
) || []
); );
}, },
getSongDetail: async (args) => { getSongDetail: async (args) => {
@@ -1022,6 +1039,7 @@ export const NavidromeController: InternalControllerEndpoint = {
const res = await NavidromeController.getSongList({ const res = await NavidromeController.getSongList({
apiClientProps, apiClientProps,
context: args.context,
query: { query: {
artistIds: [query.artistId], artistIds: [query.artistId],
sortBy: SongListSort.PLAY_COUNT, sortBy: SongListSort.PLAY_COUNT,
+32 -9
View File
@@ -1,6 +1,5 @@
import { initClient, initContract } from '@ts-rest/core'; import { initClient, initContract } from '@ts-rest/core';
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios'; import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';
import omitBy from 'lodash/omitBy';
import qs from 'qs'; import qs from 'qs';
import { z } from 'zod'; import { z } from 'zod';
@@ -362,7 +361,7 @@ axiosClient.interceptors.response.use(
if (data['subsonic-response'].error.code !== 0) { if (data['subsonic-response'].error.code !== 0) {
toast.error({ toast.error({
message: data['subsonic-response'].error.message, message: data['subsonic-response'].error.message,
title: i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string, title: i18n.t('error.genericError') as string,
}); });
// Since we do status === 200, override this value with the error code // Since we do status === 200, override this value with the error code
@@ -377,11 +376,39 @@ axiosClient.interceptors.response.use(
}, },
); );
const keysToSkipEmptyCheck = new Set([
'artist',
'comment',
'genre',
'name',
'query',
'u',
'username',
]);
const parsePath = (fullPath: string) => { const parsePath = (fullPath: string) => {
const [path, params] = fullPath.split('?'); const [path, params] = fullPath.split('?');
const parsedParams = qs.parse(params, { arrayLimit: 99999, parameterLimit: 99999 }); const url = new URLSearchParams(params);
const notNilParams = omitBy(parsedParams, (value) => value === 'undefined' || value === 'null'); const notNilParams: Record<string, string[]> = {};
for (const [key, value] of url) {
if (!keysToSkipEmptyCheck.has(key) && (value === 'undefined' || value === 'null')) {
continue;
}
let realKey = key;
if (key.includes('[') && key.includes(']')) {
realKey = key.split('[')[0];
}
if (realKey in notNilParams) {
notNilParams[realKey].push(value);
} else {
notNilParams[realKey] = [value];
}
}
return { return {
params: notNilParams, params: notNilParams,
@@ -496,11 +523,7 @@ export const ssApiClient = (args: {
} catch (e: any | AxiosError | Error) { } catch (e: any | AxiosError | Error) {
if (isAxiosError(e)) { if (isAxiosError(e)) {
if (e.code === 'ERR_NETWORK') { if (e.code === 'ERR_NETWORK') {
throw new Error( throw new Error(i18n.t('error.networkError') as string);
i18n.t('error.networkError', {
postProcess: 'sentenceCase',
}) as string,
);
} }
const error = e as AxiosError; const error = e as AxiosError;
@@ -2015,8 +2015,12 @@ export const SubsonicController: InternalControllerEndpoint = {
}, },
}); });
// If the server returns an error for transcodeDecision, fall back to direct stream so that we don't break the player
if (transcodeDecision.status !== 200) { if (transcodeDecision.status !== 200) {
throw new Error('Failed to get transcode decision'); logFn.error(
`Failed to get transcode decision for song ${id}, falling back to direct stream`,
);
return streamUrl;
} }
const td = transcodeDecision.body.transcodeDecision; const td = transcodeDecision.body.transcodeDecision;
@@ -2121,6 +2125,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const res = await SubsonicController.getSongList({ const res = await SubsonicController.getSongList({
apiClientProps, apiClientProps,
context,
query: { query: {
artistIds: [query.artistId], artistIds: [query.artistId],
sortBy: SongListSort.PLAY_COUNT, sortBy: SongListSort.PLAY_COUNT,
+2 -7
View File
@@ -22,12 +22,7 @@ import { WebAudio } from '/@/shared/types/types';
import '/@/shared/styles/global.css'; import '/@/shared/styles/global.css';
import { PlayerProvider } from '/@/renderer/features/player/context/player-context'; import { PlayerProvider } from '/@/renderer/features/player/context/player-context';
import { AudioPlayers } from '/@/renderer/features/player/components/audio-players'; import { AudioPlayers } from '/@/renderer/features/player/components/audio-players';
import { ReleaseNotesModal } from '/@/renderer/release-notes-modal';
const ReleaseNotesModal = lazy(() =>
import('./release-notes-modal').then((module) => ({
default: module.ReleaseNotesModal,
})),
);
const UpdateAvailableDialog = lazy(() => const UpdateAvailableDialog = lazy(() =>
import('./update-available-dialog').then((module) => ({ import('./update-available-dialog').then((module) => ({
@@ -82,8 +77,8 @@ const AppShell = memo(function AppShell() {
<AppRouter /> <AppRouter />
</PlayerProvider> </PlayerProvider>
</WebAudioContext.Provider> </WebAudioContext.Provider>
<ReleaseNotesModal />
<Suspense fallback={null}> <Suspense fallback={null}>
<ReleaseNotesModal />
<UpdateAvailableDialog /> <UpdateAvailableDialog />
</Suspense> </Suspense>
</> </>
+340 -302
View File
@@ -169,6 +169,292 @@ export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
showRating: boolean; showRating: boolean;
} }
type ItemCardData = NonNullable<ItemCardProps['data']>;
const ItemCardStandardImageArea = memo(function ItemCardStandardImageArea({
controls,
data,
enableExpansion,
enableImageViewport = true,
enableNavigation,
handleContextMenu,
handleImageClick,
handleLinkDragStart,
imageAsLink,
imageFetchPriority,
internalState,
isRound,
itemType,
navigationPath,
showRating,
variant,
withControls,
}: {
controls?: ItemControls;
data: ItemCardData;
enableExpansion?: boolean;
enableImageViewport?: boolean;
enableNavigation?: boolean;
handleContextMenu: (e: React.MouseEvent<HTMLElement>) => void;
handleImageClick: (e: React.MouseEvent<HTMLElement>) => void;
handleLinkDragStart: (e: React.DragEvent<HTMLAnchorElement>) => void;
imageAsLink?: boolean;
imageFetchPriority?: 'auto' | 'high' | 'low';
internalState?: ItemListStateActions;
isRound?: boolean;
itemType: LibraryItem;
navigationPath: null | string;
showRating: boolean;
variant: 'default' | 'poster';
withControls?: boolean;
}) {
const [showControls, setShowControls] = useState(false);
const handleMouseEnter = () => {
if (withControls) {
setShowControls(true);
}
};
const handleMouseLeave = () => {
if (withControls) {
setShowControls(false);
}
};
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.isRound]: isRound,
});
const isFavorite = 'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
const userRating =
'userRating' in data &&
typeof (data as { userRating: null | number }).userRating === 'number'
? (data as { userRating: null | number }).userRating
: null;
const hasRating = showRating && userRating !== null && userRating > 0;
const imageContainerContent = (
<>
{itemType === LibraryItem.GENRE &&
data &&
'name' in data &&
typeof (data as Genre).name === 'string' ? (
<GenreImagePlaceholder
className={clsx(styles.image, styles.genrePlaceholder, {
[styles.isRound]: isRound,
})}
name={(data as Genre).name}
/>
) : (
<ItemImage
className={clsx(styles.image, { [styles.isRound]: isRound })}
enableDebounce={false}
{...(variant === 'poster' ? { enableViewport: enableImageViewport } : {})}
explicitStatus={'explicitStatus' in data && data ? data.explicitStatus : null}
fetchPriority={imageFetchPriority}
id={(data as { imageId?: string })?.imageId}
itemType={itemType}
src={(data as { imageUrl?: string })?.imageUrl}
type="itemCard"
/>
)}
{isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence>
{withControls && showControls && (
<ItemCardControls
controls={controls}
enableExpansion={enableExpansion}
{...(variant === 'poster' ? { internalState } : {})}
item={data}
itemType={itemType}
showRating={showRating}
type={variant}
/>
)}
</AnimatePresence>
</>
);
return enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
<Link
className={imageContainerClassName}
draggable={false}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onDragStart={handleLinkDragStart}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
state={{ item: data }}
to={navigationPath}
>
{imageContainerContent}
</Link>
) : (
<div
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</div>
);
});
ItemCardStandardImageArea.displayName = 'ItemCardStandardImageArea';
const CompactItemCardImageArea = memo(function CompactItemCardImageArea({
controls,
data,
enableExpansion,
enableNavigation,
handleContextMenu,
handleImageClick,
handleLinkDragStart,
imageAsLink,
imageFetchPriority,
internalState,
isRound,
itemType,
navigationPath,
rows,
showRating,
withControls,
}: {
controls?: ItemControls;
data: ItemCardData;
enableExpansion?: boolean;
enableNavigation?: boolean;
handleContextMenu: (e: React.MouseEvent<HTMLElement>) => void;
handleImageClick: (e: React.MouseEvent<HTMLElement>) => void;
handleLinkDragStart: (e: React.DragEvent<HTMLAnchorElement>) => void;
imageAsLink?: boolean;
imageFetchPriority?: 'auto' | 'high' | 'low';
internalState?: ItemListStateActions;
isRound?: boolean;
itemType: LibraryItem;
navigationPath: null | string;
rows: DataRow[];
showRating: boolean;
withControls?: boolean;
}) {
const [showControls, setShowControls] = useState(false);
const handleMouseEnter = () => {
if (withControls) {
setShowControls(true);
}
};
const handleMouseLeave = () => {
if (withControls) {
setShowControls(false);
}
};
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.isRound]: isRound,
});
const isFavorite = 'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
const userRating =
'userRating' in data &&
typeof (data as { userRating: null | number }).userRating === 'number'
? (data as { userRating: null | number }).userRating
: null;
const hasRating = showRating && userRating !== null && userRating > 0;
const imageContainerContent = (
<>
{itemType === LibraryItem.GENRE &&
data &&
'name' in data &&
typeof (data as Genre).name === 'string' ? (
<GenreImagePlaceholder
className={clsx(styles.image, styles.genrePlaceholder, {
[styles.isRound]: isRound,
})}
name={(data as Genre).name}
/>
) : (
<ItemImage
className={clsx(styles.image, {
[styles.isRound]: isRound,
})}
enableDebounce={false}
explicitStatus={'explicitStatus' in data && data ? data.explicitStatus : null}
fetchPriority={imageFetchPriority}
id={data?.imageId}
itemType={itemType}
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
type="itemCard"
/>
)}
{isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence>
{withControls && showControls && data && (
<ItemCardControls
controls={controls}
enableExpansion={enableExpansion}
internalState={internalState}
item={data}
itemType={itemType}
showRating={showRating}
type="compact"
/>
)}
</AnimatePresence>
<div className={clsx(styles.detailContainer, styles.compact)}>
{rows
.filter(
(row): row is NonNullable<typeof row> => row !== null && row !== undefined,
)
.map((row, index) => (
<ItemCardRow
data={data!}
index={index}
key={row.id}
row={row}
type="compact"
/>
))}
</div>
</>
);
return enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
<Link
className={imageContainerClassName}
draggable={false}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onDragStart={handleLinkDragStart}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
state={{ item: data }}
to={navigationPath}
>
{imageContainerContent}
</Link>
) : (
<div
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</div>
);
});
CompactItemCardImageArea.displayName = 'CompactItemCardImageArea';
const CompactItemCard = ({ const CompactItemCard = ({
controls, controls,
data, data,
@@ -185,7 +471,6 @@ const CompactItemCard = ({
showRating, showRating,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
const itemRowId = const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data data && internalState && typeof data === 'object' && 'id' in data
? internalState.extractRowId(data) ? internalState.extractRowId(data)
@@ -297,18 +582,6 @@ const CompactItemCard = ({
if (data) { if (data) {
const navigationPath = getItemNavigationPath(data, itemType); const navigationPath = getItemNavigationPath(data, itemType);
const handleMouseEnter = () => {
if (withControls) {
setShowControls(true);
}
};
const handleMouseLeave = () => {
if (withControls) {
setShowControls(false);
}
};
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => { const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
if (!data || !controls) { if (!data || !controls) {
return; return;
@@ -338,81 +611,6 @@ const CompactItemCard = ({
e.stopPropagation(); e.stopPropagation();
}; };
const isFavorite =
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
const userRating =
'userRating' in data &&
typeof (data as { userRating: null | number }).userRating === 'number'
? (data as { userRating: null | number }).userRating
: null;
const hasRating = showRating && userRating !== null && userRating > 0;
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.isRound]: isRound,
});
const imageContainerContent = (
<>
{itemType === LibraryItem.GENRE &&
data &&
'name' in data &&
typeof (data as Genre).name === 'string' ? (
<GenreImagePlaceholder
className={clsx(styles.image, styles.genrePlaceholder, {
[styles.isRound]: isRound,
})}
name={(data as Genre).name}
/>
) : (
<ItemImage
className={clsx(styles.image, {
[styles.isRound]: isRound,
})}
enableDebounce={false}
explicitStatus={
'explicitStatus' in data && data ? data.explicitStatus : null
}
fetchPriority={imageFetchPriority}
id={data?.imageId}
itemType={itemType}
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
type="itemCard"
/>
)}
{isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence>
{withControls && showControls && data && (
<ItemCardControls
controls={controls}
enableExpansion={enableExpansion}
internalState={internalState}
item={data}
itemType={itemType}
showRating={showRating}
type="compact"
/>
)}
</AnimatePresence>
<div className={clsx(styles.detailContainer, styles.compact)}>
{rows
.filter(
(row): row is NonNullable<typeof row> =>
row !== null && row !== undefined,
)
.map((row, index) => (
<ItemCardRow
data={data!}
index={index}
key={row.id}
row={row}
type="compact"
/>
))}
</div>
</>
);
return ( return (
<div <div
className={clsx(styles.container, styles.compact, { className={clsx(styles.container, styles.compact, {
@@ -421,31 +619,24 @@ const CompactItemCard = ({
})} })}
ref={ref} ref={ref}
> >
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? ( <CompactItemCardImageArea
<Link controls={controls}
className={imageContainerClassName} data={data}
draggable={false} enableExpansion={enableExpansion}
onClick={handleImageClick} enableNavigation={enableNavigation}
onContextMenu={handleContextMenu} handleContextMenu={handleContextMenu}
onDragStart={handleLinkDragStart} handleImageClick={handleImageClick}
onMouseEnter={handleMouseEnter} handleLinkDragStart={handleLinkDragStart}
onMouseLeave={handleMouseLeave} imageAsLink={imageAsLink}
state={{ item: data }} imageFetchPriority={imageFetchPriority}
to={navigationPath} internalState={internalState}
> isRound={isRound}
{imageContainerContent} itemType={itemType}
</Link> navigationPath={navigationPath}
) : ( rows={rows}
<div showRating={showRating}
className={imageContainerClassName} withControls={withControls}
onClick={handleImageClick} />
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</div>
)}
</div> </div>
); );
} }
@@ -491,7 +682,6 @@ const DefaultItemCard = ({
showRating, showRating,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
const itemRowId = const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data data && internalState && typeof data === 'object' && 'id' in data
? internalState.extractRowId(data) ? internalState.extractRowId(data)
@@ -538,18 +728,6 @@ const DefaultItemCard = ({
if (data) { if (data) {
const navigationPath = getItemNavigationPath(data, itemType); const navigationPath = getItemNavigationPath(data, itemType);
const handleMouseEnter = () => {
if (withControls) {
setShowControls(true);
}
};
const handleMouseLeave = () => {
if (withControls) {
setShowControls(false);
}
};
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => { const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
if (!data || !controls) { if (!data || !controls) {
return; return;
@@ -579,93 +757,30 @@ const DefaultItemCard = ({
e.stopPropagation(); e.stopPropagation();
}; };
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.isRound]: isRound,
});
const isFavorite =
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
const userRating =
'userRating' in data &&
typeof (data as { userRating: null | number }).userRating === 'number'
? (data as { userRating: null | number }).userRating
: null;
const hasRating = showRating && userRating !== null && userRating > 0;
const imageContainerContent = (
<>
{itemType === LibraryItem.GENRE &&
data &&
'name' in data &&
typeof (data as Genre).name === 'string' ? (
<GenreImagePlaceholder
className={clsx(styles.image, styles.genrePlaceholder, {
[styles.isRound]: isRound,
})}
name={(data as Genre).name}
/>
) : (
<ItemImage
className={clsx(styles.image, { [styles.isRound]: isRound })}
enableDebounce={false}
explicitStatus={
'explicitStatus' in data && data ? data.explicitStatus : null
}
fetchPriority={imageFetchPriority}
id={data?.imageId}
itemType={itemType}
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
type="itemCard"
/>
)}
{isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence>
{withControls && showControls && (
<ItemCardControls
controls={controls}
enableExpansion={enableExpansion}
item={data}
itemType={itemType}
showRating={showRating}
type="default"
/>
)}
</AnimatePresence>
</>
);
return ( return (
<div <div
className={clsx(styles.container, { className={clsx(styles.container, {
[styles.selected]: isSelected, [styles.selected]: isSelected,
})} })}
> >
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? ( <ItemCardStandardImageArea
<Link controls={controls}
className={imageContainerClassName} data={data}
draggable={false} enableExpansion={enableExpansion}
onClick={handleImageClick} enableNavigation={enableNavigation}
onContextMenu={handleContextMenu} handleContextMenu={handleContextMenu}
onDragStart={handleLinkDragStart} handleImageClick={handleImageClick}
onMouseEnter={handleMouseEnter} handleLinkDragStart={handleLinkDragStart}
onMouseLeave={handleMouseLeave} imageAsLink={imageAsLink}
state={{ item: data }} imageFetchPriority={imageFetchPriority}
to={navigationPath} internalState={internalState}
> isRound={isRound}
{imageContainerContent} itemType={itemType}
</Link> navigationPath={navigationPath}
) : ( showRating={showRating}
<div variant="default"
className={imageContainerClassName} withControls={withControls}
onClick={handleImageClick} />
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</div>
)}
<div className={styles.detailContainer}> <div className={styles.detailContainer}>
{rows {rows
.filter( .filter(
@@ -728,7 +843,6 @@ const PosterItemCard = ({
showRating, showRating,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
const itemRowId = const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data data && internalState && typeof data === 'object' && 'id' in data
? internalState.extractRowId(data) ? internalState.extractRowId(data)
@@ -840,18 +954,6 @@ const PosterItemCard = ({
if (data) { if (data) {
const navigationPath = getItemNavigationPath(data, itemType); const navigationPath = getItemNavigationPath(data, itemType);
const handleMouseEnter = () => {
if (withControls) {
setShowControls(true);
}
};
const handleMouseLeave = () => {
if (withControls) {
setShowControls(false);
}
};
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => { const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
if (!data || !controls) { if (!data || !controls) {
return; return;
@@ -881,63 +983,6 @@ const PosterItemCard = ({
e.stopPropagation(); e.stopPropagation();
}; };
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.isRound]: isRound,
});
const isFavorite =
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
const userRating =
'userRating' in data &&
typeof (data as { userRating: null | number }).userRating === 'number'
? (data as { userRating: null | number }).userRating
: null;
const hasRating = showRating && userRating !== null && userRating > 0;
const imageContainerContent = (
<>
{itemType === LibraryItem.GENRE &&
data &&
'name' in data &&
typeof (data as Genre).name === 'string' ? (
<GenreImagePlaceholder
className={clsx(styles.image, styles.genrePlaceholder, {
[styles.isRound]: isRound,
})}
name={(data as Genre).name}
/>
) : (
<ItemImage
className={clsx(styles.image, { [styles.isRound]: isRound })}
enableDebounce={false}
explicitStatus={
'explicitStatus' in data && data ? data.explicitStatus : null
}
fetchPriority={imageFetchPriority}
id={(data as { imageId: string })?.imageId}
itemType={itemType}
src={(data as { imageUrl: string })?.imageUrl}
type="itemCard"
/>
)}
{isFavorite && <div className={styles.favoriteBadge} />}
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
<AnimatePresence>
{withControls && showControls && data && (
<ItemCardControls
controls={controls}
enableExpansion={enableExpansion}
internalState={internalState}
item={data}
itemType={itemType}
showRating={showRating}
type="poster"
/>
)}
</AnimatePresence>
</>
);
return ( return (
<div <div
className={clsx(styles.container, styles.poster, { className={clsx(styles.container, styles.poster, {
@@ -946,31 +991,24 @@ const PosterItemCard = ({
})} })}
ref={ref} ref={ref}
> >
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? ( <ItemCardStandardImageArea
<Link controls={controls}
className={imageContainerClassName} data={data}
draggable={false} enableExpansion={enableExpansion}
onClick={handleImageClick} enableNavigation={enableNavigation}
onContextMenu={handleContextMenu} handleContextMenu={handleContextMenu}
onDragStart={handleLinkDragStart} handleImageClick={handleImageClick}
onMouseEnter={handleMouseEnter} handleLinkDragStart={handleLinkDragStart}
onMouseLeave={handleMouseLeave} imageAsLink={imageAsLink}
state={{ item: data }} imageFetchPriority={imageFetchPriority}
to={navigationPath} internalState={internalState}
> isRound={isRound}
{imageContainerContent} itemType={itemType}
</Link> navigationPath={navigationPath}
) : ( showRating={showRating}
<div variant="poster"
className={imageContainerClassName} withControls={withControls}
onClick={handleImageClick} />
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</div>
)}
{data && ( {data && (
<div className={styles.detailContainer}> <div className={styles.detailContainer}>
{rows {rows
@@ -6,8 +6,8 @@ import {
ItemListStateItemWithRequiredProperties, ItemListStateItemWithRequiredProperties,
} from '/@/renderer/components/item-list/helpers/item-list-state'; } from '/@/renderer/components/item-list/helpers/item-list-state';
import { ItemControls } from '/@/renderer/components/item-list/types'; import { ItemControls } from '/@/renderer/components/item-list/types';
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
import { useHotkeySettings, usePlayButtonBehavior } from '/@/renderer/store'; import { useHotkeySettings, usePlayButtonBehavior } from '/@/renderer/store';
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
@@ -385,8 +385,8 @@ const BaseItemGridList = ({
rows, rows,
size = 'default', size = 'default',
}: ItemGridListProps) => { }: ItemGridListProps) => {
const rootRef = useRef(null); const rootRef = useRef<HTMLDivElement | null>(null);
const outerRef = useRef(null); const outerRef = useRef<HTMLDivElement | null>(null);
const listRef = useRef<FixedSizeList<GridItemProps>>(null); const listRef = useRef<FixedSizeList<GridItemProps>>(null);
const { ref: containerRef, width: containerWidth } = useElementSize(); const { ref: containerRef, width: containerWidth } = useElementSize();
const { focused, ref: containerFocusRef } = useFocusWithin(); const { focused, ref: containerFocusRef } = useFocusWithin();
@@ -486,7 +486,7 @@ const BaseItemGridList = ({
}, [itemsPerRow, rows?.length, size]); }, [itemsPerRow, rows?.length, size]);
useLayoutEffect(() => { useLayoutEffect(() => {
const { current: container } = containerRef; const container = rootRef.current;
if (!container) return; if (!container) return;
throttledSetTableMeta(containerWidth, resolvedItemCount, (meta) => { throttledSetTableMeta(containerWidth, resolvedItemCount, (meta) => {
@@ -500,13 +500,15 @@ const BaseItemGridList = ({
current.rowCount !== meta.rowCount current.rowCount !== meta.rowCount
) { ) {
tableMetaRef.current = meta; tableMetaRef.current = meta;
container.style.setProperty('--grid-column-count', String(meta.columnCount)); const el = rootRef.current;
container.style.setProperty('--grid-item-height', `${meta.itemHeight}px`); if (!el) return;
container.style.setProperty('--grid-row-count', String(meta.rowCount)); el.style.setProperty('--grid-column-count', String(meta.columnCount));
el.style.setProperty('--grid-item-height', `${meta.itemHeight}px`);
el.style.setProperty('--grid-row-count', String(meta.rowCount));
setTableMetaVersion((v) => v + 1); setTableMetaVersion((v) => v + 1);
} }
}); });
}, [containerWidth, resolvedItemCount, throttledSetTableMeta, containerRef]); }, [containerWidth, resolvedItemCount, throttledSetTableMeta]);
const controls = useDefaultItemListControls({ const controls = useDefaultItemListControls({
enableMultiSelect, enableMultiSelect,
@@ -313,12 +313,10 @@ const PlaylistReorderColumnBase = (props: ItemTableListInnerColumn) => {
<> <>
<Stack gap="xs" justify="center"> <Stack gap="xs" justify="center">
<Text fw={500} ta="center"> <Text fw={500} ta="center">
{t('action.moveUp', { postProcess: 'sentenceCase' })} {t('action.moveUp')}
</Text> </Text>
<Text fw={500} isMuted size="xs" ta="center"> <Text fw={500} isMuted size="xs" ta="center">
{t('action.holdToMoveToTop', { {t('action.holdToMoveToTop')}
postProcess: 'sentenceCase',
})}
</Text> </Text>
</Stack> </Stack>
</> </>
@@ -336,12 +334,10 @@ const PlaylistReorderColumnBase = (props: ItemTableListInnerColumn) => {
<> <>
<Stack gap="xs" justify="center"> <Stack gap="xs" justify="center">
<Text fw={500} ta="center"> <Text fw={500} ta="center">
{t('action.moveDown', { postProcess: 'sentenceCase' })} {t('action.moveDown')}
</Text> </Text>
<Text fw={500} isMuted size="xs" ta="center"> <Text fw={500} isMuted size="xs" ta="center">
{t('action.holdToMoveToBottom', { {t('action.holdToMoveToBottom')}
postProcess: 'sentenceCase',
})}
</Text> </Text>
</Stack> </Stack>
</> </>
@@ -17,7 +17,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.albumGroup', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.albumGroup'),
pinned: 'left', pinned: 'left',
value: TableColumn.ALBUM_GROUP, value: TableColumn.ALBUM_GROUP,
width: 200, width: 200,
@@ -26,7 +26,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rowIndex'),
pinned: null, pinned: null,
value: TableColumn.ROW_INDEX, value: TableColumn.ROW_INDEX,
width: 60, width: 60,
@@ -35,7 +35,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.image'),
pinned: null, pinned: null,
value: TableColumn.IMAGE, value: TableColumn.IMAGE,
width: 70, width: 70,
@@ -44,7 +44,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.title'),
pinned: null, pinned: null,
value: TableColumn.TITLE, value: TableColumn.TITLE,
width: 300, width: 300,
@@ -53,7 +53,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.titleCombined', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.titleCombined'),
pinned: null, pinned: null,
value: TableColumn.TITLE_COMBINED, value: TableColumn.TITLE_COMBINED,
width: 300, width: 300,
@@ -62,7 +62,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.titleArtist', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.titleArtist'),
pinned: null, pinned: null,
value: TableColumn.TITLE_ARTIST, value: TableColumn.TITLE_ARTIST,
width: 300, width: 300,
@@ -71,7 +71,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.duration', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.duration'),
pinned: null, pinned: null,
value: TableColumn.DURATION, value: TableColumn.DURATION,
width: 100, width: 100,
@@ -80,7 +80,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.album', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.album'),
pinned: null, pinned: null,
value: TableColumn.ALBUM, value: TableColumn.ALBUM,
width: 300, width: 300,
@@ -89,7 +89,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: true, autoSize: true,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.albumArtist', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.albumArtist'),
pinned: null, pinned: null,
value: TableColumn.ALBUM_ARTIST, value: TableColumn.ALBUM_ARTIST,
width: 300, width: 300,
@@ -98,7 +98,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.artist', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.artist'),
pinned: null, pinned: null,
value: TableColumn.ARTIST, value: TableColumn.ARTIST,
width: 300, width: 300,
@@ -107,7 +107,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.composer', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.composer'),
pinned: null, pinned: null,
value: TableColumn.COMPOSER, value: TableColumn.COMPOSER,
width: 300, width: 300,
@@ -116,7 +116,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.genre', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.genre'),
pinned: null, pinned: null,
value: TableColumn.GENRE, value: TableColumn.GENRE,
width: 300, width: 300,
@@ -125,7 +125,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.genreBadge', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.genreBadge'),
pinned: null, pinned: null,
value: TableColumn.GENRE_BADGE, value: TableColumn.GENRE_BADGE,
width: 300, width: 300,
@@ -134,7 +134,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.year', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.year'),
pinned: null, pinned: null,
value: TableColumn.YEAR, value: TableColumn.YEAR,
width: 200, width: 200,
@@ -143,7 +143,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.releaseDate', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.releaseDate'),
pinned: null, pinned: null,
value: TableColumn.RELEASE_DATE, value: TableColumn.RELEASE_DATE,
width: 240, width: 240,
@@ -152,7 +152,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.discNumber', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.discNumber'),
pinned: null, pinned: null,
value: TableColumn.DISC_NUMBER, value: TableColumn.DISC_NUMBER,
width: 100, width: 100,
@@ -161,7 +161,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.trackNumber', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.trackNumber'),
pinned: null, pinned: null,
value: TableColumn.TRACK_NUMBER, value: TableColumn.TRACK_NUMBER,
width: 100, width: 100,
@@ -170,7 +170,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.bitDepth', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.bitDepth'),
pinned: null, pinned: null,
value: TableColumn.BIT_DEPTH, value: TableColumn.BIT_DEPTH,
width: 100, width: 100,
@@ -179,7 +179,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.bitrate', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.bitrate'),
pinned: null, pinned: null,
value: TableColumn.BIT_RATE, value: TableColumn.BIT_RATE,
width: 100, width: 100,
@@ -188,7 +188,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.codec', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.codec'),
pinned: null, pinned: null,
value: TableColumn.CODEC, value: TableColumn.CODEC,
width: 100, width: 100,
@@ -197,7 +197,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.sampleRate', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.sampleRate'),
pinned: null, pinned: null,
value: TableColumn.SAMPLE_RATE, value: TableColumn.SAMPLE_RATE,
width: 100, width: 100,
@@ -206,7 +206,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.lastPlayed', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.lastPlayed'),
pinned: null, pinned: null,
value: TableColumn.LAST_PLAYED, value: TableColumn.LAST_PLAYED,
width: 150, width: 150,
@@ -215,7 +215,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.note', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.note'),
pinned: null, pinned: null,
value: TableColumn.COMMENT, value: TableColumn.COMMENT,
width: 300, width: 300,
@@ -224,7 +224,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.channels', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.channels'),
pinned: null, pinned: null,
value: TableColumn.CHANNELS, value: TableColumn.CHANNELS,
width: 100, width: 100,
@@ -233,7 +233,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.bpm', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.bpm'),
pinned: null, pinned: null,
value: TableColumn.BPM, value: TableColumn.BPM,
width: 100, width: 100,
@@ -242,7 +242,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.dateAdded', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.dateAdded'),
pinned: null, pinned: null,
value: TableColumn.DATE_ADDED, value: TableColumn.DATE_ADDED,
width: 120, width: 120,
@@ -251,7 +251,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.path', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.path'),
pinned: null, pinned: null,
value: TableColumn.PATH, value: TableColumn.PATH,
width: 300, width: 300,
@@ -260,7 +260,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.playCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.playCount'),
pinned: null, pinned: null,
value: TableColumn.PLAY_COUNT, value: TableColumn.PLAY_COUNT,
width: 100, width: 100,
@@ -269,7 +269,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.size', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.size'),
pinned: null, pinned: null,
value: TableColumn.SIZE, value: TableColumn.SIZE,
width: 100, width: 100,
@@ -278,7 +278,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.favorite', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.favorite'),
pinned: null, pinned: null,
value: TableColumn.USER_FAVORITE, value: TableColumn.USER_FAVORITE,
width: 60, width: 60,
@@ -287,7 +287,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.rating', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rating'),
pinned: null, pinned: null,
value: TableColumn.USER_RATING, value: TableColumn.USER_RATING,
width: 100, width: 100,
@@ -296,7 +296,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.actions'),
pinned: null, pinned: null,
value: TableColumn.ACTIONS, value: TableColumn.ACTIONS,
width: 60, width: 60,
@@ -310,7 +310,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rowIndex'),
pinned: null, pinned: null,
value: TableColumn.ROW_INDEX, value: TableColumn.ROW_INDEX,
width: 60, width: 60,
@@ -319,7 +319,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.image'),
pinned: null, pinned: null,
value: TableColumn.IMAGE, value: TableColumn.IMAGE,
width: 70, width: 70,
@@ -328,7 +328,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.title'),
pinned: null, pinned: null,
value: TableColumn.TITLE, value: TableColumn.TITLE,
width: 300, width: 300,
@@ -337,7 +337,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.titleCombined', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.titleCombined'),
pinned: null, pinned: null,
value: TableColumn.TITLE_COMBINED, value: TableColumn.TITLE_COMBINED,
width: 300, width: 300,
@@ -346,7 +346,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.titleArtist', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.titleArtist'),
pinned: null, pinned: null,
value: TableColumn.TITLE_ARTIST, value: TableColumn.TITLE_ARTIST,
width: 300, width: 300,
@@ -355,7 +355,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.duration', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.duration'),
pinned: null, pinned: null,
value: TableColumn.DURATION, value: TableColumn.DURATION,
width: 100, width: 100,
@@ -364,7 +364,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: true, autoSize: true,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.albumArtist', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.albumArtist'),
pinned: null, pinned: null,
value: TableColumn.ALBUM_ARTIST, value: TableColumn.ALBUM_ARTIST,
width: 300, width: 300,
@@ -373,7 +373,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.artist', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.artist'),
pinned: null, pinned: null,
value: TableColumn.ARTIST, value: TableColumn.ARTIST,
width: 300, width: 300,
@@ -382,7 +382,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.composer', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.composer'),
pinned: null, pinned: null,
value: TableColumn.COMPOSER, value: TableColumn.COMPOSER,
width: 300, width: 300,
@@ -391,7 +391,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.songCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.songCount'),
pinned: null, pinned: null,
value: TableColumn.SONG_COUNT, value: TableColumn.SONG_COUNT,
width: 100, width: 100,
@@ -400,7 +400,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.genre', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.genre'),
pinned: null, pinned: null,
value: TableColumn.GENRE, value: TableColumn.GENRE,
width: 300, width: 300,
@@ -409,7 +409,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.genreBadge', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.genreBadge'),
pinned: null, pinned: null,
value: TableColumn.GENRE_BADGE, value: TableColumn.GENRE_BADGE,
width: 300, width: 300,
@@ -418,7 +418,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.year', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.year'),
pinned: null, pinned: null,
value: TableColumn.YEAR, value: TableColumn.YEAR,
width: 200, width: 200,
@@ -427,7 +427,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.releaseDate', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.releaseDate'),
pinned: null, pinned: null,
value: TableColumn.RELEASE_DATE, value: TableColumn.RELEASE_DATE,
width: 240, width: 240,
@@ -436,7 +436,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.lastPlayed', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.lastPlayed'),
pinned: null, pinned: null,
value: TableColumn.LAST_PLAYED, value: TableColumn.LAST_PLAYED,
width: 150, width: 150,
@@ -445,7 +445,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.dateAdded', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.dateAdded'),
pinned: null, pinned: null,
value: TableColumn.DATE_ADDED, value: TableColumn.DATE_ADDED,
width: 120, width: 120,
@@ -454,7 +454,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.playCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.playCount'),
pinned: null, pinned: null,
value: TableColumn.PLAY_COUNT, value: TableColumn.PLAY_COUNT,
width: 100, width: 100,
@@ -463,7 +463,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.favorite', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.favorite'),
pinned: null, pinned: null,
value: TableColumn.USER_FAVORITE, value: TableColumn.USER_FAVORITE,
width: 60, width: 60,
@@ -472,7 +472,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.rating', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rating'),
pinned: null, pinned: null,
value: TableColumn.USER_RATING, value: TableColumn.USER_RATING,
width: 100, width: 100,
@@ -481,7 +481,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.actions'),
pinned: null, pinned: null,
value: TableColumn.ACTIONS, value: TableColumn.ACTIONS,
width: 60, width: 60,
@@ -493,7 +493,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rowIndex'),
pinned: null, pinned: null,
value: TableColumn.ROW_INDEX, value: TableColumn.ROW_INDEX,
width: 60, width: 60,
@@ -502,7 +502,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.image'),
pinned: null, pinned: null,
value: TableColumn.IMAGE, value: TableColumn.IMAGE,
width: 70, width: 70,
@@ -511,7 +511,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.title'),
pinned: null, pinned: null,
value: TableColumn.TITLE, value: TableColumn.TITLE,
width: 300, width: 300,
@@ -520,7 +520,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.duration', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.duration'),
pinned: null, pinned: null,
value: TableColumn.DURATION, value: TableColumn.DURATION,
width: 100, width: 100,
@@ -529,7 +529,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.biography', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.biography'),
pinned: null, pinned: null,
value: TableColumn.BIOGRAPHY, value: TableColumn.BIOGRAPHY,
width: 300, width: 300,
@@ -538,7 +538,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.genre', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.genre'),
pinned: null, pinned: null,
value: TableColumn.GENRE, value: TableColumn.GENRE,
width: 300, width: 300,
@@ -547,7 +547,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.lastPlayed', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.lastPlayed'),
pinned: null, pinned: null,
value: TableColumn.LAST_PLAYED, value: TableColumn.LAST_PLAYED,
width: 150, width: 150,
@@ -556,7 +556,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.playCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.playCount'),
pinned: null, pinned: null,
value: TableColumn.PLAY_COUNT, value: TableColumn.PLAY_COUNT,
width: 100, width: 100,
@@ -565,7 +565,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('filter.albumCount', { postProcess: 'titleCase' }), label: i18n.t('filter.albumCount'),
pinned: null, pinned: null,
value: TableColumn.ALBUM_COUNT, value: TableColumn.ALBUM_COUNT,
width: 100, width: 100,
@@ -574,7 +574,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.songCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.songCount'),
pinned: null, pinned: null,
value: TableColumn.SONG_COUNT, value: TableColumn.SONG_COUNT,
width: 100, width: 100,
@@ -583,7 +583,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.favorite', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.favorite'),
pinned: null, pinned: null,
value: TableColumn.USER_FAVORITE, value: TableColumn.USER_FAVORITE,
width: 60, width: 60,
@@ -592,7 +592,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.rating', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rating'),
pinned: null, pinned: null,
value: TableColumn.USER_RATING, value: TableColumn.USER_RATING,
width: 100, width: 100,
@@ -601,7 +601,7 @@ export const ALBUM_ARTIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.actions'),
pinned: null, pinned: null,
value: TableColumn.ACTIONS, value: TableColumn.ACTIONS,
width: 60, width: 60,
@@ -613,7 +613,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rowIndex'),
pinned: null, pinned: null,
value: TableColumn.ROW_INDEX, value: TableColumn.ROW_INDEX,
width: 60, width: 60,
@@ -622,7 +622,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.image'),
pinned: null, pinned: null,
value: TableColumn.IMAGE, value: TableColumn.IMAGE,
width: 70, width: 70,
@@ -631,7 +631,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.title'),
pinned: null, pinned: null,
value: TableColumn.TITLE, value: TableColumn.TITLE,
width: 300, width: 300,
@@ -640,7 +640,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.titleCombined', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.titleCombined'),
pinned: null, pinned: null,
value: TableColumn.TITLE_COMBINED, value: TableColumn.TITLE_COMBINED,
width: 300, width: 300,
@@ -649,7 +649,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.duration', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.duration'),
pinned: null, pinned: null,
value: TableColumn.DURATION, value: TableColumn.DURATION,
width: 100, width: 100,
@@ -658,7 +658,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.owner', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.owner'),
pinned: null, pinned: null,
value: TableColumn.OWNER, value: TableColumn.OWNER,
width: 150, width: 150,
@@ -667,7 +667,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.songCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.songCount'),
pinned: null, pinned: null,
value: TableColumn.SONG_COUNT, value: TableColumn.SONG_COUNT,
width: 100, width: 100,
@@ -676,7 +676,7 @@ export const PLAYLIST_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.actions'),
pinned: null, pinned: null,
value: TableColumn.ACTIONS, value: TableColumn.ACTIONS,
width: 60, width: 60,
@@ -688,7 +688,7 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.rowIndex'),
pinned: null, pinned: null,
value: TableColumn.ROW_INDEX, value: TableColumn.ROW_INDEX,
width: 60, width: 60,
@@ -697,7 +697,7 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'start', align: 'start',
autoSize: true, autoSize: true,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.title'),
pinned: null, pinned: null,
value: TableColumn.TITLE, value: TableColumn.TITLE,
width: 300, width: 300,
@@ -706,7 +706,7 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.songCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.songCount'),
pinned: null, pinned: null,
value: TableColumn.SONG_COUNT, value: TableColumn.SONG_COUNT,
width: 100, width: 100,
@@ -715,7 +715,7 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: true, isEnabled: true,
label: i18n.t('table.config.label.albumCount', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.albumCount'),
pinned: null, pinned: null,
value: TableColumn.ALBUM_COUNT, value: TableColumn.ALBUM_COUNT,
width: 100, width: 100,
@@ -724,7 +724,7 @@ export const GENRE_TABLE_COLUMNS: DefaultTableColumn[] = [
align: 'center', align: 'center',
autoSize: false, autoSize: false,
isEnabled: false, isEnabled: false,
label: i18n.t('table.config.label.actions', { postProcess: 'titleCase' }), label: i18n.t('table.config.label.actions'),
pinned: null, pinned: null,
value: TableColumn.ACTIONS, value: TableColumn.ACTIONS,
width: 60, width: 60,
@@ -22,17 +22,17 @@ const controls = [
{ {
control1: <Kbd>CTRL</Kbd>, control1: <Kbd>CTRL</Kbd>,
control2: <Kbd>A</Kbd>, control2: <Kbd>A</Kbd>,
label: i18n.t('action.selectAll', { postProcess: 'sentenceCase' }), label: i18n.t('action.selectAll'),
}, },
{ {
control1: <Kbd>CTRL</Kbd>, control1: <Kbd>CTRL</Kbd>,
control2: <Icon fill="default" icon="mouseLeftClick" />, control2: <Icon fill="default" icon="mouseLeftClick" />,
label: i18n.t('action.addOrRemoveFromSelection', { postProcess: 'sentenceCase' }), label: i18n.t('action.addOrRemoveFromSelection'),
}, },
{ {
control1: <Kbd>SHIFT</Kbd>, control1: <Kbd>SHIFT</Kbd>,
control2: <Icon fill="default" icon="mouseLeftClick" />, control2: <Icon fill="default" icon="mouseLeftClick" />,
label: i18n.t('action.selectRangeOfItems', { postProcess: 'sentenceCase' }), label: i18n.t('action.selectRangeOfItems'),
}, },
]; ];
@@ -79,14 +79,12 @@ export const QueryBuilder = ({
{ {
label: t('form.queryEditor.input', { label: t('form.queryEditor.input', {
context: 'optionMatchAll', context: 'optionMatchAll',
postProcess: 'sentenceCase',
}), }),
value: 'all', value: 'all',
}, },
{ {
label: t('form.queryEditor.input', { label: t('form.queryEditor.input', {
context: 'optionMatchAny', context: 'optionMatchAny',
postProcess: 'sentenceCase',
}), }),
value: 'any', value: 'any',
}, },
@@ -146,9 +144,7 @@ export const QueryBuilder = ({
leftSection={<Icon icon="add" />} leftSection={<Icon icon="add" />}
onClick={handleAddRuleGroup} onClick={handleAddRuleGroup}
> >
{t('form.queryEditor.addRuleGroup', { {t('form.queryEditor.addRuleGroup')}
postProcess: 'sentenceCase',
})}
</DropdownMenu.Item> </DropdownMenu.Item>
{level > 0 && ( {level > 0 && (
@@ -156,9 +152,7 @@ export const QueryBuilder = ({
leftSection={<Icon icon="delete" />} leftSection={<Icon icon="delete" />}
onClick={handleDeleteRuleGroup} onClick={handleDeleteRuleGroup}
> >
{t('form.queryEditor.removeRuleGroup', { {t('form.queryEditor.removeRuleGroup')}
postProcess: 'sentenceCase',
})}
</DropdownMenu.Item> </DropdownMenu.Item>
)} )}
{level === 0 && ( {level === 0 && (
@@ -169,18 +163,14 @@ export const QueryBuilder = ({
leftSection={<Icon color="error" icon="refresh" />} leftSection={<Icon color="error" icon="refresh" />}
onClick={onResetFilters} onClick={onResetFilters}
> >
{t('form.queryEditor.resetToDefault', { {t('form.queryEditor.resetToDefault')}
postProcess: 'sentenceCase',
})}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
isDanger isDanger
leftSection={<Icon color="error" icon="delete" />} leftSection={<Icon color="error" icon="delete" />}
onClick={onClearFilters} onClick={onClearFilters}
> >
{t('form.queryEditor.clearFilters', { {t('form.queryEditor.clearFilters')}
postProcess: 'sentenceCase',
})}
</DropdownMenu.Item> </DropdownMenu.Item>
</> </>
)} )}
@@ -28,11 +28,7 @@ export const SelectWithInvalidData = ({ data, defaultValue, ...props }: SelectPr
<Select <Select
data={fullData} data={fullData}
defaultValue={defaultValue} defaultValue={defaultValue}
error={ error={hasError ? t('error.badValue', { value: defaultValue }) : undefined}
hasError
? t('error.badValue', { postProcess: 'sentenceCase', value: defaultValue })
: undefined
}
{...props} {...props}
/> />
); );
@@ -74,10 +70,7 @@ export const MultiSelectWithInvalidData = ({
}, [data, currentValue]); }, [data, currentValue]);
const error = useMemo( const error = useMemo(
() => () => (missing.length ? t('error.badValue', { value: missing }) : undefined),
missing.length
? t('error.badValue', { postProcess: 'sentenceCase', value: missing })
: undefined,
[missing, t], [missing, t],
); );
@@ -80,7 +80,7 @@ function ServerSelector() {
/> />
), ),
size: 'sm', size: 'sm',
title: t('form.updateServer.title', { postProcess: 'titleCase' }), title: t('form.updateServer.title'),
}); });
}; };
@@ -31,12 +31,12 @@ const ActionRequiredRoute = () => {
const checks = [ const checks = [
{ {
component: <ServerCredentialRequired />, component: <ServerCredentialRequired />,
title: t('error.credentialsRequired', { postProcess: 'sentenceCase' }), title: t('error.credentialsRequired'),
valid: !isCredentialRequired, valid: !isCredentialRequired,
}, },
{ {
component: <ServerRequired />, component: <ServerRequired />,
title: t('error.serverRequired', { postProcess: 'serverRequired' }), title: t('error.serverRequired'),
valid: !isServerRequired, valid: !isServerRequired,
}, },
]; ];
@@ -47,7 +47,7 @@ const ActionRequiredRoute = () => {
const handleManageServersModal = () => { const handleManageServersModal = () => {
openModal({ openModal({
children: <ServerList />, children: <ServerList />,
title: t('page.appMenu.manageServers', { postProcess: 'sentenceCase' }), title: t('page.appMenu.manageServers'),
}); });
}; };
@@ -79,9 +79,7 @@ const ActionRequiredRoute = () => {
onClick={handleManageServersModal} onClick={handleManageServersModal}
variant="filled" variant="filled"
> >
{t('page.appMenu.manageServers', { {t('page.appMenu.manageServers')}
postProcess: 'sentenceCase',
})}
</Button> </Button>
</Group> </Group>
)} )}
@@ -21,9 +21,7 @@ const InvalidRoute = () => {
<Stack> <Stack>
<Group justify="center" wrap="nowrap"> <Group justify="center" wrap="nowrap">
<Icon color="warn" icon="error" /> <Icon color="warn" icon="error" />
<Text size="xl"> <Text size="xl">{t('error.apiRouteError')}</Text>
{t('error.apiRouteError', { postProcess: 'sentenceCase' })}
</Text>
</Group> </Group>
<Text>{location.pathname}</Text> <Text>{location.pathname}</Text>
<ActionIcon icon="arrowLeftS" onClick={() => navigate(-1)} variant="filled" /> <ActionIcon icon="arrowLeftS" onClick={() => navigate(-1)} variant="filled" />
@@ -28,12 +28,10 @@ const NoNetworkRoute = () => {
<Icon icon="wifiOff" size="4rem" /> <Icon icon="wifiOff" size="4rem" />
<Stack gap="md"> <Stack gap="md">
<Text size="xl" weight={600}> <Text size="xl" weight={600}>
{t('error.noNetwork', { postProcess: 'sentenceCase' })} {t('error.noNetwork')}
</Text> </Text>
<Text c="dimmed" size="sm"> <Text c="dimmed" size="sm">
{t('error.noNetworkDescription', { {t('error.noNetworkDescription')}
postProcess: 'sentenceCase',
})}
</Text> </Text>
</Stack> </Stack>
<Button <Button
@@ -41,7 +39,7 @@ const NoNetworkRoute = () => {
onClick={handleRetry} onClick={handleRetry}
variant="filled" variant="filled"
> >
{t('common.retry', { postProcess: 'sentenceCase' })} {t('common.retry')}
</Button> </Button>
</Stack> </Stack>
</Center> </Center>
@@ -31,6 +31,7 @@ import {
} from '/@/renderer/features/shared/components/list-sort-by-dropdown'; } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
import { ListSortOrderToggleButtonControlled } from '/@/renderer/features/shared/components/list-sort-order-toggle-button'; import { ListSortOrderToggleButtonControlled } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
import { FILTER_KEYS, searchLibraryItems } from '/@/renderer/features/shared/utils'; import { FILTER_KEYS, searchLibraryItems } from '/@/renderer/features/shared/utils';
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, usePlayerSong } from '/@/renderer/store'; import { useCurrentServer, usePlayerSong } from '/@/renderer/store';
import { useExternalLinks, useSettingsStore } from '/@/renderer/store/settings.store'; import { useExternalLinks, useSettingsStore } from '/@/renderer/store/settings.store';
@@ -49,7 +50,6 @@ import { Stack } from '/@/shared/components/stack/stack';
import { TextInput } from '/@/shared/components/text-input/text-input'; import { TextInput } from '/@/shared/components/text-input/text-input';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value'; import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value';
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
import { import {
Album, Album,
AlbumListSort, AlbumListSort,
@@ -127,9 +127,7 @@ const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => {
...releaseTypes, ...releaseTypes,
{ {
id: 'isCompilation', id: 'isCompilation',
value: album?.isCompilation value: album?.isCompilation ? t('filter.isCompilation') : undefined,
? t('filter.isCompilation', { postProcess: 'sentenceCase' })
: undefined,
}, },
...releaseCountries, ...releaseCountries,
...releaseStatuses, ...releaseStatuses,
@@ -137,9 +135,9 @@ const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => {
id: 'explicitStatus', id: 'explicitStatus',
value: value:
album.explicitStatus === ExplicitStatus.EXPLICIT album.explicitStatus === ExplicitStatus.EXPLICIT
? t('common.explicit', { postProcess: 'sentenceCase' }) ? t('common.explicit')
: album.explicitStatus === ExplicitStatus.CLEAN : album.explicitStatus === ExplicitStatus.CLEAN
? t('common.clean', { postProcess: 'sentenceCase' }) ? t('common.clean')
: undefined, : undefined,
}, },
); );
@@ -210,15 +208,12 @@ const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => {
return ( return (
<> <>
<MetadataPillGroup <MetadataPillGroup items={defaultTagItems} title={t('common.tags')} />
items={defaultTagItems}
title={t('common.tags', { postProcess: 'sentenceCase' })}
/>
{recordLabels.length > 0 && ( {recordLabels.length > 0 && (
<Stack align="center" className={styles.metadataPillGroup} gap="xs"> <Stack align="center" className={styles.metadataPillGroup} gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase"> <Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('common.recordLabel', { postProcess: 'sentenceCase' })} {t('common.recordLabel')}
</Text> </Text>
<div className={styles['pill-group-wrapper']}> <div className={styles['pill-group-wrapper']}>
<Pill.Group> <Pill.Group>
@@ -242,15 +237,12 @@ const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => {
</Stack> </Stack>
)} )}
<MetadataPillGroup <MetadataPillGroup items={moodTagItems} title={t('common.mood')} />
items={moodTagItems}
title={t('common.mood', { postProcess: 'sentenceCase' })}
/>
{groupingItems.length > 0 && ( {groupingItems.length > 0 && (
<Stack align="center" className={styles.metadataPillGroup} gap="xs"> <Stack align="center" className={styles.metadataPillGroup} gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase"> <Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('common.grouping', { postProcess: 'sentenceCase' })} {t('common.grouping')}
</Text> </Text>
<div className={styles['pill-group-wrapper']}> <div className={styles['pill-group-wrapper']}>
<Pill.Group> <Pill.Group>
@@ -402,9 +394,7 @@ const AlbumMetadataExternalLinks = ({
return ( return (
<Stack gap="xs"> <Stack gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase"> <Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('common.externalLinks', { {t('common.externalLinks')}
postProcess: 'sentenceCase',
})}
</Text> </Text>
<Group className={styles.externalLinksGroup} gap="xs"> <Group className={styles.externalLinksGroup} gap="xs">
{lastFM && ( {lastFM && (
@@ -634,7 +624,7 @@ const DiscGroupRow = ({ discGroup, groupItems, internalState, t }: DiscGroupRowP
id={`disc-${discGroup.discNumber}`} id={`disc-${discGroup.discNumber}`}
label={ label={
<Text component="label" size="sm" truncate> <Text component="label" size="sm" truncate>
{t('common.disc', { postProcess: 'sentenceCase' })} {discGroup.discNumber} {t('common.disc')} {discGroup.discNumber}
{discGroup.discSubtitle && ` - ${discGroup.discSubtitle}`} {discGroup.discSubtitle && ` - ${discGroup.discSubtitle}`}
</Text> </Text>
} }
@@ -691,7 +681,7 @@ function AlbumDetailCarousels({ data }: { data: Album }) {
rowCount: 1, rowCount: 1,
sortBy: AlbumListSort.YEAR, sortBy: AlbumListSort.YEAR,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
title: t('page.albumDetail.moreFromArtist', { postProcess: 'sentenceCase' }), title: t('page.albumDetail.moreFromArtist'),
uniqueId: moreFromArtistUniqueId, uniqueId: moreFromArtistUniqueId,
}, },
...genreCarousels, ...genreCarousels,
@@ -878,7 +868,7 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
flex={1} flex={1}
leftSection={<Icon icon="search" />} leftSection={<Icon icon="search" />}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('common.search', { postProcess: 'sentenceCase' })} placeholder={t('common.search')}
radius="xl" radius="xl"
ref={searchInputRef} ref={searchInputRef}
rightSection={ rightSection={
@@ -138,12 +138,10 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
const playCount = album?.playCount; const playCount = album?.playCount;
const releasePrefix = originalDifferentFromRelease const releasePrefix = originalDifferentFromRelease ? t('page.albumDetail.released') : '♫';
? t('page.albumDetail.released', { postProcess: 'sentenceCase' })
: '♫';
const releaseYearPrefix = originalYearDifferentFromRelease const releaseYearPrefix = originalYearDifferentFromRelease
? t('page.albumDetail.released', { postProcess: 'sentenceCase' }) ? t('page.albumDetail.released')
: '♫'; : '♫';
if (album.originalDate) { if (album.originalDate) {
@@ -38,8 +38,8 @@ export const AlbumListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarge
const choice = useMemo(() => { const choice = useMemo(() => {
return target === GenreTarget.ALBUM return target === GenreTarget.ALBUM
? t('entity.album', { count: 2, postProcess: 'titleCase' }) ? t('entity.album', { count: 2 })
: t('entity.track', { count: 2, postProcess: 'titleCase' }); : t('entity.track', { count: 2 });
}, [target, t]); }, [target, t]);
const handleToggleGenreTarget = useCallback(() => { const handleToggleGenreTarget = useCallback(() => {
@@ -55,7 +55,7 @@ const AlbumListHeaderBadge = () => {
const PageTitle = ({ title }: { title?: string }) => { const PageTitle = ({ title }: { title?: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { pageKey } = useListContext(); const { pageKey } = useListContext();
const pageTitle = title || t('page.albumList.title', { postProcess: 'titleCase' }); const pageTitle = title || t('page.albumList.title');
switch (pageKey) { switch (pageKey) {
case ItemListKey.ALBUM_ARTIST_ALBUM: case ItemListKey.ALBUM_ARTIST_ALBUM:
@@ -80,7 +80,7 @@ export const JellyfinAlbumFilters = ({
const yesNoFilter = useMemo(() => { const yesNoFilter = useMemo(() => {
const filters = [ const filters = [
{ {
label: t('filter.isFavorited', { postProcess: 'sentenceCase' }), label: t('filter.isFavorited'),
onChange: (favoriteValue?: boolean) => { onChange: (favoriteValue?: boolean) => {
setFavorite(favoriteValue ?? null); setFavorite(favoriteValue ?? null);
}, },
@@ -90,7 +90,7 @@ export const JellyfinAlbumFilters = ({
if (query.artistIds?.length) { if (query.artistIds?.length) {
filters.push({ filters.push({
label: t('filter.isCompilation', { postProcess: 'sentenceCase' }), label: t('filter.isCompilation'),
onChange: (compilationValue?: boolean) => { onChange: (compilationValue?: boolean) => {
setCompilation(compilationValue ?? null); setCompilation(compilationValue ?? null);
}, },
@@ -228,16 +228,16 @@ export const JellyfinAlbumFilters = ({
return ( return (
<Group gap="xs" justify="space-between" w="100%"> <Group gap="xs" justify="space-between" w="100%">
<Text fw={500} size="sm"> <Text fw={500} size="sm">
{t('entity.artist', { count: 2, postProcess: 'sentenceCase' })} {t('entity.artist', { count: 2 })}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={[ data={[
{ {
label: t('common.filter_single', { postProcess: 'titleCase' }), label: t('common.filter_single'),
value: 'single', value: 'single',
}, },
{ {
label: t('common.filter_multiple', { postProcess: 'titleCase' }), label: t('common.filter_multiple'),
value: 'multi', value: 'multi',
}, },
]} ]}
@@ -253,16 +253,16 @@ export const JellyfinAlbumFilters = ({
return ( return (
<Group gap="xs" justify="space-between" w="100%"> <Group gap="xs" justify="space-between" w="100%">
<Text fw={500} size="sm"> <Text fw={500} size="sm">
{t('entity.genre', { count: 2, postProcess: 'sentenceCase' })} {t('entity.genre', { count: 2 })}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={[ data={[
{ {
label: t('common.filter_single', { postProcess: 'titleCase' }), label: t('common.filter_single'),
value: 'single', value: 'single',
}, },
{ {
label: t('common.filter_multiple', { postProcess: 'titleCase' }), label: t('common.filter_multiple'),
value: 'multi', value: 'multi',
}, },
]} ]}
@@ -320,7 +320,7 @@ export const JellyfinAlbumFilters = ({
<Group grow> <Group grow>
<NumberInput <NumberInput
hideControls={false} hideControls={false}
label={t('filter.fromYear', { postProcess: 'sentenceCase' })} label={t('filter.fromYear')}
max={2300} max={2300}
min={1700} min={1700}
onChange={(e) => debouncedHandleMinYearFilter(e)} onChange={(e) => debouncedHandleMinYearFilter(e)}
@@ -329,7 +329,7 @@ export const JellyfinAlbumFilters = ({
/> />
<NumberInput <NumberInput
hideControls={false} hideControls={false}
label={t('filter.toYear', { postProcess: 'sentenceCase' })} label={t('filter.toYear')}
max={2300} max={2300}
min={1700} min={1700}
onChange={(e) => debouncedHandleMaxYearFilter(e)} onChange={(e) => debouncedHandleMaxYearFilter(e)}
@@ -101,15 +101,15 @@ export const NavidromeAlbumFilters = ({
const segmentedControlData = useMemo( const segmentedControlData = useMemo(
() => [ () => [
{ {
label: t('common.none', { postProcess: 'titleCase' }), label: t('common.none'),
value: 'none', value: 'none',
}, },
{ {
label: t('common.yes', { postProcess: 'titleCase' }), label: t('common.yes'),
value: 'true', value: 'true',
}, },
{ {
label: t('common.no', { postProcess: 'titleCase' }), label: t('common.no'),
value: 'false', value: 'false',
}, },
], ],
@@ -119,7 +119,7 @@ export const NavidromeAlbumFilters = ({
const toggleFilters = useMemo( const toggleFilters = useMemo(
() => [ () => [
{ {
label: t('filter.isRecentlyPlayed', { postProcess: 'sentenceCase' }), label: t('filter.isRecentlyPlayed'),
onChange: (e: ChangeEvent<HTMLInputElement>) => { onChange: (e: ChangeEvent<HTMLInputElement>) => {
const recentlyPlayed = e.currentTarget.checked ? true : undefined; const recentlyPlayed = e.currentTarget.checked ? true : undefined;
setRecentlyPlayed(recentlyPlayed ?? null); setRecentlyPlayed(recentlyPlayed ?? null);
@@ -239,16 +239,16 @@ export const NavidromeAlbumFilters = ({
return ( return (
<Group gap="xs" justify="space-between" w="100%"> <Group gap="xs" justify="space-between" w="100%">
<Text fw={500} size="sm"> <Text fw={500} size="sm">
{t('entity.artist', { count: 2, postProcess: 'sentenceCase' })} {t('entity.artist', { count: 2 })}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={[ data={[
{ {
label: t('common.filter_single', { postProcess: 'titleCase' }), label: t('common.filter_single'),
value: 'single', value: 'single',
}, },
{ {
label: t('common.filter_multiple', { postProcess: 'titleCase' }), label: t('common.filter_multiple'),
value: 'multi', value: 'multi',
}, },
]} ]}
@@ -264,16 +264,16 @@ export const NavidromeAlbumFilters = ({
return ( return (
<Group gap="xs" justify="space-between" w="100%"> <Group gap="xs" justify="space-between" w="100%">
<Text fw={500} size="sm"> <Text fw={500} size="sm">
{t('entity.genre', { count: 2, postProcess: 'sentenceCase' })} {t('entity.genre', { count: 2 })}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={[ data={[
{ {
label: t('common.filter_single', { postProcess: 'titleCase' }), label: t('common.filter_single'),
value: 'single', value: 'single',
}, },
{ {
label: t('common.filter_multiple', { postProcess: 'titleCase' }), label: t('common.filter_multiple'),
value: 'multi', value: 'multi',
}, },
]} ]}
@@ -289,7 +289,7 @@ export const NavidromeAlbumFilters = ({
<Stack px="md" py="md"> <Stack px="md" py="md">
<Stack gap="xs"> <Stack gap="xs">
<Text size="sm" weight={500}> <Text size="sm" weight={500}>
{t('filter.isFavorited', { postProcess: 'sentenceCase' })} {t('filter.isFavorited')}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={segmentedControlData} data={segmentedControlData}
@@ -303,7 +303,7 @@ export const NavidromeAlbumFilters = ({
</Stack> </Stack>
<Stack gap="xs"> <Stack gap="xs">
<Text size="sm" weight={500}> <Text size="sm" weight={500}>
{t('filter.isRated', { postProcess: 'sentenceCase' })} {t('filter.isRated')}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={segmentedControlData} data={segmentedControlData}
@@ -317,7 +317,7 @@ export const NavidromeAlbumFilters = ({
</Stack> </Stack>
<Stack gap="xs"> <Stack gap="xs">
<Text size="sm" weight={500}> <Text size="sm" weight={500}>
{t('filter.isCompilation', { postProcess: 'sentenceCase' })} {t('filter.isCompilation')}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={segmentedControlData} data={segmentedControlData}
@@ -370,7 +370,7 @@ export const NavidromeAlbumFilters = ({
<Divider my="md" /> <Divider my="md" />
<NumberInput <NumberInput
hideControls={false} hideControls={false}
label={t('common.year', { postProcess: 'titleCase' })} label={t('common.year')}
max={5000} max={5000}
min={0} min={0}
onChange={(e) => debouncedHandleYearFilter(e)} onChange={(e) => debouncedHandleYearFilter(e)}
@@ -137,7 +137,7 @@ export const SubsonicAlbumFilters = ({
const genreFilterLabel = useMemo(() => { const genreFilterLabel = useMemo(() => {
return ( return (
<Text fw={500} size="sm"> <Text fw={500} size="sm">
{t('entity.genre', { count: 1, postProcess: 'sentenceCase' })} {t('entity.genre', { count: 1 })}
</Text> </Text>
); );
}, [t]); }, [t]);
@@ -145,7 +145,7 @@ export const SubsonicAlbumFilters = ({
const toggleFilters = useMemo( const toggleFilters = useMemo(
() => [ () => [
{ {
label: t('filter.isFavorited', { postProcess: 'sentenceCase' }), label: t('filter.isFavorited'),
onChange: (e: ChangeEvent<HTMLInputElement>) => { onChange: (e: ChangeEvent<HTMLInputElement>) => {
if (isFavoriteDisabled && e.target.checked) return; // Prevent setting if disabled if (isFavoriteDisabled && e.target.checked) return; // Prevent setting if disabled
const favoriteValue = e.target.checked ? true : undefined; const favoriteValue = e.target.checked ? true : undefined;
@@ -229,16 +229,16 @@ export const SubsonicAlbumFilters = ({
return ( return (
<Group gap="xs" justify="space-between" w="100%"> <Group gap="xs" justify="space-between" w="100%">
<Text fw={500} size="sm"> <Text fw={500} size="sm">
{t('entity.artist', { count: 2, postProcess: 'sentenceCase' })} {t('entity.artist', { count: 2 })}
</Text> </Text>
<SegmentedControl <SegmentedControl
data={[ data={[
{ {
label: t('common.filter_single', { postProcess: 'titleCase' }), label: t('common.filter_single'),
value: 'single', value: 'single',
}, },
{ {
label: t('common.filter_multiple', { postProcess: 'titleCase' }), label: t('common.filter_multiple'),
value: 'multi', value: 'multi',
}, },
]} ]}
@@ -302,7 +302,7 @@ export const SubsonicAlbumFilters = ({
<NumberInput <NumberInput
disabled={isYearDisabled} disabled={isYearDisabled}
hideControls={false} hideControls={false}
label={t('filter.fromYear', { postProcess: 'sentenceCase' })} label={t('filter.fromYear')}
max={5000} max={5000}
min={0} min={0}
onChange={(e) => debouncedHandleMinYearFilter(e)} onChange={(e) => debouncedHandleMinYearFilter(e)}
@@ -311,7 +311,7 @@ export const SubsonicAlbumFilters = ({
<NumberInput <NumberInput
disabled={isYearDisabled} disabled={isYearDisabled}
hideControls={false} hideControls={false}
label={t('filter.toYear', { postProcess: 'sentenceCase' })} label={t('filter.toYear')}
max={5000} max={5000}
min={0} min={0}
onChange={(e) => debouncedHandleMaxYearFilter(e)} onChange={(e) => debouncedHandleMaxYearFilter(e)}
@@ -234,7 +234,7 @@ const DummyAlbumDetailRoute = () => {
<Group mr={5}> <Group mr={5}>
<Icon fill="error" icon="error" size={30} /> <Icon fill="error" icon="error" size={30} />
</Group> </Group>
<h2>{t('error.badAlbum', { postProcess: 'sentenceCase' })}</h2> <h2>{t('error.badAlbum')}</h2>
</Center> </Center>
</section> </section>
</div> </div>
@@ -43,6 +43,7 @@ import { searchLibraryItems } from '/@/renderer/features/shared/utils';
import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { import {
ArtistItem, ArtistItem,
@@ -75,7 +76,6 @@ import { TextInput } from '/@/shared/components/text-input/text-input';
import { TextTitle } from '/@/shared/components/text-title/text-title'; import { TextTitle } from '/@/shared/components/text-title/text-title';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value'; import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value';
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
import { useLocalStorage } from '/@/shared/hooks/use-local-storage'; import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
import { import {
Album, Album,
@@ -142,11 +142,7 @@ const AlbumArtistActionButtons = ({
size="compact-md" size="compact-md"
variant="transparent" variant="transparent"
> >
{String( {String(t('player.artistRadio')).toUpperCase()}
t('player.artistRadio', {
postProcess: 'sentenceCase',
}),
).toUpperCase()}
</Button> </Button>
)} )}
</Group> </Group>
@@ -414,9 +410,7 @@ const AlbumArtistMetadataTopSongsContent = ({
<div className={styles.albumSectionTitle}> <div className={styles.albumSectionTitle}>
<Group> <Group>
<TextTitle fw={700} order={3}> <TextTitle fw={700} order={3}>
{t('page.albumArtistDetail.topSongs', { {t('page.albumArtistDetail.topSongs')}
postProcess: 'sentenceCase',
})}
</TextTitle> </TextTitle>
{!isLoading && <Badge>{songs.length}</Badge>} {!isLoading && <Badge>{songs.length}</Badge>}
</Group> </Group>
@@ -431,9 +425,7 @@ const AlbumArtistMetadataTopSongsContent = ({
uppercase uppercase
variant="subtle" variant="subtle"
> >
{t('page.albumArtistDetail.viewAll', { {t('page.albumArtistDetail.viewAll')}
postProcess: 'sentenceCase',
})}
</Button> </Button>
{songs.length > 0 && ( {songs.length > 0 && (
<ActionIconGroup> <ActionIconGroup>
@@ -485,9 +477,7 @@ const AlbumArtistMetadataTopSongsContent = ({
flex={1} flex={1}
leftSection={<Icon icon="search" />} leftSection={<Icon icon="search" />}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('common.search', { placeholder={t('common.search')}
postProcess: 'sentenceCase',
})}
radius="xl" radius="xl"
rightSection={ rightSection={
searchTerm ? ( searchTerm ? (
@@ -510,15 +500,11 @@ const AlbumArtistMetadataTopSongsContent = ({
<SegmentedControl <SegmentedControl
data={[ data={[
{ {
label: t('page.albumArtistDetail.topSongsCommunity', { label: t('page.albumArtistDetail.topSongsCommunity'),
postProcess: 'sentenceCase',
}),
value: 'community', value: 'community',
}, },
{ {
label: t('page.albumArtistDetail.topSongsPersonal', { label: t('page.albumArtistDetail.topSongsPersonal'),
postProcess: 'sentenceCase',
}),
value: 'personal', value: 'personal',
}, },
]} ]}
@@ -716,9 +702,7 @@ const AlbumArtistMetadataFavoriteSongs = ({
<div className={styles.albumSectionTitle}> <div className={styles.albumSectionTitle}>
<Group> <Group>
<TextTitle fw={700} order={3}> <TextTitle fw={700} order={3}>
{t('page.albumArtistDetail.favoriteSongs', { {t('page.albumArtistDetail.favoriteSongs')}
postProcess: 'sentenceCase',
})}
</TextTitle> </TextTitle>
{!isLoading && <Badge>{songs.length}</Badge>} {!isLoading && <Badge>{songs.length}</Badge>}
</Group> </Group>
@@ -736,9 +720,7 @@ const AlbumArtistMetadataFavoriteSongs = ({
uppercase uppercase
variant="subtle" variant="subtle"
> >
{t('page.albumArtistDetail.viewAll', { {t('page.albumArtistDetail.viewAll')}
postProcess: 'sentenceCase',
})}
</Button> </Button>
{songs.length > 0 && ( {songs.length > 0 && (
<ActionIconGroup> <ActionIconGroup>
@@ -790,9 +772,7 @@ const AlbumArtistMetadataFavoriteSongs = ({
flex={1} flex={1}
leftSection={<Icon icon="search" />} leftSection={<Icon icon="search" />}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('common.search', { placeholder={t('common.search')}
postProcess: 'sentenceCase',
})}
radius="xl" radius="xl"
rightSection={ rightSection={
searchTerm ? ( searchTerm ? (
@@ -941,9 +921,7 @@ const AlbumArtistMetadataExternalLinks = ({
<Grid.Col order={order} span={12}> <Grid.Col order={order} span={12}>
<Stack gap="xs"> <Stack gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase"> <Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('common.externalLinks', { {t('common.externalLinks')}
postProcess: 'sentenceCase',
})}
</Text> </Text>
<Group gap="xs"> <Group gap="xs">
{lastFM && ( {lastFM && (
@@ -1092,9 +1070,7 @@ const AlbumArtistMetadataSimilarArtists = ({
() => ( () => (
<div className={styles.similarArtistsTitle}> <div className={styles.similarArtistsTitle}>
<TextTitle fw={700} order={3}> <TextTitle fw={700} order={3}>
{t('page.albumArtistDetail.relatedArtists', { {t('page.albumArtistDetail.relatedArtists')}
postProcess: 'sentenceCase',
})}
</TextTitle> </TextTitle>
<div className={styles.albumSectionDividerContainer}> <div className={styles.albumSectionDividerContainer}>
<div className={styles.albumSectionDivider} /> <div className={styles.albumSectionDivider} />
@@ -1214,7 +1190,7 @@ export const AlbumArtistDetailContent = ({
artistSongsLink={artistSongsLink} artistSongsLink={artistSongsLink}
onArtistRadio={handleArtistRadio} onArtistRadio={handleArtistRadio}
/> />
<Grid gutter="2xl"> <Grid gap="2xl">
<AlbumArtistMetadataGenres <AlbumArtistMetadataGenres
genres={detailQuery.data?.genres} genres={detailQuery.data?.genres}
order={genresOrder} order={genresOrder}
@@ -1432,7 +1408,7 @@ const AlbumSection = memo(function AlbumSection({
{hasMoreAlbums && !showAll && ( {hasMoreAlbums && !showAll && (
<Group justify="center" w="100%"> <Group justify="center" w="100%">
<Button onClick={() => setShowAll(true)} variant="subtle"> <Button onClick={() => setShowAll(true)} variant="subtle">
{t('action.viewMore', { postProcess: 'sentenceCase' })} {t('action.viewMore')}
</Button> </Button>
</Group> </Group>
)} )}
@@ -1521,7 +1497,7 @@ const ArtistAlbums = ({ albumsQuery, order }: ArtistAlbumsProps) => {
flex={1} flex={1}
leftSection={<Icon icon="search" />} leftSection={<Icon icon="search" />}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('common.search', { postProcess: 'sentenceCase' })} placeholder={t('common.search')}
radius="xl" radius="xl"
ref={searchInputRef} ref={searchInputRef}
rightSection={ rightSection={
@@ -1585,17 +1561,13 @@ function GroupingTypeSelector() {
isSelected={groupingType === 'all'} isSelected={groupingType === 'all'}
onClick={() => setAlbumArtistDetailGroupingType('all')} onClick={() => setAlbumArtistDetailGroupingType('all')}
> >
{t('page.albumArtistDetail.groupingTypeAll', { {t('page.albumArtistDetail.groupingTypeAll')}
postProcess: 'sentenceCase',
})}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
isSelected={groupingType === 'primary'} isSelected={groupingType === 'primary'}
onClick={() => setAlbumArtistDetailGroupingType('primary')} onClick={() => setAlbumArtistDetailGroupingType('primary')}
> >
{t('page.albumArtistDetail.groupingTypePrimary', { {t('page.albumArtistDetail.groupingTypePrimary')}
postProcess: 'sentenceCase',
})}
</DropdownMenu.Item> </DropdownMenu.Item>
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
@@ -24,10 +24,7 @@ export const AlbumArtistDetailFavoriteSongsListHeader = ({
<LibraryHeaderBar ignoreMaxWidth> <LibraryHeaderBar ignoreMaxWidth>
<LibraryHeaderBar.PlayButton itemType={LibraryItem.SONG} songs={data} /> <LibraryHeaderBar.PlayButton itemType={LibraryItem.SONG} songs={data} />
<LibraryHeaderBar.Title order={2}> <LibraryHeaderBar.Title order={2}>
{t('page.albumArtistDetail.favoriteSongsFrom', { {t('page.albumArtistDetail.favoriteSongsFrom', { title })}
postProcess: 'titleCase',
title,
})}
</LibraryHeaderBar.Title> </LibraryHeaderBar.Title>
<Badge> <Badge>
{itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount} {itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount}
@@ -24,10 +24,7 @@ export const AlbumArtistDetailTopSongsListHeader = ({
<LibraryHeaderBar ignoreMaxWidth> <LibraryHeaderBar ignoreMaxWidth>
<LibraryHeaderBar.PlayButton itemType={LibraryItem.SONG} songs={data} /> <LibraryHeaderBar.PlayButton itemType={LibraryItem.SONG} songs={data} />
<LibraryHeaderBar.Title order={2}> <LibraryHeaderBar.Title order={2}>
{t('page.albumArtistDetail.topSongsFrom', { {t('page.albumArtistDetail.topSongsFrom', { title })}
postProcess: 'titleCase',
title,
})}
</LibraryHeaderBar.Title> </LibraryHeaderBar.Title>
<Badge> <Badge>
{itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount} {itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount}
@@ -19,7 +19,7 @@ interface AlbumArtistListHeaderProps {
export const AlbumArtistListHeader = ({ title }: AlbumArtistListHeaderProps) => { export const AlbumArtistListHeader = ({ title }: AlbumArtistListHeaderProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const pageTitle = title || t('page.albumArtistList.title', { postProcess: 'titleCase' }); const pageTitle = title || t('page.albumArtistList.title');
return ( return (
<Stack gap={0}> <Stack gap={0}>
@@ -19,7 +19,7 @@ interface ArtistListHeaderProps {
export const ArtistListHeader = ({ title }: ArtistListHeaderProps) => { export const ArtistListHeader = ({ title }: ArtistListHeaderProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const pageTitle = title || t('entity.artist', { count: 2, postProcess: 'titleCase' }); const pageTitle = title || t('entity.artist', { count: 2 });
return ( return (
<Stack gap={0}> <Stack gap={0}>
@@ -13,6 +13,15 @@ export type GroupingType = 'all' | 'primary';
const PRIMARY_RELEASE_TYPES = ['album', 'broadcast', 'ep', 'other', 'single']; const PRIMARY_RELEASE_TYPES = ['album', 'broadcast', 'ep', 'other', 'single'];
const getNormalizedReleaseTypes = (album: Album): string[] => {
const rawReleaseTypes = [...(album.releaseTypes || []), album.releaseType || ''];
const normalizedReleaseTypes = rawReleaseTypes
.map((type) => type.trim().toLowerCase())
.filter(Boolean);
return [...new Set(normalizedReleaseTypes)];
};
export const groupAlbumsByReleaseType = ( export const groupAlbumsByReleaseType = (
albums: Album[], albums: Album[],
routeId: string, routeId: string,
@@ -44,10 +53,9 @@ export const groupAlbumsByReleaseType = (
} }
// Group by all release types // Group by all release types
const releaseTypes = album.releaseTypes || []; const normalizedTypes = getNormalizedReleaseTypes(album);
if (releaseTypes.length > 0) { if (normalizedTypes.length > 0) {
// Sort release types: primaries first (alphabetically), then secondaries (alphabetically) // Sort release types: primaries first (alphabetically), then secondaries (alphabetically)
const normalizedTypes = releaseTypes.map((type) => type.toLowerCase());
const primaryTypes = normalizedTypes const primaryTypes = normalizedTypes
.filter((type) => PRIMARY_RELEASE_TYPES.includes(type)) .filter((type) => PRIMARY_RELEASE_TYPES.includes(type))
.sort(); .sort();
@@ -92,8 +100,7 @@ export const groupAlbumsByReleaseType = (
return acc; return acc;
} }
const releaseTypes = album.releaseTypes || []; const normalizedTypes = getNormalizedReleaseTypes(album);
const normalizedTypes = releaseTypes.map((type) => type.toLowerCase());
let matchedType: null | string = null; let matchedType: null | string = null;
@@ -107,6 +114,8 @@ export const groupAlbumsByReleaseType = (
matchedType = 'broadcast'; matchedType = 'broadcast';
} else if (normalizedTypes.includes('other')) { } else if (normalizedTypes.includes('other')) {
matchedType = 'other'; matchedType = 'other';
} else if (normalizedTypes.length > 0) {
matchedType = normalizedTypes[0];
} else { } else {
matchedType = 'album'; matchedType = 'album';
} }
@@ -173,77 +182,43 @@ export const getArtistAlbumsGrouped = (
const getDisplayNameForType = (releaseType: string): string => { const getDisplayNameForType = (releaseType: string): string => {
switch (releaseType) { switch (releaseType) {
case 'album': case 'album':
return t('releaseType.primary.album', { return t('releaseType.primary.album');
postProcess: 'sentenceCase',
});
case 'appears-on': case 'appears-on':
return t('page.albumArtistDetail.appearsOn', { return t('page.albumArtistDetail.appearsOn');
postProcess: 'sentenceCase',
});
case 'audiobook': case 'audiobook':
return t('releaseType.secondary.audiobook', { return t('releaseType.secondary.audiobook');
postProcess: 'sentenceCase',
});
case 'audio drama': case 'audio drama':
return t('releaseType.secondary.audioDrama', { return t('releaseType.secondary.audioDrama');
postProcess: 'sentenceCase',
});
case 'broadcast': case 'broadcast':
return t('releaseType.primary.broadcast', { return t('releaseType.primary.broadcast');
postProcess: 'sentenceCase',
});
case 'compilation': case 'compilation':
return t('releaseType.secondary.compilation', { return t('releaseType.secondary.compilation');
postProcess: 'sentenceCase',
});
case 'demo': case 'demo':
return t('releaseType.secondary.demo', { return t('releaseType.secondary.demo');
postProcess: 'sentenceCase',
});
case 'dj-mix': case 'dj-mix':
return t('releaseType.secondary.djMix', { return t('releaseType.secondary.djMix');
postProcess: 'sentenceCase',
});
case 'ep': case 'ep':
return t('releaseType.primary.ep', { return t('releaseType.primary.ep', {
postProcess: 'upperCase', postProcess: 'upperCase',
}); });
case 'field recording': case 'field recording':
return t('releaseType.secondary.fieldRecording', { return t('releaseType.secondary.fieldRecording');
postProcess: 'sentenceCase',
});
case 'interview': case 'interview':
return t('releaseType.secondary.interview', { return t('releaseType.secondary.interview');
postProcess: 'sentenceCase',
});
case 'live': case 'live':
return t('releaseType.secondary.live', { return t('releaseType.secondary.live');
postProcess: 'sentenceCase',
});
case 'mixtape/street': case 'mixtape/street':
return t('releaseType.secondary.mixtape', { return t('releaseType.secondary.mixtape');
postProcess: 'sentenceCase',
});
case 'other': case 'other':
return t('releaseType.primary.other', { return t('releaseType.primary.other');
postProcess: 'sentenceCase',
});
case 'remix': case 'remix':
return t('releaseType.secondary.remix', { return t('releaseType.secondary.remix');
postProcess: 'sentenceCase',
});
case 'single': case 'single':
return t('releaseType.primary.single', { return t('releaseType.primary.single');
postProcess: 'sentenceCase',
});
case 'soundtrack': case 'soundtrack':
return t('releaseType.secondary.soundtrack', { return t('releaseType.secondary.soundtrack');
postProcess: 'sentenceCase',
});
case 'spokenword': case 'spokenword':
return t('releaseType.secondary.spokenWord', { return t('releaseType.secondary.spokenWord');
postProcess: 'sentenceCase',
});
default: default:
return titleCase(releaseType); return titleCase(releaseType);
} }
@@ -292,11 +267,11 @@ export const getArtistAlbumsGrouped = (
const types = releaseType.split('/'); const types = releaseType.split('/');
return types.some((type) => { return types.some((type) => {
const enumValue = releaseTypeToEnumMap[type]; const enumValue = releaseTypeToEnumMap[type];
return enumValue ? enabledReleaseTypeEnums.has(enumValue) : false; return enumValue ? enabledReleaseTypeEnums.has(enumValue) : true;
}); });
} }
const enumValue = releaseTypeToEnumMap[releaseType]; const enumValue = releaseTypeToEnumMap[releaseType];
return enumValue ? enabledReleaseTypeEnums.has(enumValue) : false; return enumValue ? enabledReleaseTypeEnums.has(enumValue) : true;
}; };
const releaseTypeEntries = Object.entries(albumsByReleaseType) const releaseTypeEntries = Object.entries(albumsByReleaseType)
@@ -165,7 +165,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
); );
const handleAddToPlaylist = useCallback( const handleAddToPlaylist = useCallback(
async (playlistId: string) => { async (playlistId: string, playlistName: string) => {
if (items.length === 0 || !serverId) return; if (items.length === 0 || !serverId) return;
try { try {
@@ -202,12 +202,8 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
} }
if (allSongIds.length === 0) { if (allSongIds.length === 0) {
toast.success({ toast.info({
message: t('form.addToPlaylist.success', { message: t('form.addToPlaylist.noneAdded', { playlist: playlistName }),
message: 0,
numOfPlaylists: 1,
postProcess: 'sentenceCase',
}),
}); });
return; return;
} }
@@ -245,12 +241,8 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
} }
if (songsToAdd.length === 0) { if (songsToAdd.length === 0) {
toast.success({ toast.info({
message: t('form.addToPlaylist.success', { message: t('form.addToPlaylist.noneAdded', { playlist: playlistName }),
message: 0,
numOfPlaylists: 1,
postProcess: 'sentenceCase',
}),
}); });
return; return;
} }
@@ -269,7 +261,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
onError: (err) => { onError: (err) => {
toast.error({ toast.error({
message: err.message, message: err.message,
title: t('error.genericError', { postProcess: 'sentenceCase' }), title: t('error.genericError'),
}); });
}, },
onSuccess: () => {}, onSuccess: () => {},
@@ -280,13 +272,12 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
message: t('form.addToPlaylist.success', { message: t('form.addToPlaylist.success', {
message: songsToAdd.length, message: songsToAdd.length,
numOfPlaylists: 1, numOfPlaylists: 1,
postProcess: 'sentenceCase',
}), }),
}); });
} catch (error) { } catch (error) {
toast.error({ toast.error({
message: (error as Error).message, message: (error as Error).message,
title: t('error.genericError', { postProcess: 'sentenceCase' }), title: t('error.genericError'),
}); });
} }
}, },
@@ -349,7 +340,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
}, },
modal: 'addToPlaylist', modal: 'addToPlaylist',
size: 'lg', size: 'lg',
title: t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' }), title: t('page.contextMenu.addToPlaylist'),
}); });
}, [itemType, items, t]); }, [itemType, items, t]);
@@ -363,12 +354,11 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
onKeyDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()}
pb="xs" pb="xs"
placeholder={t('common.search', { postProcess: 'sentenceCase' })} placeholder={t('common.search')}
rightSection={ rightSection={
<Tooltip <Tooltip
label={t('form.addToPlaylist.input', { label={t('form.addToPlaylist.input', {
context: 'skipDuplicates', context: 'skipDuplicates',
postProcess: 'titleCase',
})} })}
> >
<Checkbox <Checkbox
@@ -395,7 +385,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
onSelect={handleOpenModal} onSelect={handleOpenModal}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' })} {t('page.contextMenu.addToPlaylist')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent stickyContent={searchInput}> <ContextMenu.SubmenuContent stickyContent={searchInput}>
@@ -405,15 +395,15 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
</ContextMenu.Item> </ContextMenu.Item>
)} )}
{playlistsQuery.isError && ( {playlistsQuery.isError && (
<ContextMenu.Item disabled> <ContextMenu.Item disabled>{t('error.genericError')}</ContextMenu.Item>
{t('error.genericError', { postProcess: 'sentenceCase' })}
</ContextMenu.Item>
)} )}
{recentPlaylist && ( {recentPlaylist && (
<> <>
<ContextMenu.Item <ContextMenu.Item
key={recentPlaylist.id} key={recentPlaylist.id}
onSelect={() => handleAddToPlaylist(recentPlaylist.id)} onSelect={() =>
handleAddToPlaylist(recentPlaylist.id, recentPlaylist.name)
}
> >
{recentPlaylist.name} {recentPlaylist.name}
</ContextMenu.Item> </ContextMenu.Item>
@@ -421,14 +411,12 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
</> </>
)} )}
{filteredPlaylists.length === 0 && !playlistsQuery.isLoading && ( {filteredPlaylists.length === 0 && !playlistsQuery.isLoading && (
<ContextMenu.Item disabled> <ContextMenu.Item disabled>{t('common.noResultsFromQuery')}</ContextMenu.Item>
{t('common.noResultsFromQuery', { postProcess: 'sentenceCase' })}
</ContextMenu.Item>
)} )}
{filteredPlaylists.map((playlist) => ( {filteredPlaylists.map((playlist) => (
<ContextMenu.Item <ContextMenu.Item
key={playlist.id} key={playlist.id}
onSelect={() => handleAddToPlaylist(playlist.id)} onSelect={() => handleAddToPlaylist(playlist.id, playlist.name)}
> >
{playlist.name} {playlist.name}
</ContextMenu.Item> </ContextMenu.Item>
@@ -38,12 +38,12 @@ export const DeletePlaylistAction = ({ disabled, items }: DeletePlaylistActionPr
navigate(AppRoute.PLAYLISTS, { replace: true }); navigate(AppRoute.PLAYLISTS, { replace: true });
toast.success({ toast.success({
message: t('action.deletePlaylist', { postProcess: 'sentenceCase' }), message: t('action.deletePlaylist'),
}); });
} catch (err: any) { } catch (err: any) {
toast.error({ toast.error({
message: err.message, message: err.message,
title: t('error.genericError', { postProcess: 'sentenceCase' }), title: t('error.genericError'),
}); });
} }
@@ -56,10 +56,10 @@ export const DeletePlaylistAction = ({ disabled, items }: DeletePlaylistActionPr
openModal({ openModal({
children: ( children: (
<ConfirmModal onConfirm={handleDeletePlaylist}> <ConfirmModal onConfirm={handleDeletePlaylist}>
<Text>{t('common.areYouSure', { postProcess: 'sentenceCase' })}</Text> <Text>{t('common.areYouSure')}</Text>
</ConfirmModal> </ConfirmModal>
), ),
title: t('form.deletePlaylist.title', { postProcess: 'sentenceCase' }), title: t('form.deletePlaylist.title'),
}); });
}, [handleDeletePlaylist, items.length, t]); }, [handleDeletePlaylist, items.length, t]);
@@ -67,7 +67,7 @@ export const DeletePlaylistAction = ({ disabled, items }: DeletePlaylistActionPr
return ( return (
<ContextMenu.Item disabled={disabled} leftIcon="remove" onSelect={openDeletePlaylistModal}> <ContextMenu.Item disabled={disabled} leftIcon="remove" onSelect={openDeletePlaylistModal}>
{t('action.deletePlaylist', { postProcess: 'sentenceCase' })} {t('action.deletePlaylist')}
</ContextMenu.Item> </ContextMenu.Item>
); );
}; };
@@ -37,7 +37,7 @@ export const DownloadAction = ({ ids }: DownloadActionProps) => {
return ( return (
<ContextMenu.Item disabled={ids.length > 1} leftIcon="download" onSelect={onSelect}> <ContextMenu.Item disabled={ids.length > 1} leftIcon="download" onSelect={onSelect}>
{t('page.contextMenu.download', { postProcess: 'sentenceCase' })} {t('page.contextMenu.download')}
</ContextMenu.Item> </ContextMenu.Item>
); );
}; };
@@ -27,7 +27,7 @@ export const EditPlaylistAction = ({ disabled, items }: EditPlaylistActionProps)
return ( return (
<ContextMenu.Item disabled={disabled} leftIcon="edit" onSelect={handleEditPlaylist}> <ContextMenu.Item disabled={disabled} leftIcon="edit" onSelect={handleEditPlaylist}>
{t('action.editPlaylist', { postProcess: 'sentenceCase' })} {t('action.editPlaylist')}
</ContextMenu.Item> </ContextMenu.Item>
); );
}; };
@@ -35,15 +35,14 @@ export const GetInfoAction = ({ disabled, items }: GetInfoActionProps) => {
}, },
title: title:
filteredItems.length === 1 filteredItems.length === 1
? filteredItems[0]?.name || ? filteredItems[0]?.name || t('page.contextMenu.showDetails')
t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }) : t('page.contextMenu.showDetails'),
: t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }),
}); });
}, [items, server, t]); }, [items, server, t]);
return ( return (
<ContextMenu.Item disabled={disabled} leftIcon="info" onSelect={onSelect}> <ContextMenu.Item disabled={disabled} leftIcon="info" onSelect={onSelect}>
{t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' })} {t('page.contextMenu.showDetails')}
</ContextMenu.Item> </ContextMenu.Item>
); );
}; };
@@ -72,13 +72,13 @@ export const GoToAction = ({ items }: GoToActionProps) => {
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('page.contextMenu.goTo', { postProcess: 'sentenceCase' })} {t('page.contextMenu.goTo')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
{hasAlbum && ( {hasAlbum && (
<ContextMenu.Item leftIcon="album" onSelect={handleGoToAlbum}> <ContextMenu.Item leftIcon="album" onSelect={handleGoToAlbum}>
{t('page.contextMenu.goToAlbum', { postProcess: 'sentenceCase' })} {t('page.contextMenu.goToAlbum')}
</ContextMenu.Item> </ContextMenu.Item>
)} )}
{albumArtists.map((albumArtist) => ( {albumArtists.map((albumArtist) => (
@@ -87,7 +87,7 @@ export const GoToAction = ({ items }: GoToActionProps) => {
leftIcon="artist" leftIcon="artist"
onSelect={() => handleGoToAlbumArtist(albumArtist.id)} onSelect={() => handleGoToAlbumArtist(albumArtist.id)}
> >
{`${t('page.contextMenu.goTo', { postProcess: 'sentenceCase' })} ${albumArtist.name}`} {`${t('page.contextMenu.goTo')} ${albumArtist.name}`}
</ContextMenu.Item> </ContextMenu.Item>
))} ))}
</ContextMenu.SubmenuContent> </ContextMenu.SubmenuContent>
@@ -33,18 +33,18 @@ export const MoveQueueItemsAction = ({ items }: MoveQueueItemsActionProps) => {
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('page.contextMenu.moveItems', { postProcess: 'sentenceCase' })} {t('page.contextMenu.moveItems')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
<ContextMenu.Item leftIcon="arrowUpToLine" onSelect={handleMoveToTop}> <ContextMenu.Item leftIcon="arrowUpToLine" onSelect={handleMoveToTop}>
{t('page.contextMenu.moveToTop', { postProcess: 'sentenceCase' })} {t('page.contextMenu.moveToTop')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handleMoveToNext}> <ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handleMoveToNext}>
{t('page.contextMenu.moveToNext', { postProcess: 'sentenceCase' })} {t('page.contextMenu.moveToNext')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="arrowDownToLine" onSelect={handleMoveToBottom}> <ContextMenu.Item leftIcon="arrowDownToLine" onSelect={handleMoveToBottom}>
{t('page.contextMenu.moveToBottom', { postProcess: 'sentenceCase' })} {t('page.contextMenu.moveToBottom')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuContent> </ContextMenu.SubmenuContent>
</ContextMenu.Submenu> </ContextMenu.Submenu>
@@ -75,28 +75,28 @@ export const PlayAction = ({ ids, itemType, songs }: PlayActionProps) => {
onSelect={defaultPlayAction} onSelect={defaultPlayAction}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
<ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayNow}> <ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayNow}>
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayNext}> <ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayNext}>
{t('player.addNext', { postProcess: 'sentenceCase' })} {t('player.addNext')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayLast}> <ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayLast}>
{t('player.addLast', { postProcess: 'sentenceCase' })} {t('player.addLast')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Divider /> <ContextMenu.Divider />
<ContextMenu.Item leftIcon="mediaShuffle" onSelect={handlePlayShuffled}> <ContextMenu.Item leftIcon="mediaShuffle" onSelect={handlePlayShuffled}>
{t('player.shuffle', { postProcess: 'sentenceCase' })} {t('player.shuffle')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayNextShuffled}> <ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayNextShuffled}>
{t('player.addNextShuffled', { postProcess: 'sentenceCase' })} {t('player.addNextShuffled')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayLastShuffled}> <ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayLastShuffled}>
{t('player.addLastShuffled', { postProcess: 'sentenceCase' })} {t('player.addLastShuffled')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuContent> </ContextMenu.SubmenuContent>
</ContextMenu.Submenu> </ContextMenu.Submenu>
@@ -73,18 +73,18 @@ export const PlayAlbumRadioAction = ({ album, disabled }: PlayAlbumRadioActionPr
onSelect={defaultPlayAlbumRadioAction} onSelect={defaultPlayAlbumRadioAction}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('player.albumRadio', { postProcess: 'sentenceCase' })} {t('player.albumRadio')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
<ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayAlbumRadioNow}> <ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayAlbumRadioNow}>
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayAlbumRadioNext}> <ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayAlbumRadioNext}>
{t('player.addNext', { postProcess: 'sentenceCase' })} {t('player.addNext')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayAlbumRadioLast}> <ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayAlbumRadioLast}>
{t('player.addLast', { postProcess: 'sentenceCase' })} {t('player.addLast')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuContent> </ContextMenu.SubmenuContent>
</ContextMenu.Submenu> </ContextMenu.Submenu>
@@ -73,18 +73,18 @@ export const PlayArtistRadioAction = ({ artist, disabled }: PlayArtistRadioActio
onSelect={defaultPlayArtistRadioAction} onSelect={defaultPlayArtistRadioAction}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('player.artistRadio', { postProcess: 'sentenceCase' })} {t('player.artistRadio')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
<ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayArtistRadioNow}> <ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayArtistRadioNow}>
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayArtistRadioNext}> <ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayArtistRadioNext}>
{t('player.addNext', { postProcess: 'sentenceCase' })} {t('player.addNext')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayArtistRadioLast}> <ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayArtistRadioLast}>
{t('player.addLast', { postProcess: 'sentenceCase' })} {t('player.addLast')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuContent> </ContextMenu.SubmenuContent>
</ContextMenu.Submenu> </ContextMenu.Submenu>
@@ -72,18 +72,18 @@ export const PlayTrackRadioAction = ({ disabled, song }: PlayTrackRadioActionPro
onSelect={defaultPlayTrackRadioAction} onSelect={defaultPlayTrackRadioAction}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('player.trackRadio', { postProcess: 'sentenceCase' })} {t('player.trackRadio')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
<ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayTrackRadioNow}> <ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayTrackRadioNow}>
{t('player.play', { postProcess: 'sentenceCase' })} {t('player.play')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayTrackRadioNext}> <ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayTrackRadioNext}>
{t('player.addNext', { postProcess: 'sentenceCase' })} {t('player.addNext')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayTrackRadioLast}> <ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayTrackRadioLast}>
{t('player.addLast', { postProcess: 'sentenceCase' })} {t('player.addLast')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuContent> </ContextMenu.SubmenuContent>
</ContextMenu.Submenu> </ContextMenu.Submenu>
@@ -39,12 +39,12 @@ export const RemoveFromPlaylistAction = ({ items }: RemoveFromPlaylistActionProp
}); });
toast.success({ toast.success({
message: t('action.removeFromPlaylist', { postProcess: 'sentenceCase' }), message: t('action.removeFromPlaylist'),
}); });
} catch (err: any) { } catch (err: any) {
toast.error({ toast.error({
message: err.message, message: err.message,
title: t('error.genericError', { postProcess: 'sentenceCase' }), title: t('error.genericError'),
}); });
} }
@@ -57,10 +57,10 @@ export const RemoveFromPlaylistAction = ({ items }: RemoveFromPlaylistActionProp
openModal({ openModal({
children: ( children: (
<ConfirmModal onConfirm={handleRemoveFromPlaylist}> <ConfirmModal onConfirm={handleRemoveFromPlaylist}>
<Text>{t('common.areYouSure', { postProcess: 'sentenceCase' })}</Text> <Text>{t('common.areYouSure')}</Text>
</ConfirmModal> </ConfirmModal>
), ),
title: t('action.removeFromPlaylist', { postProcess: 'sentenceCase' }), title: t('action.removeFromPlaylist'),
}); });
}, [handleRemoveFromPlaylist, ids, playlistId, t]); }, [handleRemoveFromPlaylist, ids, playlistId, t]);
@@ -68,7 +68,7 @@ export const RemoveFromPlaylistAction = ({ items }: RemoveFromPlaylistActionProp
return ( return (
<ContextMenu.Item leftIcon="remove" onSelect={openRemoveFromPlaylistModal}> <ContextMenu.Item leftIcon="remove" onSelect={openRemoveFromPlaylistModal}>
{t('action.removeFromPlaylist', { postProcess: 'sentenceCase' })} {t('action.removeFromPlaylist')}
</ContextMenu.Item> </ContextMenu.Item>
); );
}; };
@@ -19,7 +19,7 @@ export const RemoveFromQueueAction = ({ items }: RemoveFromQueueActionProps) =>
return ( return (
<ContextMenu.Item leftIcon="remove" onSelect={onSelect}> <ContextMenu.Item leftIcon="remove" onSelect={onSelect}>
{t('action.removeFromQueue', { postProcess: 'sentenceCase' })} {t('action.removeFromQueue')}
</ContextMenu.Item> </ContextMenu.Item>
); );
}; };
@@ -51,15 +51,15 @@ export const SetFavoriteAction = ({ ids, itemType }: SetFavoriteActionProps) =>
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('common.favorite', { postProcess: 'sentenceCase' })} {t('common.favorite')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
<ContextMenu.Item leftIcon="favorite" onSelect={handleAddToFavorites}> <ContextMenu.Item leftIcon="favorite" onSelect={handleAddToFavorites}>
{t('action.addToFavorites', { postProcess: 'sentenceCase' })} {t('action.addToFavorites')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item leftIcon="unfavorite" onSelect={handleRemoveFromFavorites}> <ContextMenu.Item leftIcon="unfavorite" onSelect={handleRemoveFromFavorites}>
{t('action.removeFromFavorites', { postProcess: 'sentenceCase' })} {t('action.removeFromFavorites')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuContent> </ContextMenu.SubmenuContent>
</ContextMenu.Submenu> </ContextMenu.Submenu>
@@ -41,7 +41,7 @@ export const SetRatingAction = ({ ids, itemType }: SetRatingActionProps) => {
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}
rightIcon="arrowRightS" rightIcon="arrowRightS"
> >
{t('action.setRating', { postProcess: 'sentenceCase' })} {t('action.setRating')}
</ContextMenu.Item> </ContextMenu.Item>
</ContextMenu.SubmenuTarget> </ContextMenu.SubmenuTarget>
<ContextMenu.SubmenuContent> <ContextMenu.SubmenuContent>
@@ -37,13 +37,13 @@ export const ShareAction = ({ ids, itemType }: ShareActionProps) => {
resourceType, resourceType,
}, },
modal: 'shareItem', modal: 'shareItem',
title: t('page.contextMenu.shareItem', { postProcess: 'titleCase' }), title: t('page.contextMenu.shareItem'),
}); });
}, [ids, resourceType, t]); }, [ids, resourceType, t]);
return ( return (
<ContextMenu.Item leftIcon="share" onSelect={onSelect}> <ContextMenu.Item leftIcon="share" onSelect={onSelect}>
{t('page.contextMenu.shareItem', { postProcess: 'sentenceCase' })} {t('page.contextMenu.shareItem')}
</ContextMenu.Item> </ContextMenu.Item>
); );
}; };

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