Compare commits

..

12 Commits

Author SHA1 Message Date
oHaoDaio d23f7619ec chore: Support pnpm v11 2026-06-09 22:15:47 -07:00
Kendall Garner 44de6f2207 chore: upgrade depdencencies (#2133)
* upgrade depdencencies
2026-06-09 21:18:47 -07:00
BotBlake f6f25154a1 add "enhancement" to exempt-issue-labels (#2061) 2026-06-09 11:22:36 -07:00
Pedro Vieira 880516069d fix: preserve infinite list cache on component remount (fixes random sort reshuffling) (#2097)
* fix: preserve infinite list cache on component remount

When browsing with random sort, navigating to a detail view and coming
back would reshuffle the list. This happens because the list component
unmounts, losing its internal ref guard, and the reset effect re-fetches
all pages — returning a new random order from the server.
2026-06-09 11:21:02 -07:00
Hosted Weblate 95970183db Translated using Weblate
Currently translated at 100.0% (1245 of 1245 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% (1245 of 1245 strings) (Catalan)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/

Translated using Weblate

Currently translated at 100.0% (1245 of 1245 strings) (Estonian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/et/

Co-authored-by: Ondo <SparkyOndo@proton.me>
Co-authored-by: rimasx <riks_12@hot.ee>
Co-authored-by: 為什麼不加空格 <c++23@users.noreply.hosted.weblate.org>
2026-06-09 07:01:35 +00:00
Hosted Weblate 3a2c952d2a Translated using Weblate
Currently translated at 75.7% (943 of 1245 strings) (Estonian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/et/

Co-authored-by: rimasx <riks_12@hot.ee>
2026-06-07 22:01:21 +02:00
jeffvli 46b94a83f1 fix reportPlayback event chain (#2131)
- properly send stopped event on song change
- properly send both starting and playing evento n song change instead of only starting
2026-06-06 18:41:59 -07:00
jeffvli 40a1d1438d re-add missing scrobble on playback start when using playback report (#2131) 2026-06-06 18:07:53 -07:00
Hosted Weblate 905088cae7 Translated using Weblate
Currently translated at 85.6% (1066 of 1245 strings) (Russian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ru/

Translated using Weblate

Currently translated at 64.2% (800 of 1245 strings) (Estonian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/et/

Translated using Weblate

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

Co-authored-by: Vladimir Levitskiy <lvdm87@gmail.com>
Co-authored-by: klodrik <klodrik@zoominn.no>
Co-authored-by: rimasx <riks_12@hot.ee>
2026-06-06 12:01:23 +00:00
Hosted Weblate 705b375dab Translated using Weblate
Currently translated at 20.1% (251 of 1245 strings) (Korean)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ko/

Translated using Weblate

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

Translated using Weblate

Currently translated at 43.2% (538 of 1245 strings) (Japanese)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/

Translated using Weblate

Currently translated at 57.7% (719 of 1245 strings) (Estonian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/et/

Co-authored-by: Benjamin <ben@iipython.dev>
Co-authored-by: Vladimir Levitskiy <lvdm87@gmail.com>
Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com>
Co-authored-by: rimasx <riks_12@hot.ee>
2026-06-05 11:01:20 +00:00
Mathieu Lemay 0b537b07ee Add zenburn theme (#2112) 2026-06-04 09:55:17 -07:00
Hosted Weblate dfa6198bdd Translated using Weblate
Currently translated at 20.0% (249 of 1245 strings) (Korean)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ko/

Translated using Weblate

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

Translated using Weblate

Currently translated at 26.9% (336 of 1245 strings) (Japanese)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/

Translated using Weblate

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

Translated using Weblate

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

Translated using Weblate

Currently translated at 46.9% (584 of 1245 strings) (Estonian)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/et/

Translated using Weblate

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

Co-authored-by: Benjamin <ben@iipython.dev>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Co-authored-by: rimasx <riks_12@hot.ee>
Co-authored-by: skajmer <skajmer@protonmail.com>
2026-06-04 10:01:27 +00:00
42 changed files with 4088 additions and 2618 deletions
-4
View File
@@ -44,8 +44,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
@@ -129,8 +127,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
-4
View File
@@ -19,8 +19,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
@@ -123,8 +121,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
-2
View File
@@ -16,8 +16,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
-2
View File
@@ -16,8 +16,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
-2
View File
@@ -38,8 +38,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
-2
View File
@@ -16,8 +16,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
-2
View File
@@ -16,8 +16,6 @@ jobs:
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
+1 -1
View File
@@ -43,6 +43,6 @@ jobs:
stale-issue-label: 'stale'
exempt-issue-labels: 'keep,security'
exempt-issue-labels: 'keep,security,enhancement'
stale-pr-label: 'stale'
exempt-pr-labels: 'keep,security'
-2
View File
@@ -12,8 +12,6 @@ jobs:
- name: Install Node.js and PNPM
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
+1 -4
View File
@@ -3,10 +3,7 @@ FROM node:23-alpine AS builder
WORKDIR /app
# Copy package.json first to cache node_modules
COPY package.json pnpm-lock.yaml .
# Match CI (pnpm/action-setup version: 10). Latest pnpm 11 fails install without approve-builds.
RUN corepack enable && corepack prepare pnpm@10 --activate
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .
RUN pnpm install
+1 -1
View File
@@ -63,7 +63,7 @@ linux:
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: '1.0.2'
appimage: '1.0.3'
npmRebuild: false
+1 -1
View File
@@ -63,7 +63,7 @@ linux:
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: '1.0.2'
appimage: '1.0.3'
npmRebuild: false
publish:
+1 -1
View File
@@ -63,7 +63,7 @@ linux:
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: '1.0.2'
appimage: '1.0.3'
npmRebuild: false
afterAllArtifactBuild: scripts/after-all-artifact-build.mjs
+42 -42
View File
@@ -69,79 +69,78 @@
"postversion": "node ./scripts/update-app-stream.mjs"
},
"resolutions": {
"react-router": "7.14.0",
"xml2js": "0.5.0"
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "1.7.7",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.5",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.2.0",
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"@mantine/colors-generator": "^9.1.1",
"@mantine/core": "^9.1.1",
"@mantine/dates": "^9.1.1",
"@mantine/form": "^9.1.1",
"@mantine/hooks": "^9.1.1",
"@mantine/modals": "^9.1.1",
"@mantine/notifications": "^9.1.1",
"@radix-ui/react-context-menu": "^2.2.16",
"@tanstack/react-query": "^5.96.2",
"@tanstack/react-query-devtools": "^5.96.2",
"@tanstack/react-query-persist-client": "^5.96.2",
"@mantine/colors-generator": "^9.3.0",
"@mantine/core": "^9.3.0",
"@mantine/dates": "^9.3.0",
"@mantine/form": "^9.3.0",
"@mantine/hooks": "^9.3.0",
"@mantine/modals": "^9.3.0",
"@mantine/notifications": "^9.3.0",
"@radix-ui/react-context-menu": "^2.3.0",
"@tanstack/react-query": "5.96.2",
"@tanstack/react-query-devtools": "5.96.2",
"@tanstack/react-query-persist-client": "5.96.2",
"@ts-rest/core": "^3.52.1",
"@wavesurfer/react": "^1.0.12",
"@xhayper/discord-rpc": "^1.3.3",
"@xhayper/discord-rpc": "^1.3.4",
"audiomotion-analyzer": "^4.5.4",
"axios": "^1.14.0",
"axios": "^1.17.0",
"butterchurn": "3.0.0-beta.5",
"butterchurn-presets": "3.0.0-beta.4",
"cheerio": "^1.2.0",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"dayjs": "^1.11.20",
"dompurify": "^3.3.3",
"dayjs": "^1.11.21",
"dompurify": "^3.4.8",
"electron-debug": "^3.2.0",
"electron-localshortcut": "^3.2.1",
"electron-log": "^5.4.3",
"electron-log": "^5.4.4",
"electron-store": "^8.2.0",
"electron-updater": "^6.8.3",
"electron-updater": "^6.8.9",
"fast-average-color": "9.5.0",
"fast-xml-parser": "^5.5.10",
"fast-xml-parser": "^5.8.0",
"format-duration": "^3.0.2",
"fuse.js": "^7.2.0",
"fuse.js": "^7.4.2",
"i18next": "^25.10.10",
"icecast-metadata-stats": "^0.1.12",
"idb-keyval": "^6.2.2",
"idb-keyval": "^6.2.5",
"immer": "^10.2.0",
"is-electron": "^2.2.2",
"lodash": "^4.18.1",
"md5": "^2.3.0",
"motion": "^12.38.0",
"motion": "^12.40.0",
"mpris-service": "^2.1.2",
"nanoid": "^3.3.11",
"nanoid": "^3.3.12",
"node-mpv": "github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f",
"overlayscrollbars": "^2.14.0",
"overlayscrollbars": "^2.16.0",
"overlayscrollbars-react": "^0.5.6",
"qs": "^6.15.0",
"react": "^19.2.4",
"qs": "^6.15.2",
"react": "^19.2.7",
"react-call": "^1.8.2",
"react-dom": "^19.2.4",
"react-dom": "^19.2.7",
"react-error-boundary": "^5.0.0",
"react-i18next": "^16.6.6",
"react-icons": "^5.6.0",
"react-player": "^2.16.1",
"react-router": "^7.14.0",
"react-router": "^7.17.0",
"react-split-pane": "^3.2.0",
"react-virtualized-auto-sizer": "^1.0.26",
"react-window": "1.8.11",
"react-window-v2": "npm:react-window@^2.2.7",
"semver": "^7.7.4",
"semver": "^7.8.2",
"string-to-color": "^2.2.2",
"wavesurfer.js": "^7.12.5",
"ws": "^8.20.0",
"wavesurfer.js": "^7.12.7",
"ws": "^8.21.0",
"zod": "^3.25.76",
"zustand": "^5.0.12"
"zustand": "^5.0.14"
},
"devDependencies": {
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
@@ -150,8 +149,8 @@
"@types/electron-localshortcut": "^3.1.3",
"@types/lodash": "^4.17.24",
"@types/md5": "^2.3.6",
"@types/node": "^24.12.2",
"@types/react": "^19.2.14",
"@types/node": "^24.13.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@types/react-window": "^1.8.8",
"@types/source-map-support": "^0.5.10",
@@ -160,32 +159,33 @@
"babel-plugin-react-compiler": "^1.0.0",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"electron": "^41.7.0",
"electron-builder": "^26.8.2",
"electron": "^41.7.1",
"electron-builder": "^26.15.0",
"electron-devtools-installer": "^4.0.0",
"electron-vite": "^4.0.1",
"eslint": "^9.39.4",
"eslint-plugin-perfectionist": "^4.15.1",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.4.26",
"i18next-parser": "^9.4.0",
"postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1",
"prettier": "^3.8.1",
"prettier": "^3.8.3",
"prettier-plugin-packagejson": "^2.5.22",
"stylelint": "^16.26.1",
"stylelint-config-css-modules": "^4.6.0",
"stylelint-config-recess-order": "^7.7.0",
"stylelint-config-standard": "^39.0.1",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite": "^7.3.5",
"vite-plugin-conditional-import": "^0.1.7",
"vite-plugin-dynamic-import": "^1.6.0",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-pwa": "^1.2.0"
"vite-plugin-pwa": "^1.3.0"
},
"packageManager": "pnpm@11.5.2",
"pnpm": {
"onlyBuiltDependencies": [
"electron",
+2186 -2388
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
allowBuilds:
abstract-socket: true
electron: true
electron-winstaller: true
esbuild: true
overrides:
"xml2js": "0.5.0"
"react-router": "7.14.0"
+2 -1
View File
@@ -1182,7 +1182,8 @@
"sleepTimer_setCustom": "Configura el temporitzador",
"sleepTimer_cancel": "Cancel·la el temporitzador",
"albumRadio": "Ràdio d'àlbums",
"scrobbleForceSubmit": "Força l'scrobble"
"scrobbleForceSubmit": "Força l'scrobble",
"sleepTimer_endOfAlbum": "Final de l'àlbum actual"
},
"error": {
"credentialsRequired": "Credencials requerides",
+5 -4
View File
@@ -49,7 +49,8 @@
"sleepTimer_setCustom": "Nastavit časovač",
"sleepTimer_cancel": "Zrušit časovač",
"albumRadio": "Rádio alba",
"scrobbleForceSubmit": "Vynutit scrobble"
"scrobbleForceSubmit": "Vynutit scrobble",
"sleepTimer_endOfAlbum": "Konec aktuálního alba"
},
"setting": {
"crossfadeStyle_description": "Vyberte způsob prolnutí u přehrávače zvuku",
@@ -1169,7 +1170,7 @@
"playlist_few": "playlisty",
"playlist_other": "Playlisty",
"artist_one": "Umělec",
"artist_few": "umělci",
"artist_few": "Umělci",
"artist_other": "Umělci",
"folderWithCount_one": "{{count}} složka",
"folderWithCount_few": "{{count}} složky",
@@ -1187,7 +1188,7 @@
"albumWithCount_few": "{{count}} alba",
"albumWithCount_other": "{{count}} alb",
"favorite_one": "Oblíbený",
"favorite_few": "oblíbené",
"favorite_few": "Oblíbené",
"favorite_other": "Oblíbené",
"artistWithCount_one": "{{count}} umělec",
"artistWithCount_few": "{{count}} umělci",
@@ -1197,7 +1198,7 @@
"folder_other": "Složky",
"smartPlaylist": "Chytrý $t(entity.playlist, {\"count\": 1})",
"album_one": "Album",
"album_few": "alba",
"album_few": "Alba",
"album_other": "Alba",
"genreWithCount_one": "{{count}} žánr",
"genreWithCount_few": "{{count}} žánry",
+2 -1
View File
@@ -49,7 +49,8 @@
"sleepTimer_endOfSong": "Fin de la canción actual",
"sleepTimer": "Temporizador de apagado",
"albumRadio": "Radio del álbum",
"scrobbleForceSubmit": "Forzar scrobble"
"scrobbleForceSubmit": "Forzar scrobble",
"sleepTimer_endOfAlbum": "Fin del álbum actual"
},
"setting": {
"crossfadeStyle_description": "Selecciona el estilo de crossfade a usar por el reproductor de audio",
+1100 -2
View File
File diff suppressed because it is too large Load Diff
+22 -6
View File
@@ -49,7 +49,8 @@
"sleepTimer_setCustom": "Définir le minuteur",
"sleepTimer_cancel": "Annuler le minuteur",
"albumRadio": "Radio d'album",
"scrobbleForceSubmit": "Forcer le scrobble"
"scrobbleForceSubmit": "Forcer le scrobble",
"sleepTimer_endOfAlbum": "Fin de l'album actuel"
},
"action": {
"editPlaylist": "Éditer $t(entity.playlist, {\"count\": 1})",
@@ -226,7 +227,8 @@
"rename": "Renommer",
"newVersionAvailable": "Une nouvelle version est disponible",
"numberOfResults": "{{numberOfResults}} résultats",
"back": "Retour"
"back": "Retour",
"openFolder": "Ouvrir le dossier"
},
"error": {
"remotePortWarning": "Redémarrer le serveur pour appliquer le nouveau port",
@@ -728,7 +730,7 @@
"translationTargetLanguage": "Langue cible de traduction",
"trayEnabled": "Afficher la barre d’état système",
"translationApiProvider_description": "Fournisseur d'API pour la traduction",
"customCss_description": "Contenu CSS personnalisé. Remarque : les propriétés 'content' et les URL distantes ne sont pas autorisées. Un aperçu de votre contenu est affiché ci-dessous. Des champs supplémentaires que vous n'avez pas définis sont présents en raison d'assainissement",
"customCss_description": "Contenu CSS personnalisé. Remarque : les propriétés 'content' et les URL distantes ne sont pas autorisées. Un aperçu de votre contenu est affiché ci-dessous. Des champs supplémentaires que vous n'avez pas définis sont présents en raison d'assainissement. Application de Bureau uniquement: feishin lit et écrit le fichier custom.css dans le répertoire de configuration de l'application et le recharge lorsque celui-ci est modifié",
"translationApiKey": "Clé API de traduction",
"translationTargetLanguage_description": "Langue cible pour la traduction",
"trayEnabled_description": "Afficher/masquer licône/le menu dans la barre d’état système. si désactivé, désactive également la réduction/fermeture vers la barre d’état système",
@@ -813,7 +815,7 @@
"queryBuilderCustomFields_description": "Ajouter des champs personnalisés à utiliser dans les constructeurs de requêtes",
"autoDJ": "DJ auto",
"autoDJ_itemCount": "Nombre d'entrée",
"autoDJ_itemCount_description": "Le nombre d'entrées tentées d'être ajoutées à la file d'attente lorsque le DJ auto est activé",
"autoDJ_itemCount_description": "Le nombre d'entrées tentées d'être ajoutées à la file d'attente",
"autoDJ_timing": "Timing",
"autoDJ_timing_description": "Le nombre de titres restant dans la file d'attente avant le déclenchement du DJ auto",
"followCurrentSong_description": "Défiler automatiquement la file d'attente jusqu'au titre en cours",
@@ -915,7 +917,16 @@
"sidebarPlaylistFolderTreeIndent": "Indentation de l'arbre",
"sidebarPlaylistMode_description": "Comment chaque liste de lecture est affichée dans la barre latérale",
"sidebarPlaylistMode": "Mode de liste de lecture de la barre latérale",
"sidebarPlaylistMode_optionCompact": "Compacte"
"sidebarPlaylistMode_optionCompact": "Compacte",
"autoDJ_mode": "Mode",
"autoDJ_mode_albums": "Albums",
"autoDJ_mode_description": "Choisissez d'ajouter des titres ou des albums entiers à la file d'attente",
"autoDJ_mode_songs": "Titres",
"autoDJ_enabled": "Activer le DJ auto",
"autoDJ_albumStrategy": "Mode de sélection d'album",
"autoDJ_songStrategy": "Mode de sélection de titre",
"autoDJ_strategy_option_library_random": "Aléatoire",
"autoDJ_strategy_option_similar": "Similaire"
},
"form": {
"deletePlaylist": {
@@ -1009,7 +1020,12 @@
"input_played": "Filtre de lecture",
"input_played_optionAll": "Toutes les pistes",
"input_played_optionUnplayed": "Seulement les pistes non jouées",
"input_played_optionPlayed": "Seulement les pistes jouées"
"input_played_optionPlayed": "Seulement les pistes jouées",
"input_kind_songs": "Titres",
"input_kind_albums": "Albums",
"input_kind": "Sélections aléatoires",
"input_limit_albums": "Combien d'albums?",
"input_limit_songs": "Combien de titres?"
},
"createRadioStation": {
"success": "Station radio créée avec succès",
+39 -23
View File
@@ -49,7 +49,8 @@
"albumRadio": "アルバム・ラジオ",
"artistRadio": "アーティストラジオ",
"trackRadio": "ラジオを追跡する",
"scrobbleForceSubmit": "強制 Scrobble"
"scrobbleForceSubmit": "強制 Scrobble",
"sleepTimer_endOfAlbum": "現在のアルバムの終了"
},
"setting": {
"crossfadeStyle_description": "オーディオプレーヤーが使用するクロスフェードのスタイルを選択します",
@@ -430,12 +431,21 @@
"playerbarWaveformStretch_description": "波形を伸縮させて、利用可能なスペースを埋めます",
"preventSuspendOnPlayback_description": "音楽再生中にアプリケーションが停止しないようにします",
"preventSuspendOnPlayback": "再生の中断を防止する",
"hotkey_listShowPlayingSong": "再生中の曲をリストに表示"
"hotkey_listShowPlayingSong": "再生中の曲をリストに表示",
"autoDJ_mode": "モード",
"autoDJ_mode_albums": "アルバム",
"autoDJ_mode_description": "キューに曲を追加するか、アルバム全体を追加するかを選択してください。",
"autoDJ_mode_songs": "曲",
"autoDJ_enabled": "Auto DJを有効にする",
"autoDJ_albumStrategy": "アルバム選択モード",
"autoDJ_songStrategy": "選曲モード",
"autoDJ_strategy_option_library_random": "ランダム",
"autoDJ_strategy_option_similar": "類似"
},
"action": {
"editPlaylist": "$t(entity.playlist, {\"count\": 1}) を編集",
"goToPage": "ページへ移動",
"moveToTop": "先頭に移動",
"moveToTop": "一番上へ移動",
"clearQueue": "キューをクリア",
"addToFavorites": "$t(entity.favorite, {\"count\": 2}) に追加",
"addToPlaylist": "$t(entity.playlist, {\"count\": 1}) に追加",
@@ -446,9 +456,9 @@
"deletePlaylist": "$t(entity.playlist, {\"count\": 1}) を削除",
"removeFromQueue": "キューから削除",
"deselectAll": "すべて選択解除",
"moveToBottom": "末尾に移動",
"setRating": "評価を設定する",
"toggleSmartPlaylistEditor": "$t(entity.smartPlaylist) エディタ切り替え",
"moveToBottom": "一番下へ移動",
"setRating": "評価を設定",
"toggleSmartPlaylistEditor": "$t(entity.smartPlaylist) エディターを切り替え",
"removeFromFavorites": "$t(entity.favorite, {\"count\": 2}) から削除",
"openIn": {
"lastfm": "Last.fm で開く",
@@ -457,9 +467,9 @@
"listenbrainz": "ListenBrainz で開く",
"qobuz": "Qobuz で開く"
},
"moveToNext": "次",
"moveToNext": "次へ進む",
"downloadStarted": "{{count}} 曲のダウンロードを開始しました",
"moveItems": "を移動",
"moveItems": "項目を移動",
"shuffle": "シャッフル",
"shuffleAll": "すべてシャッフル",
"shuffleSelected": "選択した曲をシャッフル",
@@ -471,28 +481,28 @@
"moveDown": "下に移動",
"holdToMoveToTop": "押し続けると一番上に移動します",
"holdToMoveToBottom": "押し続けると一番下に移動します",
"openApplicationDirectory": "アプリケーションディレクトリを開く",
"openApplicationDirectory": "アプリディレクトリを開く",
"selectRangeOfItems": "項目の範囲を選択",
"addOrRemoveFromSelection": "選択に追加または削除",
"addOrRemoveFromSelection": "選択に追加または選択から除外",
"goToCurrent": "現在の項目へ移動",
"collapseAllFolders": "すべてのフォルダーを折りたたむ",
"expandAllFolders": "すべてのフォルダーを展開する"
},
"common": {
"backward": "戻る",
"backward": "逆行",
"increase": "増加",
"rating": "評価",
"bpm": "BPM",
"refresh": "再読み込み",
"unknown": "不明",
"areYouSure": "実行しすか?",
"areYouSure": "実行してもよろしいですか",
"edit": "編集",
"favorite": "お気に入り",
"left": "左側",
"save": "保存",
"right": "右側",
"currentSong": "現在の $t(entity.track, {\"count\": 1})",
"collapse": "折りたた",
"collapse": "折りたた",
"trackNumber": "トラック",
"descending": "降順",
"add": "追加",
@@ -534,7 +544,7 @@
"confirm": "確認",
"resetToDefault": "デフォルトにリセット",
"home": "ホーム",
"comingSoon": "近日利用可能になる予定です…",
"comingSoon": "近日公開…",
"reset": "リセット",
"channel_other": "チャンネル",
"disable": "無効",
@@ -543,7 +553,7 @@
"menu": "メニュー",
"restartRequired": "再起動が必要です",
"previousSong": "前の $t(entity.track, {\"count\": 1})",
"noResultsFromQuery": "条件にマッチするものがありません",
"noResultsFromQuery": "クエリに一致する結果がありません",
"quit": "終了",
"expand": "展開",
"search": "検索",
@@ -553,11 +563,11 @@
"random": "ランダム",
"size": "サイズ",
"biography": "バイオグラフィー",
"note": "ノート",
"note": "注記",
"explicitStatus": "明示的なステータス",
"additionalParticipants": "追加参加者",
"newVersion": "新しいバージョン ({{version}}) がインストールされました",
"viewReleaseNotes": "リリースノートを表示する",
"viewReleaseNotes": "リリースノートを表示",
"bitDepth": "ビット深度",
"close": "閉じる",
"codec": "コーデック",
@@ -565,7 +575,7 @@
"sampleRate": "サンプルレート",
"preview": "プレビュー",
"private": "プライベート",
"public": "パブリック",
"public": "公開",
"share": "共有",
"tags": "タグ",
"trackGain": "トラックゲイン",
@@ -598,7 +608,8 @@
"newVersionAvailable": "新しいバージョンが利用可能です",
"numberOfResults": "{{numberOfResults}} 件の結果",
"grouping": "グループ化",
"back": "戻る"
"back": "戻る",
"openFolder": "フォルダーを開く"
},
"table": {
"config": {
@@ -710,7 +721,7 @@
}
},
"error": {
"remotePortWarning": "新たなポート設定を適用するためサーバーを再起動してください",
"remotePortWarning": "新しいポート設定を反映させるには、サーバーを再起動してください",
"systemFontError": "システムフォントを取得する際にエラーが発生しました",
"playbackError": "メディアの再生開始時にエラーが発生しました",
"remotePortError": "リモートサーバーのポート設定時にエラーが発生しました",
@@ -725,7 +736,7 @@
"serverNotSelectedError": "サーバーが選択されていません",
"remoteDisableError": "リモートサーバーを$t(common.disable)にする際にエラーが発生しました",
"mpvRequired": "MPV が必要です",
"audioDeviceFetchError": "オーディオデバイス取得にエラーが発生しました",
"audioDeviceFetchError": "オーディオデバイス取得しようとした際にエラーが発生しました",
"invalidServer": "無効なサーバー",
"loginRateError": "ログイン試行回数が多すぎます。数秒後に再試行してください",
"endpointNotImplementedError": "{{serverType}} にはエンドポイント {{endpoint}} が実装されていません",
@@ -733,7 +744,7 @@
"networkError": "ネットワークエラーが発生しました",
"notificationDenied": "通知の許可が拒否されました。この設定は効果がありません",
"openError": "ファイルを開けませんでした",
"badValue": "無効なオプション「{{value}}」。この値は存在しません",
"badValue": "無効なオプション「{{value}}」です。この値は存在しません",
"multipleServerSaveQueueError": "再生キューに現在のサーバーに存在しない曲が 1 曲以上あります。これはサポートされていません",
"noNetwork": "サーバーが利用できません",
"noNetworkDescription": "このサーバーに接続できませんでした",
@@ -1109,7 +1120,12 @@
"input_played_optionAll": "すべてのトラック",
"input_played_optionUnplayed": "未再生のトラックのみ",
"input_played_optionPlayed": "再生されたトラックのみ",
"input_played": "再生フィルター"
"input_played": "再生フィルター",
"input_kind_albums": "アルバム",
"input_kind_songs": "曲",
"input_kind": "ランダムピック",
"input_limit_albums": "アルバムは何枚ですか?",
"input_limit_songs": "何曲ですか?"
},
"saveQueue": {
"success": "プレイキューをサーバーに保存しました"
+244 -24
View File
@@ -17,7 +17,10 @@
"removeFromPlaylist": "$t(entity.playlist, {\"count\": 1})에서 제거",
"openIn": {
"musicbrainz": "MusicBrainz에서 보기",
"lastfm": "Last.fm에서 보기"
"lastfm": "Last.fm에서 보기",
"listenbrainz": "ListenBrainz에서 열기",
"qobuz": "Qobuz에서 열기",
"spotify": "Spotify에서 열기"
},
"viewPlaylists": "$t(entity.playlist, {\"count\": 2}) 보기",
"setRating": "평점 지정",
@@ -37,7 +40,10 @@
"shuffleAll": "모두 섞기",
"shuffleSelected": "선택항목 섞기",
"viewMore": "더 보기",
"openApplicationDirectory": "앱 디렉토리 열기"
"openApplicationDirectory": "앱 디렉토리 열기",
"goToCurrent": "현재 항목으로 이동",
"collapseAllFolders": "모든 폴더 접기",
"expandAllFolders": "모든 폴더 확장"
},
"common": {
"translation": "번역",
@@ -149,7 +155,18 @@
"sort": "정렬",
"gridRows": "행 그리드",
"tableColumns": "테이블 열",
"itemsMore": "{{count}}개 더"
"itemsMore": "{{count}}개 더",
"back": "뒤로",
"example": "예",
"openFolder": "폴더 열기",
"filter_single": "미혼",
"filter_multiple": "다중",
"grouping": "그룹화",
"mood": "기분",
"numberOfResults": "결과 {{numberOfResults}}개",
"retry": "다시 해 보다",
"rename": "이름 변경",
"newVersionAvailable": "새로운 버전이 나왔습니다"
},
"entity": {
"albumWithCount_other": "{{count}} 앨범",
@@ -197,7 +214,15 @@
"localFontAccessDenied": "로컬 글꼴에 접근 거부되었습니다",
"apiRouteError": "요청 보내기 실패",
"badValue": "옵션이 없습니다 {{value}}. 이 값은 더이상 존재하지 않습니다",
"notificationDenied": "알림에 대한 권한이 거부되었습니다. 이 설정은 변경되지 않습니다"
"notificationDenied": "알림에 대한 권한이 거부되었습니다. 이 설정은 변경되지 않습니다",
"invalidJson": "유효하지 않은 JSON",
"multipleServerSaveQueueError": "재생 대기열에 현재 서버에 속하지 않은 곡이 하나 이상 포함되어 있습니다. 이는 지원되지 않습니다",
"noNetwork": "서버를 이용할 수 없음",
"noNetworkDescription": "이 서버에 연결할 수 없습니다",
"playbackPausedDueToError": "오류로 인해 재생이 일시 중지되었습니다",
"saveQueueFailed": "큐 저장 실패",
"serverLockSingleServer": "서버가 잠겨 있을 때는 서버를 하나만 허용합니다",
"settingsSyncError": "렌더러와 메인 프로세스의 설정 간에 불일치가 발견되었습니다. 변경 사항을 적용하려면 애플리케이션을 다시 시작하십시오"
},
"filter": {
"title": "곡명",
@@ -222,7 +247,7 @@
"disc": "디스크",
"bitrate": "비트 전송률",
"biography": "바이오그래피",
"channels": "$t(common.channel_other)",
"channels": "$t(common.channel, {\"count\": 2})",
"duration": "길이",
"bpm": "BPM",
"albumCount": "$t(entity.album, {\"count\": 2}) 앨범수",
@@ -242,7 +267,10 @@
"songCount": "곡 갯수",
"toYear": "년도까지",
"trackNumber": "트랙",
"explicitStatus": "$t(common.explicitStatus)"
"explicitStatus": "$t(common.explicitStatus)",
"matchAnd": "그리고",
"matchOr": "또는",
"sortName": "이름 정렬"
},
"form": {
"addServer": {
@@ -258,7 +286,10 @@
"input_legacyAuthentication": "레거시 인증 사용",
"input_username": "유저 이름",
"input_preferInstantMix": "즉석 믹스 선호",
"input_preferInstantMixDescription": "비슷한 곳을 찾기 위해 즉석 믹스를 사용합니다. 이 명령을 수정하기 위한 플러그인을 설치한 경우 유용합니다"
"input_preferInstantMixDescription": "비슷한 곳을 찾기 위해 즉석 믹스를 사용합니다. 이 명령을 수정하기 위한 플러그인을 설치한 경우 유용합니다",
"input_preferRemoteUrl": "공개 URL 선호",
"input_remoteUrl": "공개 URL",
"input_remoteUrlPlaceholder": "선택 사항: 외부 기능을 위한 공개 URL"
},
"addToPlaylist": {
"input_skipDuplicates": "중복 건너뛰기",
@@ -266,7 +297,8 @@
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
"success": "$t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })에 $t(entity.trackWithCount, {\"count\": {{message}} })가 추가되었습니다",
"create": "$t(entity.playlist, {\"count\": 1}) {{playlist}} 생성",
"searchOrCreate": "$t(entity.playlist, {\"count\": 2}) 검색 또는 입력하여 새로 만들기"
"searchOrCreate": "$t(entity.playlist, {\"count\": 2}) 검색 또는 입력하여 새로 만들기",
"noneAdded": "$t(entity.playlist, {\"count\": 1}) '{{playlist}}'에 트랙이 추가되지 않았습니다"
},
"lyricSearch": {
"title": "가사 검색",
@@ -276,7 +308,11 @@
"queryEditor": {
"input_optionMatchAll": "모두 일치",
"input_optionMatchAny": "무엇이든 일치",
"title": "쿼리 편집기"
"title": "쿼리 편집기",
"addRuleGroup": "규칙 그룹 추가",
"removeRuleGroup": "규칙 그룹 제거",
"resetToDefault": "기본값으로 초기화",
"clearFilters": "필터 초기화"
},
"editPlaylist": {
"title": "$t(entity.playlist, {\"count\": 1}) 편집",
@@ -289,7 +325,9 @@
"success": "클립보드에 공유 링크를 복사했습니다 (또는 열어보려면 클릭하세요)",
"expireInvalid": "만료 날짜는 미래 날짜여야만 합니다",
"createFailed": "공유 링크를 생성하는데 실패하였습니다 (혹시 공유하기 설정되어 있나요?)",
"setExpiration": "만료 기간 설정하기"
"setExpiration": "만료 기간 설정하기",
"copyToClipboard": "클립보드로 복사: Ctrl+C, Enter",
"successMustClick": "공유가 성공적으로 생성되었습니다. 여기를 클릭하여 여세요"
},
"updateServer": {
"title": "서버 업데이트",
@@ -312,6 +350,44 @@
"enabled": "프라이빗 모드가 활성화되었습니다. 재생상태가 외부 서비스에 지금부터 노출되지 않습니다",
"disabled": "프라이빗 모드가 비활성화되었습니다. 재생상태가 외부서비스에서 지금부터 표시됩니다",
"title": "프라이빗 모드"
},
"largeFetchConfirmation": {
"title": "대기열에 항목을 추가하세요",
"description": "이 작업은 현재 필터링된 보기의 모든 항목을 추가합니다"
},
"createRadioStation": {
"success": "라디오 방송국이 성공적으로 생성되었습니다",
"title": "라디오 방송국 만들기",
"input_homepageUrl": "홈페이지 URL",
"input_name": "명의",
"input_streamUrl": "스트림 URL"
},
"editRadioStation": {
"success": "라디오 방송국이 성공적으로 업데이트되었습니다"
},
"lyricsExport": {
"export": "가사 내보내기",
"input_synced": "동기화된 가사 내보내기",
"input_offset": "$t(setting.lyricOffset)"
},
"saveQueue": {
"success": "재생 대기열을 서버에 저장했습니다"
},
"shuffleAll": {
"title": "무작위 재생",
"input_kind_albums": "앨범",
"input_kind_songs": "노래들",
"input_kind": "무작위 선택",
"input_limit_albums": "앨범이 몇 장인가요?",
"input_limit_songs": "몇 곡인가요?",
"input_genre": "$t(entity.genre, {\"count\": 1})",
"input_limit": "몇 곡인가요?",
"input_minYear": "연도부터",
"input_maxYear": "연도까지",
"input_played": "재생 필터",
"input_played_optionAll": "모든 트랙",
"input_played_optionUnplayed": "재생하지 않은 트랙만",
"input_played_optionPlayed": "재생된 트랙만"
}
},
"page": {
@@ -325,7 +401,13 @@
"collapseSidebar": "사이드바 줄이기",
"expandSidebar": "사이드바 확장",
"privateModeOff": "프라이빗 모드 끄기",
"privateModeOn": "프라이빗 모드 켜기"
"privateModeOn": "프라이빗 모드 켜기",
"commandPalette": "명령 팔레트 열기",
"quit": "$t(common.quit)",
"selectMusicFolder": "음악 폴더 선택",
"noMusicFolder": "음악 폴더가 선택되지 않았습니다",
"multipleMusicFolders": "{{count}}개의 음악 폴더가 선택되었습니다",
"settings": "$t(common.setting, {\"count\": 2})"
},
"manageServers": {
"title": "서버 설정하기",
@@ -350,7 +432,9 @@
"lyricGap": "가사 간격",
"lyricSize": "가사 크기",
"showLyricMatch": "가사 일치 표시",
"showLyricProvider": "가사 제공자 표시"
"showLyricProvider": "가사 제공자 표시",
"lyricOpacityNonActive": "비활성 가사 불투명도",
"lyricScaleNonActive": "비활성 서정적 척도"
},
"lyrics": "가사",
"related": "관련",
@@ -364,7 +448,27 @@
"shareItem": "공유",
"goToAlbum": "$t(entity.album, {\"count\": 1})으로 이동",
"goToAlbumArtist": "$t(entity.albumArtist, {\"count\": 1})으로 이동",
"showDetails": "추가정보"
"showDetails": "추가정보",
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"moveItems": "$t(action.moveItems)",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"play": "$t(player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"goTo": "이동"
},
"albumArtistDetail": {
"about": "{{artist}}에 대해",
@@ -375,7 +479,13 @@
"topSongs": "최고의 곡들",
"topSongsFrom": "{{title}}이 포함된 최고의 곡들",
"viewAll": "전부 보이기",
"viewAllTracks": "$t(entity.track, {\"count\": 2}) 전부 보이기"
"viewAllTracks": "$t(entity.track, {\"count\": 2}) 전부 보이기",
"favoriteSongs": "좋아하는 노래들",
"groupingTypeAll": "모든 릴리스 유형",
"groupingTypePrimary": "주요 릴리스 유형",
"topSongsCommunity": "공동체",
"topSongsPersonal": "개인의",
"favoriteSongsFrom": "{{title}}에서 가장 좋아하는 곡들"
},
"albumArtistList": {
"title": "$t(entity.albumArtist, {\"count\": 2})"
@@ -386,11 +496,14 @@
"released": "발매"
},
"albumList": {
"artistAlbums": "{{artist}}의 앨범"
"artistAlbums": "{{artist}}의 앨범",
"genreAlbums": "\"{{genre}}\" $t(entity.album, {\"count\": 2})",
"title": "$t(entity.album, {\"count\": 2})"
},
"genreList": {
"showAlbums": "$t(entity.genre, {\"count\": 1}) $t(entity.album, {\"count\": 2}) 표시",
"showTracks": "$t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2}) 표시"
"showTracks": "$t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2}) 표시",
"title": "$t(entity.genre, {\"count\": 2})"
},
"globalSearch": {
"commands": {
@@ -405,7 +518,9 @@
"mostPlayed": "자주 플레이된 곡",
"newlyAdded": "최근에 추가된 곡",
"recentlyPlayed": "최근에 플레이된 곡",
"recentlyReleased": "최근에 발매된 곡"
"recentlyReleased": "최근에 발매된 곡",
"genres": "$t(entity.genre, {\"count\": 2})",
"title": "$t(common.home)"
},
"itemDetail": {
"copyPath": "클립보드에 경로를 복사",
@@ -420,15 +535,71 @@
"generalTab": "일반",
"hotkeysTab": "단축키",
"playbackTab": "재생",
"windowTab": "윈도우"
"windowTab": "윈도우",
"analytics": "해석학",
"updates": "업데이트",
"cache": "은닉처",
"application": "애플리케이션",
"queryBuilder": "쿼리 빌더",
"theme": "테마",
"controls": "통제 수단",
"sidebar": "사이드바",
"exportImport": "가져오기/내보내기",
"audio": "오디오",
"lyrics": "가사",
"lyricsDisplay": "가사 표시",
"transcoding": "트랜스코딩",
"discord": "Discord",
"logger": "로거",
"playerFilters": "선수 필터"
},
"sidebar": {
"myLibrary": "내 라이브러리",
"nowPlaying": "재생중",
"shared": "공유 $t(entity.playlist, {\"count\": 2})"
"shared": "공유 $t(entity.playlist, {\"count\": 2})",
"albumArtists": "$t(entity.albumArtist, {\"count\": 2})",
"albums": "$t(entity.album, {\"count\": 2})",
"collections": "컬렉션",
"artists": "$t(entity.artist, {\"count\": 2})",
"favorites": "$t(entity.favorite, {\"count\": 2})",
"folders": "$t(entity.folder, {\"count\": 2})",
"genres": "$t(entity.genre, {\"count\": 2})",
"home": "$t(common.home)",
"radio": "$t(entity.radioStation, {\"count\": 2})",
"playlists": "$t(entity.playlist, {\"count\": 2})",
"search": "$t(common.search)",
"settings": "$t(common.setting, {\"count\": 2})",
"tracks": "$t(entity.track, {\"count\": 2})"
},
"trackList": {
"artistTracks": "{{artist}}의 음악"
"artistTracks": "{{artist}}의 음악",
"genreTracks": "\"{{genre}}\" $t(entity.track, {\"count\": 2})",
"title": "$t(entity.track, {\"count\": 2})"
},
"radioList": {
"title": "라디오 방송국"
},
"releasenotes": {
"commitsSinceStable": "{{stable}} 이후 커밋",
"noNewCommits": "이 범위에 새로운 커밋이 없습니다",
"noStableReleaseToCompare": "비교할 수 있는 안정화 릴리스가 없습니다"
},
"favorites": {
"title": "$t(entity.favorite, {\"count\": 2})"
},
"windowBar": {
"paused": "(일시 정지됨) ",
"privateMode": "(비공개 모드)"
},
"folderList": {
"title": "$t(entity.folder, {\"count\": 2})"
},
"playlistList": {
"title": "$t(entity.playlist, {\"count\": 2})"
},
"collections": {
"overrideExisting": "기존 항목 덮어쓰기",
"saveAsCollection": "컬렉션으로 저장"
}
},
"table": {
@@ -473,7 +644,25 @@
"toggleFullscreenPlayer": "전체화면으로 전환",
"unfavorite": "즐겨찾기 취소",
"pause": "멈춤",
"viewQueue": "대기열 보기"
"viewQueue": "대기열 보기",
"addLastShuffled": "마지막 (섞인)",
"addNextShuffled": "다음 (무작위)",
"albumRadio": "앨범 라디오",
"artistRadio": "아티스트 라디오",
"holdToShuffle": "길게 눌러 섞기",
"lyrics": "가사",
"restoreQueueFromServer": "서버에서 큐 복원",
"saveQueueToServer": "대기열을 서버에 저장",
"trackRadio": "라디오 추적",
"sleepTimer": "취침 타이머",
"sleepTimer_endOfSong": "현재 곡 종료",
"sleepTimer_endOfAlbum": "현재 앨범의 끝",
"sleepTimer_minutes": "{{count}}분",
"sleepTimer_hours": "{{count}}시간",
"sleepTimer_off": "끄다",
"sleepTimer_timeRemaining": "{{time}} 남음",
"sleepTimer_setCustom": "타이머 설정",
"sleepTimer_cancel": "타이머 취소"
},
"setting": {
"accentColor_description": "앱의 강조색상 설정",
@@ -482,7 +671,7 @@
"albumBackground": "앨범 배경이미지",
"albumBackgroundBlur_description": "앨범 배경이미지의 흐려짐 정도 조정",
"albumBackgroundBlur": "앨범배경이미지 흐려짐 크기",
"applicationHotkeys_description": "앱의 단축키 설정. 앱 전체에 적용되는 단축키 설정하기 위해서는 체크박스에 체크하세요(PC만 가능)",
"applicationHotkeys_description": "애플리케이션 단축키 설정합니다. 체크박스를 전환하여 전역 단축키 설정하세요(데스크톱 전용)",
"applicationHotkeys": "앱 단축키",
"artistBackground": "아티스트 배경이미지",
"artistBackground_description": "아티스트 페이지에 아티스트가 포함된 배경이미지를 추가",
@@ -492,7 +681,7 @@
"artistConfiguration_description": "앨범아티스트 페이지에 표시할 정보 및 순서 설정",
"audioDevice_description": "음악재생에 사용할 장치 선택(웹플레이어만 가능)",
"audioDevice": "오디오 장치",
"audioExclusiveMode_description": "단독재생모드 켜기. 이 모드에서는 일반적으로 시스템의 재생장치가 고정되며 MPV로만 오디오가 재생됩니다",
"audioExclusiveMode_description": "독점 출력 모드를 활성화합니다. 이 모드에서는 일반적으로 시스템의 오디오 출력이 차단되며, 오직 mpv만이 오디오를 출력할 수 있습니다. 이 모드가 활성화된 동안에는 비주얼라이저의 시스템 오디오 캡처 기능이 작동하지 않습니다",
"audioExclusiveMode": "오디오 단독재생모드",
"audioPlayer_description": "재생을 위한 오디오 플레이어 선택",
"audioPlayer": "오디오 플레이어",
@@ -505,7 +694,8 @@
"broadcast": "방송",
"ep": "ep앨범",
"other": "기타",
"single": "싱글"
"single": "싱글",
"album": "$t(entity.album, {\"count\": 1})"
},
"secondary": {
"audiobook": "오디오북",
@@ -521,5 +711,35 @@
"soundtrack": "사운드트랙",
"spokenWord": "보컬사운드"
}
},
"datetime": {
"minuteShort": "분",
"secondShort": "초",
"hourShort": "시간",
"dayShort": "일"
},
"filterOperator": {
"after": "~ 뒤에 있나요",
"afterDate": "(날짜) 이후입니까",
"before": "~보다 앞서 있다",
"beforeDate": "(날짜) 이전인가요",
"contains": "포함",
"endsWith": "~로 끝남",
"inPlaylist": "~ 안에 있다",
"inTheLast": "마지막에 있습니다",
"inTheRange": "범위 내에 있습니다",
"inTheRangeDate": "범위 내에 있음 (날짜)",
"is": "~이다",
"isNot": "~이 아닙니까",
"isGreaterThan": "~보다 크다",
"isLessThan": "~보다 작다",
"matchesRegex": "정규식과 일치",
"notContains": "함유하지 않음",
"notInPlaylist": "~ 안에 있지 않다",
"notInTheLast": "마지막에 있지 않다",
"startsWith": "~로 시작함"
},
"queryBuilder": {
"customTags": "사용자 정의 태그"
}
}
+25 -4
View File
@@ -3,7 +3,9 @@
"openIn": {
"lastfm": "Åpne i Last.fm",
"musicbrainz": "Åpne i MusicBrainz",
"spotify": "Åpne i Spotify"
"spotify": "Åpne i Spotify",
"listenbrainz": "Åpne i ListenBrainz",
"qobuz": "Åpne i Qobuz"
},
"moveToBottom": "Flytt til bunnen",
"deletePlaylist": "Slett $t(entity.playlist, {\"count\": 1})",
@@ -38,7 +40,10 @@
"shuffleAll": "Tilfelding avspilling av alt",
"shuffleSelected": "Tilfelding avspilling av utvalgte",
"viewMore": "Se mer",
"openApplicationDirectory": "Åpne applikasjonskatalogen"
"openApplicationDirectory": "Åpne applikasjonskatalogen",
"goToCurrent": "Gå til gjeldende element",
"collapseAllFolders": "Skjul alle mapper",
"expandAllFolders": "Utvid alle mapper"
},
"common": {
"bpm": "Bpm",
@@ -161,7 +166,11 @@
"tableColumns": "Tabellkolonner",
"itemsMore": "{{count}} fler",
"explicitStatus": "Grovhetsstatus",
"newVersionAvailable": "En ny version er tilgjengelig"
"newVersionAvailable": "En ny version er tilgjengelig",
"back": "Tilbake",
"openFolder": "Åpne mappe",
"grouping": "Grupper",
"numberOfResults": "{{numberOfResults}} resultater"
},
"entity": {
"smartPlaylist": "Smart $t(entity.playlist, {\"count\": 1})",
@@ -667,7 +676,19 @@
"b": "B",
"c": "C",
"d": "D",
"z": "Z"
"z": "Z",
"none": "Ingen"
},
"frequencyScale": {
"linear": "Lineær skala",
"log": "Logaritmisk skala"
},
"channelLayout": {
"single": "Enkel"
},
"gradient": {
"rainbow": "Regnbue",
"prism": "Prisme"
}
}
}
+14 -13
View File
@@ -178,14 +178,14 @@
},
"entity": {
"genre_one": "Gatunek",
"genre_few": "gatunki",
"genre_many": "gatunków",
"genre_few": "Gatunki",
"genre_many": "Gatunków",
"playlistWithCount_one": "{{count}} playlista",
"playlistWithCount_few": "{{count}} playlisty",
"playlistWithCount_many": "{{count}} playlist",
"playlist_one": "Playlista",
"playlist_few": "playlisty",
"playlist_many": "playlist",
"playlist_few": "Playlisty",
"playlist_many": "Playlist",
"artist_one": "Wykonawca",
"artist_few": "wykonawcy",
"artist_many": "wykonawców",
@@ -196,8 +196,8 @@
"albumArtist_few": "Wykonawców albumów",
"albumArtist_many": "Wykonawców albumów",
"track_one": "Utwór",
"track_few": "utwory",
"track_many": "utworów",
"track_few": "Utwory",
"track_many": "Utworów",
"albumArtistCount_one": "{{count}} wykonawca albumu",
"albumArtistCount_few": "{{count}} wykonawców albumu",
"albumArtistCount_many": "{{count}} wykonawców albumu",
@@ -205,18 +205,18 @@
"albumWithCount_few": "{{count}} albumy",
"albumWithCount_many": "{{count}} albumów",
"favorite_one": "Ulubiony",
"favorite_few": "ulubione",
"favorite_many": "ulubionych",
"favorite_few": "Ulubione",
"favorite_many": "Ulubionych",
"artistWithCount_one": "{{count}} wykonawca",
"artistWithCount_few": "{{count}} wykonawców",
"artistWithCount_many": "{{count}} wykonawców",
"folder_one": "Katalog",
"folder_few": "katalogi",
"folder_many": "katalogów",
"folder_few": "Katalogi",
"folder_many": "Katalogów",
"smartPlaylist": "Inteligentna $t(entity.playlist, {\"count\": 1})",
"album_one": "Album",
"album_few": "albumy",
"album_many": "albumów",
"album_few": "Albumy",
"album_many": "Albumów",
"genreWithCount_one": "{{count}} gatunek",
"genreWithCount_few": "{{count}} gatunki",
"genreWithCount_many": "{{count}} gatunków",
@@ -700,7 +700,8 @@
"sleepTimer_setCustom": "Ustaw wyłącznik",
"sleepTimer_cancel": "Anuluj wyłączanie",
"albumRadio": "Radio albumu",
"scrobbleForceSubmit": "Wymuś scrobble"
"scrobbleForceSubmit": "Wymuś scrobble",
"sleepTimer_endOfAlbum": "Koniec aktualnego albumu"
},
"setting": {
"crossfadeStyle_description": "Wybierz styl przenikania, który ma być używany do odtwarzania dźwięku",
+172 -9
View File
@@ -174,7 +174,8 @@
"explicitStatus": "Признак нецензурного контента",
"newVersionAvailable": "Доступна новая версия",
"numberOfResults": "{{numberOfResults}} результатов",
"back": "Назад"
"back": "Назад",
"openFolder": "Открыть папку"
},
"entity": {
"album_one": "Альбом",
@@ -240,7 +241,10 @@
"table": {
"config": {
"view": {
"table": "Таблица"
"table": "Таблица",
"detail": "Детали",
"grid": "Сетка",
"list": "Список"
},
"general": {
"displayType": "Тип отображения",
@@ -250,7 +254,29 @@
"followCurrentSong": "Следовать за исполняемым треком",
"size": "$t(common.size)",
"itemSize": "Размер элементов (px)",
"itemGap": "Отступ между элементами (px)"
"itemGap": "Отступ между элементами (px)",
"advancedSettings": "Расширенные настройки",
"autosize": "Автоматический выбор размера",
"moveUp": "Переместить выше",
"moveDown": "Переместить ниже",
"pinToLeft": "Закрепить слева",
"pinToRight": "Закрепить права",
"alignLeft": "Выровнять по левой стороне",
"alignCenter": "Выровнять по центру",
"alignRight": "Выровнять по правой стороне",
"itemsPerRow": "Элементов в строке",
"size_default": "По-умолчанию",
"size_compact": "Компактный",
"size_large": "Большой",
"pagination": "Пагинация",
"pagination_itemsPerPage": "Элементов на странице",
"pagination_infinite": "Бесконечно",
"pagination_paginate": "Разбитый по страницам",
"alternateRowColors": "Переменный цвет строк",
"horizontalBorders": "Границы строки",
"rowHoverHighlight": "Подсветка строки при наведении",
"showHeader": "Показать заголовок",
"verticalBorders": "Границы колонки"
},
"label": {
"releaseDate": "Дата выхода",
@@ -276,7 +302,10 @@
"favorite": "$t(common.favorite)",
"year": "$t(common.year)",
"codec": "$t(common.codec)",
"titleArtist": "$t(common.title) (артист)"
"titleArtist": "$t(common.title) (артист)",
"albumGroup": "Группа альбома",
"composer": "Композитор",
"image": "Изображение"
}
},
"column": {
@@ -299,7 +328,14 @@
"comment": "Комментарий",
"bitrate": "Битрейт",
"channels": "$t(common.channel_other)",
"bpm": "BPM"
"bpm": "BPM",
"albumCount": "Альбомы",
"artist": "Исполнители",
"bitDepth": "Битовая глубина",
"genre": "Жанр",
"sampleRate": "Частота дискретизации",
"songCount": "Треки",
"owner": "Правообладатель"
}
},
"error": {
@@ -434,7 +470,8 @@
"sleepTimer_setCustom": "Установить таймер",
"sleepTimer_custom": "Пользовательский",
"sleepTimer_cancel": "Отменить таймер",
"scrobbleForceSubmit": "Принудительная скробблинг"
"scrobbleForceSubmit": "Принудительная скробблинг",
"sleepTimer_endOfAlbum": "Конец этого альбома"
},
"page": {
"sidebar": {
@@ -756,7 +793,12 @@
"input_played_optionAll": "Все треки",
"input_played_optionUnplayed": "Только не игранные треки",
"input_played_optionPlayed": "Только воспроизведённые треки",
"input_genre": "$t(entity.genre, {\"count\": 1})"
"input_genre": "$t(entity.genre, {\"count\": 1})",
"input_kind_albums": "Альбомы",
"input_kind_songs": "Песни",
"input_kind": "Случайный выбор",
"input_limit_albums": "Сколько альбомов?",
"input_limit_songs": "Сколько песен?"
},
"editRadioStation": {
"success": "Радиостанция успешно обновлена"
@@ -1087,7 +1129,78 @@
"audioFadeOnStatusChange": "плавное изменение звука",
"audioFadeOnStatusChange_description": "включает эффекты затухания и появления звука при изменении статуса (пауза/проигрывание)",
"preventSleepOnPlayback_description": "запрещает спящий режим экрана, пока играет музыка",
"preventSleepOnPlayback": "не переходить в спящий режим"
"preventSleepOnPlayback": "не переходить в спящий режим",
"autoDJ_mode": "Режим",
"autoDJ_mode_albums": "Альбомы",
"autoDJ_mode_description": "Добавь песни или целые альбомы в очередь",
"autoDJ_mode_songs": "Песни",
"autoDJ_enabled": "Включить Auto DJ",
"autoDJ_albumStrategy": "Режим выбора альбома",
"autoDJ_songStrategy": "Режим выбора песни",
"autoDJ_strategy_option_library_random": "Случайно",
"autoDJ_strategy_option_similar": "Похожие",
"hotkey_listShowPlayingSong": "Показать текущую песню в списке",
"listenbrainz_description": "Показать ссылки на ListenBrains на страницах исполнителя/альбома",
"listenbrainz": "Показать ссылки на ListenBrainz",
"qobuz_description": "Показать ссылки на Qobuz на страницах исполнителя/альбома",
"qobuz": "Показать ссылки на Qobuz",
"spotify_description": "Показать ссылки на Spotify на странице исполнителя/альбома",
"spotify": "Показать ссылки на Spotify",
"nativeSpotify_description": "Открывать в приложении Spotify вместо браузера",
"nativeSpotify": "Использовать приложение Spotify",
"imageResolution_optionTable": "Таблица",
"preventSuspendOnPlayback_description": "Не приостанавливать приложение во время проигрывания музыки",
"preventSuspendOnPlayback": "Не приостанавливать во время проигрывания",
"playerItemConfiguration_description": "Настроить какие элементы и в каком порядке видны в полноэкранном плеере",
"playerItemConfiguration": "Настройка плеера",
"sidebarPlaylistFolders": "Включить папки",
"sidebarPlaylistFolderSeparator_description": "Символ (или строка), который разделяет уровни папок в названии плейлиста",
"sidebarPlaylistFolderSeparator": "Разделитель папок",
"sidebarPlaylistFolderView_description": "Как отображать папки в боковой панели",
"sidebarPlaylistFolderView": "Вид папок",
"sidebarPlaylistFolderView_optionSingle": "Единстванная папка",
"sidebarPlaylistFolderView_optionTree": "Вид дерева",
"sidebarPlaylistFolderView_optionNavigation": "Вид навигации",
"sidebarPlaylistFolderTreeIndent_description": "Отступ в пикселях на каждом уровне дерева",
"sidebarPlaylistFolderTreeIndent": "Отступ в дереве",
"sidebarPlaylistFolderTreeLineColor_description": "Цвет линий соединения в дереве (оставь пустым, чтобы использовать настройки темы)",
"sidebarPlaylistFolderTreeLineColor": "Цвет линии в дереве",
"sidebarPlaylistMode_description": "Как отображать каждый плейлист в списке в боковой панели",
"sidebarPlaylistMode": "Режим плейлиста в боковой панели",
"sidebarPlaylistMode_optionCompact": "Компактный",
"sidebarPlaylistMode_optionExpanded": "Просторный",
"sidebarPlaylistSorting_description": "Разрешить ручную сортировку плейлистов в боковой панели с помощью перетаскивания вместо сортировки со стороны сервера",
"sidebarPlaylistSorting": "Сортировка плейлистов в боковой панели",
"sidebarPlaylistListFilterRegex_description": "Скрывать плейлисты в боковой панели, которые соответствуют этому регулярному выражению",
"sidebarPlaylistListFilterRegex_placeholder": "Например ^daily mix.*",
"sidebarPlaylistListFilterRegex": "Регулярное выражение для фильтрации плейлистов",
"sidePlayQueueLayout": "Макет очереди проигрывания сбоку",
"sidePlayQueueLayout_description": "Задает макет прикрепленной очереди проигрывания сбоку",
"sidePlayQueueLayout_optionHorizontal": "Горизонтальный",
"sidePlayQueueLayout_optionVertical": "Вертикальный",
"mediaSession_description": "Включает интеграцию сессии медиа, отображая элементы управления и метаданные медиа в системном оверлее управления громкостью и на экране блокировки. Требуется Web Audio Player.",
"mediaSession": "Включить сессию медиа",
"skipPlaylistPage_description": "Когда переходишь в плейлист, откроется страница со списком песен плейлиста, вместо страницы по-умолчанию",
"transcode": "Включить транскодирование",
"transcodeFormat_description": "Выбирает форматы для транскодирования. Оставь пустым, чтобы решение принимал сервер",
"translationApiKey_description": "Ключ API для перевода (только эндпойнт глобального сервиса)",
"translationApiKey": "Ключ API перевода",
"translationApiProvider_description": "Поставщик API для перевода",
"translationApiProvider": "Поставщик API перевода",
"translationTargetLanguage_description": "На какой язык выполнять перевод",
"translationTargetLanguage": "На какой язык переводить",
"trayEnabled_description": "Показать/скрыть иконку/меню в трее. Если скрыто, то также отключается сворачивать в трей/свернуть в трей при выходе",
"trayEnabled": "Показать в трее",
"queryBuilder": "Создатель очереди",
"queryBuilderCustomFields_inputLabel": "Метка",
"queryBuilderCustomFields_inputTag": "Тег",
"queryBuilderCustomFields": "Пользовательские поля",
"queryBuilderCustomFields_description": "Добавь пользовательские поля для использования создателями очереди",
"hotkey_listNavigateToPage": "Перейти к странице элемента",
"hotkey_listPlayDefault": "Воспроизвести список",
"hotkey_listPlayLast": "Воспроизвести последний в списке",
"hotkey_listPlayNext": "Воспроизвести следующий в списке",
"sidebarPlaylistFolders_description": "Создать вид папки для плейлистов, которые включают настраиваемый разделитель в имени"
},
"releaseType": {
"secondary": {
@@ -1162,6 +1275,56 @@
"presetName": "Название пресета",
"presetNamePlaceholder": "Введите название пресета",
"general": "Главная",
"lineWidth": "Ширина линии"
"lineWidth": "Ширина линии",
"systemAudioConsentAllow": "Разрешить",
"systemAudioConsentBody": "Для работы визуализатора требуется доступ к аудио в системе",
"systemAudioConsentDecline": "Запретить",
"systemAudioConsentTitle": "Разрешить доступ к аудио в системе?",
"systemAudioCaptureFailed": "Не удается начать захват: {{message}}",
"visualizerType": "Тип визуализатора",
"cyclePresets": "Переключаться между наборами настроек",
"cycleTime": "Время между переключениями (в секундах)",
"includeAllPresets": "Включить все наборы настроек",
"ignoredPresets": "Игнорируемые наборы настроек",
"selectedPresets": "Выбранные наборы настроек",
"randomizeNextPreset": "Выбирать следующий набор настроек случайным образом",
"blendTime": "Время смешивания",
"mode": "Режим",
"mode1To8": "Режимы 1-8",
"mode10": "Режим 10",
"maxFPS": "Максимум кадров в секунду",
"opacity": "Прозрачность",
"customGradients": "Пользовательские градиенты",
"addCustomGradient": "Добавить пользовательский градиент",
"gradientName": "Название градиента",
"gradientNamePlaceholder": "Название градиента",
"vertical": "Вертикальный",
"horizontal": "Горизонтальный",
"addColor": "Добавить цвет",
"position": "Расположение",
"level": "Уровень",
"remove": "Удалить",
"pasteGradient": "Вставить градиент",
"pasteGradientPlaceholder": "Вставить JSON с градиентом сюда...",
"custom": "Пользовательский",
"builtIn": "Встроенный",
"colors": "Цвета",
"colorMode": "Цветовой режим",
"gradient": "Градиент",
"gradientLeft": "Градиент слева",
"gradientRight": "Градиент справа",
"smoothing": "Сглаживание",
"minimumFrequency": "Минимальная частота",
"maximumFrequency": "Максимальная частота",
"sensitivity": "Чуствительность",
"minimumDecibels": "Минимум децибел",
"maximumDecibels": "Максимум децибел",
"linearAmplitude": "Линейная амплитуда",
"showPeaks": "Показывать пики"
},
"dragDropZone": {
"error_oneFileOnly": "Выбери только 1 файл",
"error_readingFile": "Проблема при чтении файла: {{errorMessage}}",
"mainText": "Перемести файл сюда"
}
}
+28 -28
View File
@@ -5,7 +5,7 @@
"bitrate": "位元率",
"bpm": "BPM",
"clear": "清空",
"collapse": "疊",
"collapse": "疊",
"comingSoon": "即將推出…",
"confirm": "確認",
"decrease": "降低",
@@ -18,9 +18,9 @@
"add": "新增",
"areYouSure": "你確定嗎?",
"ascending": "升冪",
"disable": "用",
"disable": "用",
"disc": "光碟",
"dismiss": "不再顯示",
"dismiss": "不理會",
"duration": "時長",
"edit": "編輯",
"enable": "啟用",
@@ -64,9 +64,9 @@
"cancel": "取消",
"center": "中央",
"channel_other": "聲道",
"configure": "設定",
"configure": "配置",
"create": "建立",
"currentSong": "目前$t(entity.track, {\"count\": 1})",
"currentSong": "當前 $t(entity.track, {\"count\": 1})",
"minimize": "最小化",
"modified": "已修改",
"name": "名稱",
@@ -75,13 +75,13 @@
"noResultsFromQuery": "未查詢到匹配結果",
"note": "注釋",
"additionalParticipants": "額外參與者",
"newVersion": "已安裝新版本 ({{version}})",
"newVersion": "新版本 ({{version}}) 已被安裝",
"viewReleaseNotes": "查看發行註記",
"albumGain": "專輯增益",
"albumPeak": "專輯峰值",
"bitDepth": "位元深度",
"close": "關閉",
"codec": "編",
"codec": "編解碼器",
"mbid": "MusicBrainz ID",
"preview": "預覽",
"reload": "重新載入",
@@ -107,7 +107,7 @@
"explicit": "露骨",
"gridRows": "網格行",
"noFilters": "未設定任何過濾器",
"countSelected": "{{count}}個已選取",
"countSelected": "{{count}} 個已選取",
"retry": "重試",
"example": "範例",
"mood": "情緒",
@@ -204,7 +204,7 @@
},
"appMenu": {
"openBrowserDevtools": "開啟瀏覽器開發者工具",
"collapseSidebar": "疊側邊欄",
"collapseSidebar": "疊側邊欄",
"expandSidebar": "展開側邊欄",
"goBack": "返回",
"goForward": "前進",
@@ -381,7 +381,7 @@
"playbackSpeed": "播放速度",
"playRandom": "隨機播放",
"previous": "上一首",
"queue_clear": "清空播放佇列",
"queue_clear": "清空佇列",
"queue_remove": "移除所選",
"repeat": "循環",
"repeat_all": "全部循環",
@@ -446,7 +446,7 @@
"crossfadeStyle_description": "選擇用於音訊播放器的淡入淡出風格",
"customFontPath": "自訂字型路徑",
"customFontPath_description": "設定應用程式要使用的自訂字型路徑",
"disableLibraryUpdateOnStartup": "用啟動時檢查新版本",
"disableLibraryUpdateOnStartup": "用啟動時檢查新版本",
"discordApplicationId": "{{discord}} 應用程式 ID",
"discordApplicationId_description": "{{discord}} Rich Presence 應用程式 ID(預設為 {{defaultId}}",
"discordIdleStatus": "顯示 Rich Presence 閒置狀態",
@@ -532,8 +532,8 @@
"showSkipButton": "顯示跳過按鈕",
"showSkipButton_description": "在播放條上顯示/隱藏跳過按鈕",
"sidebarPlaylistList": "側邊欄播放清單列表",
"sidebarCollapsedNavigation": "側邊欄(已疊)導航",
"sidebarCollapsedNavigation_description": "在疊的側邊欄中顯示或隱藏導航",
"sidebarCollapsedNavigation": "側邊欄(已疊)導航",
"sidebarCollapsedNavigation_description": "在疊的側邊欄中顯示或隱藏導航",
"sidebarConfiguration": "側邊欄設定",
"sidebarConfiguration_description": "選擇側邊欄包含的項目與順序",
"sidebarPlaylistList_description": "顯示或隱藏側邊欄歌單清單",
@@ -562,7 +562,7 @@
"exitToTray_description": "退出應用程式時最小化到系統匣而非關閉",
"followLyric_description": "滾動歌詞到目前播放位置",
"font": "字型",
"globalMediaHotkeys_description": "啟用或用系統媒體快捷鍵以控制播放",
"globalMediaHotkeys_description": "啟用或用系統媒體快捷鍵以控制播放",
"hotkey_browserBack": "瀏覽器返回",
"hotkey_favoriteCurrentSong": "收藏 $t(common.currentSong)",
"hotkey_playbackStop": "停止",
@@ -573,7 +573,7 @@
"remotePassword": "遠端控制伺服器密碼",
"remotePassword_description": "設定遠端控制伺服器的密碼。這些憑證預設以不安全的方式傳輸,因此您應該使用一個您不在意的唯一密碼",
"remotePort_description": "設定遠端控制伺服器的連接埠",
"remoteUsername_description": "設定遠端控制伺服器的使用者名稱。如果使用者名稱和密碼都為空,則身分驗證將被用",
"remoteUsername_description": "設定遠端控制伺服器的使用者名稱。如果使用者名稱和密碼都為空,則身分驗證將被用",
"replayGainClipping_description": "自動降低增益以防止{{ReplayGain}}造成削波",
"showSkipButtons": "顯示跳過按鈕",
"themeDark_description": "應用程式將使用深色主題",
@@ -933,34 +933,34 @@
"title": "標題",
"trackNumber": "曲目編號",
"size": "大小",
"codec": "編",
"codec": "編解碼器",
"owner": "擁有者",
"bitDepth": "位元深度",
"sampleRate": "取樣率"
}
},
"action": {
"addToFavorites": "新增$t(entity.favorite, {\"count\": 2})",
"clearQueue": "清空播放佇列",
"createPlaylist": "建立$t(entity.playlist, {\"count\": 1})",
"deletePlaylist": "刪除$t(entity.playlist, {\"count\": 1})",
"addToPlaylist": "新增$t(entity.playlist, {\"count\": 1})",
"addToFavorites": "新增$t(entity.favorite, {\"count\": 2})",
"clearQueue": "清空佇列",
"createPlaylist": "建立 $t(entity.playlist, {\"count\": 1})",
"deletePlaylist": "刪除 $t(entity.playlist, {\"count\": 1})",
"addToPlaylist": "新增$t(entity.playlist, {\"count\": 1})",
"deselectAll": "取消全選",
"editPlaylist": "編輯 $t(entity.playlist, {\"count\": 1})",
"goToPage": "前往頁面",
"moveToBottom": "移至底部",
"moveToTop": "移至頂部",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "從$t(entity.favorite, {\"count\": 2})移除",
"removeFromPlaylist": "從$t(entity.playlist, {\"count\": 1})移除",
"removeFromQueue": "從播放佇列中移除",
"removeFromFavorites": "從 $t(entity.favorite, {\"count\": 2}) 移除",
"removeFromPlaylist": "從 $t(entity.playlist, {\"count\": 1}) 移除",
"removeFromQueue": "從佇列中移除",
"setRating": "評分",
"toggleSmartPlaylistEditor": "切換$t(entity.smartPlaylist)編輯器",
"viewPlaylists": "查看$t(entity.playlist, {\"count\": 2})",
"toggleSmartPlaylistEditor": "切換 $t(entity.smartPlaylist) 編輯器",
"viewPlaylists": "查看 $t(entity.playlist, {\"count\": 2})",
"moveToNext": "移至下一項",
"openIn": {
"lastfm": "在Last.fm開啟",
"musicbrainz": "在MusicBrainz開啟",
"lastfm": "在 Last.fm開啟",
"musicbrainz": "在 MusicBrainz 開啟",
"spotify": "在 Spotify 中開啟",
"listenbrainz": "在 ListenBrainz 中開啟",
"qobuz": "在 Qobuz 中開啟"
+1 -1
View File
@@ -72,7 +72,7 @@ export const orderSearchResults = (args: {
searchResults = Array.from(combinedResults.values());
} else {
searchResults = fuse.search<InternetProviderLyricSearchResponse>({
searchResults = fuse.search({
...(params.artist && { artist: params.artist }),
...(params.name && { name: params.name }),
});
@@ -1793,6 +1793,17 @@ export const JellyfinController: InternalControllerEndpoint = {
return null;
}
if (query.event === 'stop') {
jfApiClient(apiClientProps).scrobbleStopped({
body: {
ItemId: query.id,
PositionTicks: position,
},
});
return null;
}
jfApiClient(apiClientProps).scrobbleProgress({
body: {
ItemId: query.id,
@@ -2309,7 +2309,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const { apiClientProps, query } = args;
if (hasFeature(apiClientProps.server, ServerFeature.REPORT_PLAYBACK)) {
if (query.submission) {
if (query.submission || query.event === 'start') {
const res = await ssApiClient(apiClientProps).scrobble({
query: {
id: query.id,
@@ -2321,38 +2321,54 @@ export const SubsonicController: InternalControllerEndpoint = {
throw new Error('Failed to scrobble');
}
return null;
if (query.submission) {
return null;
}
}
let state: 'paused' | 'playing' | 'starting' | 'stopped' = 'playing';
const defaultParams = {
ignoreScrobble: true,
mediaId: query.id,
mediaType: query.mediaType,
playbackRate: query.playbackRate,
positionMs: query.position ?? 0,
};
const reportPlayback = (state: 'paused' | 'playing' | 'starting' | 'stopped') => {
return ssApiClient(apiClientProps).reportPlayback({
query: {
...defaultParams,
state,
},
});
};
const promises: Promise<any>[] = [];
switch (query.event) {
case 'pause':
state = 'paused';
promises.push(reportPlayback('paused'));
break;
case 'start':
state = 'starting';
promises.push(reportPlayback('starting'));
promises.push(reportPlayback('playing'));
break;
case 'stop':
promises.push(reportPlayback('stopped'));
break;
case 'unpause':
state = 'playing';
promises.push(reportPlayback('playing'));
break;
default:
state = 'playing';
break;
}
const res = await ssApiClient(apiClientProps).reportPlayback({
query: {
ignoreScrobble: true,
mediaId: query.id,
mediaType: query.mediaType,
playbackRate: query.playbackRate,
positionMs: query.position ?? 0,
state,
},
});
for (const promise of promises) {
const res = await promise;
if (res.status !== 200) {
throw new Error('Failed to report playback');
if (res.status !== 200) {
throw new Error('Failed to report playback');
}
}
return null;
@@ -13,7 +13,7 @@ import { useListContext } from '/@/renderer/context/list-context';
import { eventEmitter } from '/@/renderer/events/event-emitter';
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
import { getListRefreshMutationKey } from '/@/renderer/features/shared/components/list-refresh-button';
import { LibraryItem } from '/@/shared/types/domain-types';
import { LibraryItem, SortKeyRandom } from '/@/shared/types/domain-types';
export const getListQueryKeyName = (itemType: LibraryItem): string => {
switch (itemType) {
@@ -108,8 +108,19 @@ export const useItemListInfiniteLoader = ({
[serverId, itemType, query],
);
const isRandomSort = query?.sortBy === SortKeyRandom;
const fetchPage = useCallback(
async (pageNumber: number) => {
if (isRandomSort) {
const existingData =
queryClient.getQueryData<InfiniteLoaderCacheData>(dataQueryKey);
if (existingData?.pagesLoaded?.[pageNumber]) {
lastFetchedPageRef.current = Math.max(lastFetchedPageRef.current, pageNumber);
return;
}
}
const startIndex = pageNumber * itemsPerPage;
const queryParams = {
limit: itemsPerPage,
@@ -118,6 +129,7 @@ export const useItemListInfiniteLoader = ({
};
const result = await queryClient.fetchQuery({
gcTime: isRandomSort ? 1000 * 60 * 10 : 1000 * 15,
queryFn: async ({ signal }) => {
const result = await listQueryFn({
apiClientProps: { serverId, signal },
@@ -127,6 +139,7 @@ export const useItemListInfiniteLoader = ({
return result;
},
queryKey: queryKeys[getListQueryKeyName(itemType)].list(serverId, queryParams),
staleTime: isRandomSort ? 1000 * 60 * 10 : 1000 * 15,
});
// Update the query data with the fetched page
@@ -154,13 +167,32 @@ export const useItemListInfiniteLoader = ({
// Track the last fetched page
lastFetchedPageRef.current = Math.max(lastFetchedPageRef.current, pageNumber);
},
[itemsPerPage, query, queryClient, serverId, dataQueryKey, listQueryFn, itemType],
[
itemsPerPage,
query,
queryClient,
serverId,
dataQueryKey,
listQueryFn,
itemType,
isRandomSort,
],
);
// Reset the loaded pages and refetch current page when the query changes
useEffect(() => {
const currentDataQueryKey = JSON.stringify(dataQueryKey);
if (isRandomSort) {
const existingData = queryClient.getQueryData<InfiniteLoaderCacheData | undefined>(
dataQueryKey,
);
if (existingData?.dataMap && existingData.dataMap.size > 0) {
previousDataQueryKeyRef.current = currentDataQueryKey;
return;
}
}
if (previousDataQueryKeyRef.current === currentDataQueryKey || isRefetchingRef.current) {
return;
}
@@ -12,7 +12,7 @@ import { useListContext } from '/@/renderer/context/list-context';
import { eventEmitter } from '/@/renderer/events/event-emitter';
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
import { getListRefreshMutationKey } from '/@/renderer/features/shared/components/list-refresh-button';
import { LibraryItem } from '/@/shared/types/domain-types';
import { LibraryItem, SortKeyRandom } from '/@/shared/types/domain-types';
const getQueryKeyName = (itemType: LibraryItem): string => {
switch (itemType) {
@@ -76,6 +76,8 @@ export const useItemListPaginatedLoader = ({
const fetchRange = getFetchRange(currentPage, itemsPerPage);
const startIndex = fetchRange.startIndex;
const isRandomSort = query?.sortBy === SortKeyRandom;
const queryParams = useMemo(
() => ({
limit: itemsPerPage,
@@ -86,7 +88,7 @@ export const useItemListPaginatedLoader = ({
);
const { data } = useQuery({
gcTime: 1000 * 15,
gcTime: isRandomSort ? 1000 * 60 * 10 : 1000 * 15,
placeholderData: { items: getInitialData(itemsPerPage) },
queryFn: async ({ signal }) => {
const result = await listQueryFn({
@@ -97,7 +99,7 @@ export const useItemListPaginatedLoader = ({
return result;
},
queryKey: queryKeys[getQueryKeyName(itemType)].list(serverId, queryParams),
staleTime: 1000 * 15,
staleTime: isRandomSort ? 1000 * 60 * 10 : 1000 * 15,
});
const refreshMutation = useMutation({
@@ -67,8 +67,9 @@ Jellyfin progress APIs still use playback position (ticks), not listen time:
- pause / unpause
Other events:
- When the song changes: sends 'start' when the new track is playing;
clears submission flag and listen accumulator for the new track.
- When the song changes: sends 'stop' for the previous track; sends 'start'
when the new track is playing; clears submission flag and listen accumulator
for the new track.
- When the song is restarted (near 0 after 10s+): clears submission flag
and listen accumulator.
@@ -129,6 +130,7 @@ export const useScrobble = () => {
const previousSongRef = useRef<QueueSong | undefined>(undefined);
const previousTimestampRef = useRef<number>(0);
const stopPositionRef = useRef<number>(0);
const lastProgressEventRef = useRef<number>(0);
const lastSeekEventRef = useRef<number>(0);
const songChangeTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
@@ -316,7 +318,10 @@ export const useScrobble = () => {
) => {
const currentSong = properties.song;
const previousSong = previousSongRef.current;
const previousPositionSec = stopPositionRef.current;
const mediaType = currentSong?._itemType.includes('song') ? 'song' : 'podcast';
const previousMediaType = previousSong?._itemType.includes('song') ? 'song' : 'podcast';
const useTicksForPrevious = previousSong?._serverType === ServerType.JELLYFIN;
// Handle notifications
if (scrobbleSettings?.notify && currentSong?.id) {
@@ -352,6 +357,7 @@ export const useScrobble = () => {
if (!isScrobbleEnabled || isPrivateModeEnabled) {
previousSongRef.current = currentSong;
previousTimestampRef.current = 0;
stopPositionRef.current = 0;
listenedMsRef.current = 0;
lastListenSampleTimeRef.current = null;
flushScrobbleDebug();
@@ -395,10 +401,42 @@ export const useScrobble = () => {
},
);
}
// Send stop scrobble for the track that was playing before the change
if (previousSong?.id) {
sendScrobble.mutate(
{
apiClientProps: { serverId: previousSong._serverId || '' },
query: {
albumId: previousSong.albumId,
event: 'stop',
id: previousSong.id,
mediaType: previousMediaType,
playbackRate: playbackRate,
position: getPositionValue(
previousPositionSec,
useTicksForPrevious,
),
submission: false,
},
},
{
onSuccess: () => {
logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledStop, {
category: LogCategory.SCROBBLE,
meta: {
id: previousSong.id,
},
});
},
},
);
}
}, 2000);
previousSongRef.current = currentSong;
previousTimestampRef.current = 0;
stopPositionRef.current = 0;
flushScrobbleDebug();
},
[
@@ -591,6 +629,7 @@ export const useScrobble = () => {
isCurrentSongScrobbledRef.current = false;
lastProgressEventRef.current = 0;
previousTimestampRef.current = 0;
stopPositionRef.current = 0;
listenedMsRef.current = 0;
lastListenSampleTimeRef.current = null;
@@ -625,6 +664,17 @@ export const useScrobble = () => {
// Update previous timestamp on progress for use in status change handler
const handleProgressUpdate = useCallback(
(properties: { timestamp: number }, prev: { timestamp: number }) => {
// Preserve last playback position when the playhead resets to the start
// (song change can fire after progress already reports 0 for the new track).
if (
properties.timestamp < SCROBBLE_TRACK_BEGIN_SEC &&
prev.timestamp >= SCROBBLE_TRACK_BEGIN_SEC
) {
stopPositionRef.current = prev.timestamp;
} else {
stopPositionRef.current = properties.timestamp;
}
previousTimestampRef.current = properties.timestamp;
handleScrobbleFromProgress(properties, prev);
flushScrobbleDebug();
@@ -224,25 +224,26 @@ export const PlaylistQueryEditor = ({
return detailQuery?.data?.rules?.order || 'asc';
}, [detailQuery?.data?.rules?.order, detailQuery?.data?.rules?.sort]);
const appliedQuery = appliedJsonState?.query;
const detailQueryRules = detailQuery?.data?.rules;
const effectiveQuery = useMemo(
() =>
appliedJsonState?.query ??
(detailQuery?.data?.rules?.all
? { all: detailQuery.data.rules.all }
: detailQuery?.data?.rules?.any
? { any: detailQuery.data.rules.any }
: detailQuery?.data?.rules),
[appliedJsonState?.query, detailQuery?.data?.rules],
appliedQuery ??
(detailQueryRules?.all
? { all: detailQueryRules.all }
: detailQueryRules?.any
? { any: detailQueryRules.any }
: detailQueryRules),
[appliedQuery, detailQueryRules],
);
const effectiveLimit = appliedJsonState?.limit ?? detailQuery?.data?.rules?.limit;
const effectiveLimitPercent =
appliedJsonState?.limitPercent ?? detailQuery?.data?.rules?.limitPercent;
const appliedSort = appliedJsonState?.sort;
const effectiveSortBy = useMemo(
() =>
(appliedJsonState?.sort ? [appliedJsonState.sort] : parseSortBy()) as
| SongListSort
| SongListSort[],
[appliedJsonState?.sort, parseSortBy],
() => (appliedSort ? [appliedSort] : parseSortBy()) as SongListSort | SongListSort[],
[appliedSort, parseSortBy],
);
const effectiveSortOrder = appliedJsonState?.sort
? appliedJsonState.sort.startsWith('-')
@@ -298,6 +298,7 @@ export const useServerAuthenticated = () => {
await new Promise((resolve) => setTimeout(resolve, NETWORK_RETRY_DELAY_MS));
// Retry authentication
// eslint-disable-next-line react-hooks/immutability
return authenticateServer(serverWithAuth, nextRetry);
}
+1 -1
View File
@@ -189,7 +189,7 @@ const appRouterModals = {
export const AppRouter = () => {
const router = (
<HashRouter unstable_useTransitions={false}>
<HashRouter>
<ModalsProvider modals={appRouterModals}>
<RouterErrorBoundary>
<Routes>
+1
View File
@@ -47,6 +47,7 @@ export const THEME_DATA = [
{ label: 'Rosé Pine', type: 'dark', value: AppTheme.ROSE_PINE },
{ label: 'Rosé Pine Moon', type: 'dark', value: AppTheme.ROSE_PINE_MOON },
{ label: 'Rosé Pine Dawn', type: 'light', value: AppTheme.ROSE_PINE_DAWN },
{ label: 'Zenburn', type: 'dark', value: AppTheme.ZENBURN },
];
export const useAppTheme = (overrideTheme?: AppTheme) => {
+1
View File
@@ -107,6 +107,7 @@ export const logMsg = {
[LogCategory.SCROBBLE]: {
scrobbledPause: 'Scrobbled a pause event',
scrobbledStart: 'Scrobbled a start event',
scrobbledStop: 'Scrobbled a stop event',
scrobbledSubmission: 'Scrobbled a submission event',
scrobbledTimeupdate: 'Scrobbled a timeupdate event',
scrobbledUnpause: 'Scrobbled an unpause event',
+1
View File
@@ -34,6 +34,7 @@ export enum AppTheme {
TOKYO_NIGHT = 'tokyoNight',
VSCODE_DARK_PLUS = 'vscodeDarkPlus',
VSCODE_LIGHT_PLUS = 'vscodeLightPlus',
ZENBURN = 'zenburn',
}
export type AppThemeConfiguration = Partial<BaseAppThemeConfiguration>;
+2
View File
@@ -35,6 +35,7 @@ import { solarizedLight } from '/@/shared/themes/solarized-light/solarized-light
import { tokyoNight } from '/@/shared/themes/tokyo-night/tokyo-night';
import { vscodeDarkPlus } from '/@/shared/themes/vscode-dark-plus/vscode-dark-plus';
import { vscodeLightPlus } from '/@/shared/themes/vscode-light-plus/vscode-light-plus';
import { zenburn } from '/@/shared/themes/zenburn/zenburn';
export const appTheme: Record<AppTheme, AppThemeConfiguration> = {
[AppTheme.AYU_DARK]: ayuDark,
@@ -68,6 +69,7 @@ export const appTheme: Record<AppTheme, AppThemeConfiguration> = {
[AppTheme.TOKYO_NIGHT]: tokyoNight,
[AppTheme.VSCODE_DARK_PLUS]: vscodeDarkPlus,
[AppTheme.VSCODE_LIGHT_PLUS]: vscodeLightPlus,
[AppTheme.ZENBURN]: zenburn,
};
export const getAppTheme = (theme: AppTheme): AppThemeConfiguration => {
+28
View File
@@ -0,0 +1,28 @@
import { AppThemeConfiguration } from '/@/shared/themes/app-theme-types';
export const zenburn: AppThemeConfiguration = {
app: {
'overlay-header':
'linear-gradient(transparent 0%, rgb(40 44 52 / 85%) 100%), var(--theme-background-noise)',
'overlay-subheader':
'linear-gradient(180deg, rgb(40 44 52 / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)',
'scrollbar-handle-background': 'rgba(160, 160, 160, 20%)',
'scrollbar-handle-hover-background': 'rgba(160, 160, 160, 40%)',
},
colors: {
background: '#3f3f3f',
'background-alternate': '#313131',
black: '#313131',
foreground: '#dcdccc',
'foreground-muted': '#d9d9d9',
primary: '#95a4b2',
'state-error': '#dca3a3',
'state-info': '#95a4b2',
'state-success': '#7f9f7f',
'state-warning': '#efdcbc',
surface: '#636363',
'surface-foreground': '#95a4b2',
white: '#dcdccc',
},
mode: 'dark',
};
+7 -5
View File
@@ -465,6 +465,8 @@ export const tagListSortMap: TagListSortMap = {
},
};
export const SortKeyRandom = 'random';
export enum AlbumListSort {
ALBUM_ARTIST = 'albumArtist',
ARTIST = 'artist',
@@ -476,7 +478,7 @@ export enum AlbumListSort {
ID = 'id',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RANDOM = SortKeyRandom,
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RECENTLY_PLAYED = 'recentlyPlayed',
@@ -598,7 +600,7 @@ export enum SongListSort {
ID = 'id',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RANDOM = SortKeyRandom,
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RECENTLY_PLAYED = 'recentlyPlayed',
@@ -725,7 +727,7 @@ export enum AlbumArtistListSort {
FAVORITED = 'favorited',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RANDOM = SortKeyRandom,
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RELEASE_DATE = 'releaseDate',
@@ -814,7 +816,7 @@ export enum ArtistListSort {
FAVORITED = 'favorited',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RANDOM = SortKeyRandom,
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RELEASE_DATE = 'releaseDate',
@@ -1363,7 +1365,7 @@ export type ScrobbleArgs = BaseEndpointArgs & {
export type ScrobbleQuery = {
albumId?: string;
event?: 'pause' | 'start' | 'unpause';
event?: 'pause' | 'start' | 'stop' | 'unpause';
id: string;
mediaType: 'podcast' | 'song';
playbackRate: number;