Compare commits

...

14 Commits

Author SHA1 Message Date
Hosted Weblate 4b4d64c7fc Translated using Weblate
Currently translated at 100.0% (1244 of 1244 strings) (Catalan)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/

Translated using Weblate

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

Translated using Weblate

Currently translated at 20.5% (256 of 1244 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: albatrays <weblate.duct925@passmail.net>
Co-authored-by: rimasx <riks_12@hot.ee>
2026-06-02 16:01:13 +02:00
Hosted Weblate f7e1198482 Added translation using Weblate (Estonian)
Co-authored-by: rimasx <riks_12@hot.ee>
2026-06-01 15:15:06 +02:00
jeffvli 7243ed7f15 fix timestamp restore when song switched before playback start (#2094) 2026-05-31 23:09:33 -07:00
York 7e9a78898f fix: punctuation hotkeys not captured (#2091) 2026-05-31 22:43:58 -07:00
Kendall Garner 6aab8d4121 fix(scrobble): use proper pause/unpause for seek, use ms for subsonic 2026-05-31 21:13:11 -07:00
Kendall Garner 70594a696b chore(context menu): show go to only for albums/tracks, prefer artist over album artist where possible 2026-05-31 19:44:58 -07:00
York 08b4c620f2 Merge pull request #2082 from york9675/fix/spacebar
Fix playback hotkeys by preventing browser default
2026-05-31 14:36:53 +00:00
Hosted Weblate 7a20cf3853 Translated using Weblate
Currently translated at 100.0% (1244 of 1244 strings) (Chinese (Traditional Han script))
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/

Co-authored-by: York <goog10216922@gmail.com>
2026-05-31 10:51:08 +02:00
Hosted Weblate dd186c570f Translated using Weblate
Currently translated at 0.8% (10 of 1244 strings) (Tagalog)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/tl/

Co-authored-by: Redd <reddtheeditor@gmail.com>
2026-05-31 08:04:07 +02:00
Hosted Weblate d5e9d491b6 Added translation using Weblate (Tagalog)
Co-authored-by: Redd <reddtheeditor@gmail.com>
2026-05-31 06:08:43 +02:00
Hosted Weblate 28dc822e4f Translated using Weblate
Currently translated at 100.0% (1244 of 1244 strings) (Spanish)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/

Translated using Weblate

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

Translated using Weblate

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

Translated using Weblate

Currently translated at 84.1% (1047 of 1244 strings) (English)
Translation: feishin/Translation
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/en/

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Lucas Winther <lucasw89@live.dk>
Co-authored-by: skajmer <skajmer@protonmail.com>
2026-05-30 19:01:12 +00:00
Kendall Garner def1b1e710 fix(butterchurn): allow unsafe wasm for butterchurn 2026-05-30 07:01:53 -07:00
Kendall Garner 2ff9e4b0a2 fix(subsonic): fix favoriting/unfavoriting on playlist songs 2026-05-29 09:19:39 -07:00
jeffvli 49bfc907cd bump lock-threads to v6 2026-05-28 23:07:47 -07:00
20 changed files with 459 additions and 77 deletions
+5 -4
View File
@@ -12,7 +12,8 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
- uses: dessant/lock-threads@v6
with:
process-only: 'issues, prs'
issue-inactive-days: 120
@@ -29,15 +30,15 @@ jobs:
days-before-pr-close: 30
stale-issue-message: >
This issue has been automatically marked as stale because it has not had recent activity. The resources of the Feishin team are limited, and so we are asking for your help.
If this is a **bug** and you can still reproduce this error on the <code>development</code> branch, please reply with all of the information you have about it in order to keep the issue open.
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
stale-pr-message: >
This PR has been automatically marked as stale because it has not had recent activity. The resources of the Feishin team are limited, and so we are asking for your help.
This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
+22 -7
View File
@@ -341,7 +341,8 @@
"rename": "Reanomena",
"newVersionAvailable": "Hi ha una nova versió disponible",
"numberOfResults": "{{numberOfResults}} resultats",
"back": "Enrere"
"back": "Enrere",
"openFolder": "Obre la carpeta"
},
"entity": {
"album_one": "Àlbum",
@@ -462,7 +463,7 @@
"expireInvalid": "La data d'expiració ha de ser al futur",
"createFailed": "No s'ha pogut crear el recurs compartit (està habilitat, l'ús compartit?)",
"copyToClipboard": "Copiar al porta-retalls: Ctrl+C, enter",
"successMustClick": "Compartició creada correctament. Feu clic aquí per obrir-la."
"successMustClick": "Compartició creada correctament. Feu clic aquí per obrir-la"
},
"updateServer": {
"success": "S'ha actualitzat el servidor amb èxit",
@@ -495,7 +496,12 @@
"input_played": "Reprodueix el filtre",
"input_played_optionAll": "Totes les pistes",
"input_played_optionUnplayed": "Només les pistes sense reproduir",
"input_played_optionPlayed": "Només les pistes reproduïdes"
"input_played_optionPlayed": "Només les pistes reproduïdes",
"input_kind_albums": "Àlbums",
"input_kind_songs": "Cançons",
"input_kind": "Seleccions a l'atzar",
"input_limit_albums": "Quants àlbums?",
"input_limit_songs": "Quantes cançons?"
},
"createRadioStation": {
"success": "Emissora de ràdio creada amb èxit",
@@ -626,7 +632,7 @@
"customCssEnable_description": "Permet escriure CSS personalitzat",
"customCssNotice": "Atenció: tot i que hi ha un filtre (no es permet ni URL() ni content:), l'ús de CSS personalitzat pot presentar riscs si canvieu la interfície",
"customCss": "Css personalitzat",
"customCss_description": "Contingut del CSS personalitzat. Nota: la propietat \"content\" i els urls remots no es permeten. A sota hi teniu una previsualització. Els camps addicionals que no establiu hi apareixin pel filtre",
"customCss_description": "Contingut del CSS personalitzat. Nota: la propietat \"content\" i els urls remots no es permeten. A sota hi teniu una previsualització. Els camps addicionals que no establiu hi apareixen a causa de la sanitització. Escriptori: Feishin llegeix i escriu custom.css al directori de configuració de l'aplicació i el recarrega quan el fitxer canvia",
"customFontPath": "Ruta de font personalitzada",
"customFontPath_description": "Estableix la ruta a una font personalitzada per utilitzar-la a l'aplicació",
"discordApplicationId": "ID d'aplicació de {{discord}}",
@@ -807,7 +813,7 @@
"releaseChannel": "Canal de versions",
"releaseChannel_description": "Trieu entre versions estables i beta o alfa (diàries) per les actualitzacions automàtiques",
"mediaSession": "Activa media session",
"mediaSession_description": "Activa la integració amb Media Session per mostrar els controls multimèdia i les metadades a l'indicador de volum del sistema i la pantalla de bloqueig",
"mediaSession_description": "Activa la integració amb Media Session per mostrar els controls multimèdia i les metadades a l'indicador de volum del sistema i la pantalla de bloqueig. Requereix el Reproductor Web d'Àudio.",
"crossfadeStyle": "Estil de fosa encadenada",
"discordRichPresence": "Estat d'activitat de {{discord}}",
"enableAutoTranslation_description": "Activa la traducció automàtica en carregar la lletra",
@@ -828,7 +834,7 @@
"transcode": "Activa la transcodificació",
"autoDJ": "DJ automàtic",
"autoDJ_itemCount": "Número d'elements",
"autoDJ_itemCount_description": "El nombre d'elements que s'intenten afegir a la cua quan el DJ automàtic està activat",
"autoDJ_itemCount_description": "El nombre d'elements que s'intenten afegir a la cua",
"autoDJ_timing": "Temps",
"autoDJ_timing_description": "El nombre de cançons que han de quedar a la cua per activar el DJ automàtic",
"analyticsDisable": "Desactiva les analítiques basades en l'ús",
@@ -958,7 +964,16 @@
"sidebarPlaylistMode_description": "Com es mostra cada llista de reproducció a la llista de la barra lateral",
"sidebarPlaylistMode": "Mode de llista de reproducció a la barra lateral",
"sidebarPlaylistMode_optionCompact": "Compacte",
"sidebarPlaylistMode_optionExpanded": "Expandit"
"sidebarPlaylistMode_optionExpanded": "Expandit",
"autoDJ_mode": "Mode",
"autoDJ_mode_albums": "Àlbums",
"autoDJ_mode_description": "Trieu si voleu afegir cançons o àlbums sencers a la cua",
"autoDJ_mode_songs": "Cançons",
"autoDJ_enabled": "Activa el DJ automàtic",
"autoDJ_albumStrategy": "Mode de selecció d'àlbum",
"autoDJ_songStrategy": "Mode de selecció de cançó",
"autoDJ_strategy_option_library_random": "A l'atzar",
"autoDJ_strategy_option_similar": "Similar"
},
"table": {
"column": {
+1 -1
View File
@@ -296,7 +296,7 @@
"releaseChannel": "Kanál vydání",
"releaseChannel_description": "Vyberte si mezi stabilními, beta nebo alpha (nočními) vydáními pro automatické aktualizace",
"mediaSession": "Povolit relaci médií",
"mediaSession_description": "Povolí integraci do služby Media Session, což zobrazí ovládání a metadata médií v překrytí systémové hlasitosti a na zamykací obrazovce",
"mediaSession_description": "Povolí integraci do služby Media Session, což zobrazí ovládání a metadata médií v překrytí systémové hlasitosti a na zamykací obrazovce. Vyžaduje webový přehrávač zvuku.",
"exportImportSettings_control_description": "Exportovat a importovat nastavení pomocí souboru JSON",
"exportImportSettings_control_exportText": "Exportovat nastavení",
"exportImportSettings_control_importText": "Importovat nastavení",
+1 -1
View File
@@ -1093,7 +1093,7 @@
"sidePlayQueueLayout_description": "Sets the layout of the attached side play queue",
"sidePlayQueueLayout_optionHorizontal": "Horizontal",
"sidePlayQueueLayout_optionVertical": "Vertical",
"mediaSession_description": "Enables media session integration, displaying media controls and metadata in the system volume overlay and lock screen",
"mediaSession_description": "Enables media session integration, displaying media controls and metadata in the system volume overlay and lock screen. Requires the Web Audio Player.",
"mediaSession": "Enable media session",
"sidePlayQueueStyle": "Side play queue style",
"skipDuration_description": "Sets the duration to skip when using the skip buttons on the player bar",
+1 -1
View File
@@ -296,7 +296,7 @@
"releaseChannel_description": "Elige entre lanzamientos estables, beta, o alpha (nightly) para las actualizaciones automáticas",
"artistBackground_description": "Añade una imagen de fondo para las páginas de artistas que contienen el arte de los artistas",
"mediaSession": "Activar sesión de medios",
"mediaSession_description": "Activa la integración de la sesión de medios, mostrando los controles de medios y los metadatos en la superposición del volumen del sistema y en la pantalla de bloqueo",
"mediaSession_description": "Activa la integración de la sesión de medios, mostrando los controles de medios y los metadatos en la superposición del volumen del sistema y en la pantalla de bloqueo. Requiere el Reproductor Web de Audio.",
"exportImportSettings_control_description": "Exporta e importa la configuración a través de JSON",
"exportImportSettings_control_exportText": "Exportar configuración",
"exportImportSettings_control_importText": "Importar configuración",
+293
View File
@@ -0,0 +1,293 @@
{
"action": {
"addToFavorites": "Lisa üksusesse $t(entity.favorite, {\"count\": 2})",
"addToPlaylist": "Lisa üksusesse $t(entity.playlist, {\"count\": 1})",
"addOrRemoveFromSelection": "Lisa või eemalda valikust",
"selectRangeOfItems": "Vali mitu üksust korraga",
"clearQueue": "Tühjenda järjekord",
"goToCurrent": "Mine praeguse üksuse juurde",
"collapseAllFolders": "Ahenda kõik kaustad",
"expandAllFolders": "Laienda kõik kaustad",
"createPlaylist": "Loo $t(entity.playlist, {\"count\": 1})",
"createRadioStation": "Loo $t(entity.radioStation, {\"count\": 1})",
"deletePlaylist": "Kustuta $t(entity.playlist, {\"count\": 1})",
"deleteRadioStation": "Kustuta $t(entity.radioStation, {\"count\": 1})",
"selectAll": "Vali kõik",
"deselectAll": "Tühista kõigi valik",
"downloadStarted": "{{count}} üksuse allalaadimine algas",
"editPlaylist": "Muuda $t(entity.playlist, {\"count\": 1})",
"goToPage": "Mine lehele",
"moveToNext": "Järgmine",
"moveToBottom": "Liiguta lõppu",
"moveToTop": "Liiguta algusesse",
"moveUp": "Liigu üles",
"moveDown": "Liigu alla",
"holdToMoveToTop": "Hoia algusesse liigutamiseks",
"holdToMoveToBottom": "Hoia lõppu liigutamiseks",
"moveItems": "Liiguta üksusi",
"shuffle": "Sega",
"shuffleAll": "Sega kõik",
"shuffleSelected": "Sega valitud",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "Eemalda üksusest $t(entity.favorite, {\"count\": 2})",
"removeFromPlaylist": "Eemalda üksusest $t(entity.playlist, {\"count\": 1})",
"removeFromQueue": "Eemalda järjekorrast",
"setRating": "Hinda",
"toggleSmartPlaylistEditor": "Lülita $t(entity.smartPlaylist) redaktor sisse/välja",
"viewPlaylists": "Vaata $t(entity.playlist, {\"count\": 2})",
"viewMore": "Vaata rohkem",
"openApplicationDirectory": "Ava rakenduste kataloog",
"openIn": {
"lastfm": "Ava Last.fm-is",
"listenbrainz": "Ava ListenBrainzis",
"musicbrainz": "Ava MusicBrainzis",
"qobuz": "Ava Qobuzis",
"spotify": "Ava Spotifys"
}
},
"common": {
"countSelected": "{{count}} valitud",
"explicitStatus": "Ebasündsa sisu olek",
"action_one": "Toiming",
"action_other": "Toimingud",
"add": "Lisa",
"additionalParticipants": "Teised osalejad",
"newVersion": "Uus versioon on paigaldatud ({{version}})",
"viewReleaseNotes": "Kuva väljalaskemärkmed",
"albumGain": "Albumi helitugevus (gain)",
"albumPeak": "Albumi tippnivoo",
"areYouSure": "Kas oled kindel?",
"ascending": "Kasvav",
"back": "Tagasi",
"backward": "Tagasi",
"biography": "Biograafia",
"bitDepth": "Bititihedus",
"bitrate": "Bitikiirus",
"bpm": "BPM",
"cancel": "Tühista",
"center": "Keskel",
"channel_one": "Kanal",
"channel_other": "Kanalid",
"clear": "Tühjenda",
"close": "Sulge",
"codec": "Koodek",
"collapse": "Ahenda",
"comingSoon": "Tulekul…",
"configure": "Seadista",
"confirm": "Kinnita",
"create": "Loo",
"currentSong": "Praegune $t(entity.track, {\"count\": 1})",
"decrease": "Vähenda",
"delete": "Kustuta",
"descending": "Kahanev",
"description": "Kirjeldus",
"disable": "Keela",
"disc": "Plaat",
"dismiss": "Peida",
"doNotShowAgain": "Ära seda enam näita",
"duration": "Kestus",
"view": "Vaata",
"edit": "Muuda",
"enable": "Luba",
"expand": "Laienda",
"example": "Näide",
"externalLinks": "Välised lingid",
"openFolder": "Ava kaust",
"faster": "Kiiremini",
"favorite": "Lemmik",
"filter_one": "Filter",
"filter_other": "Filtrid",
"filters": "Filtrid",
"filter_multiple": "Mitu",
"filter_single": "Üksik",
"forceRestartRequired": "Muudatuste rakendamiseks taaskäivita… taaskäivitamiseks sule teavitus",
"forward": "Edasi",
"gap": "Vahe",
"home": "Avaleht",
"increase": "Suurenda",
"left": "Vasak",
"limit": "Limiit",
"manage": "Halda",
"maximize": "Maksimeeri",
"menu": "Menüü",
"minimize": "Minimeeri",
"modified": "Muudetud",
"mbid": "MusicBrainz ID",
"grouping": "Rühmitamine",
"mood": "Meeleolu",
"name": "Nimi",
"no": "Ei",
"none": "Puudub",
"noResultsFromQuery": "Päring ei andnud vasteid",
"numberOfResults": "{{numberOfResults}} vastet",
"noFilters": "Seadistatud filtreid pole",
"note": "Märkus",
"ok": "Ok",
"owner": "Omanik",
"playerMustBePaused": "Mängija peab olema pausil",
"preview": "Eelvaade",
"previousSong": "Eelmine $t(entity.track, {\"count\": 1})",
"private": "Privaatne",
"public": "Avalik",
"quit": "Välju",
"random": "Juhuslik",
"rating": "Hinne",
"retry": "Proovi uuesti",
"recordLabel": "Plaadifirma",
"releaseType": "Väljaande tüüp",
"refresh": "Värskenda",
"reload": "Laadi uuesti",
"rename": "Nimeta ümber",
"reset": "Lähtesta",
"resetToDefault": "Taasta vaikeväärtused",
"restartRequired": "Vajalik on taaskäivitamine",
"right": "Parem",
"sampleRate": "Diskreetimissagedus",
"save": "Salvesta",
"saveAndReplace": "Salvesta ja asenda",
"saveAs": "Salvesta nimega",
"search": "Otsi",
"setting_one": "Säte",
"setting_other": "Sätted",
"slower": "Aeglasemalt",
"share": "Jaga",
"size": "Suurus",
"sort": "Järjesta",
"sortOrder": "Järjestus",
"tags": "Sildid",
"title": "Pealkiri",
"trackNumber": "Pala",
"trackGain": "Pala võimendus",
"trackPeak": "Pala tippväärtus",
"translation": "Tõlge",
"unknown": "Tundmatu",
"version": "Versioon",
"year": "Aasta",
"yes": "Jah",
"explicit": "Ebatsensuurne",
"clean": "Puhas",
"gridRows": "Ruudustiku ridu",
"tableColumns": "Tabeli veerge",
"itemsMore": "{{count}} veel",
"newVersionAvailable": "Saadaval on uus versioon",
"path": "Asukoht"
},
"entity": {
"album_one": "Album",
"album_other": "Albumid",
"albumArtist_one": "Albumi esitaja",
"albumArtist_other": "Albumi esitajad",
"albumArtistCount_one": "{{count}} albumi esitaja",
"albumArtistCount_other": "{{count}} albumi esitajat",
"albumWithCount_one": "{{count}} album",
"albumWithCount_other": "{{count}} albumit",
"radioStation_one": "Raadiojaam",
"radioStation_other": "Raadiojaamad",
"radioStationWithCount_one": "{{count}} raadiojaam",
"radioStationWithCount_other": "{{count}} raadiojaama",
"artist_one": "Esitaja",
"artist_other": "Esitajad",
"artistWithCount_one": "{{count}} esitaja",
"artistWithCount_other": "{{count}} esitajat",
"favorite_one": "Lemmik",
"favorite_other": "Lemmikud",
"folder_one": "Kaust",
"folder_other": "Kaustad",
"folderWithCount_one": "{{count}} kaust",
"folderWithCount_other": "{{count}} kausta",
"genre_one": "Žanr",
"genre_other": "Žanrid",
"genreWithCount_one": "{{count}} žanr",
"genreWithCount_other": "{{count}} žanrit",
"playlist_one": "Esitusloend",
"playlist_other": "Esitusloendid",
"play_one": "{{count}} esitus",
"play_other": "{{count}} esitust",
"playlistWithCount_one": "{{count}} esitusloend",
"playlistWithCount_other": "{{count}} esitusloendit",
"smartPlaylist": "Nutikas $t(entity.playlist, {\"count\": 1})",
"track_one": "Rada",
"track_other": "Rajad",
"song_one": "Lugu",
"song_other": "Lood",
"trackWithCount_one": "{{count}} rada",
"trackWithCount_other": "{{count}} rada"
},
"error": {
"apiRouteError": "Päringut ei saanud edastada",
"audioDeviceFetchError": "Heliseadmete hankimisel tekkis viga",
"authenticationFailed": "Autentimine nurjus",
"badAlbum": "Näed seda lehte, kuna see lugu ei kuulu ühegi albumi alla. Kõige sagedamini juhtub see siis, kui lugu asub sinu muusikakausta juurkataloogis (otse muusikakaustas). Jellyfin rühmitab lood albumiteks ainult siis, kui need asuvad eraldi kaustas",
"badValue": "Kehtetu valik \"{{value}}\". Seda väärtust enam ei eksisteeri",
"credentialsRequired": "Nõutav on autentimine",
"endpointNotImplementedError": "Lõpp-punkt {{endpoint}} pole serveri {{serverType}} puhul toetatud",
"genericError": "Tekkis viga",
"invalidJson": "Vigane JSON",
"invalidServer": "Vigane server",
"localFontAccessDenied": "Juurdepääs kohalikele fontidele on keelatud",
"loginRateError": "Liiga palju sisselogimiskatseid, proovi mõne sekundi pärast uuesti",
"mpvRequired": "Vajalik on MPV",
"multipleServerSaveQueueError": "Esitusjärjekorras on lugusi, mis ei pärine praegusest serverist. See pole toetatud",
"networkError": "Tekkis võrguviga",
"noNetwork": "Server pole saadaval",
"noNetworkDescription": "Selle serveriga ei õnnestunud ühendust luua",
"notificationDenied": "Märguanded on keelatud. Sellel seadel pole mõju",
"openError": "Faili avamine nurjus",
"playbackError": "Meedia esitamisel tekkis viga",
"playbackPausedDueToError": "Taasesitus peatati vea tõttu",
"remoteDisableError": "Kaugserveri toimingu $t(common.disable) käigus tekkis viga",
"remoteEnableError": "Tõrge kaugserveri toimingul: $t(common.enable)",
"remotePortError": "Kaugserveri pordi määramisel tekkis viga",
"remotePortWarning": "Uue pordi rakendamiseks taaskäivita server",
"saveQueueFailed": "Järjekorra salvestamine nurjus",
"serverLockSingleServer": "Lukustatud serveri korral on lubatud ainult üks server",
"serverNotSelectedError": "Serverit pole valitud",
"serverRequired": "Vajalik on server",
"sessionExpiredError": "Su seanss on aegunud",
"systemFontError": "Süsteemifontide hankimisel ilmnes viga",
"settingsSyncError": "Esitaja (renderer) ja peaprotsessi seadete vahel leiti lahknevusi. Muudatuste rakendamiseks taaskäivita rakendus"
},
"filter": {
"album": "$t(entity.album, {\"count\": 1})",
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"matchAnd": "ja",
"matchOr": "või",
"albumCount": "$t(entity.album, {\"count\": 2}) arv",
"artist": "$t(entity.artist, {\"count\": 1})",
"biography": "Biograafia",
"bitrate": "Bitikiirus",
"bpm": "BPM",
"channels": "$t(common.channel, {\"count\": 2})",
"comment": "Kommentaar",
"communityRating": "Kogukonna hinne",
"criticRating": "Kriitikute hinne",
"dateAdded": "Lisatud",
"disc": "Plaat",
"duration": "Kestus",
"favorited": "Lemmikuks lisatud",
"fromYear": "Aastast",
"genre": "$t(entity.genre, {\"count\": 1})",
"id": "ID",
"isCompilation": "on kogumik",
"isFavorited": "on lemmik",
"isPublic": "on avalik",
"isRated": "on hinnatud",
"isRecentlyPlayed": "on hiljuti esitatud",
"lastPlayed": "Viimati esitatud",
"mostPlayed": "Enim esitatud",
"name": "Nimi",
"note": "Märkus",
"owner": "$t(common.owner)",
"path": "Asukoht",
"playCount": "Esituskordi",
"random": "Juhuslik",
"rating": "Hinne",
"recentlyAdded": "Viimati lisatud",
"recentlyPlayed": "Hiljuti esitatud",
"recentlyUpdated": "Hiljuti uuendatud",
"releaseDate": "Ilmumiskuupäev",
"releaseYear": "Ilmumisaasta",
"search": "Otsing",
"songCount": "Lugude arv"
}
}
+1 -1
View File
@@ -979,7 +979,7 @@
"preservePitch": "Utrzymuj ton",
"preventSleepOnPlayback_description": "Powstrzymuje ekran przed uśpieniem, gdy muzyka jest odtwarzana",
"preventSleepOnPlayback": "Powstrzymuj uśpienie podczas odtwarzania",
"mediaSession_description": "Włącza integrację z Media Session, wyświetlając sterowanie mediami i metadane w systemowym oknie zmiany głośności i na ekranie blokady",
"mediaSession_description": "Włącza integrację z Media Session, wyświetlając sterowanie mediami i metadane w systemowym oknie zmiany głośności i na ekranie blokad. Wymaga odtwarzacza web audio.",
"mediaSession": "Włącz media session",
"transcode": "Włącz transkodowanie",
"queryBuilder": "Kreator zaptań",
+14
View File
@@ -0,0 +1,14 @@
{
"action": {
"addToFavorites": "Idagdag sa $t(entity.favorite, {\"count\": 2})",
"addToPlaylist": "Idagdag sa $t(entity.playlist, {\"count\": 1})",
"addOrRemoveFromSelection": "Idagdag o alisin sa pinili",
"collapseAllFolders": "Isara lahat ng mga folder",
"expandAllFolders": "Buksan lahat ng mga folder",
"createPlaylist": "Gumawa $t(entity.playlist, {\"count\": 1})",
"createRadioStation": "Gumawa $t(entity.radioStation, {\"count\": 1})",
"selectAll": "Piliin lahat",
"deselectAll": "Huwag piliin lahat",
"downloadStarted": "Nagsimulang mag-dowload ng {{count}} (mga) aytem"
}
}
+2 -1
View File
@@ -169,7 +169,8 @@
"tableColumns": "Стовпці таблиці",
"itemsMore": "{{count}} більше",
"numberOfResults": "{{numberOfResults}} результатів",
"newVersionAvailable": "Доступна нова версія"
"newVersionAvailable": "Доступна нова версія",
"back": "Повернутися"
},
"entity": {
"album_one": "Альбом",
+1 -1
View File
@@ -666,7 +666,7 @@
"preventSleepOnPlayback": "防止播放時進入睡眠狀態",
"preventSleepOnPlayback_description": "在音樂播放時防止螢幕進入睡眠狀態",
"mediaSession": "啟用 Media Session",
"mediaSession_description": "啟用 Media Session 整合功能,系統音量 Overlay 和鎖定畫面顯示媒體資料與控制面板",
"mediaSession_description": "啟用 Media Session 整合功能,系統音量疊加層和鎖定畫面顯示媒體控制項與中繼資料。此功能需要使用網頁播放器。",
"releaseChannel": "發佈通道",
"analyticsDisable": "選擇退出使用情況分析",
"analyticsDisable_description": "經過匿名處理的使用情況資料將傳送給開發者,以協助改進應用程式",
+1 -1
View File
@@ -1039,7 +1039,7 @@ if (!singleInstance) {
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"script-src 'self' 'unsafe-inline' https://umami.jeffvli.org; style-src 'self' 'unsafe-inline'; media-src 'self' http: https: data: blob:; img-src 'self' http: https: data: blob:; connect-src 'self' http: https: ws: wss:; default-src 'self';",
"script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' https://umami.jeffvli.org; style-src 'self' 'unsafe-inline'; media-src 'self' http: https: data: blob:; img-src 'self' http: https: data: blob:; connect-src 'self' http: https: ws: wss:; default-src 'self';",
],
},
});
@@ -366,7 +366,10 @@ export const SubsonicController: InternalControllerEndpoint = {
query.type === LibraryItem.ALBUM_ARTIST || query.type === LibraryItem.ARTIST
? query.id
: undefined,
id: query.type === LibraryItem.SONG ? query.id : undefined,
id:
query.type === LibraryItem.SONG || query.type === LibraryItem.PLAYLIST_SONG
? query.id
: undefined,
},
});
@@ -419,7 +422,10 @@ export const SubsonicController: InternalControllerEndpoint = {
query.type === LibraryItem.ALBUM_ARTIST || query.type === LibraryItem.ARTIST
? query.id
: undefined,
id: query.type === LibraryItem.SONG ? query.id : undefined,
id:
query.type === LibraryItem.SONG || query.type === LibraryItem.PLAYLIST_SONG
? query.id
: undefined,
},
});
@@ -2327,7 +2333,6 @@ export const SubsonicController: InternalControllerEndpoint = {
case 'start':
state = 'starting';
break;
case 'timeupdate':
case 'unpause':
state = 'playing';
break;
@@ -4,50 +4,38 @@ import { generatePath, useNavigate } from 'react-router';
import { AppRoute } from '/@/renderer/router/routes';
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
import {
Album,
AlbumArtist,
Artist,
LibraryItem,
QueueSong,
Song,
} from '/@/shared/types/domain-types';
import { Album, LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
interface GoToActionProps {
items: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[];
items: Album[] | QueueSong[] | Song[];
}
export const GoToAction = ({ items }: GoToActionProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { albumArtists, albumId } = useMemo(() => {
const { albumId, artists } = useMemo(() => {
const firstItem = items[0];
if (firstItem._itemType === LibraryItem.ALBUM) {
return {
albumArtists: firstItem.albumArtists || [],
albumId: firstItem.id,
};
} else if (firstItem._itemType === LibraryItem.SONG) {
return {
albumArtists: firstItem.albumArtists || [],
albumId: firstItem.albumId,
};
} else if (
firstItem._itemType === LibraryItem.ARTIST ||
firstItem._itemType === LibraryItem.ALBUM_ARTIST
) {
return {
albumArtists: [{ id: firstItem.id, name: firstItem.name }],
albumId: null,
};
switch (firstItem._itemType) {
case LibraryItem.ALBUM:
return {
albumId: firstItem.id,
artists: firstItem.albumArtists || [],
};
case LibraryItem.SONG:
return {
albumId: firstItem.albumId,
artists:
(firstItem.artists?.length ? firstItem.artists : firstItem.albumArtists) ||
[],
};
default:
return {
albumId: null,
artists: [],
};
}
return {
albumArtists: [],
albumId: null,
};
}, [items]);
const handleGoToAlbum = useCallback(() => {
@@ -55,7 +43,7 @@ export const GoToAction = ({ items }: GoToActionProps) => {
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId }));
}, [albumId, navigate]);
const handleGoToAlbumArtist = useCallback(
const handleGoToArtist = useCallback(
(albumArtistId: string) => {
navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId }));
},
@@ -81,13 +69,13 @@ export const GoToAction = ({ items }: GoToActionProps) => {
{t('page.contextMenu.goToAlbum')}
</ContextMenu.Item>
)}
{albumArtists.map((albumArtist) => (
{artists.map((artist) => (
<ContextMenu.Item
key={albumArtist.id}
key={artist.id}
leftIcon="artist"
onSelect={() => handleGoToAlbumArtist(albumArtist.id)}
onSelect={() => handleGoToArtist(artist.id)}
>
{`${t('page.contextMenu.goTo')} ${albumArtist.name}`}
{`${t('page.contextMenu.goTo')} ${artist.name}`}
</ContextMenu.Item>
))}
</ContextMenu.SubmenuContent>
@@ -3,7 +3,6 @@ import { useMemo } from 'react';
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
import { PlayArtistRadioAction } from '/@/renderer/features/context-menu/actions/play-artist-radio-action';
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
@@ -39,8 +38,6 @@ export const AlbumArtistContextMenu = ({ items, type }: AlbumArtistContextMenuPr
<DownloadAction ids={ids} />
<ShareAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
<ContextMenu.Divider />
<GoToAction items={items} />
<ContextMenu.Divider />
<GetInfoAction disabled={items.length === 0} items={items} />
</ContextMenu.Content>
);
@@ -3,7 +3,6 @@ import { useMemo } from 'react';
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
import { PlayArtistRadioAction } from '/@/renderer/features/context-menu/actions/play-artist-radio-action';
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
@@ -39,8 +38,6 @@ export const ArtistContextMenu = ({ items, type }: ArtistContextMenuProps) => {
<DownloadAction ids={ids} />
<ShareAction ids={ids} itemType={LibraryItem.ARTIST} />
<ContextMenu.Divider />
<GoToAction items={items} />
<ContextMenu.Divider />
<GetInfoAction disabled={items.length === 0} items={items} />
</ContextMenu.Content>
);
@@ -19,6 +19,8 @@ import {
import { toast } from '/@/shared/components/toast/toast';
import { PlayerStatus } from '/@/shared/types/types';
let startupRestoreSessionHandled = false;
export const useQueueRestoreTimestamp = () => {
const { mediaSeekToTimestamp } = usePlayerActions();
@@ -51,28 +53,65 @@ export const useInitialTimestampRestore = () => {
const startupRestoreInitializedRef = useRef(false);
const startupSeekArmedRef = useRef<null | number>(null);
const startupSeekTargetUniqueIdRef = useRef<null | string>(null);
const startupSeekAppliedRef = useRef(false);
const applyStartupSeek = useCallback(() => {
const cancelStartupSeek = useCallback(() => {
if (startupSeekAppliedRef.current) {
return;
}
const seekTimestamp = startupSeekArmedRef.current;
if (!seekTimestamp || seekTimestamp <= 0) {
return;
}
startupSeekAppliedRef.current = true;
startupSeekArmedRef.current = null;
startupSeekTargetUniqueIdRef.current = null;
}, []);
const applyStartupSeek = useCallback(() => {
const seekTimestamp = startupSeekArmedRef.current;
if (startupSeekAppliedRef.current) {
return;
}
if (!seekTimestamp || seekTimestamp <= 0) {
return;
}
const targetUniqueId = startupSeekTargetUniqueIdRef.current;
const currentUniqueId = usePlayerStore.getState().getQueue().items[
usePlayerStore.getState().player.index
]?._uniqueId;
if (targetUniqueId && currentUniqueId !== targetUniqueId) {
cancelStartupSeek();
return;
}
startupSeekAppliedRef.current = true;
startupSeekArmedRef.current = null;
startupSeekTargetUniqueIdRef.current = null;
setTimeout(() => {
mediaSeekToTimestamp(seekTimestamp);
}, 100);
}, [mediaSeekToTimestamp]);
}, [cancelStartupSeek, mediaSeekToTimestamp]);
useEffect(() => {
if (startupRestoreInitializedRef.current) {
const targetUniqueId = startupSeekTargetUniqueIdRef.current;
if (
!targetUniqueId ||
startupSeekAppliedRef.current ||
!currentSong ||
currentSong._uniqueId === targetUniqueId
) {
return;
}
cancelStartupSeek();
}, [cancelStartupSeek, currentSong]);
useEffect(() => {
if (startupRestoreInitializedRef.current || startupRestoreSessionHandled) {
return;
}
@@ -81,9 +120,11 @@ export const useInitialTimestampRestore = () => {
}
startupRestoreInitializedRef.current = true;
startupRestoreSessionHandled = true;
if (timestamp > 0) {
startupSeekArmedRef.current = timestamp;
startupSeekTargetUniqueIdRef.current = currentSong._uniqueId;
}
if (playerStatus === PlayerStatus.PLAYING) {
@@ -45,7 +45,7 @@ const getPositionValue = (seconds: number, useTicks: boolean) => {
return Math.round(seconds * 1e7);
}
return seconds;
return seconds * 1000;
};
/*
@@ -459,12 +459,14 @@ export const useScrobble = () => {
lastProgressEventRef.current = properties.timestamp;
lastSeekEventRef.current = now;
const currentStatus = usePlayerStore.getState().player.status;
sendScrobble.mutate(
{
apiClientProps: { serverId: currentSong._serverId || '' },
query: {
albumId: currentSong.albumId,
event: 'timeupdate',
event: currentStatus === PlayerStatus.PLAYING ? 'unpause' : 'pause',
id: currentSong.id,
mediaType: mediaType,
playbackRate: playbackRate,
+1 -1
View File
@@ -1363,7 +1363,7 @@ export type ScrobbleArgs = BaseEndpointArgs & {
export type ScrobbleQuery = {
albumId?: string;
event?: 'pause' | 'start' | 'timeupdate' | 'unpause';
event?: 'pause' | 'start' | 'unpause';
id: string;
mediaType: 'podcast' | 'song';
playbackRate: number;
+20 -1
View File
@@ -2,6 +2,20 @@ import type { HotkeyItem } from '@mantine/hooks';
const RESERVED_KEYS = new Set(['alt', 'ctrl', 'meta', 'mod', 'shift']);
const PUNCTUATION_KEY_TO_PHYSICAL: Record<string, string> = {
"'": 'Quote',
',': 'Comma',
'-': 'Minus',
'.': 'Period',
'/': 'Slash',
';': 'Semicolon',
'=': 'Equal',
'[': 'BracketLeft',
'\\': 'Backslash',
']': 'BracketRight',
'`': 'Backquote',
};
/**
* Converts stored hotkey strings to Mantine's physical-key format.
* Mantine matches KeyboardEvent.code via normalizeKey, which turns Digit1 into
@@ -25,6 +39,11 @@ export const toPhysicalHotkey = (hotkey: string): string =>
return `Digit${part}`;
}
const punctuationPhysical = PUNCTUATION_KEY_TO_PHYSICAL[part];
if (punctuationPhysical) {
return punctuationPhysical;
}
return part;
})
.join('+');
@@ -33,5 +52,5 @@ export const withPhysicalKeys = (hotkeys: HotkeyItem[]): HotkeyItem[] =>
hotkeys.map(([hotkey, handler, options]) => [
toPhysicalHotkey(hotkey),
handler,
{ ...options, usePhysicalKeys: true },
{ ...options, preventDefault: true, usePhysicalKeys: true },
]);
@@ -3,7 +3,12 @@ const CODE_TO_HOTKEY_KEY: Record<string, string> = {
ArrowLeft: 'arrowleft',
ArrowRight: 'arrowright',
ArrowUp: 'arrowup',
Backquote: '`',
Backslash: '\\',
Backspace: 'backspace',
BracketLeft: '[',
BracketRight: ']',
Comma: ',',
Delete: 'delete',
End: 'end',
Enter: 'enter',
@@ -14,6 +19,10 @@ const CODE_TO_HOTKEY_KEY: Record<string, string> = {
Minus: 'minus',
PageDown: 'pagedown',
PageUp: 'pageup',
Period: '.',
Quote: "'",
Semicolon: ';',
Slash: '/',
Space: 'space',
Tab: 'tab',
};