Compare commits

..

273 Commits

Author SHA1 Message Date
0e94c72eef Bump version to 3.2.1 2024-01-01 09:22:46 +09:00
c055641351 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-01-01 01:19:52 +01:00
c0a3aa99de chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2024-01-01 01:19:52 +01:00
8a8976acef chore(i18n): Translated using Weblate (Czech)
Currently translated at 94.8% (311 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-01 01:06:02 +01:00
e409165e1b chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2024-01-01 01:06:02 +01:00
b278140796 chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2024-01-01 01:01:00 +01:00
397056a54d chore(i18n): Translated using Weblate (Turkish)
Currently translated at 26.5% (87 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-01-01 01:01:00 +01:00
edecd65419 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-01-01 01:01:00 +01:00
4d2d0b7bd6 chore(i18n): Translated using Weblate (Czech)
Currently translated at 85.6% (281 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-01 01:01:00 +01:00
0ca4e34efd chore(i18n): Translated using Weblate (Czech)
Currently translated at 85.6% (281 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-01 01:00:59 +01:00
43f3226c3a fix: fix #1574 2024-01-01 08:36:48 +09:00
0a6dbecc05 fix: fix #1575 2024-01-01 08:36:22 +09:00
f5aa179cd6 chore(i18n): Translated using Weblate
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: inson1 <vaclav.svarc01@seznam.cz>
Co-authored-by: Anonymous <noreply@weblate.org>
2024-01-01 04:53:59 +09:00
3140e91dda Update changelog for v3.2.0 2023-12-31 16:31:59 +00:00
022f8ff65c Merge branch 'master' of https://github.com/th-ch/youtube-music 2024-01-01 01:23:01 +09:00
5e63cc2e89 Bump version to 3.2.0 2024-01-01 01:22:48 +09:00
880ed99846 Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2"
This reverts commit 050d55c736.
2024-01-01 00:34:49 +09:00
222e78c85b fix(in-app-menu): fix in-app-menu tooltip position 2023-12-31 23:48:01 +09:00
050d55c736 fix(deps): update dependency @xhayper/discord-rpc to v1.1.2 2023-12-31 23:40:26 +09:00
13ef8560ff fix: pnpm build error 2023-12-31 23:40:09 +09:00
78d990c079 feat(album-color-theme): improve Album Color Theme style (#1571) 2023-12-31 23:04:44 +09:00
4d3e2c09da feat(menu): add more detail in Menu (#1570) 2023-12-31 20:56:24 +09:00
aa899d247a chore(plugins): change default config album-actions, music-together 2023-12-31 13:56:42 +09:00
ee0c512529 feat(music-together): Add new plugin Music Together (#1562)
* feat(music-together): test `peerjs`

* feat(music-together): replace `prompt` to `custom-electron-prompt`

* fix(music-together): fix

* test fix

* wow

* test

* feat(music-together): improve `onStart`

* fix: adblocker

* fix(adblock): fix crash with `peerjs`

* feat(music-together): add host UI

* feat(music-together): implement addSong, removeSong, syncQueue

* feat(music-together): inject panel

* feat(music-together): redesign music together panel

* feat(music-together): sync queue, profile

* feat(music-together): sync progress, song, state

* fix(music-together): fix some bug

* fix(music-together): fix sync queue

* feat(music-together): support i18n

* feat(music-together): improve sync queue

* feat(music-together): add profile in music item

* refactor(music-together): refactor structure

* feat(music-together): add permission

* fix(music-together): fix queue sync bug

* fix(music-together): fix some bugs

* fix(music-together): fix permission not working on guest mode

* fix(music-together): fix queue sync relate bugs

* fix(music-together): fix automix items not append using music together

* fix(music-together): fix

* feat(music-together): improve video injection

* fix(music-together): fix injection code

* fix(music-together): fix broadcast guest

* feat(music-together): add more permission

* fix(music-together): fix injector

* fix(music-together): fix guest add song logic

* feat(music-together): add popup close listener

* fix(music-together): fix connection issue

* fix(music-together): fix connection issue 2

* feat(music-together): reserve playlist

* fix(music-together): exclude automix songs

* fix(music-together): fix playlist index sync bug

* fix(music-together): fix connection failed error and sync index

* fix(music-together): fix host set index bug

* fix: apply fix from eslint

* feat(util): add `ImageElementFromSrc`

* chore(util): update jsdoc

* feat(music-together): add owner name

* chore(music-together): add translation

* feat(music-together): add progress sync

* chore(music-together): remove `console.log`

---------

Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-31 13:52:15 +09:00
5f9b522307 chore(deps): update dependency rollup to v4.9.2 (#1567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-31 04:05:49 +09:00
c207e29980 fix(deps): update dependency i18next to v23.7.13 (#1569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-31 04:05:44 +09:00
df4d2d6b72 chore(ambient-mode): remove console.log 2023-12-31 02:28:31 +09:00
c3dd20cabd chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (296 of 296 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-30 17:09:06 +00:00
7a6db95d1a chore(i18n): Translated using Weblate (Czech)
Currently translated at 80.4% (238 of 296 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-30 17:09:05 +00:00
bc6825d63b chore(i18n): Translated using Weblate (Czech)
Currently translated at 76.8% (226 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-29 16:14:02 +01:00
5e79e9e0f2 feat: Add new plugin Album actions (#1515)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-30 00:13:56 +09:00
5e303c2ba8 fix(deps): update dependency i18next to v23.7.12 (#1564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-29 22:26:41 +09:00
0bd9c16356 fix: Only apply scale factor on Windows (#1565) 2023-12-29 22:26:24 +09:00
f0f5d9da2f feat(ambient-mode): support ambient mode on Song section
resolve #1555
2023-12-29 21:46:27 +09:00
TC
f46c431f4c Move cask definition to separate repo 2023-12-29 11:26:55 +01:00
62410e9ee2 feat(in-app-menu): add show on hover 2023-12-29 17:52:31 +09:00
46f76f1408 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 3.7% (11 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2023-12-27 18:20:59 +01:00
5e071e16d8 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 29.2% (86 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2023-12-27 18:20:58 +01:00
c0238588bd chore(i18n): Translated using Weblate (Russian)
Currently translated at 78.5% (231 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-27 18:20:58 +01:00
Nik
30002d660a chore(i18n): Translated using Weblate (Russian)
Currently translated at 78.5% (231 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-27 18:20:58 +01:00
48eeb6bca3 fix: picture-in-picture icon 2023-12-28 02:17:42 +09:00
e67699fed5 fix: fixed an issue with the download button disappearing
- resolve #1551
2023-12-28 02:08:03 +09:00
8aeae45965 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 77.2% (227 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-27 13:09:36 +01:00
ce7491941b chore(i18n): Translated using Weblate (Russian)
Currently translated at 74.8% (220 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-27 13:09:36 +01:00
1dce03c4f2 chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-27 13:09:36 +01:00
62eae6d5d0 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.16.0 (#1556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:37:06 +09:00
15b2b26b84 chore(deps): update pnpm to v8.13.1 (#1557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:36:57 +09:00
9664c17c47 chore(deps): update dependency ws to v8.16.0 (#1559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:36:51 +09:00
8067dad2fa fix(deps): update dependency youtubei.js to v8.1.0 (#1560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:36:42 +09:00
4dcaa510d9 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 3.7% (11 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2023-12-25 12:42:54 +01:00
b6e918089d chore(i18n): Translated using Weblate (Turkish)
Currently translated at 29.2% (86 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2023-12-25 12:42:54 +01:00
1c9e6b1bb8 chore(i18n): Translated using Weblate (Polish)
Currently translated at 97.9% (288 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-25 12:42:54 +01:00
ebd304c252 fix(deps): update dependency node-html-parser to v6.1.12 (#1554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-25 20:37:05 +09:00
36083c4173 Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2" (#1552) 2023-12-25 03:48:15 +09:00
a084b060d8 chore(deps): update dependency electron to v28.1.0 2023-12-25 03:32:28 +09:00
432c79b606 fix(deps): update dependency @xhayper/discord-rpc to v1.1.2 2023-12-25 03:32:07 +09:00
0f1f0ee933 chore(deps): update dependency eslint-plugin-prettier to v5.1.2 2023-12-25 03:31:59 +09:00
9b1a4b8d88 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-23 21:07:03 +01:00
1a7a665915 chore(i18n): Translated using Weblate (Czech)
Currently translated at 76.5% (225 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-22 03:06:46 +01:00
623ecf7fb8 chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-20 17:44:47 +01:00
0dc9c6a1a9 chore(i18n): Translated using Weblate (Czech)
Currently translated at 72.7% (214 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 17:44:47 +01:00
72c5eaa5ff chore(i18n): Translated using Weblate (Czech)
Currently translated at 82.3% (242 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 13:54:56 +01:00
0f47b94b7d chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-20 13:53:36 +01:00
9abe15f1ad chore(i18n): Translated using Weblate (Czech)
Currently translated at 82.3% (242 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 13:53:35 +01:00
96afda92c8 chore(i18n): Translated using Weblate (Czech)
Currently translated at 80.9% (238 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 13:33:50 +01:00
5c6fd4a739 Update README-ko.md 2023-12-20 14:11:53 +02:00
23b87a876d chore(deps): update dependency eslint-plugin-prettier to v5.1.0 2023-12-20 13:59:40 +09:00
737fd05369 chore(deps): update dependency electron-vite to v2.0.0-beta.2 2023-12-20 13:59:28 +09:00
c5bcd89f16 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.15.0 2023-12-19 16:32:46 +09:00
377e1be0b2 fix(deps): update dependency @foobar404/wave to v2.0.5 2023-12-19 16:32:31 +09:00
a92049c0c9 chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2023-12-19 07:21:28 +01:00
27a2955bba fix: fix homebrew cask
- resolve #1514
2023-12-18 22:02:38 +09:00
cc940e2020 Update changelog for v3.1.1 2023-12-18 12:57:28 +00:00
203c80767d Bump version to 3.1.1 2023-12-18 21:49:31 +09:00
f564039438 chore: fix the example plugins in the README 2023-12-18 14:47:18 +02:00
d6566fb870 chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 84.3% (248 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2023-12-18 13:46:47 +01:00
55ab17e789 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-18 13:43:08 +01:00
e0eeb720cd feat: upgrade to Vite v5 2023-12-18 21:10:47 +09:00
53ac7ff257 fix: electron-vite dev --watch 2023-12-18 20:00:00 +09:00
a4e2f10afa chore(deps): update dependency playwright to v1.41.0-alpha-dec-18-2023 2023-12-18 19:58:33 +09:00
c9c9d766e6 chore(deps): update dependency rollup to v4.9.1 2023-12-18 19:56:14 +09:00
b82d161180 chore: Update bug_report.yml 2023-12-18 19:47:51 +09:00
7cadacd8cf fix: vite dev mode
fix an issue caused by da3bc5aeb7
2023-12-18 19:07:43 +09:00
fc1a7cda62 chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 84.3% (248 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2023-12-18 10:56:31 +01:00
da3bc5aeb7 fix: renderer plugin load timing
MAGIC OF JAVASCRIPT
2023-12-18 18:42:01 +09:00
ee98344064 chore(i18n): Added translation using Weblate (Lithuanian) 2023-12-17 21:43:08 +01:00
da10eabf99 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 69.3% (204 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-17 14:55:42 +01:00
ce3c63c386 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 73.8% (217 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-17 14:55:42 +01:00
f8c3cff5a9 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-17 14:55:41 +01:00
08f369f8bc chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 43.5% (128 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-17 08:28:44 +01:00
1517230ede chore(i18n): Translated using Weblate (German)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-17 08:28:44 +01:00
45aeadef86 fix(song-info-front): fix type 2023-12-17 16:05:26 +09:00
5ec7c346ca fix(song-info-front): fix parser to parse album name 2023-12-17 15:51:35 +09:00
4b68de1606 fix: update types 2023-12-16 22:39:57 +09:00
7710bcdacc chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-16 11:58:32 +01:00
4c19bcc983 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 26.1% (77 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2023-12-16 03:33:06 +01:00
2f50b1423a chore(i18n): Translated using Weblate (French)
Currently translated at 95.9% (282 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-16 03:33:06 +01:00
15768c9691 chore(deps): update dependency eslint to v8.56.0 2023-12-16 11:28:58 +09:00
442eb75b8d chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 43.1% (127 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-16 03:09:12 +01:00
8facd27125 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-16 03:09:11 +01:00
a302cb6ebf chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-16 03:09:11 +01:00
32422c7ba9 fix(deps): update dependency i18next to v23.7.11 2023-12-15 13:42:50 +09:00
ca131cb156 chore(deps): update dependency eslint-plugin-import to v2.29.1 2023-12-15 13:42:44 +09:00
5f8262ede1 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 65.3% (192 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-14 02:11:40 +01:00
88db9d3693 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-13 21:33:32 +01:00
0fa6f344f9 chore(i18n): Translated using Weblate (Polish)
Currently translated at 97.6% (287 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-13 21:33:31 +01:00
d1642b3dfb chore(i18n): Translated using Weblate (Polish)
Currently translated at 97.6% (287 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-13 21:33:31 +01:00
7599fce5f5 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 97.6% (287 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-13 21:33:31 +01:00
9bd0dc9a7b chore(i18n): Translated using Weblate (Greek)
Currently translated at 23.8% (70 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/el/
2023-12-13 21:33:31 +01:00
dad7cafebc chore(i18n): Translated using Weblate (Russian)
Currently translated at 72.7% (214 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-13 16:20:54 +01:00
4febb28201 chore(deps): update dependency rollup to v4.9.0 2023-12-13 21:13:41 +09:00
ced943bad3 chore(deps): update pnpm to v8.12.1 2023-12-13 21:12:07 +09:00
202b8717a2 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-13 13:10:46 +01:00
eba7026b89 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2023-12-13 13:10:46 +01:00
6b2adca1f4 fix(downloader): fix download button i18n 2023-12-13 18:02:18 +09:00
0c9d3cc057 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 42.8% (126 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-13 05:07:02 +01:00
45ec11aaed chore(deps): update pnpm-lock.yaml 2023-12-13 13:06:10 +09:00
4d47fa3fac fix(deps): update dependency @electron/remote to v2.1.1 2023-12-13 12:24:55 +09:00
6322d5bb09 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 3.0% (9 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2023-12-12 21:56:36 +01:00
2e8a579049 chore(i18n): Translated using Weblate (Thai)
Currently translated at 1.0% (3 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2023-12-12 21:56:36 +01:00
9db7e99d7f chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-12 21:56:36 +01:00
ef73989995 chore(i18n): Translated using Weblate (Czech)
Currently translated at 79.9% (235 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-12 21:56:36 +01:00
61024084e9 chore(deps): update dependency ws to v8.15.1 2023-12-13 05:46:56 +09:00
1c71b2020f fix(deps): update dependency i18next to v23.7.9 2023-12-13 05:45:52 +09:00
3b2b545388 chore(i18n): Translated using Weblate (Russian)
Currently translated at 67.6% (199 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-12 21:44:51 +01:00
fafc7f94fd chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-12 21:44:50 +01:00
5fa88b5ce9 fix: fix renderer plugin load timing
- fix #1522
2023-12-12 12:53:41 +09:00
a8fe4167db fix(pip): fix invalid position config 2023-12-12 11:11:41 +09:00
0e6b0ddc26 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-12 02:24:41 +01:00
af88db3865 chore(i18n): Translated using Weblate (Japanese)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-12 02:24:41 +01:00
0a1bef556c chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 02:24:41 +01:00
7030181dc1 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 02:24:41 +01:00
98cb2f61ab chore(deps): update dependency playwright to v1.41.0-alpha-dec-12-2023 2023-12-12 10:23:59 +09:00
a601d0b3d2 chore(deps): update dependency rollup to v4.8.0 2023-12-12 10:22:23 +09:00
8d18923065 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 75.1% (221 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-12 01:08:34 +01:00
4b6a7e0bd7 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 01:08:34 +01:00
44f87853d6 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 74.8% (220 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-12 01:02:37 +01:00
4ff47bee03 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 74.1% (218 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-12 00:54:03 +01:00
7a2c28df21 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 00:54:02 +01:00
f7241d1e4a chore(i18n): Added translation using Weblate (Indonesian) 2023-12-12 00:33:55 +01:00
2b5daf3a75 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.14.0 2023-12-12 06:43:20 +09:00
d7184cf75d chore(i18n): Translated using Weblate (Russian)
Currently translated at 54.0% (159 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-11 19:59:59 +01:00
1313d18d44 chore(i18n): Translated using Weblate (Czech)
Currently translated at 80.9% (238 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-11 19:59:58 +01:00
605e9481d9 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-11 19:59:58 +01:00
cf4c89f825 chore(i18n): Added translation using Weblate (Thai) 2023-12-11 10:17:18 +01:00
f4f1d4d373 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 46.2% (136 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-11 09:00:43 +01:00
6557b40924 Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-11 00:20:44 +01:00
e01a29c5d1 Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-11 00:20:44 +01:00
d9a7e16d63 Translated using Weblate (Italian)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-11 00:20:44 +01:00
d170199d85 Update changelog for v3.1.0 2023-12-10 23:10:52 +00:00
e650aae491 Bump version to 3.1.0 2023-12-11 08:00:42 +09:00
49058dbeab chore: update pnpm-lock.yaml 2023-12-11 07:59:42 +09:00
31d2d91566 Translated using Weblate (Japanese)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-10 23:57:22 +01:00
abf2a52b46 fix: in-player adblocker inject timing issue
- fix #1478
2023-12-11 07:46:01 +09:00
d0ca10e1a1 Translated using Weblate (Chinese (Simplified))
Currently translated at 1.0% (3 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2023-12-10 22:57:39 +01:00
fe99a04174 Translated using Weblate (Portuguese)
Currently translated at 31.6% (93 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-10 22:57:39 +01:00
916900ae6a Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:57:39 +01:00
826c9ba48e fix(package.json): fix RPM version libuuid issue
- fix #1508
2023-12-11 06:57:32 +09:00
7783cc5f30 Translated using Weblate (Chinese (Simplified))
Currently translated at 0.3% (1 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2023-12-10 22:49:58 +01:00
035d7f19ea Translated using Weblate (Spanish)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:49:58 +01:00
294cf15d58 Translated using Weblate (Spanish)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:49:58 +01:00
11fa0dcbc6 Translated using Weblate (Spanish)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:49:58 +01:00
85b53db439 Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-10 22:49:57 +01:00
200df100f4 Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-10 22:49:57 +01:00
ba202a8572 chore(deps): update dependency electron to v28 (#1498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-11 06:44:59 +09:00
daf05239a1 Enable/Disable Navigation without restart (#1507) 2023-12-11 04:21:17 +09:00
b73b5735ec Translated using Weblate (Spanish)
Currently translated at 90.8% (267 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 04:08:22 +01:00
292248dd35 Translated using Weblate (Spanish)
Currently translated at 90.8% (267 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 04:08:22 +01:00
2e240541c8 Turkish(tr)_lang_file (#1513) 2023-12-10 08:41:46 +09:00
ab62ae3682 chore(deps): update dependency ws to v8.15.0 2023-12-10 04:28:02 +09:00
5c3f5d05d3 chore(deps): update pnpm to v8.12.0 2023-12-09 12:38:32 +09:00
c81af537b6 Translated using Weblate (Spanish)
Currently translated at 83.5% (244 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-09 01:37:30 +00:00
7295c73371 Translated using Weblate (Spanish)
Currently translated at 83.5% (244 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-09 01:37:29 +00:00
5a65e08a94 Translated using Weblate (Spanish)
Currently translated at 83.5% (244 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-09 01:37:28 +00:00
45e5bc7df5 Skip Disliked Songs (#1505)
* Added skip-disliked-songs

* Changed it to activate and deactivate without restart

* Added waiter for Element

* MutationObserver can be null

Co-authored-by: JellyBrick <shlee1503@naver.com>

* MutationObserver could not exist

Co-authored-by: JellyBrick <shlee1503@naver.com>

* Update src/plugins/skip-disliked-songs/index.ts

Co-authored-by: JellyBrick <shlee1503@naver.com>

* Replaced double quotes with single quotes

---------

Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-09 10:37:17 +09:00
a81cb9515c Translated using Weblate (Spanish)
Currently translated at 39.7% (116 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:55:06 +01:00
88e5cc2728 Translated using Weblate (Spanish)
Currently translated at 39.7% (116 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:55:06 +01:00
1685648328 Translated using Weblate (Spanish)
Currently translated at 39.0% (114 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:54:40 +01:00
9334adacf6 Translated using Weblate (Spanish)
Currently translated at 39.0% (114 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:54:39 +01:00
efbe557dce Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-08 20:37:43 +01:00
52b2625486 Translated using Weblate (Spanish)
Currently translated at 10.6% (31 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 20:37:43 +01:00
aa0424db08 Translated using Weblate (Spanish)
Currently translated at 10.6% (31 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 20:37:43 +01:00
157619aa4f chore(deps): update dependency rollup to v4.7.0 2023-12-09 03:23:17 +09:00
ce637968b2 Translated using Weblate (Italian)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-08 11:32:34 +01:00
b2a3ed7428 Translated using Weblate (Spanish)
Currently translated at 4.1% (12 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 11:08:18 +01:00
ca856e4d88 Translated using Weblate (Spanish)
Currently translated at 4.1% (12 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 11:08:17 +01:00
2ef2536766 Translated using Weblate (Italian)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-08 11:08:17 +01:00
1b22633388 Translated using Weblate (Italian)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-08 11:08:17 +01:00
db306ad4e0 Translated using Weblate (Italian)
Currently translated at 65.4% (191 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 19:20:03 +01:00
34a44cb9c6 Translated using Weblate (Italian)
Currently translated at 65.4% (191 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 19:20:03 +01:00
8c499a6e20 Translated using Weblate (Italian)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:58:33 +01:00
3b816a6fd9 Translated using Weblate (Italian)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:58:33 +01:00
35aba8e25a Translated using Weblate (Italian)
Currently translated at 46.9% (137 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:52:44 +01:00
0eb6723f27 Translated using Weblate (Italian)
Currently translated at 46.9% (137 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:52:44 +01:00
2250ec9372 Translated using Weblate (Spanish)
Currently translated at 1.0% (3 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-07 13:48:08 +00:00
ef02b53284 Translated using Weblate (Italian)
Currently translated at 42.1% (123 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 13:48:08 +00:00
57df7cf3f9 Translated using Weblate (Italian)
Currently translated at 42.1% (123 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 13:48:07 +00:00
f0cd540726 Translated using Weblate (Ukrainian)
Currently translated at 1.0% (3 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-07 13:48:06 +00:00
7e919395eb chore(deps): update dependency typescript to v5.3.3 2023-12-07 10:03:26 +09:00
facc7252c2 fix(deps): update dependency i18next to v23.7.8 2023-12-07 10:03:06 +09:00
09b8943f69 Translated using Weblate (Italian)
Currently translated at 32.1% (94 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 01:45:31 +01:00
aa84ffc7e1 Translated using Weblate (French)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-07 01:45:31 +01:00
10162c948b chore: Create zh-CN.json 2023-12-07 09:45:08 +09:00
e8c1cbdd94 BC-BREAK: bump node engines version 2023-12-07 02:52:14 +09:00
5459f623ec Translated using Weblate (Italian)
Currently translated at 32.1% (94 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 18:59:01 +01:00
2a2cba3539 Translated using Weblate (Italian)
Currently translated at 32.1% (94 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 18:59:01 +01:00
1ce84607e6 Translated using Weblate (Portuguese)
Currently translated at 30.8% (90 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-05 17:59:26 +01:00
075c54d39f Translated using Weblate (Portuguese)
Currently translated at 30.8% (90 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-05 17:59:26 +01:00
aabb826cb8 Translated using Weblate (Italian)
Currently translated at 13.0% (38 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 17:59:26 +01:00
3791b1ae1c Translated using Weblate (Italian)
Currently translated at 13.0% (38 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 17:59:26 +01:00
d4b87d098b Added translation using Weblate (Portuguese) 2023-12-05 17:39:07 +01:00
57e9c13d13 Translated using Weblate (Italian)
Currently translated at 6.8% (20 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 05:59:05 +01:00
e48e570e69 Translated using Weblate (Italian)
Currently translated at 6.8% (20 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 05:59:05 +01:00
b7d4d5b022 Translated using Weblate (Czech)
Currently translated at 61.3% (179 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-05 05:59:05 +01:00
dc8b0173e9 Added translation using Weblate (Spanish) 2023-12-05 05:59:05 +01:00
840729126e fix(macos): fix app crash in macOS 2023-12-05 13:36:23 +09:00
ed90b97a92 chore(deps): update dependency vite to v4.5.1 2023-12-05 13:29:45 +09:00
bbe4f07651 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.13.2 (#1452)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 13:29:23 +09:00
66df83a86d fix: Homebrew latest release url parsing (#1496)
Co-authored-by: Gian Marco Cinalli <gianmarco.cinalli@immobiliare.it>
2023-12-05 10:56:59 +09:00
dfedd8f091 Translated using Weblate (Italian)
Currently translated at 4.1% (12 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-04 19:15:58 +01:00
58ec6eebfb Translated using Weblate (Czech)
Currently translated at 58.5% (171 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 19:15:58 +01:00
3ba4130bc6 Translated using Weblate (Czech)
Currently translated at 58.5% (171 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 19:15:58 +01:00
7dbbf38e78 Added translation using Weblate (Italian) 2023-12-04 17:31:24 +01:00
51e04162a7 Translated using Weblate (Czech)
Currently translated at 57.1% (167 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 09:58:03 +01:00
d3325e2490 Translated using Weblate (Czech)
Currently translated at 56.1% (164 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 05:05:31 +01:00
d2708ee32f Translated using Weblate (Czech)
Currently translated at 56.1% (164 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 05:05:31 +01:00
781a09c717 Translated using Weblate (Czech)
Currently translated at 55.4% (162 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 04:55:52 +01:00
11605293dd Translated using Weblate (Czech)
Currently translated at 55.4% (162 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 04:55:52 +01:00
3f0b946190 Translated using Weblate (Czech)
Currently translated at 52.7% (154 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:49:11 +01:00
74972f053a Translated using Weblate (Czech)
Currently translated at 52.7% (154 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:49:11 +01:00
d85190ace1 Translated using Weblate (Czech)
Currently translated at 51.7% (151 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:39:39 +01:00
5755b4ac7f Translated using Weblate (Czech)
Currently translated at 51.7% (151 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:39:38 +01:00
3c24f2edcd Translated using Weblate (Czech)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:26:36 +01:00
6828520853 Translated using Weblate (Czech)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:26:36 +01:00
2ba7dddb95 Translated using Weblate (Czech)
Currently translated at 50.6% (148 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 22:17:53 +00:00
57a8922d04 Translated using Weblate (Czech)
Currently translated at 51.0% (149 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 22:17:53 +00:00
640098860a Translated using Weblate (Czech)
Currently translated at 51.0% (149 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 22:17:52 +00:00
53384d9f3b Translated using Weblate (Russian)
Currently translated at 13.6% (40 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-03 22:17:51 +00:00
89d8d98a35 fix: remove import 'process' 2023-12-04 03:53:24 +09:00
7b78ba6761 Translated using Weblate (Polish)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-03 17:43:49 +01:00
3926a9a0c0 Translated using Weblate (French)
Currently translated at 99.6% (291 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-03 17:43:48 +01:00
0708cd5a38 Added translation using Weblate (Polish) 2023-12-03 11:35:51 +01:00
e20e9ca771 Added translation using Weblate (Ukrainian) 2023-12-03 10:26:51 +01:00
be1038bafd Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-03 01:50:48 +01:00
ebc087963b Translated using Weblate (French)
Currently translated at 85.2% (249 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-03 01:50:48 +01:00
020bdc0811 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 23:23:55 +00:00
a0d1ad6a47 Translated using Weblate (French)
Currently translated at 19.1% (56 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-02 23:23:54 +00:00
62e5679791 fix(deps): update dependency ts-morph to v21 2023-12-03 07:50:56 +09:00
35568bd299 Translated using Weblate (Chinese (Traditional))
Currently translated at 46.5% (136 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 21:13:54 +01:00
1e40b377af Translated using Weblate (Chinese (Traditional))
Currently translated at 46.5% (136 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 21:13:54 +01:00
1073de1b45 Translated using Weblate (Czech)
Currently translated at 46.5% (136 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-02 21:13:45 +01:00
1d5788acaf Translated using Weblate (Chinese (Traditional))
Currently translated at 35.6% (104 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 20:27:58 +01:00
8fb446588d Translated using Weblate (Chinese (Traditional))
Currently translated at 35.6% (104 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 20:27:58 +01:00
6cec34b2ac Translated using Weblate (Chinese (Traditional))
Currently translated at 35.6% (104 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 20:27:58 +01:00
763d3e8f74 Translated using Weblate (German)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 20:27:58 +01:00
7da0a913f1 Update changelog for v3.0.2 2023-12-02 18:39:16 +00:00
ca04c4561b Bump version to 3.0.2 2023-12-03 03:30:08 +09:00
7d8fbf49a8 Translated using Weblate (Chinese (Traditional))
Currently translated at 23.6% (69 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 19:29:34 +01:00
75e15b948d Translated using Weblate (Chinese (Traditional))
Currently translated at 23.6% (69 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 19:29:28 +01:00
125b69fd75 Translated using Weblate (Chinese (Traditional))
Currently translated at 23.6% (69 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 19:29:28 +01:00
a68d6b64dd Translated using Weblate (French)
Currently translated at 8.9% (26 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-02 19:29:28 +01:00
a60d4264dc fix: pnpm install doesn't refresh lock file 2023-12-03 03:16:07 +09:00
9e2c6b1afa fix: update pnpm-lock.yaml 2023-12-03 03:07:11 +09:00
14965a93e9 fix: simple-youtube-age-restriction-bypass 2023-12-03 02:49:17 +09:00
f62664b6a5 fix(adblocker): fix In-Player adblocker
- resolves #1478
2023-12-03 02:06:49 +09:00
60cb7f32f1 fix(in-app-menu/i18n): fix hide-dom-window-controls 2023-12-03 01:00:33 +09:00
008b3ad710 Translated using Weblate (Chinese (Traditional))
Currently translated at 1.0% (3 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 16:59:02 +01:00
8cae64f496 fix(menu): crash on linux
- resolves #1477
2023-12-03 00:54:36 +09:00
5cdc1bc762 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-02 16:29:40 +01:00
15c455105b Translated using Weblate (French)
Currently translated at 8.5% (25 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-02 16:29:40 +01:00
14407a98c9 Translated using Weblate (English)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-02 16:29:40 +01:00
0d004d5caf Added translation using Weblate (Chinese (Traditional)) 2023-12-02 16:29:40 +01:00
4b75a2405c Update changelog for v3.0.1 2023-12-02 14:32:40 +00:00
91 changed files with 10831 additions and 1176 deletions

View File

@ -24,6 +24,11 @@ body:
placeholder: 2.0.0 placeholder: 2.0.0
validations: validations:
required: true required: true
- type: checkboxes
attributes:
label: Are you using the portable version of the YouTube Music Application?
options:
- label: I use the portable version of the YouTube Music Application.
- type: dropdown - type: dropdown
attributes: attributes:
label: What operating system are you using? label: What operating system are you using?

View File

@ -49,9 +49,10 @@ this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Insta
### MacOS ### MacOS
You can install the app using Homebrew: You can install the app using Homebrew (see the [cask definition](https://github.com/th-ch/homebrew-youtube-music)):
```bash ```bash
brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master/youtube-music.rb brew install th-ch/youtube-music/youtube-music
``` ```
If you install the app manually and get an error "is damaged and cant be opened." when launching the app, run the following in the Terminal: If you install the app manually and get an error "is damaged and cant be opened." when launching the app, run the following in the Terminal:
@ -290,9 +291,9 @@ import style from './style.css?inline'; // import style as inline
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
const builder = createPlugin({ export default createPlugin({
name: 'Plugin Label', name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic show restart dialog restartNeeded: true, // if value is true, ytmusic will show a restart dialog
config: { config: {
enabled: false, enabled: false,
}, // your custom config }, // your custom config
@ -306,9 +307,9 @@ const builder = createPlugin({
```typescript ```typescript
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
const builder = createPlugin({ export default createPlugin({
name: 'Plugin Label', name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic show restart dialog restartNeeded: true, // if value is true, ytmusic will show the restart dialog
config: { config: {
enabled: false, enabled: false,
}, // your custom config }, // your custom config

View File

@ -2,7 +2,76 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v3.0.0](https://github.com/th-ch/youtube-music/compare/v2.2.0...v3.0.0) #### [v3.2.0](https://github.com/th-ch/youtube-music/compare/v3.1.1...v3.2.0)
- feat(album-color-theme): improve `Album Color Theme` style [`#1571`](https://github.com/th-ch/youtube-music/pull/1571)
- feat(menu): add more detail in Menu [`#1570`](https://github.com/th-ch/youtube-music/pull/1570)
- feat(music-together): Add new plugin `Music Together` [`#1562`](https://github.com/th-ch/youtube-music/pull/1562)
- chore(deps): update dependency rollup to v4.9.2 [`#1567`](https://github.com/th-ch/youtube-music/pull/1567)
- fix(deps): update dependency i18next to v23.7.13 [`#1569`](https://github.com/th-ch/youtube-music/pull/1569)
- feat: Add new plugin `Album actions` [`#1515`](https://github.com/th-ch/youtube-music/pull/1515)
- fix(deps): update dependency i18next to v23.7.12 [`#1564`](https://github.com/th-ch/youtube-music/pull/1564)
- fix: Only apply scale factor on Windows [`#1565`](https://github.com/th-ch/youtube-music/pull/1565)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.16.0 [`#1556`](https://github.com/th-ch/youtube-music/pull/1556)
- chore(deps): update pnpm to v8.13.1 [`#1557`](https://github.com/th-ch/youtube-music/pull/1557)
- chore(deps): update dependency ws to v8.16.0 [`#1559`](https://github.com/th-ch/youtube-music/pull/1559)
- fix(deps): update dependency youtubei.js to v8.1.0 [`#1560`](https://github.com/th-ch/youtube-music/pull/1560)
- fix(deps): update dependency node-html-parser to v6.1.12 [`#1554`](https://github.com/th-ch/youtube-music/pull/1554)
- Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2" [`#1552`](https://github.com/th-ch/youtube-music/pull/1552)
- feat(ambient-mode): support ambient mode on `Song section` [`#1555`](https://github.com/th-ch/youtube-music/issues/1555)
- fix: fixed an issue with the download button disappearing [`#1551`](https://github.com/th-ch/youtube-music/issues/1551)
- fix: fix `homebrew cask` [`#1514`](https://github.com/th-ch/youtube-music/issues/1514)
- fix: pnpm build error [`13ef856`](https://github.com/th-ch/youtube-music/commit/13ef8560ff43353030537403be7da82542ba535e)
- chore(i18n): Translated using Weblate (Czech) [`0dc9c6a`](https://github.com/th-ch/youtube-music/commit/0dc9c6a1a90bce6505614617b827e816cbaaf875)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.15.0 [`c5bcd89`](https://github.com/th-ch/youtube-music/commit/c5bcd89f164b51d7380486a8ae35edd0caeea842)
#### [v3.1.1](https://github.com/th-ch/youtube-music/compare/v3.1.0...v3.1.1)
> 18 December 2023
- fix: fix renderer plugin load timing [`#1522`](https://github.com/th-ch/youtube-music/issues/1522)
- chore(i18n): Translated using Weblate (Lithuanian) [`fc1a7cd`](https://github.com/th-ch/youtube-music/commit/fc1a7cda62b6e33e5f5d57a5a6e0adef6a32bf9a)
- chore(i18n): Translated using Weblate (Chinese (Simplified)) [`eba7026`](https://github.com/th-ch/youtube-music/commit/eba7026b89bbfdd3ac07cf728a66ba9bdd274ec0)
- chore(deps): update dependency rollup to v4.8.0 [`a601d0b`](https://github.com/th-ch/youtube-music/commit/a601d0b3d2dee0fabad79a18e1a7dd0ca84ccf01)
#### [v3.1.0](https://github.com/th-ch/youtube-music/compare/v3.0.2...v3.1.0)
> 11 December 2023
- chore(deps): update dependency electron to v28 [`#1498`](https://github.com/th-ch/youtube-music/pull/1498)
- Enable/Disable Navigation without restart [`#1507`](https://github.com/th-ch/youtube-music/pull/1507)
- Turkish(tr)_lang_file [`#1513`](https://github.com/th-ch/youtube-music/pull/1513)
- Skip Disliked Songs [`#1505`](https://github.com/th-ch/youtube-music/pull/1505)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.13.2 [`#1452`](https://github.com/th-ch/youtube-music/pull/1452)
- fix: Homebrew latest release url parsing [`#1496`](https://github.com/th-ch/youtube-music/pull/1496)
- fix: in-player adblocker inject timing issue [`#1478`](https://github.com/th-ch/youtube-music/issues/1478)
- fix(package.json): fix RPM version `libuuid` issue [`#1508`](https://github.com/th-ch/youtube-music/issues/1508)
- Translated using Weblate (Polish) [`7b78ba6`](https://github.com/th-ch/youtube-music/commit/7b78ba67613f14be65a45751efeb06431b405a91)
- Translated using Weblate (French) [`ebc0879`](https://github.com/th-ch/youtube-music/commit/ebc087963b23265ff00528c8305d51597abf587a)
- Translated using Weblate (Chinese (Traditional)) [`020bdc0`](https://github.com/th-ch/youtube-music/commit/020bdc0811ea45ad6c2853c62a05ae6695c5c4f9)
#### [v3.0.2](https://github.com/th-ch/youtube-music/compare/v3.0.1...v3.0.2)
> 3 December 2023
- fix(adblocker): fix In-Player adblocker [`#1478`](https://github.com/th-ch/youtube-music/issues/1478)
- fix(menu): crash on linux [`#1477`](https://github.com/th-ch/youtube-music/issues/1477)
- fix: update pnpm-lock.yaml [`9e2c6b1`](https://github.com/th-ch/youtube-music/commit/9e2c6b1afa33b5708853c8328946e68ec45b09c3)
- Translated using Weblate (Chinese (Traditional)) [`125b69f`](https://github.com/th-ch/youtube-music/commit/125b69fd75a05c3eb893886119e2d9f2332b3e56)
- Translated using Weblate (French) [`15c4551`](https://github.com/th-ch/youtube-music/commit/15c455105b5100a8ee2bd0a4631548d3d455f047)
#### [v3.0.1](https://github.com/th-ch/youtube-music/compare/v3.0.0...v3.0.1)
> 2 December 2023
- hotfix(adblocker): fix #1475 [`#1475`](https://github.com/th-ch/youtube-music/issues/1475)
- Translated using Weblate (French) [`7f02afc`](https://github.com/th-ch/youtube-music/commit/7f02afc5a6839adfe8437d4e2cc8dee13a93b311)
- Update changelog for v3.0.0 [`d8c8bd1`](https://github.com/th-ch/youtube-music/commit/d8c8bd17ecfbdf96ebd29eb4c5748c07876ee242)
- Translated using Weblate (German) [`0660f0b`](https://github.com/th-ch/youtube-music/commit/0660f0b7ce6895ef5800f48ade1da2d7f8e0c1f7)
### [v3.0.0](https://github.com/th-ch/youtube-music/compare/v2.2.0...v3.0.0)
> 2 December 2023
- Add text to Translation section [`#1470`](https://github.com/th-ch/youtube-music/pull/1470) - Add text to Translation section [`#1470`](https://github.com/th-ch/youtube-music/pull/1470)
- fix(deps): update dependency youtubei.js to v8 [`#1473`](https://github.com/th-ch/youtube-music/pull/1473) - fix(deps): update dependency youtubei.js to v8 [`#1473`](https://github.com/th-ch/youtube-music/pull/1473)

View File

@ -258,7 +258,7 @@ import style from './style.css?inline'; // 스타일을 인라인으로 가져
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
const builder = createPlugin({ export default createPlugin({
name: 'Plugin Label', name: 'Plugin Label',
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다 restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
config: { config: {
@ -274,7 +274,7 @@ const builder = createPlugin({
```typescript ```typescript
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
const builder = createPlugin({ export default createPlugin({
name: 'Plugin Label', name: 'Plugin Label',
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다 restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
config: { config: {

View File

@ -1,15 +1,18 @@
import { resolve } from 'node:path'; import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig, defineViteConfig } from 'electron-vite'; import { defineConfig, defineViteConfig } from 'electron-vite';
import builtinModules from 'builtin-modules'; import builtinModules from 'builtin-modules';
import viteResolve from 'vite-plugin-resolve'; import viteResolve from 'vite-plugin-resolve';
import Inspect from 'vite-plugin-inspect'; import Inspect from 'vite-plugin-inspect';
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer'; import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs';
import pluginLoader from './vite-plugins/plugin-loader'; import pluginLoader from './vite-plugins/plugin-loader.mjs';
import type { UserConfig } from 'vite'; import type { UserConfig } from 'vite';
import { i18nImporter } from './vite-plugins/i18n-importer'; import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const resolveAlias = { const resolveAlias = {
'@': resolve(__dirname, './src'), '@': resolve(__dirname, './src'),

View File

@ -1,7 +1,7 @@
{ {
"name": "youtube-music", "name": "youtube-music",
"productName": "YouTube Music", "productName": "YouTube Music",
"version": "3.0.1", "version": "3.2.1",
"description": "YouTube Music Desktop App - including custom plugins", "description": "YouTube Music Desktop App - including custom plugins",
"main": "./dist/main/index.js", "main": "./dist/main/index.js",
"license": "MIT", "license": "MIT",
@ -77,6 +77,11 @@
"rpm" "rpm"
] ]
}, },
"rpm": {
"depends": [
"/usr/lib64/libuuid.so.1"
]
},
"snap": { "snap": {
"slots": [ "slots": [
{ {
@ -97,50 +102,52 @@
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect", "vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
"start": "electron-vite preview", "start": "electron-vite preview",
"start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start", "start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
"dev": "electron-vite dev", "dev": "electron-vite dev --watch",
"dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev", "dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect", "clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
"dist": "pnpm clean && pnpm build && electron-builder --win --mac --linux -p never", "dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
"dist:linux": "pnpm clean && pnpm build && electron-builder --linux -p never", "dist:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p never",
"dist:mac": "pnpm clean && pnpm build && electron-builder --mac dmg:x64 -p never", "dist:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:x64 -p never",
"dist:mac:arm64": "pnpm clean && pnpm build && electron-builder --mac dmg:arm64 -p never", "dist:mac:arm64": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:arm64 -p never",
"dist:win": "pnpm clean && pnpm build && electron-builder --win -p never", "dist:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p never",
"dist:win:x64": "pnpm clean && pnpm build && electron-builder --win nsis-web:x64 -p never", "dist:win:x64": "pnpm clean && pnpm build && pnpm electron-builder --win nsis-web:x64 -p never",
"lint": "eslint .", "lint": "eslint .",
"changelog": "npx --yes auto-changelog", "changelog": "npx --yes auto-changelog",
"release:linux": "pnpm clean && pnpm build && electron-builder --linux -p always -c.snap.publish=github", "release:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "pnpm clean && pnpm build && electron-builder --mac -p always", "release:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac -p always",
"release:win": "pnpm clean && pnpm build && electron-builder --win -p always", "release:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p always",
"typecheck": "tsc -p tsconfig.json --noEmit" "typecheck": "tsc -p tsconfig.json --noEmit"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=18.0.0"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"esbuild": "0.18.20",
"usocket": "1.0.1", "usocket": "1.0.1",
"rollup": "4.6.1", "rollup": "4.9.2",
"node-gyp": "10.0.1", "node-gyp": "10.0.1",
"xml2js": "0.6.2", "xml2js": "0.6.2",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"@electron/universal": "2.0.0", "@electron/universal": "2.0.1",
"@babel/runtime": "7.23.2" "@babel/runtime": "7.23.7"
} }
}, },
"dependencies": { "dependencies": {
"@cliqz/adblocker-electron": "1.26.12", "@cliqz/adblocker-electron": "1.26.12",
"@cliqz/adblocker-electron-preload": "1.26.12", "@cliqz/adblocker-electron-preload": "1.26.12",
"@electron-toolkit/tsconfig": "1.0.1", "@electron-toolkit/tsconfig": "1.0.1",
"@electron/remote": "2.1.0", "@electron/remote": "2.1.1",
"@ffmpeg.wasm/core-mt": "0.12.0", "@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.12.0", "@ffmpeg.wasm/main": "0.12.0",
"@foobar404/wave": "2.0.4", "@foobar404/wave": "2.0.5",
"@jellybrick/electron-better-web-request": "1.0.4", "@jellybrick/electron-better-web-request": "1.0.4",
"@jellybrick/mpris-service": "2.1.4", "@jellybrick/mpris-service": "2.1.4",
"@xhayper/discord-rpc": "1.1.1", "@xhayper/discord-rpc": "1.1.1",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"butterchurn": "3.0.0-beta.4", "butterchurn": "3.0.0-beta.4",
"butterchurn-presets": "3.0.0-beta.4", "butterchurn-presets": "3.0.0-beta.4",
"color": "4.2.3",
"conf": "10.2.0", "conf": "10.2.0",
"custom-electron-prompt": "1.5.7", "custom-electron-prompt": "1.5.7",
"dbus-next": "0.10.2", "dbus-next": "0.10.2",
@ -156,48 +163,53 @@
"filenamify": "6.0.0", "filenamify": "6.0.0",
"howler": "2.2.4", "howler": "2.2.4",
"html-to-text": "9.0.5", "html-to-text": "9.0.5",
"i18next": "23.7.7", "i18next": "23.7.13",
"keyboardevent-from-electron-accelerator": "2.0.0", "keyboardevent-from-electron-accelerator": "2.0.0",
"keyboardevents-areequal": "0.2.2", "keyboardevents-areequal": "0.2.2",
"node-html-parser": "6.1.11", "node-html-parser": "6.1.12",
"node-id3": "0.2.6", "node-id3": "0.2.6",
"peerjs": "1.5.2",
"semver": "7.5.4",
"serve": "14.2.1", "serve": "14.2.1",
"simple-youtube-age-restriction-bypass": "git+https://github.com/organization/Simple-YouTube-Age-Restriction-Bypass.git#v2.5.8", "simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
"ts-morph": "20.0.0", "ts-morph": "21.0.1",
"vudio": "2.1.1", "vudio": "2.1.1",
"x11": "2.3.0", "x11": "2.3.0",
"youtubei.js": "8.0.0" "youtubei.js": "8.1.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.40.1", "@playwright/test": "1.41.0-alpha-dec-18-2023",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.5.1",
"@types/color": "3.0.6",
"@types/electron-localshortcut": "3.1.3", "@types/electron-localshortcut": "3.1.3",
"@types/howler": "2.2.11", "@types/howler": "2.2.11",
"@types/html-to-text": "9.0.4", "@types/html-to-text": "9.0.4",
"@typescript-eslint/eslint-plugin": "6.13.1", "@types/semver": "7.5.6",
"@typescript-eslint/eslint-plugin": "6.16.0",
"bufferutil": "4.0.8", "bufferutil": "4.0.8",
"builtin-modules": "3.3.0", "builtin-modules": "3.3.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"del-cli": "5.1.0", "del-cli": "5.1.0",
"electron": "27.1.3", "electron": "28.1.0",
"electron-builder": "24.9.1", "electron-builder": "24.9.1",
"electron-devtools-installer": "3.2.0", "electron-devtools-installer": "3.2.0",
"electron-vite": "1.0.29", "electron-vite": "2.0.0-beta.2",
"eslint": "8.55.0", "esbuild": "0.18.20",
"eslint": "8.56.0",
"eslint-import-resolver-exports": "1.0.0-beta.5", "eslint-import-resolver-exports": "1.0.0-beta.5",
"eslint-import-resolver-typescript": "3.6.1", "eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.29.0", "eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.0.1", "eslint-plugin-prettier": "5.1.2",
"glob": "10.3.10", "glob": "10.3.10",
"node-gyp": "10.0.1", "node-gyp": "10.0.1",
"playwright": "1.40.1", "playwright": "1.41.0-alpha-dec-18-2023",
"rollup": "4.6.1", "rollup": "4.9.2",
"typescript": "5.3.2", "typescript": "5.3.3",
"utf-8-validate": "6.0.3", "utf-8-validate": "6.0.3",
"vite": "4.5.0", "vite": "5.0.10",
"vite-plugin-inspect": "0.8.1", "vite-plugin-inspect": "0.8.1",
"vite-plugin-resolve": "2.5.1", "vite-plugin-resolve": "2.5.1",
"ws": "8.14.2" "ws": "8.16.0"
}, },
"auto-changelog": { "auto-changelog": {
"hideCredit": true, "hideCredit": true,
@ -205,5 +217,5 @@
"unreleased": true, "unreleased": true,
"output": "changelog.md" "output": "changelog.md"
}, },
"packageManager": "pnpm@8.11.0" "packageManager": "pnpm@8.13.1"
} }

1431
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
export interface LanguageResources { export interface LanguageResources {
[lang: string]: { [lang: string]: {
translation: Record<string, unknown> & { translation: Record<string, unknown> & {
language: { language?: {
name: string; name: string;
'local-name': string; 'local-name': string;
code: string; code: string;

View File

@ -2,8 +2,13 @@
"common": { "common": {
"console": { "console": {
"plugins": { "plugins": {
"execute-failed": "Selhalo spuštění pluginu {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} spuštěn za {{ms}}ms",
"initialize-failed": "Selhalo zapnutí \"{{pluginName}}\" pluginu",
"load-all": "Načítání všech pluginů", "load-all": "Načítání všech pluginů",
"loaded": "Plugin \"{{pluginName}}\" načten" "load-failed": "Selhalo načtení \"{{pluginName}}\" pluginu",
"loaded": "Plugin \"{{pluginName}}\" načten",
"unload-failed": "Selhalo unload \"{{pluginName}}\" pluginu"
} }
} }
}, },
@ -14,34 +19,52 @@
}, },
"main": { "main": {
"console": { "console": {
"did-finish-load": {
"dev-tools": "Načítání dokončeno. Vývojářské nástroje se otevřely"
},
"i18n": { "i18n": {
"loaded": "i18n načteno" "loaded": "i18n načteno"
}, },
"second-instance": { "second-instance": {
"receive-command": "Received command přes protokol: \"{{command}}\"" "receive-command": "Přijmut příkaz přes protokol: \"{{command}}\""
}, },
"theme": { "theme": {
"css-file-not-found": "CSS soubor \"{{cssFile}}\" neexistuje, ignoring" "css-file-not-found": "CSS soubor \"{{cssFile}}\" neexistuje, ignorováno"
},
"unresponsive": {
"details": "Chyba - Aplikace nereaguje!\n{{error}}"
}, },
"when-ready": { "when-ready": {
"clearing-cache-after-20s": "Čištění mezipaměti aplikace" "clearing-cache-after-20s": "Čištění mezipaměti aplikace"
},
"window": {
"tried-to-render-offscreen": "Okno se pokusilo vykreslit na pozadí, velikost okna = {{windowSize}}, display velikost = {{displaySize}}, pozice = {{position}}"
} }
}, },
"dialog": { "dialog": {
"hide-menu-enabled": {
"detail": "Menu je skryté, stiskněte 'Alt' k jeho zobrazení (nebo 'Escape', pokud používáte in-app-menu)",
"message": "Skrýt menu je povoleno",
"title": "Skrýt menu Povolené"
},
"need-to-restart": { "need-to-restart": {
"buttons": { "buttons": {
"later": "Později", "later": "Později",
"restart-now": "Restartovat nyní" "restart-now": "Restartovat nyní"
} },
"detail": "\"{{pluginName}}\" plugin vyžaduje restart, aby se projevil",
"message": "\"{{pluginName}}\" potřebuje restartovat",
"title": "Restart vyžadován"
}, },
"unresponsive": { "unresponsive": {
"buttons": { "buttons": {
"quit": "Ukončení", "quit": "Ukončit",
"relaunch": "Spustit znovu", "relaunch": "Spustit znovu",
"wait": "Počkat" "wait": "Počkat"
}, },
"detail": "Omlouváme se za způsobené nepříjemnosti! prosím vyberte, co dělat:", "detail": "Omlouváme se za způsobené nepříjemnosti! prosím vyberte, co dělat:",
"message": "Aplikace nereaguje" "message": "Aplikace nereaguje",
"title": "Okno nereaguje"
}, },
"update-available": { "update-available": {
"buttons": { "buttons": {
@ -49,8 +72,9 @@
"download": "Stáhnout", "download": "Stáhnout",
"ok": "OK" "ok": "OK"
}, },
"detail": "Nová verze je k dispozici a lze ji stáhnout na {{downloadLink}}",
"message": "Nová verze je dostupná", "message": "Nová verze je dostupná",
"title": "Aktualizace k dispozici" "title": "Aktualizace je k dispozici"
} }
}, },
"menu": { "menu": {
@ -58,9 +82,10 @@
"navigation": { "navigation": {
"label": "Navigace", "label": "Navigace",
"submenu": { "submenu": {
"copy-current-url": "Kopírovat aktuální URL adresu", "copy-current-url": "Zkopírovat aktuální URL adresu",
"go-back": "Jít zpátky", "go-back": "Jít zpátky",
"go-forward": "Jít dopředu", "go-forward": "Jít dopředu",
"quit": "Ukončit",
"restart": "Restartovat aplikaci" "restart": "Restartovat aplikaci"
} }
}, },
@ -70,22 +95,29 @@
"advanced-options": { "advanced-options": {
"label": "Pokročilé možnosti", "label": "Pokročilé možnosti",
"submenu": { "submenu": {
"auto-reset-app-cache": "Při spuštění aplikace se resetuje její mezipaměť",
"disable-hardware-acceleration": "Vypnout hardware zrychlení", "disable-hardware-acceleration": "Vypnout hardware zrychlení",
"edit-config-json": "Upravit config.json", "edit-config-json": "Upravit config.json",
"override-user-agent": "Přepsat User-Agent", "override-user-agent": "Přepsat uživatelského agenta",
"restart-on-config-changes": "Restartovat na změny v configu", "restart-on-config-changes": "Restartovat aplikaci na změny v konfiguraci",
"set-proxy": { "set-proxy": {
"label": "Nastavit proxy", "label": "Nastavit proxy",
"prompt": { "prompt": {
"placeholder": "Příklad: socks5://127.0.0.1:9999", "label": "Zadejte adresu proxy: (k vypnutí nechte pole prázdné)",
"placeholder": "Příklad: SOCKS5://127.0.0.1:9999",
"title": "Nastavit proxy" "title": "Nastavit proxy"
} }
} },
"toggle-dev-tools": "Přepínat vývojářské nástroje"
} }
}, },
"always-on-top": "Vždy na vrchu", "always-on-top": "Vždy na vrchu",
"auto-update": "Automatické aktualizace", "auto-update": "Automatické aktualizace",
"hide-menu": { "hide-menu": {
"dialog": {
"message": "Menu bude skryto na dalším spuštěním, použijte [Alt] k jeho zobrazení (nebo backtick [`] pokud používáte in-app-menu)",
"title": "Skrýt menu Povoleno"
},
"label": "Skrýt menu" "label": "Skrýt menu"
}, },
"language": { "language": {
@ -98,24 +130,36 @@
"to-help-translate": "Chcete pomoc s překladem? Klikněte zde" "to-help-translate": "Chcete pomoc s překladem? Klikněte zde"
} }
}, },
"resume-on-start": "Při spuštění aplikace, pokračovat na poslední písničce",
"single-instance-lock": "Zámek pro jednu instanci",
"start-at-login": "Zapnutí aplikace po přihlášení",
"starting-page": {
"label": "Úvodní stránka",
"unset": "Nenastaveno"
},
"tray": { "tray": {
"label": "Tray",
"submenu": { "submenu": {
"disabled": "Vypnuto",
"enabled-and-hide-app": "Povolit a skrýt aplikaci", "enabled-and-hide-app": "Povolit a skrýt aplikaci",
"enabled-and-show-app": "Enabled a show aplikaci",
"play-pause-on-click": "Přehrát/Pozastavit na kliknutí" "play-pause-on-click": "Přehrát/Pozastavit na kliknutí"
} }
}, },
"visual-tweaks": { "visual-tweaks": {
"label": "Vzhledové vylepšení",
"submenu": { "submenu": {
"like-buttons": { "like-buttons": {
"default": "Výchozí", "default": "Výchozí",
"hide": "Schovat", "force-show": "Vynutit zobrazení",
"hide": "Skrýt",
"label": "Like tlačítka" "label": "Like tlačítka"
}, },
"remove-upgrade-button": "Remove upgrade tlačítko", "remove-upgrade-button": "Odebrat upgrade tlačítko",
"theme": { "theme": {
"label": "Motiv", "label": "Motiv",
"submenu": { "submenu": {
"import-css-file": "Import custom CSS soubor", "import-css-file": "Vložit vlastní CSS soubor",
"no-theme": "Žádný motiv" "no-theme": "Žádný motiv"
} }
} }
@ -125,10 +169,16 @@
}, },
"plugins": { "plugins": {
"enabled": "Povoleno", "enabled": "Povoleno",
"label": "Pluginy" "label": "Pluginy",
"new": "NOVÉ"
}, },
"view": { "view": {
"label": "Zobrazení",
"submenu": { "submenu": {
"force-reload": "Vynutit znovu načtení",
"reload": "Obnovit",
"reset-zoom": "Skutečná velikost",
"toggle-fullscreen": "Přepnout režim celé obrazovky",
"zoom-in": "Přiblížit", "zoom-in": "Přiblížit",
"zoom-out": "Oddálit" "zoom-out": "Oddálit"
} }
@ -136,11 +186,11 @@
}, },
"tray": { "tray": {
"next": "Další", "next": "Další",
"play-pause": "Hrát/Zastavit", "play-pause": "Přehrát/Pozastavit",
"previous": "Minulý", "previous": "Minulý",
"quit": "Ukončit", "quit": "Ukončit",
"restart": "Restartovat aplikaci", "restart": "Restartovat aplikaci",
"show": "Ukázat okno" "show": "Zobrazit okno"
} }
}, },
"plugins": { "plugins": {
@ -151,14 +201,19 @@
}, },
"name": "Blokovač reklam" "name": "Blokovač reklam"
}, },
"album-actions": {
"description": "Přidává Undislike, Dislike, Like, a Unlike tlačítka k apply this ke všem písničkám v seznamu písniček nebo albumu.",
"name": "Album akce"
},
"album-color-theme": { "album-color-theme": {
"description": "Použije dynamický motiv a visuální efekty based na paletě barev alba", "description": "Používá dynamický motiv a vizuální efekty na základě palety barev alba",
"name": "Album Color Motiv" "name": "Motiv podle barvy Alba"
}, },
"ambient-mode": { "ambient-mode": {
"description": "Applies a lighting efekty by casting gentle colors z videa, into your screens pozadí.", "description": "Applies bleskové efekty pomocí casting jemných barev z videa, do vašeho pozadí obrazovky.",
"menu": { "menu": {
"blur-amount": { "blur-amount": {
"label": "Množství rozmazání",
"submenu": { "submenu": {
"pixels": "{{blurAmount}} pixelů" "pixels": "{{blurAmount}} pixelů"
} }
@ -190,21 +245,29 @@
"smoothness-transition": { "smoothness-transition": {
"label": "Plynulý přechod", "label": "Plynulý přechod",
"submenu": { "submenu": {
"during": "Během {{interpolationTime}}s" "during": "Během {{interpolationTime}} s"
} }
},
"use-fullscreen": {
"label": "Používání režimu celé obrazovky"
} }
}, },
"name": "Ambientní režim" "name": "Ambientní režim"
}, },
"audio-compressor": {
"description": "Apply compression k audiu (snižuje hlasitost nejhlasitěších částí signálu and zvyšuje hlasitost nejjemnějších částí)",
"name": "Audio kompresor"
},
"blur-nav-bar": { "blur-nav-bar": {
"description": "Udělá navigační panel průhledným a rozmazaným" "description": "Udělá navigační panel průhledný a rozmazaný",
"name": "Rozmazaný navigační panel"
}, },
"bypass-age-restrictions": { "bypass-age-restrictions": {
"description": "Obejít ověření věku na YouTube", "description": "Obejít ověření věku na YouTube",
"name": "Obejít věková omezení" "name": "Obejít věková omezení"
}, },
"captions-selector": { "captions-selector": {
"description": "Titulkový selector pro YouTube Music audio tracks", "description": "Titulkový selector pro zvukové stopy v YouTube Music",
"menu": { "menu": {
"autoload": "Automaticky vybrat naposledy použité titulky", "autoload": "Automaticky vybrat naposledy použité titulky",
"disable-captions": "Žádné titulky ve vychozím nastavení" "disable-captions": "Žádné titulky ve vychozím nastavení"
@ -222,13 +285,15 @@
} }
}, },
"compact-sidebar": { "compact-sidebar": {
"description": "Vždy set the sidebar in compact mode" "description": "Vždy nastavit postranní panel do kompaktního režimu",
"name": "Kompaktní postranní panel"
}, },
"crossfade": { "crossfade": {
"description": "Crossfade mezi písničkami", "description": "Prolínání mezi písničkami",
"menu": { "menu": {
"advanced": "Pokročilý" "advanced": "Pokročilý"
}, },
"name": "Prolínání [beta]",
"prompt": { "prompt": {
"options": { "options": {
"multi-input": { "multi-input": {
@ -243,19 +308,36 @@
} }
}, },
"disable-autoplay": { "disable-autoplay": {
"name": "Zrušit automatické přehrávání" "description": "Spustí písničku v režimu \"pozastaveno\"",
"menu": {
"apply-once": "Applies jenom na spuštění aplikace"
},
"name": "Vypnout automatické přehrávání"
}, },
"discord": { "discord": {
"backend": { "backend": {
"already-connected": "Pokusilo se spojit s aktivním spojením",
"connected": "Připojeno k Discordu", "connected": "Připojeno k Discordu",
"disconnected": "Odpojeno od Discordu" "disconnected": "Odpojeno od Discordu"
}, },
"description": "Ukažte svým přátelům, co posloucháte s Rich Presence", "description": "Ukažte svým přátelům, co posloucháte s Bohatou přítomností",
"menu": { "menu": {
"auto-reconnect": "Automaticky znovu připojit",
"clear-activity": "Vymazat aktivitu",
"clear-activity-after-timeout": "Vymazat aktivitu po timeout",
"connected": "Připojeno", "connected": "Připojeno",
"disconnected": "Odpojeno", "disconnected": "Odpojeno",
"hide-duration-left": "Skrýt zbývající duration",
"hide-github-button": "Skrýt tlačítko s odkazem na GitHub", "hide-github-button": "Skrýt tlačítko s odkazem na GitHub",
"play-on-youtube-music": "Hrát na YouTube Music" "play-on-youtube-music": "Hrát na YouTube Music",
"set-inactivity-timeout": "Nastavit timeout pro neaktivitu"
},
"name": "Discord Bohatá přítomnost",
"prompt": {
"set-inactivity-timeout": {
"label": "Zadejte timeout neaktivity v sekundách:",
"title": "Nastavit timeout pro neaktivitu"
}
} }
}, },
"downloader": { "downloader": {
@ -273,67 +355,139 @@
"ok": "OK" "ok": "OK"
}, },
"detail": "({{playlistSize}} písničky)", "detail": "({{playlistSize}} písničky)",
"message": "Stahování Playlistu {{playlistTitle}}", "message": "Stahování seznamu písniček {{playlistTitle}}",
"title": "Stahování začalo" "title": "Stahování začalo"
} }
}, },
"feedback": { "feedback": {
"conversion-progress": "Konverze: {{percent}}%",
"done": "Hotovo: {{filePath}}", "done": "Hotovo: {{filePath}}",
"download-info": "Stahování {{artist}} - {{title}} [{{videoId}}", "download-info": "Stahování {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Stahování: {{percent}}%",
"downloading": "Stahování…", "downloading": "Stahování…",
"downloading-counter": "Stahování {{current}}/{{total}}…", "downloading-counter": "Stahování {{current}}/{{total}}…",
"downloading-playlist": "Downloading playlist \"{{playlistTitle}}\" - {{playlistSize}} písničky ({{playlistId}})", "downloading-playlist": "Stahování seznamu písniček \"{{playlistTitle}}\" - {{playlistSize}} písničky ({{playlistId}})",
"error-while-downloading": "Chyba při stahování \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Složka {{playlistFolder}} již existuje", "folder-already-exists": "Složka {{playlistFolder}} již existuje",
"getting-playlist-info": "Získávání informací o seznamu písniček…",
"loading": "Načítání…", "loading": "Načítání…",
"playlist-has-only-one-song": "Playlist má jenom jeden položku, downloading it directly", "playlist-has-only-one-song": "Seznam písniček má pouze jednu položku, stahuje se přímo",
"playlist-id-not-found": "Žádný playlist ID nenalezen", "playlist-id-not-found": "Žádné ID seznamu písnček nenalezeno",
"playlist-is-empty": "Playlist je prázdný", "playlist-is-empty": "Seznam písniček je prázdný",
"playlist-is-mix-or-private": "Chyba při získávání informací o seznamu písniček: ujistite se, že se nejedná o soukromý nebo \"Namíchaný pro vás\" seznam písniček\n\n{{error}}",
"preparing-file": "Připravování souboru…", "preparing-file": "Připravování souboru…",
"saving": "Ukládání…", "saving": "Ukládání…",
"video-id-not-found": "Video nebylo nalezeno" "trying-to-get-playlist-id": "Trying se získat ID seznamu písniček: {{playlistId}}",
"video-id-not-found": "Video nebylo nalezeno",
"writing-id3": "Psaní ID3 značek…"
} }
}, },
"description": "Stahuje MP3 / source audio přímo z rozhraní",
"menu": { "menu": {
"choose-download-folder": "Vybrat download složku", "choose-download-folder": "Vybrat složku pro stahování",
"download-playlist": "Stáhnout playlist", "download-playlist": "Stáhnout seznam písniček",
"presets": "Předvolby",
"skip-existing": "Přeskočit existující soubory" "skip-existing": "Přeskočit existující soubory"
}, },
"name": "Stahovač", "name": "Stahovač",
"renderer": {
"can-not-update-progress": "Progress nemůže být aktualizován"
},
"templates": { "templates": {
"button": "Stáhnout" "button": "Stáhnout"
} }
}, },
"exponential-volume": { "exponential-volume": {
"description": "Dělá posuvník hlasitosti exponenciální, takže je snazší vybrat nižší hlasitost.",
"name": "Exponenciální hlasitost" "name": "Exponenciální hlasitost"
}, },
"in-app-menu": {
"description": "Dává menu panelům fancy, tmavý nebo album-color vzhled",
"menu": {
"hide-dom-window-controls": "Skrýt DOM window controls"
}
},
"last-fm": { "last-fm": {
"description": "Přidat scrobbling podporu pro Last.fm",
"name": "Last.fm" "name": "Last.fm"
}, },
"lumiastream": {
"description": "Přidává Lumia Stream podporu",
"name": "Lumia Stream [beta]"
},
"lyrics-genius": { "lyrics-genius": {
"description": "Přidat lyrics podporu pro většinu písniček" "description": "Přidává lyrics podporu pro většinu písniček",
"renderer": {
"fetched-lyrics": "Fetched lyrics pro Genius"
}
},
"music-together": {
"dialog": {
"enter-host": "Zadejte Host ID"
},
"internal": {
"save": "Uložit",
"unknown-user": "Neznámý uživatel"
},
"menu": {
"close": "Zavřít Hudba Spolu",
"connected-users": "Připojení uživatelé",
"disconnect": "Odpojit od Hudby Spolu",
"empty-user": "Žadní připojení uživatelé",
"host": "Hudba Spolu Host",
"join": "Připojit se k Hudbě Spolu",
"permission": {
"playlist": "Seznam písniček Control"
},
"set-permission": "Změnit Control oprávnění",
"status": {
"disconnected": "Odpojen",
"guest": "Připojený/á jako Guest",
"host": "Připojený/á jako Host"
}
},
"name": "Hudba Spolu [Beta]",
"toast": {
"add-song-failed": "Selhalo přidání písničky",
"closed": "Hudba Spolu zavřena",
"disconnected": "Hudba Spolu odpojena",
"host-failed": "Selhalo hostování Hudby Spolu",
"id-copied": "Host ID zkopírováno do schránky",
"join-failed": "Selhalo připojení k Hudba Spolu",
"joined": "Připojil/a jste se k Hudbě Spolu",
"permission-changed": "Oprávnění Hudby Spolu se změnilo na \"{{permission}}\"",
"remove-song-failed": "Selhalo odstranění písničky",
"user-connected": "{{name}} se připojil/a k Hudbě Spolu",
"user-disconnected": "{{name}} odpustil/a Hudba Spolu"
}
}, },
"navigation": { "navigation": {
"description": "Další/Zpátky navigační šipky přímo integrovány do rozhraní, jako ve vašem oblíbeném prohlížeči",
"name": "Navigace" "name": "Navigace"
}, },
"no-google-login": { "no-google-login": {
"description": "Odstranit Google login tlačítka a odkazy z rozhraní", "description": "Odstranit tlačítka Google přihlášení a odkazy z rozhraní",
"name": "Žádné Google přihlášení" "name": "Žádné Google přihlášení"
}, },
"notifications": { "notifications": {
"description": "Display oznámení when a písnička starts hraje (interactive notifications are available on Windows)", "description": "Zobrazit oznámení, když písnička začne hrát (interaktiv notifikace jsou dostupné na Windows)",
"menu": { "menu": {
"interactive": "Interaktivní oznámení",
"interactive-settings": { "interactive-settings": {
"label": "Interactive Nastavení", "label": "Interactive Nastavení",
"submenu": { "submenu": {
"hide-button-text": "Skrýt text tlačítka", "hide-button-text": "Skrýt text tlačítka",
"tray-controls": "Otevřít/Zavřít on tray click" "refresh-on-play-pause": "Refresh na Přehrát/Pozastavit",
"tray-controls": "Otevřít/Zavřít aplikaci na kliknutí na tray ikonu"
} }
}, },
"priority": "Priorita Oznámení" "priority": "Priorita Oznámení",
"unpause-notification": "Zobrazit oznámení na unpause"
}, },
"name": "Oznámení" "name": "Oznámení"
}, },
"picture-in-picture": { "picture-in-picture": {
"description": "Povoluje switch aplikaci do režimu obrázek v obrázku",
"menu": { "menu": {
"always-on-top": "Vždy na vrchu", "always-on-top": "Vždy na vrchu",
"hotkey": { "hotkey": {
@ -341,7 +495,9 @@
"prompt": { "prompt": {
"keybind-options": { "keybind-options": {
"hotkey": "Klávesová zkratka" "hotkey": "Klávesová zkratka"
} },
"label": "Vybrat klávesovou zkratku pro přepínání obrázek v obrázku",
"title": "klávesová zkratka pro obrázek v obrázku"
} }
}, },
"save-window-position": "Uložit pozici okna", "save-window-position": "Uložit pozici okna",
@ -354,23 +510,31 @@
} }
}, },
"playback-speed": { "playback-speed": {
"description": "Posloiuchej rychle, poslouchej pomalu! Adds a slider, který kontroluje rychlost písníčky", "description": "Poslouchej rychle, poslouchej pomalu! Přidává slider, který kontroluje rychlost písníčky",
"name": "Playback rychlost", "name": "Rychlost přehrávání",
"templates": { "templates": {
"button": "Rychlost" "button": "Rychlost"
} }
}, },
"precise-volume": { "precise-volume": {
"description": "Přesná kontrola hlasitosti pomocí kolečka myši/klávesnicových zkratek, s vlastní HUD a customizable hlasitostních steps",
"menu": { "menu": {
"custom-volume-steps": "Nastavit vlastní hlasitostní steps",
"global-shortcuts": "Globální klávesové zkratky" "global-shortcuts": "Globální klávesové zkratky"
}, },
"name": "Precise hlasitost", "name": "Přesná hlasitost",
"prompt": { "prompt": {
"global-shortcuts": { "global-shortcuts": {
"keybind-options": { "keybind-options": {
"decrease": "Snížit hlasitost", "decrease": "Snížit hlasitost",
"increase": "Zvýšit hlasitost" "increase": "Zvýšit hlasitost"
} },
"label": "Vybrat globální klávesnicové zkratky:",
"title": "Globální klávesnicové zkratky hlasitosti"
},
"volume-steps": {
"label": "Vybrat Zvýšení/Snížení hlasitost Steps",
"title": "Hlasitostní steps"
} }
} }
}, },
@ -383,9 +547,16 @@
"title": "Vybrat kvalitu videa" "title": "Vybrat kvalitu videa"
} }
} }
} },
"description": "Umožňuje měnit kvalitu videa pomocí tlačítka na video overlay",
"name": "Měnič kvality videa"
}, },
"shortcuts": { "shortcuts": {
"description": "Dovoluje nastavit globální klávesové zkratky pro playback (přehrát/pozastavit/další/předchozí) a vypínání media OSD pomocí přepisování media klíčů, zapínání Ctrl/CMD + F k vyhledávání, zapínání Linux MPRIS podporu pro media klíče, a vlastní klávesové zkratky pro pokročilé uživatele.",
"menu": {
"override-media-keys": "Přepsat media klíče",
"set-keybinds": "Nastavit globální Controls písniček"
},
"name": "Zkratky (& MPRIS)", "name": "Zkratky (& MPRIS)",
"prompt": { "prompt": {
"keybind": { "keybind": {
@ -393,33 +564,68 @@
"next": "Další", "next": "Další",
"play-pause": "Přehrát / Pozastavit", "play-pause": "Přehrát / Pozastavit",
"previous": "Předchozí" "previous": "Předchozí"
} },
"label": "Vybrat globální klávesnicové zkratky pro ovládání písniček:",
"title": "Globální klávesnicové zkratky"
} }
} }
}, },
"skip-disliked-songs": {
"description": "Přeskakovat disliked písničky",
"name": "Přeskočit Disliked písničky"
},
"skip-silences": { "skip-silences": {
"description": "Automaticky přeskakovat tichá místa v písničkách", "description": "Automaticky přeskakovat tichá místa v písničkách",
"name": "Přeskočit Tichá místa" "name": "Přeskakovat Tichá místa"
},
"sponsorblock": {
"description": "Automaticky přeskakuje nehudební části jako intro/outro nebo části hudebních videí, kde nehraje písnčka",
"name": "SponsorBlock"
}, },
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Kontrolovat playback z vašeho Windows taskbar" "description": "Ovládejte přehrávání z vašeho hlavního panelu Windows",
"name": "Hlavní panel Media Control"
},
"touchbar": {
"description": "Přidává Touch Bar widget pro macOS uživatele",
"name": "Touch Bar"
},
"tuna-obs": {
"description": "Integrace s OBS's plugin Tuna",
"name": "Tuna OBS"
}, },
"video-toggle": { "video-toggle": {
"description": "Přidává tlačítko k switch mezi video/písničko režimem. Může také odstranit celou video kartu",
"menu": { "menu": {
"align": { "align": {
"label": "Zarovnání", "label": "Zarovnání",
"submenu": { "submenu": {
"left": "Vlevo", "left": "Vlevo",
"middle": "Uprostřed",
"right": "Pravo" "right": "Pravo"
} }
}, },
"force-hide": "Vynutit odstranění karty videa",
"mode": { "mode": {
"label": "Režim" "label": "Režim",
"submenu": {
"custom": "Vlastní přepínač",
"disabled": "Vypnuto",
"native": "Původní přepínač"
}
} }
}, },
"name": "Přepínač videa",
"templates": { "templates": {
"button": "Písnička" "button": "Písnička"
} }
},
"visualizer": {
"description": "Přidá vizualizér do přehrávače",
"menu": {
"visualizer-type": "Typ vizualizéru"
},
"name": "Vizualizér"
} }
} }
} }

View File

@ -44,7 +44,7 @@
}, },
"dialog": { "dialog": {
"hide-menu-enabled": { "hide-menu-enabled": {
"detail": "Menü ist versteckt, nutze 'Alt', um es zu zeigen (oder 'Escape' beim Verwenden des In-App-Menüs)", "detail": "Das Menü ist versteckt, nutze 'Alt', um es zu aufzurufen (oder 'Escape' beim Verwenden des In-App-Menüs)",
"message": "Menü verstecken ist aktiviert", "message": "Menü verstecken ist aktiviert",
"title": "Menü Verstecken Aktiviert" "title": "Menü Verstecken Aktiviert"
}, },
@ -105,7 +105,7 @@
"label": "Proxy setzen", "label": "Proxy setzen",
"prompt": { "prompt": {
"label": "Proxy-Adresse eingeben: (leer lassen zum Ausschalten)", "label": "Proxy-Adresse eingeben: (leer lassen zum Ausschalten)",
"placeholder": "Beispiel: socks5://127.0.0.1:9999", "placeholder": "Beispiel: SOCKS5://127.0.0.1:9999",
"title": "Proxy setzen" "title": "Proxy setzen"
} }
}, },
@ -470,9 +470,9 @@
"save-window-size": "Fenstergröße speichern", "save-window-size": "Fenstergröße speichern",
"use-native-pip": "Browsereigenes PiP verwenden" "use-native-pip": "Browsereigenes PiP verwenden"
}, },
"name": "Bild im Bild", "name": "Bild-im-Bild",
"templates": { "templates": {
"button": "Bild im Bild" "button": "Bild-im-Bild"
} }
}, },
"playback-speed": { "playback-speed": {
@ -519,9 +519,9 @@
"name": "Videoqualitätsänderer" "name": "Videoqualitätsänderer"
}, },
"shortcuts": { "shortcuts": {
"description": "Ermöglicht das Festlegen globaler Hotkeys für die Wiedergabe (Abspielen/Pause/Nächstes/Vorheriges) + Deaktivieren des Medien-OSD durch Überschreiben der Medientasten + Aktivieren von Strg/CMD + F zum Suchen + Aktivieren der Linux mpris-Unterstützung für Medientasten + Angepasste Tastenkürzel für fortgeschrittene Benutzer", "description": "Ermöglicht das Festlegen globaler Hotkeys für die Wiedergabe (Abspielen/Pause/Nächster/Vorheriger) + Deaktivieren des Medien-OSD durch Überschreiben der Medientasten + Aktivieren von Strg/CMD + F zum Suchen + Aktivieren der Linux mpris-Unterstützung für Medientasten + Angepasste Tastenkürzel für fortgeschrittene Benutzer.",
"menu": { "menu": {
"override-media-keys": "Medienschlüssel überschreiben", "override-media-keys": "Medientasten überschreiben",
"set-keybinds": "Globale Liedsteuerung setzen" "set-keybinds": "Globale Liedsteuerung setzen"
}, },
"name": "Abkürzungen (& MPRIS)", "name": "Abkürzungen (& MPRIS)",
@ -537,6 +537,10 @@
} }
} }
}, },
"skip-disliked-songs": {
"description": "Überspringt Lieder, die ihnen nicht gefallen",
"name": "Überspring Lieder, die ihnen nicht gefallen"
},
"skip-silences": { "skip-silences": {
"description": "Automatisch stille Abschnitte in Liedern überspringen", "description": "Automatisch stille Abschnitte in Liedern überspringen",
"name": "Stille überspringen" "name": "Stille überspringen"

View File

@ -1,4 +1,11 @@
{ {
"common": {
"console": {
"plugins": {
"execute-failed": "Αποτυχία εκτέλεσης προσθέτου {{pluginName}}::{{contextName}}"
}
}
},
"language": { "language": {
"code": "el", "code": "el",
"local-name": "Ελληνικά", "local-name": "Ελληνικά",

View File

@ -21,7 +21,7 @@
"main": { "main": {
"console": { "console": {
"did-finish-load": { "did-finish-load": {
"dev-tools": "did finish load. dev tools opened" "dev-tools": "Finished loading. DevTools opened"
}, },
"i18n": { "i18n": {
"loaded": "i18n loaded" "loaded": "i18n loaded"
@ -44,7 +44,7 @@
}, },
"dialog": { "dialog": {
"hide-menu-enabled": { "hide-menu-enabled": {
"detail": "Menu is hidden, use 'Alt' to show it (or 'Escape' if using in-app-menu)", "detail": "Menu is hidden, use 'Alt' to show it (or 'Escape' if using In-App Menu)",
"message": "Hide Menu is enabled", "message": "Hide Menu is enabled",
"title": "Hide Menu Enabled" "title": "Hide Menu Enabled"
}, },
@ -96,7 +96,7 @@
"advanced-options": { "advanced-options": {
"label": "Advanced options", "label": "Advanced options",
"submenu": { "submenu": {
"auto-reset-app-cache": "Reset App cache when app starts", "auto-reset-app-cache": "Reset app cache when app starts",
"disable-hardware-acceleration": "Disable hardware acceleration", "disable-hardware-acceleration": "Disable hardware acceleration",
"edit-config-json": "Edit config.json", "edit-config-json": "Edit config.json",
"override-user-agent": "Override User-Agent", "override-user-agent": "Override User-Agent",
@ -105,7 +105,7 @@
"label": "Set proxy", "label": "Set proxy",
"prompt": { "prompt": {
"label": "Enter Proxy Address: (leave empty to disable)", "label": "Enter Proxy Address: (leave empty to disable)",
"placeholder": "Example: socks5://127.0.0.1:9999", "placeholder": "Example: SOCKS5://127.0.0.1:9999",
"title": "Set proxy" "title": "Set proxy"
} }
}, },
@ -170,7 +170,8 @@
}, },
"plugins": { "plugins": {
"enabled": "Enabled", "enabled": "Enabled",
"label": "Plugins" "label": "Plugins",
"new": "NEW"
}, },
"view": { "view": {
"label": "View", "label": "View",
@ -201,6 +202,10 @@
}, },
"name": "Adblocker" "name": "Adblocker"
}, },
"album-actions": {
"description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album.",
"name": "Album actions"
},
"album-color-theme": { "album-color-theme": {
"description": "Applies a dynamic theme and visual effects based on the album color palette", "description": "Applies a dynamic theme and visual effects based on the album color palette",
"name": "Album Color Theme" "name": "Album Color Theme"
@ -241,7 +246,7 @@
"smoothness-transition": { "smoothness-transition": {
"label": "Smoothness transition", "label": "Smoothness transition",
"submenu": { "submenu": {
"during": "During {{interpolationTime}}s" "during": "During {{interpolationTime}} s"
} }
}, },
"use-fullscreen": { "use-fullscreen": {
@ -293,8 +298,8 @@
"prompt": { "prompt": {
"options": { "options": {
"multi-input": { "multi-input": {
"fade-in-duration": "Fade in duration (milliseconds)", "fade-in-duration": "Fade in duration (ms)",
"fade-out-duration": "Fade out duration (milliseconds)", "fade-out-duration": "Fade out duration (ms)",
"fade-scaling": { "fade-scaling": {
"label": "Fade scaling", "label": "Fade scaling",
"linear": "Linear", "linear": "Linear",
@ -426,6 +431,51 @@
"fetched-lyrics": "Fetched lyrics for Genius" "fetched-lyrics": "Fetched lyrics for Genius"
} }
}, },
"music-together": {
"description": "Share a playlist with others. When the host plays a song, everyone else will hear the same song",
"dialog": {
"enter-host": "Enter Host ID"
},
"internal": {
"save": "Save",
"track-source": "Track Source",
"unknown-user": "Unknown User"
},
"menu": {
"click-to-copy-id": "Copy Host ID",
"close": "Close Music Together",
"connected-users": "Connected Users",
"disconnect": "Disconnect Music Together",
"empty-user": "No connected users",
"host": "Music Together Host",
"join": "Join Music Together",
"permission": {
"all": "Allow guests to control playlist and player",
"host-only": "Only the host can control playlist and player",
"playlist": "Allow guests to control playlist"
},
"set-permission": "Change Control Permission",
"status": {
"disconnected": "Disconnected",
"guest": "Connected as Guest",
"host": "Connected as Host"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Failed to add song",
"closed": "Music Together closed",
"disconnected": "Music Together disconnected",
"host-failed": "Failed to host Music Together",
"id-copied": "Host ID copied to clipboard",
"join-failed": "Failed to join Music Together",
"joined": "Joined Music Together",
"permission-changed": "Music Together permission changed to \"{{permission}}\"",
"remove-song-failed": "Failed to remove song",
"user-connected": "{{name}} joined Music Together",
"user-disconnected": "{{name}} left Music Together"
}
},
"navigation": { "navigation": {
"description": "Next/Back navigation arrows directly integrated in the interface, like in your favorite browser", "description": "Next/Back navigation arrows directly integrated in the interface, like in your favorite browser",
"name": "Navigation" "name": "Navigation"
@ -462,17 +512,17 @@
"keybind-options": { "keybind-options": {
"hotkey": "Hotkey" "hotkey": "Hotkey"
}, },
"label": "Choose a hotkey for toggle Picture in Picture", "label": "Choose a hotkey to toggle picture-in-picture",
"title": "Picture in Picture Hotkey" "title": "Picture-in-picture Hotkey"
} }
}, },
"save-window-position": "Save window position", "save-window-position": "Save window position",
"save-window-size": "Save window size", "save-window-size": "Save window size",
"use-native-pip": "Use browser native PiP" "use-native-pip": "Use browser native PiP"
}, },
"name": "Picture in Picture", "name": "Picture-in-picture",
"templates": { "templates": {
"button": "Picture in Picture" "button": "Picture-in-picture"
} }
}, },
"playback-speed": { "playback-speed": {
@ -519,7 +569,7 @@
"name": "Video Quality Changer" "name": "Video Quality Changer"
}, },
"shortcuts": { "shortcuts": {
"description": "Allows setting global hotkeys for playback (play/pause/next/previous) + disable media osd by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for mediakeys + custom hotkeys for advanced users", "description": "Allows setting global hotkeys for playback (play/pause/next/previous) and turning off media OSD by overriding media keys, turning on Ctrl/CMD + F to search, turning on Linux MPRIS support for media keys, and custom hotkeys for advanced users.",
"menu": { "menu": {
"override-media-keys": "Override Media Keys", "override-media-keys": "Override Media Keys",
"set-keybinds": "Set Global Song Controls" "set-keybinds": "Set Global Song Controls"
@ -537,6 +587,10 @@
} }
} }
}, },
"skip-disliked-songs": {
"description": "Skips disliked songs",
"name": "Skip Disliked Songs"
},
"skip-silences": { "skip-silences": {
"description": "Automatically skip silences sections in songs", "description": "Automatically skip silences sections in songs",
"name": "Skip Silences" "name": "Skip Silences"

648
src/i18n/resources/es.json Normal file
View File

@ -0,0 +1,648 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Error al ejecutar el plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}: {{contextName}} ejecutado en {{ms}}ms",
"initialize-failed": "Error al inicializar plugin \"{{pluginName}}\"",
"load-all": "Cargando todos los plugins",
"load-failed": "Error al cargar el plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" cargado",
"unload-failed": "No se ha podido descargar el plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" descargado"
}
}
},
"language": {
"code": "es",
"local-name": "Inglés",
"name": "Spanish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Carga finalizada. DevTools abiertos"
},
"i18n": {
"loaded": "i18n cargado"
},
"second-instance": {
"receive-command": "Comando recibido sobre el protocolo: \"{{command}}\""
},
"theme": {
"css-file-not-found": "El archivo CSS \"{{cssFile}}\" no existe, ignorando"
},
"unresponsive": {
"details": "¡Error sin repuesta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Borrar caché de la aplicación"
},
"window": {
"tried-to-render-offscreen": "La ventana intentó mostrarse fuera de la pantalla, windowSize={{windowSize}}, displaySize={{displaySize}}, posicion={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "El menú está oculto, utiliza \"Alt\" para mostrarlo (o \"Escape\" si utilizas el menú integrado en la aplicación)",
"message": "Menu oculto esta deshabilitado",
"title": "Menú oculto activado"
},
"need-to-restart": {
"buttons": {
"later": "Más tarde",
"restart-now": "Reiniciar ahora"
},
"detail": "\"{{pluginName}}\" se requiere reiniciar para que el plugin tome efecto",
"message": "\"{{pluginName}}\" necesita reiniciar",
"title": "Se requiere reinicio"
},
"unresponsive": {
"buttons": {
"quit": "Dejar",
"relaunch": "Volver a abrir",
"wait": "Espera"
},
"detail": "Sentimos las molestias. Por favor, elija qué hacer:",
"message": "La aplicación no responde",
"title": "La ventana no responde"
},
"update-available": {
"buttons": {
"disable": "Desactivar actualizaciones",
"download": "Descargar",
"ok": "OK"
},
"detail": "Una nueva versión está disponible y puede descargarse en {{downloadLink}}",
"message": "Ya está disponible una nueva versión",
"title": "Actualización disponible"
}
},
"menu": {
"about": "Acerca de",
"navigation": {
"label": "Navegación",
"submenu": {
"copy-current-url": "Copiar la URL actual",
"go-back": "Regresar",
"go-forward": "Adelante",
"quit": "Salir",
"restart": "Reiniciar la aplicación"
}
},
"options": {
"label": "Opciones",
"submenu": {
"advanced-options": {
"label": "Opciones avanzadas",
"submenu": {
"auto-reset-app-cache": "Restablecer la caché de la aplicación al iniciarla",
"disable-hardware-acceleration": "Desactivar la aceleración por hardware",
"edit-config-json": "Editar config.json",
"override-user-agent": "sobrescribir User-Agent",
"restart-on-config-changes": "Reinicie al cambiar la configuración",
"set-proxy": {
"label": "Definir proxy",
"prompt": {
"label": "Introduzca la dirección del proxy: (déjela vacía para desactivarla)",
"placeholder": "Ejemplo: SOCKS5://127.0.0.1:9999",
"title": "Establecer proxy"
}
},
"toggle-dev-tools": "Activar DevTools"
}
},
"always-on-top": "Siempre arriba",
"auto-update": "Actualización automática",
"hide-menu": {
"dialog": {
"message": "El menú se ocultará la próxima vez que lo inicies, usa [Alt] para mostrarlo (o pulsa [`] si usas el menú dentro de la aplicación)",
"title": "Ocultar menú habilitado"
},
"label": "Ocultar menú"
},
"language": {
"dialog": {
"message": "El idioma se cambiará después de reiniciar",
"title": "Se cambio el idioma"
},
"label": "Idioma",
"submenu": {
"to-help-translate": "¿Quieres ayudar a traducir? Haz clic aquí"
}
},
"resume-on-start": "Reanudar la última canción al iniciar la aplicación",
"single-instance-lock": "Bloquear en una instancia unica",
"start-at-login": "Comenzar al iniciar sesión",
"starting-page": {
"label": "Página de inicio",
"unset": "Sin configurar"
},
"tray": {
"label": "Bandeja",
"submenu": {
"disabled": "Desactivado",
"enabled-and-hide-app": "Activar y ocultar la aplicación",
"enabled-and-show-app": "Activado y mostrar aplicación",
"play-pause-on-click": "Reproducir/Pausar al hacer clic"
}
},
"visual-tweaks": {
"label": "Ajustes visuales",
"submenu": {
"like-buttons": {
"default": "Predeterminado",
"force-show": "Forzar la visualización",
"hide": "Ocultar",
"label": "Botones de \"Me Gusta\""
},
"remove-upgrade-button": "Remover el botón de Actualización",
"theme": {
"label": "Tema",
"submenu": {
"import-css-file": "Importar archivo CSS personalizado",
"no-theme": "Sin temas"
}
}
}
}
}
},
"plugins": {
"enabled": "Habilitado",
"label": "Plugins",
"new": "NUEVO"
},
"view": {
"label": "Ver",
"submenu": {
"force-reload": "Forzar la recarga",
"reload": "Recargar",
"reset-zoom": "Tamaño actual",
"toggle-fullscreen": "Alternar pantalla completa",
"zoom-in": "Acercar",
"zoom-out": "Alejar"
}
}
},
"tray": {
"next": "Siguiente",
"play-pause": "Reproducir/Pausar",
"previous": "Anterior",
"quit": "Salir",
"restart": "Reiniciar la aplicación",
"show": "Mostrar ventana"
}
},
"plugins": {
"adblocker": {
"description": "Bloquear todos los anuncios y el rastreo",
"menu": {
"blocker": "Bloqueador"
},
"name": "Adblocker"
},
"album-actions": {
"description": "Añade los botones \"No me gusta\", \"No me gusta\", \"Me gusta\" y \"No me gusta\" para aplicarlos a todas las canciones de una lista de reproducción o un álbum.",
"name": "Acciones del álbum"
},
"album-color-theme": {
"description": "Aplica un tema dinámico y efectos visuales basados en la paleta de colores del álbum",
"name": "Color del álbum"
},
"ambient-mode": {
"description": "Aplica un efecto de iluminación proyectando colores suaves del vídeo en el fondo de la pantalla.",
"menu": {
"blur-amount": {
"label": "Cantidad de desenfoque",
"submenu": {
"pixels": "{{blurAmount}} pixeles"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Transparencia",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Calidad",
"submenu": {
"pixels": "{{quality}} píxeles"
}
},
"size": {
"label": "Tamaño",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transición suave",
"submenu": {
"during": "Durante {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Usando Pantalla Completa"
}
},
"name": "Modo ambiente"
},
"audio-compressor": {
"description": "Aplicar compresión al audio (reduce la diferencia entre las partes más fuertes y más suaves de una pista para que tenga un nivel más consistente)",
"name": "Compresor de audio"
},
"blur-nav-bar": {
"description": "Hace que la barra de navegación sea transparente y borrosa",
"name": "Desenfocar barra de navegación"
},
"bypass-age-restrictions": {
"description": "Saltar la verificación de edad de YouTube",
"name": "Saltar las restricciones de edad"
},
"captions-selector": {
"description": "Selector de subtítulos para pistas de audio de YouTube Music",
"menu": {
"autoload": "Seleccionar automáticamente el último subtítulo utilizado",
"disable-captions": "Sin subtítulos por defecto"
},
"name": "Selector de subtítulos",
"prompt": {
"selector": {
"label": "Idioma actual: {{language}}",
"none": "Ninguno",
"title": "Seleccionar idioma de los subtítulos"
}
},
"templates": {
"title": "Abra el selector de subtítulos"
}
},
"compact-sidebar": {
"description": "Poner siempre la barra lateral en modo compacto",
"name": "Barra lateral compacta"
},
"crossfade": {
"description": "Crossfade entre canciones",
"menu": {
"advanced": "Avanzado"
},
"name": "Crossfade [beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Duración del fundido (ms)",
"fade-out-duration": "Duración del fundido de salida (ms)",
"fade-scaling": {
"label": "Escala de fundido",
"linear": "Lineal",
"logarithmic": "Logarítmico"
},
"seconds-before-end": "Crossfade N segundos antes del final"
},
"title": "Opciones de crossfade"
}
}
},
"disable-autoplay": {
"description": "Hace que la canción comience en modo \"pausado\"",
"menu": {
"apply-once": "Sólo se aplica al inicio"
},
"name": "Desactivar reproducción automática"
},
"discord": {
"backend": {
"already-connected": "Se intentó conectar con una conexión activa",
"connected": "Conectado a Discord",
"disconnected": "Desconectado de Discord"
},
"description": "Muestra a tus amigos lo que escuchas con Rich Presence",
"menu": {
"auto-reconnect": "Reconectar automáticamente",
"clear-activity": "Borrar actividad",
"clear-activity-after-timeout": "Borrar actividad después de un tiempo",
"connected": "Conectado",
"disconnected": "Desconectado",
"hide-duration-left": "Ocultar la duración restante",
"hide-github-button": "Ocultar el botón de enlace a GitHub",
"play-on-youtube-music": "Reproducir en YouTube Music",
"set-inactivity-timeout": "Establecer tiempo de inactividad"
},
"name": "Estado de actividad de Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Introduzca el tiempo de inactividad en segundos:",
"title": "Establecer tiempo de inactividad"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "¡Argh! Lo siento, la descarga falló…",
"title": "¡Error en la descarga!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} canciones)",
"message": "Descargar Playlist {{playlistTitle}}",
"title": "Descarga iniciada"
}
},
"feedback": {
"conversion-progress": "Conversión: {{percent}}%",
"converting": "Convirtiendo…",
"done": "Listo: {{filePath}}",
"download-info": "Descargando {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Descarga: {{percent}}%",
"downloading": "Descargando…",
"downloading-counter": "Descargando {{current}}/{{total}}…",
"downloading-playlist": "Descargar lista de reproducción \"{{playlistTitle}}\" - {{playlistSize}} canciones ({{playlistId}})",
"error-while-downloading": "Error al descargar \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "La carpeta {{playlistFolder}} ya existe",
"getting-playlist-info": "Obteniendo información de la lista de reproducción…",
"loading": "Cargando…",
"playlist-has-only-one-song": "La lista de reproducción sólo tiene un elemento, descárgala directamente",
"playlist-id-not-found": "No se ha encontrado el ID de la lista de reproducción",
"playlist-is-empty": "La lista de reproducción está vacía",
"playlist-is-mix-or-private": "Error obteniendo información de la lista de reproducción: asegúrese de que no es una lista privada o \"Mixed for you\"\n\n{{error}}",
"preparing-file": "Preparando archivo…",
"saving": "Guardando…",
"trying-to-get-playlist-id": "Intentando obtener el ID de la lista de reproducción: {{playlistId}}",
"video-id-not-found": "Video no encontrado",
"writing-id3": "Escribiendo las etiquetas ID3…"
}
},
"description": "Descarga MP3 / audio fuente directamente desde la interfaz",
"menu": {
"choose-download-folder": "Elija la carpeta de descarga",
"download-playlist": "Descargar lista de reproducción",
"presets": "Preajustes",
"skip-existing": "Saltar archivos existentes"
},
"name": "Descargador",
"renderer": {
"can-not-update-progress": "No se puede actualizar el progreso"
},
"templates": {
"button": "Descargar"
}
},
"exponential-volume": {
"description": "Hace que el control deslizante de volumen sea exponencial para que sea más fácil seleccionar volúmenes más bajos.",
"name": "Volumen exponencial"
},
"in-app-menu": {
"description": "Da a las barras de menú un aspecto elegante, oscuro o del color de un álbum",
"menu": {
"hide-dom-window-controls": "Ocultar controles de ventana DOM"
},
"name": "Menú de aplicación"
},
"last-fm": {
"description": "Añade soporte de scrobbling para Last.fm",
"name": "Last.fm"
},
"lumiastream": {
"description": "Agrega soporte para Lumia Stream",
"name": "Lumia Stream [beta]"
},
"lyrics-genius": {
"description": "Añade el soporte para las letras para la mayoría de las canciones",
"menu": {
"romanized-lyrics": "Letras Romanizadas"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Letras recuperadas de Genius"
}
},
"music-together": {
"description": "Comparte una lista de reproducción con los demás. Cuando el anfitrión reproduzca una canción, todos los demás escucharán la misma",
"dialog": {
"enter-host": "Introduzca el ID del host"
},
"internal": {
"save": "Guardar",
"track-source": "Fuente de la pista",
"unknown-user": "Usuario desconocido"
},
"menu": {
"click-to-copy-id": "Copiar el ID del host",
"close": "Cerrar Music Together",
"connected-users": "Usuarios conectados",
"disconnect": "Desactivar Music Together",
"empty-user": "No hay usuarios conectados",
"host": "Host de Music Together",
"join": "Únase a Music Together",
"permission": {
"all": "Todo el control",
"host-only": "Solo anfitrión",
"playlist": "Control de las listas de reproducción"
},
"set-permission": "Permiso de control de cambios",
"status": {
"disconnected": "Desconectado",
"guest": "Conectado como invitado",
"host": "Conectado como anfitrión"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "No se puede añadir la canción",
"closed": "Music Together cerrado",
"disconnected": "Music Together desconectados",
"host-failed": "Fallo el host de Music Together",
"id-copied": "ID del host copiado en el portapapeles",
"join-failed": "Fallo en la unión a Music Together",
"joined": "Unido a Music Together",
"permission-changed": "Permiso de Music Together cambiado a \"{{permission}}\"",
"remove-song-failed": "Error al eliminar la canción",
"user-connected": "{{name}} se unió a Music Together",
"user-disconnected": "{{name}} dejó Music Together"
}
},
"navigation": {
"description": "Flechas de navegación Siguiente/Atrás directamente integradas en la interfaz, como en tu navegador favorito",
"name": "Navegación"
},
"no-google-login": {
"description": "Eliminar los botones y enlaces de inicio de sesión de Google de la interfaz",
"name": "Sin inicio de sesión de Google"
},
"notifications": {
"description": "Mostrar una notificación cuando empiece a sonar una canción (las notificaciones interactivas están disponibles en Windows)",
"menu": {
"interactive": "Notificaciones interactivas",
"interactive-settings": {
"label": "Ajustes interactivos",
"submenu": {
"hide-button-text": "Ocultar el texto del botón",
"refresh-on-play-pause": "Actualizar al reproducir/pausar",
"tray-controls": "Abrir/Cerrar al hacer clic en la bandeja"
}
},
"priority": "Prioridad de notificación",
"toast-style": "Estilo de mensaje emergente",
"unpause-notification": "Mostrar notificación al reanudar"
},
"name": "Notificaciones"
},
"picture-in-picture": {
"description": "Permite cambiar la aplicación al modo de imagen en imagen",
"menu": {
"always-on-top": "Siempre encima",
"hotkey": {
"label": "Tecla de acceso rápido",
"prompt": {
"keybind-options": {
"hotkey": "Tecla de acceso rápido"
},
"label": "Elige una tecla de acceso rápido para activar la función de imagen en imagen",
"title": "Tecla de acceso directo a imagen en imagen"
}
},
"save-window-position": "Guardar la posición de la ventana",
"save-window-size": "Guardar tamaño de la ventana",
"use-native-pip": "Utilizar \"Dos imágenes a la vez\" PiP nativo del navegador"
},
"name": "Imagen en imagen",
"templates": {
"button": "Imagen en imagen"
}
},
"playback-speed": {
"description": "Escucha rápido, escucha despacio! Añade un control deslizante que ajusta la velocidad de la canción",
"name": "Velocidad de reproducción",
"templates": {
"button": "Velocidad"
}
},
"precise-volume": {
"description": "Controla el volumen de manera precisa utilizando la rueda del ratón/teclas de acceso rápido, con una interfaz personalizada y pasos de volumen personalizables",
"menu": {
"arrows-shortcuts": "Controles de teclas de flechas locales",
"custom-volume-steps": "Establecer niveles de volumen personalizados",
"global-shortcuts": "Teclas de acceso rápido globales"
},
"name": "Volumen preciso",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Disminuir el volumen",
"increase": "Aumentar el volumen"
},
"label": "Elija combinaciones de teclas para el volumen:",
"title": "Combinaciones de teclas para el volumen"
},
"volume-steps": {
"label": "Escoge los pasos de aumento o disminución del volumen",
"title": "Niveles de volumen"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Calidad actual: {{quality}}",
"message": "Elija la calidad de vídeo:",
"title": "Elija la calidad de vídeo"
}
}
},
"description": "Permite cambiar la calidad del vídeo con un botón sobre puesto en el vídeo",
"name": "Ajustador de calidad de vídeo"
},
"shortcuts": {
"description": "Permite configurar teclas de acceso rápido globales para la reproducción (reproducir/pausa/siguiente/anterior) y desactivar el OSD multimedia anulando las teclas multimedia, activar Ctrl/CMD + F para buscar, activar la compatibilidad con MPRIS de Linux para las teclas multimedia y teclas de acceso rápido personalizadas para usuarios avanzados.",
"menu": {
"override-media-keys": "Anular teclas de medios",
"set-keybinds": "Configurar controles globales de canciones"
},
"name": "Atajos (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Siguiente",
"play-pause": "Reproducir / Pausa",
"previous": "Anterior"
},
"label": "Elija combinaciones de teclas para el control de las canciones:",
"title": "Atajos de teclado globales"
}
}
},
"skip-disliked-songs": {
"description": "Omite las canciones que no le gustan",
"name": "Saltar canciones que no me gustan"
},
"skip-silences": {
"description": "Salta automáticamente las secciones silenciosas de las canciones",
"name": "Saltar silencios"
},
"sponsorblock": {
"description": "Salta automáticamente las partes no musicales como la introducción/final o secciones de videos musicales donde la canción no está sonando",
"name": "SponsorBlock"
},
"taskbar-mediacontrol": {
"description": "Controla la reproducción desde la barra de tareas de Windows",
"name": "Control de medios de la barra de tareas"
},
"touchbar": {
"description": "Añade un widget TouchBar para los usuarios de macOS",
"name": "TouchBar"
},
"tuna-obs": {
"description": "Integración con el complemento Tuna de OBS",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "Añade un botón para cambiar entre el modo Vídeo/Canción. también puede eliminar opcionalmente toda la pestaña de vídeo",
"menu": {
"align": {
"label": "Alineación",
"submenu": {
"left": "Izquierda",
"middle": "Medio",
"right": "Derecha"
}
},
"force-hide": "Forzar eliminación de la pestaña de vídeo",
"mode": {
"label": "Modo",
"submenu": {
"custom": "Alternador personalizado",
"disabled": "Desactivado",
"native": "Alternador nativo"
}
}
},
"name": "Alternador de vídeo",
"templates": {
"button": "Canción"
}
},
"visualizer": {
"description": "Añadir un visualizador al reproductor",
"menu": {
"visualizer-type": "Tipo de visualizador"
},
"name": "Visualizador"
}
}
}

View File

@ -1,4 +1,18 @@
{ {
"common": {
"console": {
"plugins": {
"execute-failed": "Échec de l'exécution du plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} exécuté en {{ms}}ms",
"initialize-failed": "Échec de l'initialisation du plugin \"{{pluginName}}\"",
"load-all": "Chargement des plugins",
"load-failed": "Échec du chargement du plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" chargé",
"unload-failed": "Échec du déchargement du plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" déchargé"
}
}
},
"language": { "language": {
"code": "fr", "code": "fr",
"local-name": "Français", "local-name": "Français",
@ -6,43 +20,578 @@
}, },
"main": { "main": {
"console": { "console": {
"did-finish-load": {
"dev-tools": "Chargement terminé. DevTools ouvert"
},
"i18n": {
"loaded": "i18n chargé"
},
"second-instance": {
"receive-command": "Received command over protocol: \"{{command}}\""
},
"theme": { "theme": {
"css-file-not-found": "Le fichier de CSS \"{{cssFile}}\" n'existe pas, ignorer" "css-file-not-found": "Le fichier de CSS \"{{cssFile}}\" n'existe pas, ignorer"
},
"unresponsive": {
"details": "Erreur: ne répond pas!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Effacement du cache de l'application"
},
"window": {
"tried-to-render-offscreen": "La fenêtre a essayé d'effectuer un rendu hors écran, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
} }
}, },
"dialog": { "dialog": {
"hide-menu-enabled": {
"detail": "Le menu est masqué, utilisez « Alt » pour l'afficher (ou « Échap » si vous utilisez le menu de l'application)",
"message": "Le masquage du menu est activé",
"title": "Masquer le menu activé"
},
"need-to-restart": {
"buttons": {
"later": "Plus tard",
"restart-now": "Redémarrer maintenant"
},
"detail": "\"{{pluginName}}\" plugin nécessite un redémarrage pour qu'il soit pris en compte",
"message": "\"{{pluginName}}\" a besoin d'un redémarrage",
"title": "Redémarrage requis"
},
"unresponsive": {
"buttons": {
"quit": "Quitté",
"relaunch": "Relancer",
"wait": "Attendre"
},
"detail": "Nous sommes désolés du dérangement! veuillez choisir quoi faire:",
"message": "L'application ne répond pas",
"title": "La fenêtre ne répond pas"
},
"update-available": { "update-available": {
"buttons": { "buttons": {
"download": "Sauvegarder" "disable": "Désactiver les mises à jour",
} "download": "Télécharger",
"ok": "Ok"
},
"detail": "Une nouvelle version est disponible et peut-être télécharger sur {{downloadLink}}",
"message": "Une nouvelle version est disponible",
"title": "Mise à jour disponible"
} }
}, },
"menu": { "menu": {
"about": "À-propos",
"navigation": {
"label": "Navigation",
"submenu": {
"copy-current-url": "Copier l'URL actuelle",
"go-back": "Retour",
"go-forward": "Avancer",
"quit": "Quitter",
"restart": "Redémarrer l'application"
}
},
"options": { "options": {
"label": "Paramètres", "label": "Paramètres",
"submenu": { "submenu": {
"advanced-options": { "advanced-options": {
"label": "Options avancée",
"submenu": { "submenu": {
"auto-reset-app-cache": "Réinitialiser le cache de l'application au démarrage",
"disable-hardware-acceleration": "Désactiver les accélérations matérielles",
"edit-config-json": "Modifier config.json", "edit-config-json": "Modifier config.json",
"override-user-agent": "Remplacer le User-Agent",
"restart-on-config-changes": "Redémarrer quand la configuration change",
"set-proxy": { "set-proxy": {
"label": "Définir un proxy",
"prompt": { "prompt": {
"placeholder": "Exemple: socks5://127.0.0.1:9999" "label": "Entrez l'adresse proxy : (laissez vide pour désactiver)",
"placeholder": "Exemple: socks5://127.0.0.1:9999",
"title": "Définir un proxy"
} }
} },
"toggle-dev-tools": "Ouvrir/fermer les outils de développement"
} }
}, },
"always-on-top": "Toujours au dessus",
"auto-update": "Mise à jour automatique",
"hide-menu": {
"dialog": {
"message": "Le menu sera masqué au prochain lancement, utilisez [Alt] pour l'afficher (ou backtick [`] si vous utilisez le menu intégré à l'application)",
"title": "Masquer le menu activé"
},
"label": "Cacher le menu"
},
"language": { "language": {
"label": "Langue" "dialog": {
"message": "La langue sera changée après le redémarrage",
"title": "Langue modifiée"
},
"label": "Langue",
"submenu": {
"to-help-translate": "Envie d'aider à la traduction ? Cliquer ici"
}
},
"resume-on-start": "Reprendre la dernière chanson quand l'application démarre",
"single-instance-lock": "Verrouillage d'instance unique",
"start-at-login": "Démarrer à la connexion",
"starting-page": {
"label": "Page de démarrage",
"unset": "Définir à vide"
},
"tray": {
"label": "Plateau",
"submenu": {
"disabled": "Désactivé",
"enabled-and-hide-app": "Activé et cacher l'app",
"enabled-and-show-app": "Activé et afficher l'application",
"play-pause-on-click": "Lecture/Pause au clic"
}
}, },
"visual-tweaks": { "visual-tweaks": {
"label": "Ajustements visuels",
"submenu": { "submenu": {
"like-buttons": { "like-buttons": {
"hide": "Cacher" "default": "Par défaut",
"force-show": "Forcer à apparaître",
"hide": "Cacher",
"label": "Boutons « J'aime »"
},
"remove-upgrade-button": "Supprimer le bouton de mise à niveau",
"theme": {
"label": "Thème",
"submenu": {
"import-css-file": "Importer fichier CSS personnalisé",
"no-theme": "Pas de thème"
}
} }
} }
} }
} }
},
"plugins": {
"enabled": "Activé",
"label": "Extensions"
},
"view": {
"label": "Vue",
"submenu": {
"force-reload": "Forcer l'actualisation",
"reload": "Actualiser",
"reset-zoom": "Taille réelle",
"toggle-fullscreen": "Basculer en plein écran",
"zoom-in": "Zoom avant",
"zoom-out": "Zoom arrière"
}
} }
},
"tray": {
"next": "Suivant",
"play-pause": "Lecture/Pause",
"previous": "Précédent",
"quit": "Quitter",
"restart": "Redémarrer l'application",
"show": "Afficher la fenêtre"
}
},
"plugins": {
"adblocker": {
"description": "Bloquer toutes les annonces et le suivi par défaut",
"menu": {
"blocker": "Bloqueur"
},
"name": "Bloqueur de publicités"
},
"album-color-theme": {
"description": "Applique un thème dynamique et des effets visuels basés sur la palette des couleurs de l'album",
"name": "Thème de couleur d'album"
},
"ambient-mode": {
"description": "Applique un effet d'éclairage en jetant des couleurs douces de la vidéo, dans le fond de votre écran.",
"menu": {
"blur-amount": {
"label": "Quantité de flou",
"submenu": {
"pixels": "{{blurAmount}} pixels"
}
},
"buffer": {
"label": "Tampon",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacité",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualité",
"submenu": {
"pixels": "{{quality}} pixels"
}
},
"size": {
"label": "Taille",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transition en douceur",
"submenu": {
"during": "Pendant {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Utilisation du mode plein écran"
}
},
"name": "Mode ambiant"
},
"audio-compressor": {
"description": "Appliquer une compression à l'audio (diminue le volume des parties les plus fortes du signal et augmente le volume des parties les plus faibles)",
"name": "Compresseur audio"
},
"blur-nav-bar": {
"description": "Rend la barre de navigation transparente et floue",
"name": "Barre de navigation floue"
},
"bypass-age-restrictions": {
"description": "Contourner la vérification de l'âge de YouTube",
"name": "Contourner les restrictions dâge"
},
"captions-selector": {
"description": "Sélecteur de sous-titres pour les pistes audio YouTube Music",
"menu": {
"autoload": "Sélectionner automatiquement la dernière légende utilisée",
"disable-captions": "Pas de sous-titres par défaut"
},
"name": "Sélecteur de sous-titres",
"prompt": {
"selector": {
"label": "Langue de sous-titrage actuelle : {{language}}",
"none": "Aucun",
"title": "Sélectionnez la langue des sous-titres"
}
},
"templates": {
"title": "Ouvrir le sélecteur de sous-titres"
}
},
"compact-sidebar": {
"description": "Toujours définir la barre latérale en mode compact",
"name": "Barre latérale compacte"
},
"crossfade": {
"description": "Fondu enchaîné entre les chansons",
"menu": {
"advanced": "Avancé"
},
"name": "Fondu enchaîné [bêta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Durée du fondu (millisecondes)",
"fade-out-duration": "Durée du fondu (millisecondes)",
"fade-scaling": {
"label": "Mise à l'échelle du fondu",
"linear": "Linéaire",
"logarithmic": "Logarithmique"
},
"seconds-before-end": "Fondu enchaîné N secondes avant la fin"
},
"title": "Options de fondu enchaîné"
}
}
},
"disable-autoplay": {
"description": "Fait démarrer la chanson en mode \"pause\"",
"menu": {
"apply-once": "S'applique seulement au démarrage"
},
"name": "Désactiver la lecture automatique"
},
"discord": {
"backend": {
"already-connected": "Tentative de connexion avec une connexion active",
"connected": "Connecté à Discord",
"disconnected": "Déconnecté de Discord"
},
"description": "Montrez à vos amis ce que vous écoutez avec Rich Presence",
"menu": {
"auto-reconnect": "Reconnexion automatique",
"clear-activity": "Effacer l'activité",
"clear-activity-after-timeout": "Effacer lactivité après un délai dattente",
"connected": "Connecté",
"disconnected": "Déconnecté",
"hide-duration-left": "Masquer la durée restante",
"hide-github-button": "Masquer le bouton du lien GitHub",
"play-on-youtube-music": "Jouer sur YouTube Music",
"set-inactivity-timeout": "Définir le délai d'inactivité"
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Entrez le délai d'inactivité en secondes :",
"title": "Définir le délai d'inactivité"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Ok"
},
"message": "Argh ! Désolé, le téléchargement a échoué…",
"title": "Erreur de téléchargement !"
},
"start-download-playlist": {
"buttons": {
"ok": "Ok"
},
"detail": "({{playlistSize}} chansons)",
"message": "Téléchargement de la playlist {{playlistTitle}}",
"title": "Téléchargement a commencé"
}
},
"feedback": {
"conversion-progress": "Conversion : {{percent}} %",
"converting": "Conversion…",
"done": "Terminé : {{filePath}}",
"download-info": "Téléchargement {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Télécharger: {{percent}}%",
"downloading": "Télécharge…",
"downloading-counter": "Télécharge {{current}}/{{total}}…",
"downloading-playlist": "Téléchargement de la playlist \"{{playlistTitle}}\"  {{playlistSize}} chansons ({{playlistId}})",
"error-while-downloading": "Erreur lors du téléchargement de \"{{author}} - {{title}}\" : {{error}}",
"folder-already-exists": "Le dossier {{playlistFolder}} existe déjà",
"getting-playlist-info": "Obtention d'informations sur la liste de lecture…",
"loading": "Chargement…",
"playlist-has-only-one-song": "La liste de lecture ne contient qu'un seul élément, téléchargement du morceau seul",
"playlist-id-not-found": "Aucun ID de liste de lecture trouvé",
"playlist-is-empty": "La liste de lecture est vide",
"playlist-is-mix-or-private": "Erreur lors de l'obtention des informations sur la liste de lecture: assurez-vous qu'il ne s'agit pas d'une liste privée ou \"Mixée pour vous\"\n\n{{error}}",
"preparing-file": "Péparer des fichier…",
"saving": "Sauvegarde…",
"trying-to-get-playlist-id": "Obtention de l'ID de la liste de lecture: {{playlistId}}",
"video-id-not-found": "Vidéo non trouvée",
"writing-id3": "Écriture des balises ID3…"
}
},
"description": "Télécharge les fichiers MP3/source audio directement depuis l'interface",
"menu": {
"choose-download-folder": "Choisissez le dossier de téléchargement",
"download-playlist": "Télécharger la liste de lecture",
"presets": "Préconfigurations",
"skip-existing": "Passer les fichiers existants"
},
"name": "Téléchargeur",
"renderer": {
"can-not-update-progress": "Impossible de mettre à jour la progression"
},
"templates": {
"button": "Télécharger"
}
},
"exponential-volume": {
"description": "Rend le curseur de volume exponentiel afin qu'il soit plus facile de sélectionner des volumes plus faibles.",
"name": "Volume exponentiel"
},
"in-app-menu": {
"description": "Donne aux barres de menus un aspect élégant, sombre ou aux couleurs de l'album",
"menu": {
"hide-dom-window-controls": "Masquer les contrôles de la fenêtre DOM"
},
"name": "Menu intégré à l'application"
},
"last-fm": {
"description": "Ajouter le support du scrobbling pour Last.fm",
"name": "Last.fm"
},
"lumiastream": {
"description": "Ajoute la prise en charge de Lumia Stream",
"name": "Lumia Stream [bêta]"
},
"lyrics-genius": {
"description": "Ajoute la prise en charge des paroles pour la plupart des chansons",
"menu": {
"romanized-lyrics": "Paroles romanisées"
},
"name": "Paroles Genius",
"renderer": {
"fetched-lyrics": "Paroles récupérées pour Genius"
}
},
"navigation": {
"description": "Flèches de navigation Suivant/Retour directement intégrées dans l'interface, comme dans votre navigateur préféré",
"name": "Navigation"
},
"no-google-login": {
"description": "Supprimer les boutons et liens de connexion Google de l'interface",
"name": "Pas de connexion Google"
},
"notifications": {
"description": "Afficher une notification quand une chanson commence à jouer (les notifications interactives sont disponibles sur Windows)",
"menu": {
"interactive": "Notifications interactives",
"interactive-settings": {
"label": "Paramètres interactifs",
"submenu": {
"hide-button-text": "Masquer le texte du bouton",
"refresh-on-play-pause": "Actualiser lors de la lecture/pause",
"tray-controls": "Ouvrir/Fermer sur le plateau, cliquez"
}
},
"priority": "Priorité des notifications",
"toast-style": "Style des notifications \"Toast\"",
"unpause-notification": "Afficher la notification lors de la reprise"
},
"name": "Notifications"
},
"picture-in-picture": {
"description": "Permet de basculer lapplication en mode image dans image",
"menu": {
"always-on-top": "Toujours en haut",
"hotkey": {
"label": "Raccourci clavier",
"prompt": {
"keybind-options": {
"hotkey": "Raccourci clavier"
},
"label": "Choisissez un raccourci clavier pour activer l'image dans l'image",
"title": "Touche de raccourci Image dans l'image"
}
},
"save-window-position": "Enregistrer la position de la fenêtre",
"save-window-size": "Enregistrer la taille de la fenêtre",
"use-native-pip": "Utiliser le mode image dans image natif du navigateur"
},
"name": "Image dans l'image",
"templates": {
"button": "Image dans l'image"
}
},
"playback-speed": {
"description": "Écoutez vite, écoutez lentement ! Ajoute un curseur qui contrôle la vitesse de la chanson",
"name": "Vitesse de lecture",
"templates": {
"button": "Vitesse"
}
},
"precise-volume": {
"description": "Contrôlez le volume avec précision à l'aide de la molette de la souris/des raccourcis clavier, avec une interface personnalisée et des étapes de volume personnalisables",
"menu": {
"arrows-shortcuts": "Contrôles avec les touches fléchées",
"custom-volume-steps": "Définir des étapes de volume personnalisées",
"global-shortcuts": "Raccourcis clavier globaux"
},
"name": "Volume précis",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Diminuer le volume",
"increase": "Augmenter le volume"
},
"label": "Choisissez les raccourcis clavier du volume global :",
"title": "Raccourcis clavier de volume global"
},
"volume-steps": {
"label": "Choisissez les étapes d'augmentation/diminution du volume",
"title": "Étapes de volume"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualité actuelle: {{quality}}",
"message": "Choisissez la qualité vidéo :",
"title": "Choisissez la qualité vidéo"
}
}
},
"description": "Permet de changer la qualité vidéo avec un bouton sur la vidéo",
"name": "Changeur de qualité vidéo"
},
"shortcuts": {
"description": "Permet de définir des raccourcis clavier globaux pour la lecture (lecture/pause/suivant/précédent) + désactiver l'OSD multimédia en remplaçant les touches multimédias + activer Ctrl/CMD + F pour rechercher + activer la prise en charge Linux MPRIS pour les touches multimédias + raccourcis clavier personnalisés pour les utilisateurs avancés",
"menu": {
"override-media-keys": "Remplacer les touches multimédias",
"set-keybinds": "Définir les contrôles globaux des morceaux"
},
"name": "Raccourcis (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Suivant",
"play-pause": "Lecture / Pause",
"previous": "Précédent"
},
"label": "Choisissez les raccourcis clavier globaux pour le contrôle des morceaux :",
"title": "Raccourcis clavier globaux"
}
}
},
"skip-disliked-songs": {
"description": "Passer les musiques que je n'aime pas"
},
"skip-silences": {
"description": "Ignorer automatiquement les sections de silence dans les chansons",
"name": "Passer les silences"
},
"sponsorblock": {
"description": "Saute automatiquement les parties non musicales comme l'intro/outro ou les parties de clips vidéo où la chanson n'est pas lue",
"name": "SponsorBlock"
},
"taskbar-mediacontrol": {
"description": "Contrôlez la lecture depuis votre barre des tâches Windows",
"name": "Contrôle multimédia de la barre des tâches"
},
"touchbar": {
"description": "Ajoute un widget TouchBar pour les utilisateurs de macOS",
"name": "TouchBar"
},
"tuna-obs": {
"description": "Intégration avec le plugin OBS Tuna",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "Ajoute un bouton pour basculer entre le mode Vidéo/Chanson. peut également supprimer tout l'onglet vidéo",
"menu": {
"align": {
"label": "Alignement",
"submenu": {
"left": "Gauche",
"middle": "Milieu",
"right": "Droite"
}
},
"force-hide": "Forcer la suppression de l'onglet vidéo",
"mode": {
"label": "Mode",
"submenu": {
"custom": "Bascule personnalisée",
"disabled": "Désactivé",
"native": "Bascule native"
}
}
},
"name": "Basculer la vidéo",
"templates": {
"button": "Musique"
}
},
"visualizer": {
"description": "Ajoute un visualiseur au lecteur",
"menu": {
"visualizer-type": "Type de visualiseur"
},
"name": "Visualiseur"
} }
} }
} }

View File

@ -0,0 +1,21 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Gagal saat mengeksekusi plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dieksekusi pada {{ms}}ms",
"initialize-failed": "Gagal dalam menginisialisasi plugin \"{{pluginName}}\"",
"load-all": "Memuat semua plugin",
"load-failed": "Gagal memuat plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" dimuat",
"unload-failed": "Gagal untuk memuat plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" telah dikeluarkan"
}
}
},
"language": {
"code": "id",
"local-name": "Bahasa Indonesia",
"name": "Indonesian"
}
}

598
src/i18n/resources/it.json Normal file
View File

@ -0,0 +1,598 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "È stato impossibile eseguire il plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Il plugin {{pluginName}}:{{contextName}} è stato eseguito a {{ms}}ms",
"initialize-failed": "Inizializzazione del plugin \"{{pluginName}}\" fallita",
"load-all": "Carica tutti i plugin",
"load-failed": "Caricamento del plugin \"{{pluginName}}\" non riuscito",
"loaded": "Plugin \"{{pluginName}}\" caricato",
"unload-failed": "Rimozione del plugin \"{{pluginName}}\" fallita",
"unloaded": "Plugin \"{{pluginName}}\" rimosso"
}
}
},
"language": {
"code": "it",
"local-name": "Italiano",
"name": "Italian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Caricamento concluso. DevTools aperto"
},
"i18n": {
"loaded": "i18n caricato"
},
"second-instance": {
"receive-command": "Comando ricevuto tramite protocollo: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Il file CSS \"{{cssFile}}\" non esiste, ignorato"
},
"unresponsive": {
"details": "Errore di mancata risposta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Sto liberando la cache dell'app"
},
"window": {
"tried-to-render-offscreen": "La finestra ha cercato di renderizzare fuori schermo, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}\""
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Il menu è nascosto, utilizza 'Alt' per visualizzarlo (o 'Escape' se si utilizza il Menu In-App)\"",
"message": "'Nascondi menu' è attivo",
"title": "'Nascondi menu' attivo"
},
"need-to-restart": {
"buttons": {
"later": "In seguito",
"restart-now": "Riavvia ora"
},
"detail": "Riavviare per attivare il plugin\"{{pluginName}}\"",
"message": "\"{{pluginName}}\" deve essere riavviato",
"title": "Riavvio richiesto"
},
"unresponsive": {
"buttons": {
"quit": "Arresta",
"relaunch": "Riavvia",
"wait": "Attendi"
},
"detail": "Ci dispiace per l'inconveniente! Scegli cosa fare:",
"message": "L'applicazione non risponde",
"title": "La finestra non risponde"
},
"update-available": {
"buttons": {
"disable": "Disattiva gli aggiornamenti",
"download": "Download",
"ok": "OK"
},
"detail": "È disponibile una nuova versione scaricabile all'indirizzo {{downloadLink}}",
"message": "È disponibile una nuova versione",
"title": "Aggiornamento disponibile"
}
},
"menu": {
"about": "Informazioni",
"navigation": {
"label": "Navigazione",
"submenu": {
"copy-current-url": "Copia l'URL corrente",
"go-back": "Pagina indietro",
"go-forward": "Pagina avanti",
"quit": "Esci",
"restart": "Riavvia l'app"
}
},
"options": {
"label": "Opzioni",
"submenu": {
"advanced-options": {
"label": "Opzioni avanzate",
"submenu": {
"auto-reset-app-cache": "Reimposta la cache dell'app quando viene riavviata",
"disable-hardware-acceleration": "Disabilita l'accelerazione hardware",
"edit-config-json": "Modificare config.json",
"override-user-agent": "Sovrascrivi User-Agent",
"restart-on-config-changes": "Riavvia alla modifica delle impostazioni",
"set-proxy": {
"label": "Imposta il proxy",
"prompt": {
"label": "Inserisci l'indirizzo proxy: (lascia vuoto per disabilitare)",
"placeholder": "Esempio: SOKS5://127.0.0.1:9999",
"title": "Imposta il proxy"
}
},
"toggle-dev-tools": "Attiva/disattiva DevTools"
}
},
"always-on-top": "Sempre in cima",
"auto-update": "Aggiornamento automatico",
"hide-menu": {
"dialog": {
"message": "Il menu verrà nascosto al prossimo avvio. Utilizzare [Alt] per mostrarlo (o backtick [`] se si utilizza il Menu In-App)",
"title": "Nascondi menu abilitato"
},
"label": "Nascondi menu"
},
"language": {
"dialog": {
"message": "La lingua verrà cambiata dopo il riavvio",
"title": "Lingua cambiata"
},
"label": "Lingua",
"submenu": {
"to-help-translate": "Vuoi aiutare a tradurre? Clicca qui"
}
},
"resume-on-start": "Riprendi a riprodurre l'ultimo brano all'avvio dell'app",
"single-instance-lock": "Permetti una sola istanza dell'app",
"start-at-login": "Avvia al login",
"starting-page": {
"label": "Pagina iniziale",
"unset": "Non impostato"
},
"tray": {
"label": "Mostra icona nel tray",
"submenu": {
"disabled": "Disabilita",
"enabled-and-hide-app": "Abilita e nascondi l'app",
"enabled-and-show-app": "Abilita e mostra l'app",
"play-pause-on-click": "Riproduci/Pausa al click sull'icona"
}
},
"visual-tweaks": {
"label": "Miglioramenti visivi",
"submenu": {
"like-buttons": {
"default": "Predefinito",
"force-show": "Forza la visualizzazione",
"hide": "Nascondi",
"label": "Pulsanti Like"
},
"remove-upgrade-button": "Rimuovi il pulsante aggiorna",
"theme": {
"label": "Tema",
"submenu": {
"import-css-file": "Importa file CSS personalizzato",
"no-theme": "Nessun tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Attivato",
"label": "Plugin"
},
"view": {
"label": "Visualizzazione",
"submenu": {
"force-reload": "Forza l'aggiornamento",
"reload": "Aggiorna",
"reset-zoom": "Ripristina dimensione",
"toggle-fullscreen": "Attiva/disattiva Schermo Intero",
"zoom-in": "Zoom in",
"zoom-out": "Zoom out"
}
}
},
"tray": {
"next": "Prossimo",
"play-pause": "Riproduci/Pausa",
"previous": "Precedente",
"quit": "Esci",
"restart": "Riavvia l'app",
"show": "Mostra finestra"
}
},
"plugins": {
"adblocker": {
"description": "Blocca tutti gli annunci e i tracker",
"menu": {
"blocker": "Blocco"
},
"name": "Adblocker"
},
"album-color-theme": {
"description": "Applica un tema dinamico e degli effetti visivi basandosi sul colore dell'album",
"name": "Tema abbinato a colore album"
},
"ambient-mode": {
"description": "Applica un effetto di illuminazione proiettando i colori delicati del video sullo sfondo dello schermo.",
"menu": {
"blur-amount": {
"label": "Intensità sfocatura",
"submenu": {
"pixels": "{{blurAmount}} pixel"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Trasparenza",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualità",
"submenu": {
"pixels": "{{quality}} pixel"
}
},
"size": {
"label": "Dimensione",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Fluidità transizione",
"submenu": {
"during": "Per {{interpolationTime}} _s"
}
},
"use-fullscreen": {
"label": "Utilizzo di schermo intero"
}
},
"name": "Modalità Ambiente"
},
"audio-compressor": {
"description": "Attiva la compressione audio (abbassa il volume delle parti più alte e alza quello delle parti più basse del segnale)",
"name": "Compressore audio"
},
"blur-nav-bar": {
"description": "Rende la barra di navigazione trasparente e sfuocata",
"name": "Barra di navigazione trasparente"
},
"bypass-age-restrictions": {
"description": "Bypassa la verifica dell'età di YouTube",
"name": "Aggira i limiti d'età"
},
"captions-selector": {
"description": "Selettore sottotitolo per le tracce audio di YouTube",
"menu": {
"autoload": "Seleziona automaticamente l'ultimo sottotitolo utilizzato",
"disable-captions": "Disattiva i sottotitoli"
},
"name": "Selettore Sottotitoli",
"prompt": {
"selector": {
"label": "Lingua del sottotitolo attuale: {{language}}",
"none": "Nessuno",
"title": "Scegli la lingua del sottotitolo"
}
},
"templates": {
"title": "Apri il selettore dei sottotitoli"
}
},
"compact-sidebar": {
"description": "Imposta sempre la barra laterale in modalità compatta",
"name": "Barra laterale compatta"
},
"crossfade": {
"description": "Crossfade tra i brani",
"menu": {
"advanced": "Impostazioni avanzate"
},
"name": "Crossfade [beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Durata dissolvenza in entrata (ms)",
"fade-out-duration": "Durata dissolvenza in uscita (ms)",
"fade-scaling": {
"label": "Transizione dissolvenza",
"linear": "Lineare",
"logarithmic": "Logaritmica"
},
"seconds-before-end": "N° secondi di crossfade prima della fine"
},
"title": "Opzioni crossfade"
}
}
},
"disable-autoplay": {
"description": "Fa iniziare i brani in modalità \"pausa\"",
"menu": {
"apply-once": "Solo all'avvio"
},
"name": "Disattiva autoplay"
},
"discord": {
"backend": {
"already-connected": "Tenta di connettersi con connessione attiva",
"connected": "Connesso a Discord",
"disconnected": "Scollegato da Discord"
},
"description": "Mostra ai tuoi amici cosa ascolti con Rich Presence",
"menu": {
"auto-reconnect": "Riconnessione automatica",
"clear-activity": "Rimuovi attività",
"clear-activity-after-timeout": "Cancella attività dopo il timeout",
"connected": "Connesso",
"disconnected": "Disconnesso",
"hide-duration-left": "Nascondi la durata rimasta",
"hide-github-button": "Nascondi il pulsante link a GitHub",
"play-on-youtube-music": "Riproduci su YouTube Music",
"set-inactivity-timeout": "Imposta il timeout di inattività"
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Inserisci il timeout di inattività in secondi:",
"title": "Imposta il timeout di inattività"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Mi dispiace, download fallito…",
"title": "Errore nel download!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} canzoni)",
"message": "Scarica Playlist {{playlistTitle}}",
"title": "Download iniziato"
}
},
"feedback": {
"conversion-progress": "Conversione: {{percent}}%",
"converting": "Sto convertendo…",
"done": "Fatto: {{filePath}}",
"download-info": "Sto scaricando {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Download: {{percent}}%",
"downloading": "Sto scaricando…",
"downloading-counter": "Sto scaricando {{current}}/{{total}}…",
"downloading-playlist": "Sto scaricando la playlist \"{{playlistTitle}}\" - {{playlistSize}} brani({{playlistId}})",
"error-while-downloading": "Errore di download \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "La cartella {{playlistFolder}} è già esistente",
"getting-playlist-info": "Sto ottenendo le info sulla playlist…",
"loading": "Caricamento…",
"playlist-has-only-one-song": "La playlist ha un solo elemento, lo sto scaricando direttamente",
"playlist-id-not-found": "Nessun ID playlist trovato",
"playlist-is-empty": "La playlist è vuota",
"playlist-is-mix-or-private": "Errore nell'ottenere info sulla playlist: assicurati che non sia una playlist privata o un \"Mixtape per te\"\n\n{{error}}",
"preparing-file": "Sto preparando il file…",
"saving": "Sto salvando…",
"trying-to-get-playlist-id": "Sto cercando di ottenere l'ID della playlist: {{playlistId}}",
"video-id-not-found": "Video non trovato",
"writing-id3": "Sto scrivendo i tag ID3…"
}
},
"description": "Download MP3 / sorgenti audio direttamente dall'interfaccia",
"menu": {
"choose-download-folder": "Scegli cartella download",
"download-playlist": "Scarica la playlist",
"presets": "Preimpostazioni",
"skip-existing": "Salta i file esistenti"
},
"name": "Downloader",
"renderer": {
"can-not-update-progress": "Impossibile aggiornare l'avanzamento"
},
"templates": {
"button": "Scarica"
}
},
"exponential-volume": {
"description": "Rende esponenziale il cursore del volume, in modo da facilitare la selezione di volumi più bassi.",
"name": "Volume esponenziale"
},
"in-app-menu": {
"description": "Migliora l'aspetto delle barre del menu con un look scuro o basato sul colore dell'album",
"menu": {
"hide-dom-window-controls": "Nascondi i controlli delle finestre DOM"
},
"name": "Menu In-App"
},
"last-fm": {
"description": "Aggiungi supporto per lo scrobbling su Last.fm",
"name": "Last.fm"
},
"lumiastream": {
"description": "Aggiungi supporto per Lumia Stream",
"name": "Lumia Stream [beta]"
},
"lyrics-genius": {
"description": "Aggiunge il supporto dei testi per la maggior parte delle canzoni",
"menu": {
"romanized-lyrics": "Alfabeto latino per i brani con testo in caratteri orientali"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Testi recuperati per Genius"
}
},
"navigation": {
"description": "Frecce di navigazione Avanti/Indietro integrate direttamente nell'interfaccia, come nel tuo browser preferito",
"name": "Navigazione"
},
"no-google-login": {
"description": "Rimuovi i pulsanti di accesso e i link di Google dall'interfaccia",
"name": "Nessun login di Google"
},
"notifications": {
"description": "Mostra una notifica quando viene riprodotto un brano (le notifiche interattive sono disponibili su Windows)",
"menu": {
"interactive": "Notifiche interattive",
"interactive-settings": {
"label": "Impostazioni dell'interazione",
"submenu": {
"hide-button-text": "Nascondi il testo del pulsante",
"refresh-on-play-pause": "Refresh quando si preme Riproduci/Pausa",
"tray-controls": "Apri/chiudi cliccando l'icona nel tray"
}
},
"priority": "Priorità di notifica",
"toast-style": "Stile Toast",
"unpause-notification": "Mostra notifica quando riprendi ascolto"
},
"name": "Notifiche"
},
"picture-in-picture": {
"description": "Consente di far passare l'app alla modalità Picture-in-Picture",
"menu": {
"always-on-top": "Sempre in primo piano",
"hotkey": {
"label": "Hotkey",
"prompt": {
"keybind-options": {
"hotkey": "Hotkey"
},
"label": "Scegliere un'hotkey per attivare Picture-in-picture",
"title": "Picture-in-picture Hotkey"
}
},
"save-window-position": "Salva la posizione della finestra",
"save-window-size": "Salva la dimensione della finestra",
"use-native-pip": "Usa il PiP nativo del browser"
},
"name": "Picture-in-Picture",
"templates": {
"button": "Picture-in-Picture"
}
},
"playback-speed": {
"description": "Ascolto veloce, ascolto lento! Aggiunge un cursore che controlla la velocità di riproduzione del brano",
"name": "Velocità riproduzione",
"templates": {
"button": "Velocità"
}
},
"precise-volume": {
"description": "Controlla con precisione il volume utilizzando la rotella del mouse, le hotkey o i tasti freccia e usando incrementi di volume personalizzabili",
"menu": {
"arrows-shortcuts": "Controlla con i tasti freccia",
"custom-volume-steps": "Imposta incrementi di volume personalizzati",
"global-shortcuts": "Hotkey globali"
},
"name": "Volume preciso",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Diminuisci volume",
"increase": "Aumenta volume"
},
"label": "Scegli i tasti di scelta rapida regolazione volume:",
"title": "Tasti di scelta rapida regolazione volume"
},
"volume-steps": {
"label": "Seleziona l'incremento/decremento del volume",
"title": "Incrementi volume"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualità attuale: {{quality}}",
"message": "Qualità Video:",
"title": "Scegli la qualità video"
}
}
},
"description": "Permette di cambiare la qualità del video con un pulsante in sovrimpressione",
"name": "Cambia qualità video"
},
"shortcuts": {
"description": "Consente di impostare tasti di scelta rapida globali per la riproduzione (riproduci/pausa/successivo/precedente) + disabilita l'OSD multimediale sovrascrivendo i tasti multimediali + abilita Ctrl/CMD + F per la ricerca + abilita il supporto Linux MPRIS per i tasti multimediali + tasti di scelta rapida personalizzati per utenti avanzati.",
"menu": {
"override-media-keys": "Ridefinisci i tasti multimediali",
"set-keybinds": "Imposta i controlli brano globali"
},
"name": "Scorciatoie (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Prossimo",
"play-pause": "Riproduci / Pausa",
"previous": "Precedente"
},
"label": "Scegli combinazioni di tasti per il controllo dei brani:",
"title": "Combinazioni di tasti"
}
}
},
"skip-disliked-songs": {
"description": "Salta i brani che non ti piacciono",
"name": "Salta i brani che non ti piacciono"
},
"skip-silences": {
"description": "Salta automaticamente le parti silenziose nei brani",
"name": "Salta silenzi"
},
"sponsorblock": {
"description": "Salta automaticamente le parti non musicali, come l'intro/outro delle canzoni o le parti dei video musicali in cui non viene riprodotto il brano",
"name": "Blocco sponsor"
},
"taskbar-mediacontrol": {
"description": "Controlla riproduzione dalla taskbar di Windows",
"name": "Controlli multimediali sulla taskbar"
},
"touchbar": {
"description": "Aggiunge un widget TouchBar per gli utenti macOS",
"name": "Touch Bar (per MacOS)"
},
"tuna-obs": {
"description": "Integrazione con il plugin OBS Tuna",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "Aggiunge un pulsante per passare dalla modalità Video a quella Brano. Può anche rimuovere l'intera scheda Brano/Video",
"menu": {
"align": {
"label": "Allineamento",
"submenu": {
"left": "Sinistra",
"middle": "Centro",
"right": "Destra"
}
},
"force-hide": "Rimuovi la scheda Brano/Video",
"mode": {
"label": "Modalità",
"submenu": {
"custom": "Brano/Video personalizzato",
"disabled": "Disattivato",
"native": "Brano/Video nativo"
}
}
},
"name": "Selettore Brano/Video",
"templates": {
"button": "Brano"
}
},
"visualizer": {
"description": "Sostituisce al Video un visualizzatore grafico",
"menu": {
"visualizer-type": "Tipo di visualizzazione"
},
"name": "Visualizzatore grafico"
}
}
}

View File

@ -105,7 +105,7 @@
"label": "プロキシ", "label": "プロキシ",
"prompt": { "prompt": {
"label": "プロキシのアドレスを入力: (空にすると無効化)", "label": "プロキシのアドレスを入力: (空にすると無効化)",
"placeholder": "例: socks5://127.0.0.1:9999", "placeholder": "例: SOCKS5://127.0.0.1:9999",
"title": "プロキシ" "title": "プロキシ"
} }
}, },
@ -519,7 +519,7 @@
"name": "ビデオ品質チェンジャー" "name": "ビデオ品質チェンジャー"
}, },
"shortcuts": { "shortcuts": {
"description": "再生用のグローバル ホットキー (再生/一時停止/次/前) の設定 + メディア キーをオーバーライドしてメディア OSD を無効にする + Ctrl/CMD + F による検索を有効にする + メディアキーの Linux mpris サポートを有効にする + 上級ユーザー向けのカスタム ホットキー を可能にします", "description": "再生用のグローバル ホットキー (再生/一時停止/次/前) の設定メディア キーをオーバーライドしてメディア OSD を無効にするCtrl/CMD + F による検索を有効にする メディアキーの Linux mpris サポートを有効にする 上級ユーザー向けのカスタム ホットキー を可能にします",
"menu": { "menu": {
"override-media-keys": "メディアキーを上書き", "override-media-keys": "メディアキーを上書き",
"set-keybinds": "グローバルソングコントロールを設定する" "set-keybinds": "グローバルソングコントロールを設定する"
@ -537,6 +537,10 @@
} }
} }
}, },
"skip-disliked-songs": {
"description": "低評価と表示された曲をスキップします",
"name": "低評価曲をスキップ"
},
"skip-silences": { "skip-silences": {
"description": "曲の無音区間を自動でスキップ", "description": "曲の無音区間を自動でスキップ",
"name": "無音区間をスキップ" "name": "無音区間をスキップ"

View File

@ -105,7 +105,7 @@
"label": "프록시 설정", "label": "프록시 설정",
"prompt": { "prompt": {
"label": "프록시 주소를 입력하세요: (비워두면 비활성화됨)", "label": "프록시 주소를 입력하세요: (비워두면 비활성화됨)",
"placeholder": "예제: socks5://127.0.0.1:9999", "placeholder": "예제: SOCKS5://127.0.0.1:9999",
"title": "프록시 설정" "title": "프록시 설정"
} }
}, },
@ -170,7 +170,8 @@
}, },
"plugins": { "plugins": {
"enabled": "활성화", "enabled": "활성화",
"label": "확장" "label": "확장",
"new": "NEW"
}, },
"view": { "view": {
"label": "보기", "label": "보기",
@ -197,9 +198,13 @@
"adblocker": { "adblocker": {
"description": "모든 광고와 트래커를 즉시 차단합니다", "description": "모든 광고와 트래커를 즉시 차단합니다",
"menu": { "menu": {
"blocker": "애드블록 타입" "blocker": "광고 차단 타입"
}, },
"name": "애드블록" "name": "광고 차단기"
},
"album-actions": {
"description": "좋아요, 싫어요 버튼을 추가하고, 결과를 재생 목록 또는 앨범의 모든 노래에 적용합니다.",
"name": "앨범 액션"
}, },
"album-color-theme": { "album-color-theme": {
"description": "앨범 색상 팔레트를 기반으로 동적 테마 및 시각 효과를 적용합니다", "description": "앨범 색상 팔레트를 기반으로 동적 테마 및 시각 효과를 적용합니다",
@ -426,6 +431,51 @@
"fetched-lyrics": "Genius에서 가사 불러옴" "fetched-lyrics": "Genius에서 가사 불러옴"
} }
}, },
"music-together": {
"description": "여러명과 함께 플레이리스트를 공유합니다. 호스트가 음악을 재생하면, 다른 사용자들도 같은 노래를 들을 수 있습니다",
"dialog": {
"enter-host": "호스트 아이디를 입력하세요"
},
"internal": {
"save": "저장",
"track-source": "재생 중인 트랙 출처",
"unknown-user": "알 수 없는 사용자"
},
"menu": {
"click-to-copy-id": "호스트 아이디 복사",
"close": "Music Together 닫기",
"connected-users": "연결된 사용자",
"disconnect": "Music Together 연결 끊기",
"empty-user": "연결된 사용자 없음",
"host": "Music Together 호스트",
"join": "Music Together 참여",
"permission": {
"all": "게스트가 모두 제어 가능",
"host-only": "호스트만 제어 가능",
"playlist": "게스트가 재생목록 제어 가능"
},
"set-permission": "제어 권한 변경",
"status": {
"disconnected": "연결 끊김",
"guest": "게스트로 연결됨",
"host": "호스트로 연결됨"
}
},
"name": "Music Together [베타]",
"toast": {
"add-song-failed": "노래 추가 실패",
"closed": "Music Together가 닫혔습니다",
"disconnected": "Music Together 연결이 끊어졌습니다",
"host-failed": "Music Together를 열 수 없습니다",
"id-copied": "호스트 아이디가 클립보드에 복사되었습니다",
"join-failed": "Music Together에 참여할 수 없습니다",
"joined": "Music Together에 참여했습니다",
"permission-changed": "Music Together 제어 권한이 \"{{permission}}\"(으)로 변경되었습니다",
"remove-song-failed": "노래 제거 실패",
"user-connected": "{{name}}님이 Music Together에 참여했습니다",
"user-disconnected": "{{name}}님이 Music Together에서 나갔습니다"
}
},
"navigation": { "navigation": {
"description": "브라우저에서처럼, UI에 직접 통합된 앞으로/뒤로 탐색하는 화살표", "description": "브라우저에서처럼, UI에 직접 통합된 앞으로/뒤로 탐색하는 화살표",
"name": "탐색" "name": "탐색"
@ -519,7 +569,7 @@
"name": "영상 품질 체인저" "name": "영상 품질 체인저"
}, },
"shortcuts": { "shortcuts": {
"description": "재생을 위한 전역 단축키 설정 허용 (재생/일시 정지/다음/이전) + 미디어 키를 재정의하여 미디어 OSD 비활성화 + Ctrl/CMD + F 검색 활성화 + 미디어 키에 대한 리눅스 MPRIS 지원 활성화 + 고급 사용자를 위한 사용자 지정 단축키 지원", "description": "재생을 위한 전역 단축키 설정 허용 (재생/일시 정지/다음/이전), 미디어 키를 재정의하여 미디어 OSD 비활성화, Ctrl/CMD + F 검색 활성화, 미디어키 지원을 위해 리눅스 MPRIS 지원 활성화, 고급 사용자를 위한 사용자 지정 단축키 지원 추가.",
"menu": { "menu": {
"override-media-keys": "미디어 키 재정의", "override-media-keys": "미디어 키 재정의",
"set-keybinds": "전역 노래 제어 설정" "set-keybinds": "전역 노래 제어 설정"
@ -537,6 +587,10 @@
} }
} }
}, },
"skip-disliked-songs": {
"description": "싫어요 표시된 노래를 건너뜁니다",
"name": "싫어요 표시 노래 건너뛰기"
},
"skip-silences": { "skip-silences": {
"description": "노래의 무음 부분을 자동으로 건너뜁니다", "description": "노래의 무음 부분을 자동으로 건너뜁니다",
"name": "무음 건너뛰기" "name": "무음 건너뛰기"

598
src/i18n/resources/lt.json Normal file
View File

@ -0,0 +1,598 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Nepavyko įvykdyti įskiepio {{pluginName}}::{{contextName}}",
"executed-at-ms": "Įskiepis {{pluginName}}::{{contextName}} įvykdytas per {{ms}}ms",
"initialize-failed": "Nepavyko inicijuoti įskiepio \"{{pluginName}}\"",
"load-all": "Kraunama visus įskiepius",
"load-failed": "Nepavyko užkrauti įskiepio \"{{pluginName}}\"",
"loaded": "Įskiepis \"{{pluginName}}\" užkrautas",
"unload-failed": "Nepavyko iškrauti įskiepio \"{{pluginName}}\"",
"unloaded": "Įskiepis \"{{pluginName}}\" iškrautas"
}
}
},
"language": {
"code": "lt",
"local-name": "Lietuvių kalba",
"name": "Lithuanian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Baigta krauti. \"DevTools\" atidaryta"
},
"i18n": {
"loaded": "\"i18n\" užkrauta"
},
"second-instance": {
"receive-command": "Gauta komanda per protokolą: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS failas \"{{cssFile}}\" neegzistuoja, ignoruojama"
},
"unresponsive": {
"details": "Nereguojanti paklaida\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Išvaloma programos talpykla"
},
"window": {
"tried-to-render-offscreen": "Langas bandė vaizduotis už ekrano ribų, langoDydis={{windowSize}}, ekranoDydis={{displaySize}}, pozicija={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Meniu yra paslėpta, naudokite 'Alt', kad ją parodyti (arba 'Escape' jei naudojama programos meniu)",
"message": "\"Paslėpti Meniu\" yra įjungta",
"title": "Įjungta \"Paslėpti Meniu\""
},
"need-to-restart": {
"buttons": {
"later": "Vėliau",
"restart-now": "Perkrauti Dabar"
},
"detail": "\"{{pluginName}}\" įskiepis reikalauja perkrovimą, kad veiktų",
"message": "\"{{pluginName}}\" reikia perkrovimo",
"title": "Reikiamas perkrovimas"
},
"unresponsive": {
"buttons": {
"quit": "Išeiti",
"relaunch": "Perleisti",
"wait": "Palaukti"
},
"detail": "Mes apgailestaujame dėl nepatogumų! prašau pasirinkti ką daryti:",
"message": "Programa Neatsako",
"title": "Langas Neatsako"
},
"update-available": {
"buttons": {
"disable": "Išjungti Atnaujinimus",
"download": "Atsisiųsti",
"ok": "Gerai"
},
"detail": "Nauja versija yra prieinama ir gali būti atsisiųsta {{downloadLink}}",
"message": "Nauja versija yra prieinama",
"title": "Prieinamas Atnaujinimas"
}
},
"menu": {
"about": "Apie",
"navigation": {
"label": "Navigacija",
"submenu": {
"copy-current-url": "Nukopijuoti dabartinį URL",
"go-back": "Grįžti Atgal",
"go-forward": "Eiti į priekį",
"quit": "Išeiti",
"restart": "Perkrauti programą"
}
},
"options": {
"label": "Nustatymai",
"submenu": {
"advanced-options": {
"label": "Išplėstiniai nustatymai",
"submenu": {
"auto-reset-app-cache": "Perkrauti programos talpyklą, kai programa paleidžiama",
"disable-hardware-acceleration": "Išjungti aparatūros pagreitį",
"edit-config-json": "Redaguoti config.json",
"override-user-agent": "Perrašyti \"User-Agent\"",
"restart-on-config-changes": "Perkrauti po config pasikeitimo",
"set-proxy": {
"label": "Nustatyti įgaliotajį serverį",
"prompt": {
"label": "Įvesti Įgaliotojo serverio adresą: (palikti tuščią, kad išjungti)",
"placeholder": "Pavyzdys: SOCKS5://127.0.0.1:9999",
"title": "Nustatyti įgaliotajį serverį"
}
},
"toggle-dev-tools": "Įjungti/Išjungti DevTools"
}
},
"always-on-top": "Visada viršuje",
"auto-update": "Automatinis Atnaujinimas",
"hide-menu": {
"dialog": {
"message": "Meniu bus paslėpta per kitą paleidimą, naudokite [Alt], kad ją parodyti (arba kairinio kirčio ženklą [`] jei naudojama programos meniu)",
"title": "\"Paslėpti Meniu\" įjungtas"
},
"label": "Paslėpti Meniu"
},
"language": {
"dialog": {
"message": "Kalba bus pakeista po perkrovimo",
"title": "Kalba Pakeista"
},
"label": "Kalba",
"submenu": {
"to-help-translate": "Norite padėti išversti? Paspauskite čia"
}
},
"resume-on-start": "Tęsti paskutinę dainą, kai programa bus paleista",
"single-instance-lock": "Vienkartinis užraktas",
"start-at-login": "Pradėti nuo prisijungimo",
"starting-page": {
"label": "Pradžios puslapis",
"unset": "Nenustatyta"
},
"tray": {
"label": "Padėklas",
"submenu": {
"disabled": "Išjungta",
"enabled-and-hide-app": "Įjungta ir slėpti programos langą",
"enabled-and-show-app": "Įjungta ir rodyti programos langą",
"play-pause-on-click": "Paleisti/Pristabdyti ant paspaudimo"
}
},
"visual-tweaks": {
"label": "Vizualiniai patobulinimai",
"submenu": {
"like-buttons": {
"default": "Numatytasis",
"force-show": "Priversti rodyti",
"hide": "Slėpti",
"label": "\"Patinka\" mygtukai"
},
"remove-upgrade-button": "Nerodyti \"Patobulinti\" mygtuko",
"theme": {
"label": "Tema",
"submenu": {
"import-css-file": "Įkelti pasirinktinį CSS failą",
"no-theme": "Be temos"
}
}
}
}
}
},
"plugins": {
"enabled": "Įjungta",
"label": "Įskiepiai"
},
"view": {
"label": "Vaizdas",
"submenu": {
"force-reload": "Priverstinai perkrauti",
"reload": "Perkrauti",
"reset-zoom": "Tikras dydis",
"toggle-fullscreen": "Įjungti/Išjungti Pilną Ekraną",
"zoom-in": "Priartinti",
"zoom-out": "Nutolinti"
}
}
},
"tray": {
"next": "Kitas",
"play-pause": "Paleisti/Pristabdyti",
"previous": "Ankstesnis",
"quit": "Išeiti",
"restart": "Perkrauti programą",
"show": "Rodyti langą"
}
},
"plugins": {
"adblocker": {
"description": "Blokuoti visas reklamas ir seklius",
"menu": {
"blocker": "Blokuotojas"
},
"name": "Reklamų blokuotojas"
},
"album-color-theme": {
"description": "Pritaiko dinamišką temą ir vizualinius efektus pagal albumo spalvų paletę",
"name": "Albumo Spalvų Tema"
},
"ambient-mode": {
"description": "Pritaiko apšvietimo efektą, perteikdamas švelnias vaizdo įrašo spalvas į ekrano foną.",
"menu": {
"blur-amount": {
"label": "Suliejimo kiekis",
"submenu": {
"pixels": "{{blurAmount}} pikseliai"
}
},
"buffer": {
"label": "Buferis",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Skaidrumas",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kokybė",
"submenu": {
"pixels": "{{quality}} pikseliai"
}
},
"size": {
"label": "Dydis",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Perliejimo švelnumas",
"submenu": {
"during": "Per {{interpolationTime}}s"
}
},
"use-fullscreen": {
"label": "Naudojamas visas ekranas"
}
},
"name": "Aplinkos rėžimas"
},
"audio-compressor": {
"description": "Pritaikyti garso kompresiją (sumažina garsiausių signalo dalių garsumą ir padidina švelniausių dalių garsumą)",
"name": "Garso Kompresorius"
},
"blur-nav-bar": {
"description": "Padaro navigacijos lentą permatomą ir susiliejusią",
"name": "Sulieti Navigacijos Lentą"
},
"bypass-age-restrictions": {
"description": "Apeiti \"Youtube\" amžiaus patikrinimą",
"name": "Apeiti Amžiaus Apribojimus"
},
"captions-selector": {
"description": "„YouTube Music“ Garso takelių antraščių parinkiklis",
"menu": {
"autoload": "Automatiškai pasirinkti paskutinę naudotą antraštę",
"disable-captions": "Pagal numatytuosius nustatymus išjungti antraštės"
},
"name": "Antraščių parinkiklis",
"prompt": {
"selector": {
"label": "Dabartinė antraščių kalba: {{language}}",
"none": "Joks",
"title": "Pasirinkti antraščių kalbą"
}
},
"templates": {
"title": "Atidaryti antraščių parinkiklį"
}
},
"compact-sidebar": {
"description": "Visada nustatyti šoninę juostą kompaktiniame rėžime",
"name": "Kompaktinė šoninė juosta"
},
"crossfade": {
"description": "Perliejimas tarp dainų",
"menu": {
"advanced": "Išplėstinė"
},
"name": "Perliejimas [beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Išblukimo trukmė (ms)",
"fade-out-duration": "Išnykimo trukmė (ms)",
"fade-scaling": {
"label": "Išblukimo stiprumas",
"linear": "Linijinis",
"logarithmic": "Logaritminis"
},
"seconds-before-end": "Pradėti lieti dainas N sekundžių prieš pabaigą"
},
"title": "Perliejimo nustatymai"
}
}
},
"disable-autoplay": {
"description": "Pradeda dainą pristabdytame rėžime",
"menu": {
"apply-once": "Pritaiko tik per programos paleidimą"
},
"name": "Išjungti Automatinį leidimą"
},
"discord": {
"backend": {
"already-connected": "Bandyta prisijungti naudojant aktyvų ryšį",
"connected": "Prisijungta prie \"Discord\"",
"disconnected": "Atsijungta nuo \"Discord\""
},
"description": "Parodyk savo draugams ko tu klausaisi su \"Turtingas Buvimas\" (Rich Presence)",
"menu": {
"auto-reconnect": "Automatiškai prisijungti",
"clear-activity": "Išvalyti veiklą",
"clear-activity-after-timeout": "Išvalyti veiklą po skirtojo laiko",
"connected": "Prisijungta",
"disconnected": "Atsijungta",
"hide-duration-left": "Slėpti kiek liko laiko",
"hide-github-button": "Slėpti \"GitHub\" nuorodos mygtuką",
"play-on-youtube-music": "Leisti ant \"Youtube Music\"",
"set-inactivity-timeout": "Nustatyti neveiklumo laiką"
},
"name": "\"Discord\" Turtingas Buvimas (Rich Presence)",
"prompt": {
"set-inactivity-timeout": {
"label": "Įveskite neveiklumo skirtąjį laiką sekundėmis:",
"title": "Nustatyti neveiklumo laiką"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Gerai"
},
"message": "Aaa! Apgailestaujame, nepavyko atsisiųsti…",
"title": "Paklaida atsisiunčiant!"
},
"start-download-playlist": {
"buttons": {
"ok": "Gerai"
},
"detail": "({{playlistSize}} dainos)",
"message": "Atsisiunčiama {{playlistTitle}} grojaraštį",
"title": "Prasidėjo atsisiuntimas"
}
},
"feedback": {
"conversion-progress": "Konversija: {{percent}}%",
"converting": "Konvertuojama…",
"done": "Baigta: {{filePath}}",
"download-info": "Atsiunčiama {{artist}} - {{title}} {{videoId}}",
"download-progress": "Atsisiuntimas: {{percent}}%",
"downloading": "Atsisiunčiama…",
"downloading-counter": "Atsisiunčiama {{current}}/{{total}}…",
"downloading-playlist": "Atsisiunčiamas grojaraštis \"{{playlistTitle}}\" - {{playlistSize}} dainų {{playlistId}}",
"error-while-downloading": "Paklaida atsisiunčiant \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Aplankas {{playlistFolder}} jau egzistuoja",
"getting-playlist-info": "Gaunama grojaraščio informacija…",
"loading": "Kraunama…",
"playlist-has-only-one-song": "Grojaraštis turi tik vieną elementą, jis atsisiunčiamas tiesiogiai",
"playlist-id-not-found": "Grojaraščio ID nerastas",
"playlist-is-empty": "Grojaraštis yra tuščias",
"playlist-is-mix-or-private": "Paklaida gaunant grojaraščio informaciją: Pasitikrink, kad jis nėra privatus ar \"Surinkta specialiai jums\" grojaraštis\n\n{{error}}",
"preparing-file": "Failas paruošiamas…",
"saving": "Išsaugojama…",
"trying-to-get-playlist-id": "Bandoma gauti grojaraščio ID: {{playlistId}}",
"video-id-not-found": "Vaizdo įrašas nerastas",
"writing-id3": "Rašoma ID3 žymes…"
}
},
"description": "Atsisiunčia MP3 / šaltinio garsą tiesiogiai iš sąsajos",
"menu": {
"choose-download-folder": "Pasirinkti atsisiuntimų aplanką",
"download-playlist": "Atsisiųsti grojaraštį",
"presets": "Iš anksto nustatyti nustatymai",
"skip-existing": "Praleisti egzistuojančius failus"
},
"name": "Atsiuntėjas",
"renderer": {
"can-not-update-progress": "Nepavyko atnaujinti eigos"
},
"templates": {
"button": "Atsisiųsti"
}
},
"exponential-volume": {
"description": "Padaro garsumo slankiklį eksponentinį, kad būtų lengviau pasirinkti mažesnį garsumą.",
"name": "Eksponentinis garsas"
},
"in-app-menu": {
"description": "Duoda meniu lentoms įmantrią, tamsią ar albumo spalvos išvaizdą",
"menu": {
"hide-dom-window-controls": "Slėpti DOM lango kontroles"
},
"name": "Programos Meniu"
},
"last-fm": {
"description": "Pridėkite Last.fm scrobble palaikymą",
"name": "Last.fm"
},
"lumiastream": {
"description": "Prideda \"Lumia Stream\" palaikymą",
"name": "Lumia Stream [beta]"
},
"lyrics-genius": {
"description": "Prideda daugumai dainių žodžių tekstus",
"menu": {
"romanized-lyrics": "Romanizuoti dainų tekstai"
},
"name": "\"Genius\" Žodžių tekstai",
"renderer": {
"fetched-lyrics": "Gauti žodžiai iš „Genius“"
}
},
"navigation": {
"description": "Kitas/Ankstenis navigacijos rodyklės tiesiogiai integruotos sąsajoje, kaip tavo mėgstamiausioje naršyklėje",
"name": "Navigacija"
},
"no-google-login": {
"description": "Pašalinti \"Google\" prisijungimo mygtukus ir nuorodas iš sąsjos",
"name": "Be \"Google\" Prisijungimo"
},
"notifications": {
"description": "Rodyti pranešimą, kai pradeda groti daina (interaktyvūs pranešimai pasiekiami sistemoje \"Windows\")",
"menu": {
"interactive": "Interaktyvūs pranešimai",
"interactive-settings": {
"label": "Interaktyvūs nustatymai",
"submenu": {
"hide-button-text": "Paslėpti mygtuko tekstą",
"refresh-on-play-pause": "Atnaujinti ant Paleidimo/Pristabdymo",
"tray-controls": "Atidaryti/Uždaryti ant padėklo paspaudimo"
}
},
"priority": "Pranešimų pirminybė",
"toast-style": "Skrudintas stilius",
"unpause-notification": "Rodyti pranešimus po dainos paleidimo"
},
"name": "Pranešimai"
},
"picture-in-picture": {
"description": "Leidžia pakeisti programą į \"picture-in-picture\" rėžimą",
"menu": {
"always-on-top": "Visada ant viršaus",
"hotkey": {
"label": "Spartusis klavišas",
"prompt": {
"keybind-options": {
"hotkey": "Spartusis klavišas"
},
"label": "Pasirinkti spartujį klaviša, kad įjungti/išjungti \"picture-in-picture\"",
"title": "\"Picture-in-picture\" Spartusis klavišas"
}
},
"save-window-position": "Išsaugoti lango poziciją",
"save-window-size": "Išsaugoti lango dydį",
"use-native-pip": "Naudoti naršyklės savajį PiP"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Klausyk greitai, klausyk lėtai! Prideda slankiklį, kuris valdo dainos greitį",
"name": "Atkūrimo Greitis",
"templates": {
"button": "Greitis"
}
},
"precise-volume": {
"description": "Tiksliai valdykite garsumą naudodami pelės ratuką / sparčiuosius klavišus, naudodami pritaikytą HUD ir pritaikomus garsumo žingsnius",
"menu": {
"arrows-shortcuts": "Vietiniai rodyklių klavišai valdikliai",
"custom-volume-steps": "Nustatykite Pasirinktinius Garsumo Žingsnius",
"global-shortcuts": "Pasauliniai spartieji klavišai"
},
"name": "Tikslus Garsas",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Pamažinti Garsą",
"increase": "Padidinti Garsą"
},
"label": "Pasirinkti Pasaulinius garso klavišus:",
"title": "Pasauliniai Garso Klavišai"
},
"volume-steps": {
"label": "Pasirinkti Garso Didinimo/Mažinimo Žingsnius",
"title": "Garso Žingsniai"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Dabartinė Kokybė: {{quality}}",
"message": "Pasirinkite Vaizdo Kokybę:",
"title": "Pasirinkite Vaizdo Kokybę"
}
}
},
"description": "Leidžia pakeisti vaizdo kokybę su mygtuku ant vaizdo perdangos",
"name": "Vaizdo Kokybės Pakeitėjas"
},
"shortcuts": {
"description": "Leidžia nustatyti visuotinius atkūrimo sparčiuosius klavišus (paleisti / pristabdyti / kitą / ankstesnį) ir išjungti medijos OSD nepaisant medijos klavišų, įjungti Ctrl / CMD + F ieškoti, įjungti Linux MPRIS palaikymą medijos klavišams ir pasirinktinius sparčiuosius klavišus pažengusiems vartotojams.",
"menu": {
"override-media-keys": "Perrašyti Medijos klavišus",
"set-keybinds": "Nustatyti Pasaulines Dainų Kontroles"
},
"name": "Spartieji klavišai (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Kitas",
"play-pause": "Paleisti / Pristabdyti",
"previous": "Ankstesnis"
},
"label": "Pasirinkti Pasaulinius Klavišus Dainų Kontroliavimui:",
"title": "Pasauliniai Klavišai"
}
}
},
"skip-disliked-songs": {
"description": "Praleidžia nepatinkančias dainas",
"name": "Praleisti Nepatinkančias Dainas"
},
"skip-silences": {
"description": "Automatiškai praleisti tylos dalis dainose",
"name": "Praleisti Tylumas"
},
"sponsorblock": {
"description": "Automatiškai praleidžia ne muzikines dalis, pvz., įžangą/užvedimą arba muzikinių vaizdo įrašų dalis, kuriose daina negrojama",
"name": "Rėmėjų blokuotojas"
},
"taskbar-mediacontrol": {
"description": "Valdykite atkūrimą iš „Windows“ užduočių juostos",
"name": "Užduočių juostos medijos valdymas"
},
"touchbar": {
"description": "Pridedamas jutiklinės juostos valdiklis MacOS vartotojams",
"name": "TouchBar"
},
"tuna-obs": {
"description": "Integracija su OBS papildiniu \"Tuna\"",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "Pridedamas mygtukas, skirtas perjungti vaizdo įrašo/dainos režimą. taip pat galite pasirinktinai pašalinti visą vaizdo įrašo skirtuką",
"menu": {
"align": {
"label": "Lygiavimas",
"submenu": {
"left": "Kairė",
"middle": "Vidurys",
"right": "Dešinė"
}
},
"force-hide": "Priverstinai pašalinti vaizdo įrašo skirtuką",
"mode": {
"label": "Rėžimas",
"submenu": {
"custom": "Pasirinktinis perjungimas",
"disabled": "Išjungta",
"native": "Vietinis perjungimas"
}
}
},
"name": "Vaizdo įrašo perjungimas",
"templates": {
"button": "Daina"
}
},
"visualizer": {
"description": "Prie grotuvo pridedamas vizualizatorius",
"menu": {
"visualizer-type": "Vizualizatoriaus tipas"
},
"name": "Vizualizatorius"
}
}
}

View File

@ -1,25 +1,160 @@
{ {
"common": {
"console": {
"plugins": {
"execute-failed": "Klarte ikke å kjøre programtillegg {{pluginName}}::{{contextName}}",
"executed-at-ms": "Det tok {{ms}} ms å kjøre {{pluginName}}::{{contextName}}",
"initialize-failed": "Klarte ikke å igangsette «{{pluginName}}»",
"load-all": "Laster inn alle programtillegg",
"load-failed": "Klarte ikke å laste inn {{pluginName}}-programtillegget",
"loaded": "Lastet inn {{pluginName}}-programtillegget",
"unload-failed": "Kunne ikke skru av {{pluginName}}-programtillegget",
"unloaded": "{{pluginName}}-programtillegg avskrudd"
}
}
},
"language": { "language": {
"code": "nb_NO", "code": "nb_NO",
"local-name": "Norsk bokmål", "local-name": "Norsk bokmål",
"name": "Norwegian Bokmål" "name": "Norwegian Bokmål"
}, },
"main": { "main": {
"console": {
"did-finish-load": {
"dev-tools": "Innlasting fullført. Utviklerverktøy åpnet."
},
"i18n": {
"loaded": "språkstøtte innlastet"
},
"second-instance": {
"receive-command": "Mottok kommando over protokoll: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS-filen «{{cssFile}}» finnes ikke. Ignorerer."
},
"unresponsive": {
"details": "Svarer ikke\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Tømmer programhurtiglager"
},
"window": {
"tried-to-render-offscreen": "Prøvde å tegne vindu utenfor skjermen. Størrelse={{windowSize}}, skjermstørrelse={{displaySize}}, posisjon={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menyen er skjult. Bruk «Alt» for å vise den, (ller «Esc» for å bruke menyen i programmet).",
"message": "Meny skjult",
"title": "Meny vist"
},
"need-to-restart": {
"buttons": {
"later": "Senere",
"restart-now": "Start på ny nå"
},
"detail": "{{pluginName}}-programtillegget krever programomstart før bruk.",
"message": "{{pluginName}}-programtillegget krever programomstart.",
"title": "Programomstart kreves"
},
"unresponsive": {
"buttons": {
"quit": "Avslutt",
"relaunch": "Start igjen",
"wait": "Vent"
},
"detail": "Velg blandt følgende:",
"message": "Programmet svarer ikke",
"title": "Vinduet svarer ikke"
},
"update-available": {
"buttons": {
"disable": "Skru av oppgraderinger",
"download": "Last ned",
"ok": "OK"
},
"detail": "En ny versjon er tilgjengelig og kan lastes ned fra {{downloadLink}}",
"message": "En ny versjon er tilgjengelig",
"title": "Oppgradering tilgjengelig"
}
},
"menu": { "menu": {
"about": "Om",
"navigation": {
"label": "Navigasjon",
"submenu": {
"copy-current-url": "Kopier nåværende nettadresse",
"go-back": "Tilbake",
"go-forward": "Forover",
"quit": "Avslutt",
"restart": "Programomstart"
}
},
"options": { "options": {
"label": "Alternativer", "label": "Alternativer",
"submenu": { "submenu": {
"advanced-options": {
"label": "Avanserte alternativer",
"submenu": {
"auto-reset-app-cache": "Tilbakestill programhurtiglager når programmet startes",
"disable-hardware-acceleration": "Skru av maskinvareakselerasjon",
"edit-config-json": "Rediger config.json",
"override-user-agent": "Overstyr brukeragent",
"restart-on-config-changes": "Omstart ved oppsettsendringer",
"set-proxy": {
"label": "Sett mellomtjener",
"prompt": {
"label": "Skriv inn mellomtjeneradresse: (la stå tom for å skru av)",
"placeholder": "Eksempel: socks5://127.0.0.1:9999",
"title": "Sett mellomtjener"
}
},
"toggle-dev-tools": "Skru av/på utviklerverktøy"
}
},
"always-on-top": "Alltid på toppen",
"auto-update": "Auto-oppdatering",
"hide-menu": { "hide-menu": {
"dialog": {
"message": "Menyen vil bli skjult ved neste programstart. Bruk [Alt] for å vise den, eller gravistegn [`] hvis du bruker menyen i programmet).",
"title": "Skjuler meny"
},
"label": "Skjul meny" "label": "Skjul meny"
}, },
"language": {
"dialog": {
"message": "Nytt språk vises når programmet startes på ny",
"title": "Språk endret"
},
"label": "Språk",
"submenu": {
"to-help-translate": "Klikk her for å bistå oversettelsen"
}
},
"resume-on-start": "Gjenoppta siste spor ved programstart",
"single-instance-lock": "Sperr én instans",
"start-at-login": "Start ved innlogging",
"starting-page": {
"label": "Startside",
"unset": "Opphev"
},
"tray": { "tray": {
"label": "Systemkurv", "label": "Systemkurv",
"submenu": { "submenu": {
"disabled": "Avskrudd" "disabled": "Avskrudd",
"play-pause-on-click": "Spill av/pause ved klikk"
} }
}, },
"visual-tweaks": { "visual-tweaks": {
"label": "Visuelle tilpasninger",
"submenu": { "submenu": {
"like-buttons": {
"default": "Forvalg",
"force-show": "Tving visning",
"hide": "Skjul",
"label": "Begunstningsknapper"
},
"remove-upgrade-button": "Fjern oppgraderingsknapp",
"theme": { "theme": {
"label": "Drakt", "label": "Drakt",
"submenu": { "submenu": {
@ -32,8 +167,28 @@
} }
}, },
"plugins": { "plugins": {
"enabled": "Påskrudd",
"label": "Programtillegg" "label": "Programtillegg"
},
"view": {
"label": "Vis",
"submenu": {
"force-reload": "Tving gjeninnlasting",
"reload": "Gjeninnlast",
"reset-zoom": "Faktisk størrelse",
"toggle-fullscreen": "Veksle fullskjermsvisning",
"zoom-in": "Forstørr",
"zoom-out": "Forminsk"
}
} }
},
"tray": {
"next": "Neste",
"play-pause": "Spill av/pause",
"previous": "Forrige",
"quit": "Avslutt",
"restart": "Programomstart",
"show": "Vis vindu"
} }
}, },
"plugins": { "plugins": {
@ -49,6 +204,7 @@
"name": "Albumsfargedrakt" "name": "Albumsfargedrakt"
}, },
"ambient-mode": { "ambient-mode": {
"description": "Ifører lyseffekt ved å hente myke farger fra videoen inn i skjermens bakgrunn.",
"menu": { "menu": {
"blur-amount": { "blur-amount": {
"label": "Tilsløringsmengde", "label": "Tilsløringsmengde",
@ -56,6 +212,12 @@
"pixels": "{{blurAmount}} piksler" "pixels": "{{blurAmount}} piksler"
} }
}, },
"buffer": {
"label": "Mellomlager",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": { "opacity": {
"label": "Dekkevne", "label": "Dekkevne",
"submenu": { "submenu": {
@ -73,10 +235,38 @@
"submenu": { "submenu": {
"percent": "{{size}}%" "percent": "{{size}}%"
} }
},
"smoothness-transition": {
"label": "Mykhetsovergang",
"submenu": {
"during": "I løpet av {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Ved bruk av fullskjerm"
} }
} },
"name": "Omgivelsesmodus"
},
"audio-compressor": {
"description": "Anvend kompresjon for lyd (senker lydstyrken for de kraftigste delene av signalet og øker nivået i de svakeste)",
"name": "Lydkompressor"
},
"blur-nav-bar": {
"description": "Gjør navigeringsbjelken gjennomsiktig og tilslørt",
"name": "Tilslør navigasjonsfelt"
},
"bypass-age-restrictions": {
"description": "Omgå YouTube sin aldersgrenser",
"name": "Omgå aldersgrense"
}, },
"captions-selector": { "captions-selector": {
"description": "Undertekstverktøy for lydspor i YouTube Music",
"menu": {
"autoload": "Auto-velg sist brukte undertekst",
"disable-captions": "Ingen undertekst som forvalg"
},
"name": "Undertekstvelger",
"prompt": { "prompt": {
"selector": { "selector": {
"label": "Nåværende tekstingsspråk: {{language}}", "label": "Nåværende tekstingsspråk: {{language}}",
@ -88,27 +278,63 @@
"title": "Åpne undertekstvelger" "title": "Åpne undertekstvelger"
} }
}, },
"compact-sidebar": {
"description": "Alltid sett sidefeltet i kompakt modus",
"name": "Kompakt sidefelt"
},
"crossfade": { "crossfade": {
"description": "Overgang mellom spor",
"menu": { "menu": {
"advanced": "Avansert" "advanced": "Avansert"
}, },
"name": "Overgang [beta]",
"prompt": { "prompt": {
"options": { "options": {
"multi-input": { "multi-input": {
"fade-in-duration": "Inntoningsvarighet (ms)",
"fade-out-duration": "Uttoningsvarighet (ms)",
"fade-scaling": { "fade-scaling": {
"label": "Overgangsskalering",
"linear": "Lineær", "linear": "Lineær",
"logarithmic": "Logaritmisk" "logarithmic": "Logaritmisk"
} },
} "seconds-before-end": "Overgang antall sekunder før slutt"
},
"title": "Overgangsalternativer"
} }
} }
}, },
"disable-autoplay": { "disable-autoplay": {
"description": "Merkerer sporstart i «pauset» modus",
"menu": {
"apply-once": "Har kun innvirkning ved oppstart"
},
"name": "Skru av autospilling" "name": "Skru av autospilling"
}, },
"discord": { "discord": {
"backend": { "backend": {
"already-connected": "Forsøkte å koble til med aktiv tilkobling",
"connected": "Tilkoblet Discord",
"disconnected": "Frakoblet fra Discord" "disconnected": "Frakoblet fra Discord"
},
"description": "Vis venne dine hva du lytter til med rik tilstedeværelse",
"menu": {
"auto-reconnect": "Automatisk retilkobling",
"clear-activity": "Tøm aktivitet",
"clear-activity-after-timeout": "Tøm aktivitet etter tidsavbrudd",
"connected": "Tilkoblet",
"disconnected": "Frakoblet",
"hide-duration-left": "Skjul gjenværende tid",
"hide-github-button": "Skjul GitHub-lenkeknapp",
"play-on-youtube-music": "Spill på YouTube Music",
"set-inactivity-timeout": "Sett tid før tidsavbrudd"
},
"name": "Rik tilstedeværelse for Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Skriv inn antall sekunder for inaktivitetstidsavbrudd:",
"title": "Sett inaktivitetstidsavbrudd"
}
} }
}, },
"downloader": { "downloader": {
@ -117,12 +343,16 @@
"error": { "error": {
"buttons": { "buttons": {
"ok": "OK" "ok": "OK"
} },
"message": "Nedlasting mislyktes …",
"title": "Feil i nedlastning."
}, },
"start-download-playlist": { "start-download-playlist": {
"buttons": { "buttons": {
"ok": "OK" "ok": "OK"
}, },
"detail": "({{playlistSize}} spor)",
"message": "Laster ned {{playlistTitle}}-spillelisten …",
"title": "Nedlasting startet" "title": "Nedlasting startet"
} }
}, },
@ -130,42 +360,147 @@
"conversion-progress": "Konvertering: {{percent}}%", "conversion-progress": "Konvertering: {{percent}}%",
"converting": "Konverterer …", "converting": "Konverterer …",
"done": "Ferdig: {{filePath}}", "done": "Ferdig: {{filePath}}",
"download-info": "Laster ned {{artist}} — {{title}} [{{videoId}} …",
"download-progress": "Nedlastet: {{percent}}%",
"downloading": "Laster ned …", "downloading": "Laster ned …",
"downloading-counter": "Laster ned {{current}}/{{total}} …",
"downloading-playlist": "Laster ned {{playlistTitle}}-spillelisten — {{playlistSize}} spor ({{playlistId}})",
"error-while-downloading": "Kunne ikke laste ned «{{author}} — {{title}}»: {{error}}",
"folder-already-exists": "{{playlistFolder}}-mappen finnes allerede",
"getting-playlist-info": "Henter spillelisteinfo …",
"loading": "Laster inn …", "loading": "Laster inn …",
"playlist-has-only-one-song": "Spillelisten har kun ett element. Laster ned direkte.",
"playlist-id-not-found": "Fant ingen spilleliste-ID",
"playlist-is-empty": "Tom spilleliste", "playlist-is-empty": "Tom spilleliste",
"playlist-is-mix-or-private": "Kunne ikke hente spillelisteinfo. Forsikre deg om at den ikke er privat eller «Mikset for deg».\n\n{{error}}",
"preparing-file": "Forbereder fil …", "preparing-file": "Forbereder fil …",
"saving": "Lagrer …" "saving": "Lagrer …",
"trying-to-get-playlist-id": "Prøver å hente spilleliste-ID: {{playlistId}}",
"video-id-not-found": "Fant ikke videoen",
"writing-id3": "Skriver ID3-tagger …"
} }
}, },
"description": "Laster ned MP3/kildelyd direkte fra grensesnittet",
"menu": { "menu": {
"choose-download-folder": "Velg nedlastningsmappe", "choose-download-folder": "Velg nedlastningsmappe",
"download-playlist": "Last ned spilleliste",
"presets": "Forhåndsinnstillinger", "presets": "Forhåndsinnstillinger",
"skip-existing": "Hopp over eksisterende filer" "skip-existing": "Hopp over eksisterende filer"
}, },
"name": "Nedlaster", "name": "Nedlaster",
"renderer": {
"can-not-update-progress": "Kan ikke oppdatere framdrift"
},
"templates": { "templates": {
"button": "Last ned" "button": "Last ned"
} }
}, },
"exponential-volume": {
"description": "Gjør lydstyrkekontrollen eksponentiell, slik at det er enklere velge lavere lydstyrker.",
"name": "Eksponentiell lydstyrke"
},
"in-app-menu": {
"description": "Gir menybjelkene stilig, mørk, eller albumfarget utseende",
"menu": {
"hide-dom-window-controls": "Skjul DOM-vinduskontroller"
},
"name": "Meny i programmet"
},
"last-fm": { "last-fm": {
"description": "Legg til lyttestatistikkstøtte for Last.fm",
"name": "Last.fm" "name": "Last.fm"
}, },
"lumiastream": {
"description": "Legger til Lumia Stream-støtte",
"name": "Lumia Stream [beta]"
},
"lyrics-genius": {
"description": "Gir sangtekststøtte for de fleste spor",
"menu": {
"romanized-lyrics": "Romaniserte sangtekster"
},
"name": "Sangtekster fra Genius",
"renderer": {
"fetched-lyrics": "Henter sangtekster for Genius"
}
},
"navigation": {
"description": "Direkte integrering av neste/tilbake-navigasjonspilene i grensesnittet, som din favorittnettleser",
"name": "Navigasjon"
},
"no-google-login": {
"description": "Fjern Google-innloggingsknapper og lenker fra grensesnittet",
"name": "Ingen Google-innlogging"
},
"notifications": { "notifications": {
"description": "Vis en merknad når et spor starter avspilling (interaktive merknader er tilgjengelig på Windows)",
"menu": {
"interactive": "Interaktive merknader",
"interactive-settings": {
"label": "Interaktive innstillinger",
"submenu": {
"hide-button-text": "Skjul knappetekst",
"refresh-on-play-pause": "Gjenoppfrisk ved avspilling/pause",
"tray-controls": "Åpne/lukk med klikk i systemkurven"
}
},
"priority": "Merknadsprioritet",
"unpause-notification": "Vis merknad ved oppheving av pause"
},
"name": "Merknader" "name": "Merknader"
}, },
"picture-in-picture": { "picture-in-picture": {
"description": "Tillater å bytte programmet til bilde-i-bilde modus",
"menu": { "menu": {
"save-window-position": "Lagre vindusposisjon" "always-on-top": "Alltid på toppen",
"hotkey": {
"label": "Hurtigtast",
"prompt": {
"keybind-options": {
"hotkey": "Hurtigtast"
},
"label": "Velg en hurtigtast for veksling av bilde-i-bilde",
"title": "Hurtigtast for bilde-i-bilde"
}
},
"save-window-position": "Lagre vindusposisjon",
"save-window-size": "Lagre vindusstørrelse",
"use-native-pip": "Bruk nettleserens innebygde bilde-i-bilde"
},
"name": "Bilde-i-bilde",
"templates": {
"button": "Bilde-i-bilde"
} }
}, },
"playback-speed": { "playback-speed": {
"description": "Legger til glidebryter som kontrollerer avspillingshastighet",
"name": "Avspillingshastighet", "name": "Avspillingshastighet",
"templates": { "templates": {
"button": "Hastighet" "button": "Hastighet"
} }
}, },
"precise-volume": { "precise-volume": {
"name": "Presis lydstyrkejustering" "description": "Kontroller lydstyrken presist ved bruk av musehjul/hurtigtaster, med egendefinert skjermoverlag og tilpassbare steg",
"menu": {
"arrows-shortcuts": "Kontroller for lokale piltaster",
"custom-volume-steps": "Sett egendefinerte lydstyrkesteg",
"global-shortcuts": "Hurtigtaster i hele programmet"
},
"name": "Presis lydstyrkejustering",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Senk lydstyrke",
"increase": "Øk lydstyrke"
},
"label": "Velg tastetilknytninger for lydstyrkejusteringskontroller i hele programmet",
"title": "Tastetilknytninger for lydstyrkejusteringskontroller i hele programmet"
},
"volume-steps": {
"label": "Velg steg for økning/senking av lydstyrke",
"title": "Lydstyrkesteg"
}
}
}, },
"quality-changer": { "quality-changer": {
"backend": { "backend": {
@ -176,30 +511,85 @@
"title": "Velg videokvalitet" "title": "Velg videokvalitet"
} }
} }
},
"description": "Tillat endring av videokvalitet med en knapp i videooverlaget",
"name": "Vindukvalitetsvelger"
},
"shortcuts": {
"description": "Tillater bruk av hurtigtaster for hele programmet til avspilling (spill/pause/neste/forrige) + skru av media-videooverlag ved å overstyre mediataster + skru på Ctrl+CMD+F for å søke, pluss å egge til MPRIS støtte på linux for mediataster pluss egendefinerte hurtigtaster for avanserte brukere",
"menu": {
"override-media-keys": "Overstyr mediataster",
"set-keybinds": "Sett kontroller for spor i hele programmet"
},
"name": "Snarveier (og MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Neste",
"play-pause": "Spill av/pause",
"previous": "Forrige"
},
"label": "Velg kontroller for spor i hele programmet:",
"title": "Tastaturtilknytninger i hele programmet"
}
} }
}, },
"skip-disliked-songs": {
"description": "Hopper over mislikte spor",
"name": "Hopp over mislikte spor"
},
"skip-silences": { "skip-silences": {
"description": "Hopp over stille deler av spor",
"name": "Hopp over pauser" "name": "Hopp over pauser"
}, },
"sponsorblock": { "sponsorblock": {
"description": "Hopper over ikke-musikalske deler, som intro/sluttsats, eller deler av musikkvideoer der ingen musikk spilles",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"taskbar-mediacontrol": {
"description": "Kontroller avspilling fra din Windows-oppgavelinje",
"name": "Oppgavelinje-mediakontroll"
},
"touchbar": {
"description": "Legger til et pekefelt-miniprogram for macOS-brukere",
"name": "Pekefelt"
},
"tuna-obs": {
"description": "Integrasjon med Tuna-programtillegget i OBS",
"name": "OBS Tuna"
},
"video-toggle": { "video-toggle": {
"description": "Leger til en knapp for å bytte mellom video/spormodus. Kan også alternativt fjerne hele videofanen.",
"menu": { "menu": {
"align": { "align": {
"label": "Justering",
"submenu": { "submenu": {
"left": "Venstre", "left": "Venstre",
"middle": "Midten", "middle": "Midten",
"right": "Høyre" "right": "Høyre"
} }
}, },
"force-hide": "Påtving fjerning av videofane",
"mode": { "mode": {
"label": "Modus" "label": "Modus",
"submenu": {
"custom": "Egendefinert veksling",
"disabled": "Avskrudd",
"native": "Innebygd veksling"
}
} }
}, },
"name": "Videoveksling",
"templates": { "templates": {
"button": "Spor" "button": "Spor"
} }
},
"visualizer": {
"description": "Legger til en visualisator i avspilleren",
"menu": {
"visualizer-type": "Visualisatortype"
},
"name": "Visualisator"
} }
} }
} }

598
src/i18n/resources/pl.json Normal file
View File

@ -0,0 +1,598 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Błąd uruchamiania wtyczki {{pluginName}}::{{contextName}}",
"executed-at-ms": "Wtyczka {{pluginName}}::{{contextName}} uruchomiona w {{ms}}ms",
"initialize-failed": "Błąd inicjalizacji wtyczki \"{{pluginName}}\"",
"load-all": "Ładowanie wszystkich wtyczek",
"load-failed": "Błąd w ładowaniu wtyczki \"{{pluginName}}\"",
"loaded": "Wtyczka \"{{pluginName}}\" załadowana",
"unload-failed": "Błąd w odłączaniu wtyczki \"{{pluginName}}\"",
"unloaded": "Wtyczka \"{{pluginName}}\" odłączona"
}
}
},
"language": {
"code": "pl",
"local-name": "Polski",
"name": "Polish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Ukończono ładowanie. Narzędzia deweloperskie otwarte"
},
"i18n": {
"loaded": "i18n załadowane"
},
"second-instance": {
"receive-command": "Otrzymano komendę przez protokół: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Plik CSS \"{{cssFile}}\" nie istnieje, ignoruję"
},
"unresponsive": {
"details": "Błąd! Brak odpowiedzi:\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Czyszczenie pamięci podręcznej aplikacji"
},
"window": {
"tried-to-render-offscreen": "Okno próbuje się renderować poza ekranem, rozmiar okna={{windowSize}}, rozmiar monitora={{displaySize}}, pozycja={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu jest ukryte, użyj przycisku [Alt] aby je pokazać (lub [Escape], jeśli używasz menu w aplikacji)",
"message": "Ukrywanie menu jest włączone",
"title": "Ukrywanie Menu Włączone"
},
"need-to-restart": {
"buttons": {
"later": "Później",
"restart-now": "Uruchom ponownie teraz"
},
"detail": "Wtyczka \"{{pluginName}}\" potrzebuje ponownego uruchomienia aby była aktywna",
"message": "\"{{pluginName}}\" potrzebuje ponownego uruchomienia",
"title": "Ponowne uruchomienie potrzebne"
},
"unresponsive": {
"buttons": {
"quit": "Wyjdź",
"relaunch": "Ponownie uruchom",
"wait": "Czekać"
},
"detail": "Przepraszamy za niedogodność! Proszę wybierz co zrobić:",
"message": "Aplikacja nie reaguje",
"title": "Okno nie reaguje"
},
"update-available": {
"buttons": {
"disable": "Wyłącz aktualizacje",
"download": "Pobierz",
"ok": "OK"
},
"detail": "Nowa wersja jest dostępna i możesz ją pobrać tutaj {{downloadLink}}",
"message": "Nowa wersja jest dostępna",
"title": "Aktualizacja jest dostępna"
}
},
"menu": {
"about": "O aplikacji",
"navigation": {
"label": "Nawigacja",
"submenu": {
"copy-current-url": "Skopiuj aktualny adres URL",
"go-back": "Wróć",
"go-forward": "Do przodu",
"quit": "Wyjście",
"restart": "Uruchom ponownie aplikację"
}
},
"options": {
"label": "Opcje",
"submenu": {
"advanced-options": {
"label": "Opcje zaawansowane",
"submenu": {
"auto-reset-app-cache": "Wyczyść pamięć podręczną aplikacji przy jej uruchomieniu",
"disable-hardware-acceleration": "Wyłącz przyspieszanie sprzętowe",
"edit-config-json": "Edytuj config.json",
"override-user-agent": "Zastąp klienta użytkownika (User-Agent)",
"restart-on-config-changes": "Uruchom ponownie po zmianie konfiguracji",
"set-proxy": {
"label": "Ustaw proxy",
"prompt": {
"label": "Podaj adres Proxy: (zostaw pusty aby wyłączyć)",
"placeholder": "Przykład: SOCKS5://127.0.0.1:9999",
"title": "Ustaw proxy"
}
},
"toggle-dev-tools": "Przełącz narzędzia deweloperskie"
}
},
"always-on-top": "Zawsze na wierzchu",
"auto-update": "Automatyczne aktualizacje",
"hide-menu": {
"dialog": {
"message": "Menu będzie ukryte po następnym uruchomieniu, użyj przycisku [Alt] aby je pokazać (lub [`], jeśli używasz menu w aplikacji)",
"title": "Ukrywanie menu włączone"
},
"label": "Ukryj menu"
},
"language": {
"dialog": {
"message": "Język będzie zmieniony po ponownym uruchomieniu",
"title": "Język zmieniony"
},
"label": "Język",
"submenu": {
"to-help-translate": "Chcesz pomóc w tłumaczeniu? Kliknij tutaj"
}
},
"resume-on-start": "Wznów ostatni utwór po uruchomieniu aplikacji",
"single-instance-lock": "Zablokuj do jednej instancji aplikacji",
"start-at-login": "Uruchom po zalogowaniu",
"starting-page": {
"label": "Strona startowa",
"unset": "Pusty"
},
"tray": {
"label": "Ikona w zasobniku",
"submenu": {
"disabled": "Wyłączone",
"enabled-and-hide-app": "Włącz i ukryj aplikację",
"enabled-and-show-app": "Włącz i pokaż aplikację",
"play-pause-on-click": "Odtwórz/Wstrzymaj po kliknięciu"
}
},
"visual-tweaks": {
"label": "Poprawki wizualne",
"submenu": {
"like-buttons": {
"default": "Domyślne",
"force-show": "Wymuś pokazywanie",
"hide": "Ukryj",
"label": "Przyciski polubienia"
},
"remove-upgrade-button": "Usuń przycisk subskrypcji premium",
"theme": {
"label": "Motyw",
"submenu": {
"import-css-file": "Importuj własny plik CSS",
"no-theme": "Bez motywu"
}
}
}
}
}
},
"plugins": {
"enabled": "Włączone",
"label": "Wtyczki"
},
"view": {
"label": "Widok",
"submenu": {
"force-reload": "Wymuś ponowne ładowanie",
"reload": "Ponowne ładowanie",
"reset-zoom": "Rozmiar rzeczywisty",
"toggle-fullscreen": "Przełącz pełny ekran",
"zoom-in": "Powiększ",
"zoom-out": "Pomniejsz"
}
}
},
"tray": {
"next": "Następny",
"play-pause": "Odtwórz/Pauza",
"previous": "Poprzedni",
"quit": "Wyjdź",
"restart": "Uruchom ponownie aplikację",
"show": "Pokaż okno"
}
},
"plugins": {
"adblocker": {
"description": "Blokuj wszystkie reklamy i śledzenie",
"menu": {
"blocker": "Metoda przechwytywania"
},
"name": "Blokowanie reklam"
},
"album-color-theme": {
"description": "Stosuje dynamiczny motyw i efekty wizualne w oparciu o paletę kolorów albumu",
"name": "Motyw kolorów albumu"
},
"ambient-mode": {
"description": "Stosuje efekt świetlny, rzucając delikatne kolory z wideo na tło ekranu.",
"menu": {
"blur-amount": {
"label": "Ilość rozmycia",
"submenu": {
"pixels": "{{blurAmount}} pikseli"
}
},
"buffer": {
"label": "Bufor",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Nieprzezroczystość",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Jakość",
"submenu": {
"pixels": "{{quality}} pikseli"
}
},
"size": {
"label": "Rozmiar",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Płynność przejścia",
"submenu": {
"during": "W czasie {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Podczas pełnego ekranu"
}
},
"name": "Tryb otoczenia"
},
"audio-compressor": {
"description": "Zastosuj kompresję do dźwięku (obniża głośność najgłośniejszych części sygnału i zwiększa głośność najcichszych części)",
"name": "Kompresor dźwięku"
},
"blur-nav-bar": {
"description": "Sprawia, że pasek nawigacji jest przezroczysty i rozmazany",
"name": "Rozmycie paska nawigacji"
},
"bypass-age-restrictions": {
"description": "Pomija weryfikację wieku",
"name": "Omiń ograniczenia wiekowe"
},
"captions-selector": {
"description": "Selektor napisów dla ścieżek audio YouTube Music",
"menu": {
"autoload": "Automatycznie wybierz ostatnio używanych napisów",
"disable-captions": "Domyślnie, brak napisów"
},
"name": "Selektor napisów",
"prompt": {
"selector": {
"label": "Bieżący język napisów: {{language}}",
"none": "Brak",
"title": "Wybierz język napisów"
}
},
"templates": {
"title": "Otwórz selektor napisów"
}
},
"compact-sidebar": {
"description": "Zawsze ustawiaj pasek boczny w trybie kompaktowym",
"name": "Kompaktowy pasek boczny"
},
"crossfade": {
"description": "Przenikanie pomiędzy utworami",
"menu": {
"advanced": "Zaawansowane"
},
"name": "Przenikanie [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Czas wnikania (ms)",
"fade-out-duration": "Czas zanikania (ms)",
"fade-scaling": {
"label": "Skalowanie zanikania",
"linear": "Liniowe",
"logarithmic": "Logarytmiczne"
},
"seconds-before-end": "Przenikanie N sekund przed końcem"
},
"title": "Opcje przenikania"
}
}
},
"disable-autoplay": {
"description": "Wyłącza automatyczne odtwarzanie utworów",
"menu": {
"apply-once": "Tylko przy uruchomieniu aplikacji"
},
"name": "Wyłącz automatyczne odtwarzanie"
},
"discord": {
"backend": {
"already-connected": "Próbowano połączyć się przy aktywnym połączeniu",
"connected": "Połączono z Discordem",
"disconnected": "Odłączono od Discorda"
},
"description": "Pokaż znajomym, czego słuchasz dzięki Rich Presence",
"menu": {
"auto-reconnect": "Automatyczne wznawianie połączenia",
"clear-activity": "Wyczyść aktywność",
"clear-activity-after-timeout": "Wyczyść aktywność po czasie",
"connected": "Połączono",
"disconnected": "Odłączono",
"hide-duration-left": "Ukryj pozostały czas trwania",
"hide-github-button": "Ukryj przycisk do GitHub",
"play-on-youtube-music": "Odtwórz w YouTube Music",
"set-inactivity-timeout": "Ustaw limit czasu bezczynności"
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Podaj limit czasu bezczynności w sekundach:",
"title": "Ustaw limit czasu bezczynności"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Argh! Przepraszamy, pobieranie nie powiodło się…",
"title": "Błąd podczas pobierania!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} utworów)",
"message": "Pobieranie playlisty {{playlistTitle}}",
"title": "Pobieranie rozpoczęte"
}
},
"feedback": {
"conversion-progress": "Konwertowanie: {{percent}}%",
"converting": "Konwertowanie…",
"done": "Gotowe: {{filePath}}",
"download-info": "Pobieranie {{artist}} - {{title}} {{videoId}}",
"download-progress": "Pobieranie: {{percent}}%",
"downloading": "Pobieranie…",
"downloading-counter": "Pobieranie {{current}}/{{total}} …",
"downloading-playlist": "Pobieranie playlisty \"{{playlistTitle}}\" - {{playlistSize}} utworów ({{playlistId}})",
"error-while-downloading": "Błąd pobierania \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Folder {{playlistFolder}} już istnieje",
"getting-playlist-info": "Pobieram informacje o playliście…",
"loading": "Ładowanie…",
"playlist-has-only-one-song": "Playlista zawiera tylko jeden element, zostanie pobrany bezpośrednio",
"playlist-id-not-found": "Nie znaleziono ID playlisty",
"playlist-is-empty": "Playlista jest pusta",
"playlist-is-mix-or-private": "Podczas pobierania informacji o playliście wystąpił błąd: upewnij się, że nie jest to playlista prywatna ani playlista „Składanki dla Ciebie”.\n\n{{error}}",
"preparing-file": "Przygotowuję plik…",
"saving": "Zapisuję…",
"trying-to-get-playlist-id": "Próbuję uzyskać ID playlisty: {{playlistId}}",
"video-id-not-found": "Nie znaleziono filmu",
"writing-id3": "Zapisywanie tagów ID3…"
}
},
"description": "Pobiera MP3/ źródło audio bezpośrednio z interfejsu",
"menu": {
"choose-download-folder": "Wybierz folder pobierania",
"download-playlist": "Pobierz playlistę",
"presets": "Predefiniowane ustawienia",
"skip-existing": "Pomiń istniejące pliki"
},
"name": "Pobieranie",
"renderer": {
"can-not-update-progress": "Nie można zaktualizować postępu"
},
"templates": {
"button": "Pobierz"
}
},
"exponential-volume": {
"description": "Sprawia, że suwak głośności jest proporcjonalna, dzięki czemu łatwiej jest wybrać niższą głośność.",
"name": "Proporcjonalna głośność"
},
"in-app-menu": {
"description": "Nadaje paskom menu elegancki, ciemny lub albumowy wygląd",
"menu": {
"hide-dom-window-controls": "Ukryj kontrolki okna DOM"
},
"name": "Menu w aplikacji"
},
"last-fm": {
"description": "Dodanie obsługi scrobblingu dla Last.fm",
"name": "Last.fm"
},
"lumiastream": {
"description": "Dodaje obsługę Lumia Stream",
"name": "Lumia Stream [beta]"
},
"lyrics-genius": {
"description": "Dodaje obsługę tekstów dla większości piosenek",
"menu": {
"romanized-lyrics": "Teksty zromanizowane"
},
"name": "Tekst piosenek od Genius",
"renderer": {
"fetched-lyrics": "Tekst dostarczony przez Genius"
}
},
"navigation": {
"description": "Strzałki nawigacyjne Dalej/Wstecz zintegrowane bezpośrednio z interfejsem, tak jak w Twojej ulubionej przeglądarce",
"name": "Nawigacja"
},
"no-google-login": {
"description": "Usuń przyciski i linki logowania Google z interfejsu",
"name": "Usuń logowanie do Google"
},
"notifications": {
"description": "Wyświetl powiadomienie, gdy rozpocznie się odtwarzanie utworu (interaktywne powiadomienia są dostępne w systemie Windows)",
"menu": {
"interactive": "Interaktywne powiadomienia",
"interactive-settings": {
"label": "Interaktywne ustawienia",
"submenu": {
"hide-button-text": "Ukryj tekst przycisku",
"refresh-on-play-pause": "Odśwież podczas odtwarzania/pauzy",
"tray-controls": "Otwórz/zamknij po kliknięciu ikony na pasku zadań"
}
},
"priority": "Priorytet powiadomień",
"toast-style": "Styl powiadomień \"Toast\"",
"unpause-notification": "Pokaż powiadomienie po wznowieniu"
},
"name": "Powiadomienia"
},
"picture-in-picture": {
"description": "Umożliwia przełączenie aplikacji w tryb obrazu w obrazie",
"menu": {
"always-on-top": "Zawsze na wierzchu",
"hotkey": {
"label": "Klawisz skrótu",
"prompt": {
"keybind-options": {
"hotkey": "Klawisz skrótu"
},
"label": "Wybierz klawisz skrótu do przełączania trybu obrazu w obrazie",
"title": "Klawisz skrótu obrazu w obrazie"
}
},
"save-window-position": "Zapisz pozycję okna",
"save-window-size": "Zapisz rozmiar okna",
"use-native-pip": "Użyj natywnego PiP dla przeglądarki"
},
"name": "Obraz w obrazie",
"templates": {
"button": "Obraz w obrazie"
}
},
"playback-speed": {
"description": "Słuchaj szybko, słuchaj powoli! Dodaje suwak kontrolujący prędkość utworu",
"name": "Szybkość odtwarzania",
"templates": {
"button": "Szybkość"
}
},
"precise-volume": {
"description": "Precyzyjnie kontroluj głośność za pomocą kółka myszy/klawiszy skrótu, z niestandardowym interfejsem HUD i konfigurowalnymi krokami głośności",
"menu": {
"arrows-shortcuts": "Sterowanie za pomocą klawiszy strzałek",
"custom-volume-steps": "Ustaw niestandardowe kroki głośności",
"global-shortcuts": "Globalne skróty klawiszowe"
},
"name": "Precyzyjna głośność",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Zmniejsz głośność",
"increase": "Zwiększ głośność"
},
"label": "Wybierz globalne skróty klawiaturowe głośności:",
"title": "Globalne skróty klawiszowe głośności"
},
"volume-steps": {
"label": "Wybierz kroki zwiększania/zmniejszania głośności",
"title": "Kroki głośności"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Aktualna jakość: {{quality}}",
"message": "Wybierz jakość wideo:",
"title": "Wybierz jakość wideo"
}
}
},
"description": "Umożliwia zmianę jakości wideo za pomocą przycisku na nakładce wideo",
"name": "Zmieniacz jakości wideo"
},
"shortcuts": {
"description": "Umożliwia ustawienie globalnych skrótów klawiszowych do odtwarzania (odtwarzanie/pauza/następny/poprzedni) + wyłączanie OSD multimediów poprzez zastąpienie klawiszy multimediów, włączając kombinację klawiszy Ctrl/CMD + F w celu wyszukiwania, obsługę Linux MPRIS dla klawiszy multimediów oraz niestandardowe skróty klawiszowe dla zaawansowanych użytkowników.",
"menu": {
"override-media-keys": "Zastąp klawisze multimediów",
"set-keybinds": "Ustaw globalne sterowanie utworem"
},
"name": "Skróty klawiszowe (oraz MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Następny",
"play-pause": "Odtwarzanie / Pauza",
"previous": "Poprzedni"
},
"label": "Wybierz globalne skróty klawiszowe do sterowania utworami:",
"title": "Globalne skróty klawiszowe"
}
}
},
"skip-disliked-songs": {
"description": "Pomija nieulubione piosenki (zaznaczone łapką w dół)",
"name": "Pomijanie nieulubionych piosenek"
},
"skip-silences": {
"description": "Automatycznie pomijaj sekcje bez dźwięku w utworach",
"name": "Pomiń ciszę"
},
"sponsorblock": {
"description": "Automatycznie pomija fragmenty niebędące muzyką, takie jak wstęp/zakończenie lub fragmenty teledysków, w których utwór nie jest odtwarzany",
"name": "Pomiń nieistotne fragmenty"
},
"taskbar-mediacontrol": {
"description": "Steruj odtwarzaniem z paska zadań systemu Windows",
"name": "Kontroler odtwarzania z paska zadań"
},
"touchbar": {
"description": "Dodaje widżet do paska dotykowego dla użytkowników systemu macOS",
"name": "Pasek dotykowy"
},
"tuna-obs": {
"description": "Integracja z wtyczką OBS Tuna",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "Dodaje przycisk do przełączania między trybem wideo a piosenki. Może również opcjonalnie usunąć całą kartę wideo",
"menu": {
"align": {
"label": "Wyrównanie",
"submenu": {
"left": "Lewo",
"middle": "Środek",
"right": "Prawo"
}
},
"force-hide": "Wymuś usunięcie zakładki wideo",
"mode": {
"label": "Tryb",
"submenu": {
"custom": "Niestandardowy przełącznik",
"disabled": "Wyłączony",
"native": "Natywny przełącznik"
}
}
},
"name": "Przełącznik wideo",
"templates": {
"button": "Utwór"
}
},
"visualizer": {
"description": "Dodaje wizualizator do odtwarzacza",
"menu": {
"visualizer-type": "Typ wizualizatora"
},
"name": "Wizualizator"
}
}
}

598
src/i18n/resources/pt.json Normal file
View File

@ -0,0 +1,598 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Falha ao executar o plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} executado com {{ms}}ms",
"initialize-failed": "Falha ao inicializar o plugin \"{{pluginName}}\"",
"load-all": "Carregando todos os plugins",
"load-failed": "Falha ao carregar o plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" carregado",
"unload-failed": "Falha ao descarregar o plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" descarregado"
}
}
},
"language": {
"code": "pt",
"local-name": "Português",
"name": "Portuguese"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Carregamento finalizado. DevTools aberto"
},
"i18n": {
"loaded": "i18n carregado"
},
"second-instance": {
"receive-command": "Comando recebido através do protocolo: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Arquivo CSS \"{{cssFile}}\" não existe, ignorando"
},
"unresponsive": {
"details": "Erro sem resposta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Limpando o cache do aplicativo"
},
"window": {
"tried-to-render-offscreen": "Janela tentou desenhar fora do ecrã, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "O menu está oculto, use a tecla 'Alt' para mostra-lo (ou 'Esc' se estiver usando o menu do aplicativo)",
"message": "Ocultar menu está ativado",
"title": "Ocultar menu ativado"
},
"need-to-restart": {
"buttons": {
"later": "Depois",
"restart-now": "Reiniciar agora"
},
"detail": "O plugin {{pluginName}} precisa ser reiniciado para ter efeito",
"message": "\"{{pluginName}}\" precisa ser reiniciado",
"title": "É necessário reiniciar"
},
"unresponsive": {
"buttons": {
"quit": "Sair",
"relaunch": "Reiniciar",
"wait": "Espere"
},
"detail": "Lamentamos o inconveniente! Por favor escolha o que fazer:",
"message": "A aplicação não está respondendo",
"title": "A janela não está respondendo"
},
"update-available": {
"buttons": {
"disable": "Desabilitar atualizações",
"download": "Baixar",
"ok": "Ok"
},
"detail": "Uma nova versão está disponível e pode ser baixada em {{downloadLink}}",
"message": "Uma nova versão está disponível",
"title": "Atualização disponível"
}
},
"menu": {
"about": "Sobre",
"navigation": {
"label": "Navegação",
"submenu": {
"copy-current-url": "Copiar URL atual",
"go-back": "Voltar",
"go-forward": "Avançar",
"quit": "Saída",
"restart": "Reiniciar aplicativo"
}
},
"options": {
"label": "Opções",
"submenu": {
"advanced-options": {
"label": "Opções avançadas",
"submenu": {
"auto-reset-app-cache": "Reiniciar cache do aplicativo quando o aplicativo abrir",
"disable-hardware-acceleration": "Desabilitar aceleração por hardware",
"edit-config-json": "Editar config.json",
"override-user-agent": "Substituir User-Agent",
"restart-on-config-changes": "Reinicie as alterações de configurações feitas",
"set-proxy": {
"label": "Definir proxy",
"prompt": {
"label": "Inserir Endereço do Proxy: (deixe em branco para desativar)",
"placeholder": "Exemplo: SOCKS5://127.0.0.1:9999",
"title": "Definir proxy"
}
},
"toggle-dev-tools": "Ativar DevTools"
}
},
"always-on-top": "Sempre no topo",
"auto-update": "Atualização automática",
"hide-menu": {
"dialog": {
"message": "O menu será ocultado na próxima inicialização, use [Alt] para mostrá-lo (ou acento grave [`] se estiver usando o menu interno do aplicativo)",
"title": "Ocultar Menu Ativado"
},
"label": "Ocultar Menu"
},
"language": {
"dialog": {
"message": "Idioma será alterado após reiniciar",
"title": "Idioma Alterado"
},
"label": "Idioma",
"submenu": {
"to-help-translate": "Quer ajudar na tradução? Clique aqui"
}
},
"resume-on-start": "Continuar última música ao iniciar o aplicativo",
"single-instance-lock": "Trava de instância única",
"start-at-login": "Iniciar no login",
"starting-page": {
"label": "Página inicial",
"unset": "Indefinido"
},
"tray": {
"label": "Bandeja",
"submenu": {
"disabled": "Desabilitado",
"enabled-and-hide-app": "Ativado e esconder aplicativo",
"enabled-and-show-app": "Ativado e mostrar aplicativo",
"play-pause-on-click": "Play/Pausa ao clicar"
}
},
"visual-tweaks": {
"label": "Tweaks Visuais",
"submenu": {
"like-buttons": {
"default": "Padrão",
"force-show": "Forçar mostrar",
"hide": "Esconder",
"label": "Botões de curtida"
},
"remove-upgrade-button": "Remover botão upgrade",
"theme": {
"label": "Tema",
"submenu": {
"import-css-file": "Importar arquivo CSS personalizado",
"no-theme": "Sem tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Ativado",
"label": "Plugins"
},
"view": {
"label": "Ver",
"submenu": {
"force-reload": "Forçar Recarregamento",
"reload": "Recarregar",
"reset-zoom": "Tamanho Atual",
"toggle-fullscreen": "Ativar Tela Cheia",
"zoom-in": "Zoom Dentro",
"zoom-out": "Zoom Fora"
}
}
},
"tray": {
"next": "Próximo",
"play-pause": "Play/Pausa",
"previous": "Anterior",
"quit": "Sair",
"restart": "Reiniciar aplicativo",
"show": "Mostrar janela"
}
},
"plugins": {
"adblocker": {
"description": "Bloquear todos os anúncios e rastreamento automaticamente",
"menu": {
"blocker": "Bloqueador"
},
"name": "Bloqueador de anúncios"
},
"album-color-theme": {
"description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum",
"name": "Tema de cores do álbum"
},
"ambient-mode": {
"description": "Aplica um efeito de iluminação lançando cores suaves do vídeo no fundo da tela.",
"menu": {
"blur-amount": {
"label": "Quantidade de desfoque",
"submenu": {
"pixels": "{{blurAmount}} píxeis"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacidade",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualidade",
"submenu": {
"pixels": "{{quality}} píxeis"
}
},
"size": {
"label": "Tamanho",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transição suave",
"submenu": {
"during": "Durante {{interpolationTime}}s"
}
},
"use-fullscreen": {
"label": "Tela cheia"
}
},
"name": "Modo Ambiente"
},
"audio-compressor": {
"description": "Aplicar compressão ao áudio (diminui o volume das partes mais altas do sinal e aumenta o volume das partes mais suaves)",
"name": "Compressor de áudio"
},
"blur-nav-bar": {
"description": "Torna a barra de navegação transparente e desfocada",
"name": "Barra de navegação desfocada"
},
"bypass-age-restrictions": {
"description": "Ignorar a verificação de idade do YouTube",
"name": "Ignorar restrições de idade"
},
"captions-selector": {
"description": "Seletor de legenda para faixas de áudio do YouTube Music",
"menu": {
"autoload": "Selecionar automaticamente a última legenda usada",
"disable-captions": "Sem legendas por padrão"
},
"name": "Seletor de legendas",
"prompt": {
"selector": {
"label": "Idioma da legenda atual: {{language}}",
"none": "Nenhuma",
"title": "Selecione o idioma da legenda"
}
},
"templates": {
"title": "Seletor de legendas aberto"
}
},
"compact-sidebar": {
"description": "Definir sempre a barra lateral no modo compacto",
"name": "Barra lateral compacta"
},
"crossfade": {
"description": "Transição entre músicas",
"menu": {
"advanced": "Avançado"
},
"name": "Transição entre músicas [beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Duração da transição no início (ms)",
"fade-out-duration": "Duração da transição no final (ms)",
"fade-scaling": {
"label": "Escala da transição",
"linear": "Linear",
"logarithmic": "Logarítmica"
},
"seconds-before-end": "Realizar transição N segundos antes do final"
},
"title": "Opções de transição"
}
}
},
"disable-autoplay": {
"description": "Faz a música começar no modo \"pausado\"",
"menu": {
"apply-once": "Aplicar apenas na inicialização"
},
"name": "Desativar reprodução automática"
},
"discord": {
"backend": {
"already-connected": "Tentativa de conexão com conexão já ativa",
"connected": "Conectado ao Discord",
"disconnected": "Desconectado do Discord"
},
"description": "Mostre aos seus amigos o que você ouve com Rich Presence",
"menu": {
"auto-reconnect": "Reconexão automática",
"clear-activity": "Limpar atividade",
"clear-activity-after-timeout": "Limpar atividade após o tempo limite",
"connected": "Conectado",
"disconnected": "Desconectado",
"hide-duration-left": "Ocultar duração restante",
"hide-github-button": "Ocultar botão de link do GitHub",
"play-on-youtube-music": "Reproduzir no YouTube Music",
"set-inactivity-timeout": "Definir tempo limite de inatividade"
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Insira o tempo limite de inatividade em segundos:",
"title": "Definir tempo limite de inatividade"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Poxa! Desculpe, o download falhou…",
"title": "Erro no download!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} músicas)",
"message": "Baixando lista de reprodução {{playlistTitle}}",
"title": "Download iniciado"
}
},
"feedback": {
"conversion-progress": "Conversão: {{percent}}%",
"converting": "Convertendo…",
"done": "Finalizado: {{filePath}}",
"download-info": "Baixando {{artist}} - {{title}} {{videoId}}",
"download-progress": "Baixando: {{percent}}%",
"downloading": "Baixando…",
"downloading-counter": "Baixando {{current}}/{{total}}…",
"downloading-playlist": "Baixando lista de reprodução \"{{playlistTitle}}\" - {{playlistSize}} músicas ({{playlistId}})",
"error-while-downloading": "Erro ao baixar \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "A pasta {{playlistFolder}} já existe",
"getting-playlist-info": "Obtendo informações da lista de reprodução…",
"loading": "Carregando…",
"playlist-has-only-one-song": "A lista de reprodução possui apenas um item, baixando-o diretamente",
"playlist-id-not-found": "ID da lista de reprodução não encontrado",
"playlist-is-empty": "Lista de reprodução vazia",
"playlist-is-mix-or-private": "Erro ao obter informações da lista de reprodução: tenha certeza que ela não está privada ou não seja \"Mixtapes criadas para você\"\n\n{{error}}",
"preparing-file": "Preparando arquivos…",
"saving": "Salvando…",
"trying-to-get-playlist-id": "Tentando pegar ID da lista de reprodução: {{playlistId}}",
"video-id-not-found": "Vídeo não encontrado",
"writing-id3": "Escrevendo tags ID3…"
}
},
"description": "Baixa MP3 / fonte de áudio diretamente da interface",
"menu": {
"choose-download-folder": "Escolha a pasta de download",
"download-playlist": "Baixar lista de reprodução",
"presets": "Predefinições",
"skip-existing": "Ignorar arquivos existentes"
},
"name": "Downloader",
"renderer": {
"can-not-update-progress": "Não é possível atualizar o progresso"
},
"templates": {
"button": "Baixar"
}
},
"exponential-volume": {
"description": "Torna o controle deslizante de volume exponencial, facilitando a seleção de volumes mais baixos.",
"name": "Volume Exponencial"
},
"in-app-menu": {
"description": "Dá às barras de menu uma aparência sofisticada, escura ou com a cor do álbum",
"menu": {
"hide-dom-window-controls": "Ocultar controles da janela DOM"
},
"name": "Menu no aplicativo"
},
"last-fm": {
"description": "Adiciona suporte de scrobbling para Last.fm",
"name": "Last.fm"
},
"lumiastream": {
"description": "Adiciona suporte Lumia Stream",
"name": "Lumia Stream [beta]"
},
"lyrics-genius": {
"description": "Adiciona suporte a letras para a maioria das músicas",
"menu": {
"romanized-lyrics": "Letras romanizadas"
},
"name": "Letras Genius",
"renderer": {
"fetched-lyrics": "Buscar letras no Genius"
}
},
"navigation": {
"description": "Setas de navegação Próximo/Voltar integradas diretamente na interface, como no seu navegador favorito",
"name": "Navegação"
},
"no-google-login": {
"description": "Remove os botões e links de login do Google da interface",
"name": "Sem login do Google"
},
"notifications": {
"description": "Exibir uma notificação quando uma música começar a tocar (notificações interativas estão disponíveis no Windows)",
"menu": {
"interactive": "Notificações interativas",
"interactive-settings": {
"label": "Configurações interativas",
"submenu": {
"hide-button-text": "Ocultar texto do botão",
"refresh-on-play-pause": "Atualizar ao reproduzir/pausar",
"tray-controls": "Abrir/Fechar no clique da bandeja"
}
},
"priority": "Prioridade de notificação",
"toast-style": "Estilo de alerta",
"unpause-notification": "Mostrar notificação ao despausar"
},
"name": "Notificações"
},
"picture-in-picture": {
"description": "Permite mudar o aplicativo para o modo picture-in-picture",
"menu": {
"always-on-top": "Sempre no topo",
"hotkey": {
"label": "Tecla de atalho",
"prompt": {
"keybind-options": {
"hotkey": "Tecla de atalho"
},
"label": "Escolha uma tecla de atalho para alternar o picture-in-picture",
"title": "Tecla de atalho picture-in-picture"
}
},
"save-window-position": "Salvar posição da janela",
"save-window-size": "Salvar tamanho da janela",
"use-native-pip": "Use PiP nativo do navegador"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Ouça rápido, ouça devagar! Adiciona um controle deslizante que controla a velocidade da música",
"name": "Velocidade de reprodução",
"templates": {
"button": "Velocidade"
}
},
"precise-volume": {
"description": "Controle o volume com precisão usando a roda do mouse/teclas de atalho, com um HUD personalizado e etapas de volume personalizáveis",
"menu": {
"arrows-shortcuts": "Controles locais das teclas de seta",
"custom-volume-steps": "Definir etapas de volume personalizadas",
"global-shortcuts": "Teclas de atalho globais"
},
"name": "Volume preciso",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Diminuir o volume",
"increase": "Aumentar o volume"
},
"label": "Escolha atalhos de teclado de volume global:",
"title": "Atalhos de teclado de volume global"
},
"volume-steps": {
"label": "Escolha as etapas de aumento/diminuição de volume",
"title": "Etapas de volume"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualidade atual: {{quality}}",
"message": "Escolha a qualidade do vídeo:",
"title": "Escolha a qualidade do vídeo"
}
}
},
"description": "Permite alterar a qualidade do vídeo com um botão na sobreposição de vídeo",
"name": "Trocador de qualidade do vídeo"
},
"shortcuts": {
"description": "Permite definir teclas de atalho globais para reprodução (reproduzir/pausar/próximo/anterior) e desligar o OSD de mídia substituindo as teclas de mídia, ativando Ctrl/CMD + F para pesquisar, ativando o suporte Linux MPRIS para teclas de mídia e teclas de atalho personalizadas para usuários avançados.",
"menu": {
"override-media-keys": "Substituir teclas de mídia",
"set-keybinds": "Definir controles globais de música"
},
"name": "Atalhos (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Próximo",
"play-pause": "Reproduzir/Pausar",
"previous": "Anterior"
},
"label": "Escolha atalhos de teclado globais para controle de músicas:",
"title": "Atalhos globais do teclado"
}
}
},
"skip-disliked-songs": {
"description": "Pula músicas com 'não gostei'",
"name": "Pular músicas com 'não gostei'"
},
"skip-silences": {
"description": "Pular automaticamente seções de silêncio nas músicas",
"name": "Pular silêncios"
},
"sponsorblock": {
"description": "Ignora automaticamente partes não musicais, como introdução/final ou partes de videoclipes onde a música não está tocando",
"name": "SponsorBlock (bloqueador de patrocínios)"
},
"taskbar-mediacontrol": {
"description": "Controle a reprodução na barra de tarefas do Windows",
"name": "Controle de mídia da barra de tarefas"
},
"touchbar": {
"description": "Adiciona um widget TouchBar para usuários do macOS",
"name": "Barra de toque"
},
"tuna-obs": {
"description": "Integração com o plugin Tuna do OBS",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "Adiciona um botão para alternar entre o modo Vídeo/Música. Também pode, opcionalmente, remover toda a guia de vídeo",
"menu": {
"align": {
"label": "Alinhamento",
"submenu": {
"left": "Esquerda",
"middle": "Centro",
"right": "Direita"
}
},
"force-hide": "Forçar remoção da guia de vídeo",
"mode": {
"label": "Modo",
"submenu": {
"custom": "Alternar personalizado",
"disabled": "Desabilitado",
"native": "Alternar nativo"
}
}
},
"name": "Botão de Alternar Vídeo",
"templates": {
"button": "Música"
}
},
"visualizer": {
"description": "Adiciona um visualizador ao player",
"menu": {
"visualizer-type": "Tipo de visualizador"
},
"name": "Visualizer"
}
}
}

View File

@ -1,49 +1,167 @@
{ {
"common": {
"console": {
"plugins": {
"execute-failed": "Ошибка загрузки плагина {{pluginName}}::{{contextName}}",
"executed-at-ms": "Плагин {{pluginName}}::{{contextName}} загружен за {{ms}}мс",
"initialize-failed": "Ошибка инициализации плагина \"{{pluginName}}\"",
"load-all": "Загружаем все плагины",
"load-failed": "Ошибка загрузки плагина \"{{pluginName}}\"",
"loaded": "Плагин \"{{pluginName}}\" загружен",
"unload-failed": "Ошибка выгрузки плагина \"{{pluginName}}\"",
"unloaded": "Плагин \"{{pluginName}}\" выгружен"
}
}
},
"language": { "language": {
"code": "ru", "code": "ru",
"local-name": "Русский", "local-name": "Русский",
"name": "Russian" "name": "Russian"
}, },
"main": { "main": {
"console": {
"did-finish-load": {
"dev-tools": "Загрузка завершена. DevTools открыт"
},
"i18n": {
"loaded": "i18n загружен"
},
"second-instance": {
"receive-command": "Получена команда по протоколу: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS файл \"{{cssFile}}\" не существует, игнорирую"
},
"unresponsive": {
"details": "Приложение не отвечает\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Очищаю кеш приложения"
},
"window": {
"tried-to-render-offscreen": "Окно попробовало открыться за пределами экрана, размер окна={{windowSize}}, разрешение экрана={{displaySize}}, местоположение={{position}}"
}
},
"dialog": { "dialog": {
"hide-menu-enabled": {
"detail": "Меню скрыто, 'Alt' чтобы показать его ('Escape' если используете внутреннее меню приложения)",
"message": "Скрытие меню включено",
"title": "Включено скрытие меню"
},
"need-to-restart": {
"buttons": {
"later": "Позже",
"restart-now": "Перезапустить сейчас"
},
"detail": "Перезагрузите приложение для включения плагина {{pluginName}}",
"message": "{{pluginName}} нуждается в перезагрузке",
"title": "Нужна перезагрузка"
},
"unresponsive": {
"buttons": {
"quit": "Выйти",
"relaunch": "Перезапустить",
"wait": "Подождать"
},
"detail": "Извиняемся за неувязку! пожалуйста выберите что вы хотите сделать:",
"message": "Приложение не отвечает",
"title": "Окно не отвечает"
},
"update-available": { "update-available": {
"buttons": { "buttons": {
"download": "Download", "disable": "Отключить обновления",
"download": "Скачать",
"ok": "OK" "ok": "OK"
} },
"detail": "Новая версия доступна для загрузки на {{downloadLink}}",
"message": "Доступная новая версия",
"title": "Доступно обновление"
} }
}, },
"menu": { "menu": {
"about": "О приложении",
"navigation": { "navigation": {
"label": "Navigation" "label": "Навигация",
"submenu": {
"copy-current-url": "Скопировать текущий URL",
"go-back": "Вернуться",
"go-forward": "Вперед",
"quit": "Выйти",
"restart": "Перезапустить приложение"
}
}, },
"options": { "options": {
"label": "Options", "label": "Настройки",
"submenu": { "submenu": {
"advanced-options": { "advanced-options": {
"label": "Расширенные настройки",
"submenu": { "submenu": {
"auto-reset-app-cache": "Очищать кеш при запуске приложения",
"disable-hardware-acceleration": "Отключить аппаратное ускорение",
"edit-config-json": "Редактировать config.json",
"override-user-agent": "Переопределить User-Agent",
"restart-on-config-changes": "Перезапускать при изменениях конфига",
"set-proxy": { "set-proxy": {
"label": "Set proxy", "label": "Задать прокси",
"prompt": { "prompt": {
"title": "Set proxy" "label": "Введите прокси адрес (оставьте поле пустым для отключения)",
"placeholder": "Пример: SOCKS5://127.0.0.1:9999",
"title": "Задать прокси"
} }
} },
"toggle-dev-tools": "Включить DevTools"
} }
}, },
"auto-update": "Auto Update", "always-on-top": "Поверх остальных окон",
"start-at-login": "Start at login", "auto-update": "Автообновление",
"hide-menu": {
"dialog": {
"message": "Меню будет скрыто при следующем запуске, используйте [Alt] чтобы показать его (или [`] если используйте меню внутри приложения)",
"title": "Скрытие меню включено"
},
"label": "Скрыть меню"
},
"language": {
"dialog": {
"message": "Язык будет сменён после перезапуска",
"title": "Язык изменён"
},
"label": "Язык",
"submenu": {
"to-help-translate": "Хотите помочь с переводом? Кликните сюда"
}
},
"resume-on-start": "Продолжать последнюю песню при запуске приложения",
"single-instance-lock": "Запрет запуска нескольких экземпляров",
"start-at-login": "Запуск при включении компьютера",
"starting-page": {
"label": "Стартовая страница",
"unset": "Не настроено"
},
"tray": { "tray": {
"label": "Tray" "label": "Трей",
"submenu": {
"disabled": "Отключено",
"enabled-and-hide-app": "Включено и скрывать приложение",
"enabled-and-show-app": "Включено и показывать приложение",
"play-pause-on-click": "Пауза/продолжить при нажатии"
}
}, },
"visual-tweaks": { "visual-tweaks": {
"label": "Визуальные настройки",
"submenu": { "submenu": {
"like-buttons": { "like-buttons": {
"default": "Default" "default": "Default",
"force-show": "Всегда показывать",
"hide": "Скрывать",
"label": "Кнопка лайка"
}, },
"remove-upgrade-button": "Убрать кнопку Youtube Premium",
"theme": { "theme": {
"label": "Theme", "label": "Тема",
"submenu": { "submenu": {
"no-theme": "No theme" "import-css-file": "Импортировать кастомный CSS файл",
"no-theme": "Без темы"
} }
} }
} }
@ -51,11 +169,28 @@
} }
}, },
"plugins": { "plugins": {
"label": "Plugins" "enabled": "Включено",
"label": "Плагины"
}, },
"view": { "view": {
"label": "View" "label": "Вид",
"submenu": {
"force-reload": "Принудительная перезагрузка",
"reload": "Перезагрузить",
"reset-zoom": "Настоящий размер",
"toggle-fullscreen": "Включить полноэкранный режим",
"zoom-in": "Приблизить",
"zoom-out": "Отдалить"
}
} }
},
"tray": {
"next": "Следующий",
"play-pause": "Пауза/Продолжить",
"previous": "Предыдущий",
"quit": "Выйти",
"restart": "Перезагрузить приложение",
"show": "Показать окно"
} }
}, },
"plugins": { "plugins": {
@ -106,7 +241,7 @@
"smoothness-transition": { "smoothness-transition": {
"label": "Плавный переход", "label": "Плавный переход",
"submenu": { "submenu": {
"during": "В течение {{interpolationTime}}s" "during": "В течение {{interpolationTime}} s"
} }
}, },
"use-fullscreen": { "use-fullscreen": {
@ -129,21 +264,78 @@
}, },
"captions-selector": { "captions-selector": {
"description": "Выбор субтитров для аудиотреков в YouTube Music", "description": "Выбор субтитров для аудиотреков в YouTube Music",
"menu": {
"autoload": "Автоматически выбирать последние использованные субтитры",
"disable-captions": "Без субтитров по умолчанию"
},
"name": "Выбор субтитров", "name": "Выбор субтитров",
"prompt": { "prompt": {
"selector": { "selector": {
"none": "None" "label": "Текущий язык субтитров:{{language}}",
"none": "Нет",
"title": "Выберите язык субтитров"
} }
},
"templates": {
"title": "Открыть выбор субтитров"
} }
}, },
"compact-sidebar": {
"description": "Боковая панель всегда в компактном режиме",
"name": "Компактная боковая панель"
},
"crossfade": { "crossfade": {
"description": "Плавный переход между песнями",
"menu": {
"advanced": "Расширенное"
},
"name": "Плавный переход [бета]",
"prompt": { "prompt": {
"options": { "options": {
"multi-input": { "multi-input": {
"fade-in-duration": "Длительность постепенного появления (мс)",
"fade-out-duration": "Длительность затухания (мс)",
"fade-scaling": { "fade-scaling": {
"linear": "Linear" "label": "Сила перехода",
} "linear": "Линейный",
} "logarithmic": "Логарифмический"
},
"seconds-before-end": "Плавных переход за N секунд перед концом"
},
"title": "Настройки плавного перехода"
}
}
},
"disable-autoplay": {
"description": "Запускает песню сразу на паузе",
"menu": {
"apply-once": "Применимо только при запуске"
},
"name": "Отключить Автопроигрыш"
},
"discord": {
"backend": {
"already-connected": "Попытка подключения при уже существующем соединении",
"connected": "Подключено к Discord",
"disconnected": "Отключено от Discord"
},
"description": "Покажите своим друзьям что вы слушаете с помощью Rich Presence",
"menu": {
"auto-reconnect": "Авто переподключение",
"clear-activity": "Очистить активность",
"clear-activity-after-timeout": "Очищать активность после таймаута",
"connected": "Подключено",
"disconnected": "Отключено",
"hide-duration-left": "Скрыть сколько осталось времени",
"hide-github-button": "Скрыть ссылку на GitHub",
"play-on-youtube-music": "Воспроизвести на YouTube Music",
"set-inactivity-timeout": "Поставить таймер неактивности"
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Введите таймер неактивности в секундах:",
"title": "Задать таймер неактивности"
} }
} }
}, },
@ -154,33 +346,92 @@
"buttons": { "buttons": {
"ok": "OK" "ok": "OK"
}, },
"message": "Ой-ой! Прошу простить, загрузка дала сбой…",
"title": "Error in download!" "title": "Error in download!"
}, },
"start-download-playlist": { "start-download-playlist": {
"buttons": { "buttons": {
"ok": "OK" "ok": "OK"
} },
"detail": "({{playlistSize}} песен)",
"message": "Скачиваем плейлист {{playlistTitle}}",
"title": "Загрузка началась"
} }
}, },
"feedback": { "feedback": {
"download-progress": "Download: {{percent}}%" "conversion-progress": "Конвертация: {{percent}}%",
"converting": "Конвертация…",
"done": "Выполнено: {{filePath}}",
"download-info": "Скачиваем {{artist}}-{{title}} {{videoId}}",
"download-progress": "Download: {{percent}}%",
"downloading": "Скачиваем…",
"downloading-counter": "Скачиваем {{current}}/{{total}}…",
"downloading-playlist": "Скачиваем плейлист \"{{playlistTitle}}\" - {{playlistSize}} песен {{playlistId}}",
"error-while-downloading": "Ошибка при скачивании \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Папка {{playlistFolder}} уже существует",
"getting-playlist-info": "Получаем информацию о плейлисте…",
"loading": "Загрузка…",
"playlist-has-only-one-song": "В плейлисте только одна песня, скачиваем напрямую",
"playlist-id-not-found": "Плейлист не найден",
"playlist-is-empty": "Плейлист пуст",
"playlist-is-mix-or-private": "Ошибка при получении данных о плейлисте: убедитесь что он не приватный или \"Только для вас\" плейлист\n\n{{error}}",
"preparing-file": "Подготавливаем файл…",
"saving": "Сохраняем…",
"trying-to-get-playlist-id": "Пытаемся получить ID плейлиста: {{playlistId}}",
"video-id-not-found": "Видео не найдено",
"writing-id3": "Пишем ID3 теги…"
} }
}, },
"description": "Скачивать MP3 / исходное аудио напрямую из интерфейса",
"menu": {
"choose-download-folder": "Выберите папку для загрузок",
"download-playlist": "Скачать плейлист",
"presets": "Пресеты",
"skip-existing": "Пропускать уже существующие файлы"
},
"name": "Загрузчик",
"renderer": {
"can-not-update-progress": "Невозможно обновить прогресс"
},
"templates": { "templates": {
"button": "Download" "button": "Download"
} }
}, },
"exponential-volume": {
"description": "Делает слайдер громкости расширенным чтобы было легче выбирать низкие уровни.",
"name": "Расширенная громкость"
},
"in-app-menu": {
"description": "Придает меню модный вид"
},
"last-fm": { "last-fm": {
"name": "Last.fm" "name": "Last.fm"
}, },
"navigation": { "navigation": {
"name": "Navigation" "name": "Навигация"
}, },
"no-google-login": { "no-google-login": {
"name": "No Google Login" "name": "No Google Login"
}, },
"notifications": { "notifications": {
"name": "Notifications" "name": "Уведомления"
},
"picture-in-picture": {
"description": "Позволяет переключить приложение в режим «картинка в картинке»",
"menu": {
"always-on-top": "Всегда наверху",
"hotkey": {
"label": "Горячая клавиша",
"prompt": {
"keybind-options": {
"hotkey": "Горячая клавиша"
},
"label": "Выберите горячую клавишу для переключения режима изображения в изображении"
}
},
"save-window-position": "Сохранить положение окна",
"save-window-size": "Сохранить размер окна"
}
}, },
"shortcuts": { "shortcuts": {
"prompt": { "prompt": {
@ -195,7 +446,7 @@
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"touchbar": { "touchbar": {
"name": "TouchBar" "name": "Тачбар"
}, },
"tuna-obs": { "tuna-obs": {
"name": "Tuna OBS" "name": "Tuna OBS"
@ -208,13 +459,27 @@
"right": "Right" "right": "Right"
} }
}, },
"force-hide": "Скрыть обложку",
"mode": { "mode": {
"label": "Mode" "label": "Mode",
"submenu": {
"custom": "Кастомный переключатель",
"disabled": "Отключен",
"native": "Стандартный переключатель"
}
} }
}, },
"name": "Переключатель видео",
"templates": { "templates": {
"button": "Song" "button": "Song"
} }
},
"visualizer": {
"description": "Заменяет обложку визуализатором музыки",
"menu": {
"visualizer-type": "Вид визуализации"
},
"name": "Визуалайзер"
} }
} }
} }

View File

@ -0,0 +1,7 @@
{
"language": {
"code": "th",
"local-name": "ภาษาไทย",
"name": "Thai"
}
}

286
src/i18n/resources/tr.json Normal file
View File

@ -0,0 +1,286 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "{{pluginName}}::{{contextName}} eklentisi çalıştırılamadı",
"executed-at-ms": "{{pluginName}}::{{contextName}} eklentisi {{ms}}ms'de çalıştırıldı",
"initialize-failed": "\"{{pluginName}}\" eklentisi başlatılamadı",
"load-all": "Tüm eklentiler yükleniyor",
"load-failed": "\"{{pluginName}}\" eklentisi yüklenemedi",
"loaded": "\"{{pluginName}}\" eklentisi yüklendi",
"unload-failed": "\"{{pluginName}}\" eklentisi kaldırılamadı.",
"unloaded": "\"{{pluginName}}\" eklentisi kaldırıldı"
}
}
},
"language": {
"code": "tr",
"local-name": "Türkçe",
"name": "Turkish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Yükleme tamamlandı. Geliştirici araçlarııldı"
},
"i18n": {
"loaded": "i18n yüklendi"
},
"second-instance": {
"receive-command": "Protokol üzerinden alınan komut: \"{{command}}\""
},
"theme": {
"css-file-not-found": "\"{{cssFile}}\" adlı CSS dosyası bulunamadı, yok sayılıyor"
},
"unresponsive": {
"details": "Yanıt verilmedi!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Uygulama ön belleği temizleme"
},
"window": {
"tried-to-render-offscreen": "Pencere ekranın dışında oluşturulmaya çalışıldı, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menü gizli, göstermek için 'Alt' tuşunu kullanın (veya Uygulama İçi Menüyü kullanıyorsanız 'Escape' tuşunu kullanın)",
"message": "Menüyü gizle etkinleştirildi",
"title": "Menüyü gizle etkinleştirildi"
},
"need-to-restart": {
"buttons": {
"restart-now": "Şimdi yeniden başlat"
}
},
"update-available": {
"buttons": {
"download": "İndir",
"ok": "Tamam"
}
}
},
"menu": {
"navigation": {
"label": "Navigasyon"
},
"options": {
"label": "Seçenekler",
"submenu": {
"advanced-options": {
"submenu": {
"set-proxy": {
"label": "Proxy ayarla",
"prompt": {
"title": "Proxy ayarla"
}
}
}
},
"auto-update": "Otomatik Güncelleme",
"language": {
"label": "Dil"
},
"start-at-login": "Başlangıçta çalıştır",
"tray": {
"label": "Tepsi",
"submenu": {
"enabled-and-hide-app": "Uygulamayı etkinleştirin gizleyin.",
"play-pause-on-click": "Tıklaynınca Oynat-Duraklat"
}
},
"visual-tweaks": {
"submenu": {
"like-buttons": {
"default": "Varsayılan"
},
"theme": {
"label": "Tema",
"submenu": {
"no-theme": "Tema Yok"
}
}
}
}
}
},
"plugins": {
"label": "Eklentiler"
},
"view": {
"label": "Görüntü"
}
}
},
"plugins": {
"adblocker": {
"description": "Tüm reklamları ve izleyicileri engelle",
"menu": {
"blocker": "Engelleme Yöntemi"
},
"name": "Adblocker"
},
"album-color-theme": {
"description": "Albümün renk paletine dayalı dinamik bir tema ve efektler uygular.",
"name": "Albüm Renk Teması"
},
"ambient-mode": {
"description": "Videodaki yumuşak renkleri ekranınızın arka planına yansıtarak bir ışık efekti uygular..",
"menu": {
"blur-amount": {
"submenu": {
"pixels": "{{blurAmount}} pixels"
}
},
"buffer": {
"label": "Önbellek",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opaklık Miktarı",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"submenu": {
"pixels": "{{quality}} pixels"
}
},
"size": {
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"submenu": {
"during": "{{interpolationTime}} saniye içinde"
}
}
}
},
"audio-compressor": {
"description": "Ses sıkıştırma (dalganın en gürültülü bölümlerinin ses düzeyini azaltır ve daha yumuşak bölümlerin ses düzeyini artırır)"
},
"blur-nav-bar": {
"description": "Gezinme çubuğunu şeffaf ve bulanık yapar"
},
"bypass-age-restrictions": {
"description": "YouTube yaş doğrulamasını atla"
},
"captions-selector": {
"prompt": {
"selector": {
"none": "Hiçbiri"
}
}
},
"compact-sidebar": {
"description": "Her zaman kompakt kenar çubugu"
},
"crossfade": {
"menu": {
"advanced": "İleri düzey için"
},
"prompt": {
"options": {
"multi-input": {
"fade-scaling": {
"linear": "Doğrusal",
"logarithmic": "Logaritmik"
}
}
}
}
},
"disable-autoplay": {
"description": "Şarkıların otomatik olarak duraklatılmasını sağlar",
"menu": {
"apply-once": "Yalnızca ilk şarkı için geçerlidir"
}
},
"discord": {
"description": "Rich Presence ile Discord'da ne dinlediğinizi gösterin.",
"menu": {
"hide-duration-left": "Kalan süreyi gizle",
"hide-github-button": "GitHub bağlantısını gizle",
"set-inactivity-timeout": "Hareketsizlik zaman aşımını ayarla"
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Tamam"
},
"title": "İndirmede hata meydana geldi!"
},
"start-download-playlist": {
"buttons": {
"ok": "Tamam"
},
"message": "Çalma listesini indir : {{playlistTitle}}",
"title": "İndirme Başladı"
}
},
"feedback": {
"conversion-progress": "Dönüştürme : {{percent}}%",
"download-progress": "İndirme : {{percent}}%",
"preparing-file": "Dosya Hazırlanıyor…"
}
},
"templates": {
"button": "İndir"
}
},
"last-fm": {
"name": "Last.fm"
},
"navigation": {
"name": "Navigasyon"
},
"no-google-login": {
"name": "Google Girişini Kaldır"
},
"notifications": {
"name": "Bildirimler"
},
"shortcuts": {
"prompt": {
"keybind": {
"keybind-options": {
"next": "İler"
}
}
}
},
"sponsorblock": {
"name": "SponsorBlock"
},
"touchbar": {
"name": "TouchBar"
},
"tuna-obs": {
"name": "Tuna OBS"
},
"video-toggle": {
"menu": {
"align": {
"submenu": {
"middle": "Orta",
"right": "Sağ"
}
},
"mode": {
"label": "Mod"
}
},
"templates": {
"button": "Şarkı"
}
}
}
}

464
src/i18n/resources/uk.json Normal file
View File

@ -0,0 +1,464 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Не вдалося запустити плагін {{pluginName}}::{{contextName}}",
"executed-at-ms": "Плагін {{pluginName}}::{{contextName}} запущено за {{ms}}ms",
"initialize-failed": "Не вдалося ініціалізувати плагін \"{{pluginName}}\"",
"load-all": "Завантаження всіх плагінів",
"load-failed": "Не вдалося завантажити плагін \"{{pluginName}}\"",
"loaded": "Плагін \"{{pluginName}}\" завантажено",
"unload-failed": "Не вдалося вивантажити плагін \"{{pluginName}}\"",
"unloaded": "Плагін \"{{pluginName}}\" вивантажено"
}
}
},
"language": {
"code": "uk",
"local-name": "Українська",
"name": "Ukrainian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Завантаження завершено. Відкрито DevTools"
},
"i18n": {
"loaded": "i18n завантажено"
},
"second-instance": {
"receive-command": "Отримано команду за протоколом: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Файл CSS \"{{cssFile}}\" не існує, ігноруємо"
},
"unresponsive": {
"details": "Невідповідна помилка!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Очищення кешу програми"
},
"window": {
"tried-to-render-offscreen": "Вікно намагалися відобразитися поза екраном, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Меню приховано, використовуйте \"Alt\", щоб показати його (або \"Escape\", якщо ви використовуєте меню в додатку)",
"message": "Приховане меню увімкнено",
"title": "Увімкнути Приховане Меню"
},
"need-to-restart": {
"buttons": {
"later": "Пізніше",
"restart-now": "Перезапустити зараз"
},
"detail": "\"{{pluginName}}\" плагін потребує перезапуску, щоб набути чинності",
"message": "\"{{pluginName}}\" необхідно перезапустити",
"title": "Потрібен перезапуск"
},
"unresponsive": {
"buttons": {
"quit": "Вийти",
"relaunch": "Перезапустити",
"wait": "Чекати"
},
"detail": "Перепрошуємо за незручності! Будь ласка, оберіть, що робити:",
"message": "Програма не відповідає",
"title": "Вікно не відповідає"
},
"update-available": {
"buttons": {
"disable": "Вимкнути оновлення",
"download": "Завантажити",
"ok": "Так"
},
"detail": "Доступна нова версія, яку можна завантажити за посиланням {{downloadLink}}",
"message": "Доступна нова версія",
"title": "Доступне оновлення"
}
},
"menu": {
"about": "Про нас",
"navigation": {
"label": "Навігація",
"submenu": {
"copy-current-url": "Копіювати поточну URL-адресу",
"go-back": "Назад",
"go-forward": "Вперед",
"quit": "Вихід",
"restart": "Перезапустити програму"
}
},
"options": {
"label": "Параметри",
"submenu": {
"advanced-options": {
"label": "Додаткові опції",
"submenu": {
"auto-reset-app-cache": "Очищення кешу програми при її запуску",
"disable-hardware-acceleration": "Вимкнути апаратне прискорення",
"edit-config-json": "Редагувати config.json",
"override-user-agent": "Змінити User-Agent",
"restart-on-config-changes": "Перезапуск після зміни конфігурації",
"set-proxy": {
"label": "Задати проксі",
"prompt": {
"label": "Введіть адресу проксі-сервера: (залиште порожнім, щоб вимкнути)",
"placeholder": "Приклад: SOCKS5://127.0.0.1:9999",
"title": "Задати проксі"
}
},
"toggle-dev-tools": "Перемкнути DevTools"
}
},
"always-on-top": "Завжди зверху",
"auto-update": "Автооновлення",
"hide-menu": {
"dialog": {
"message": "Меню буде приховано при наступному запуску, використовуйте [Alt], щоб показати його (або клавішу [`], якщо ви використовуєте вбудоване меню)",
"title": "Приховане меню Увімкнено"
},
"label": "Приховане меню"
},
"language": {
"dialog": {
"message": "Мова буде змінена після перезапуску",
"title": "Мову змінено"
},
"label": "Мова",
"submenu": {
"to-help-translate": "Хочете допомогти з перекладом? Клацніть тут"
}
},
"resume-on-start": "Відновлювати останню пісню при запуску програми",
"single-instance-lock": "Заблокувати єдиним інстансом",
"start-at-login": "Запустити при вході в систему",
"starting-page": {
"label": "Початкова сторінка",
"unset": "Не задано"
},
"tray": {
"label": "Таця",
"submenu": {
"disabled": "Вимкнено",
"enabled-and-hide-app": "Запустити та приховати програму",
"enabled-and-show-app": "Запустити та відобразити додаток",
"play-pause-on-click": "Відтворення/Пауза за натисканням"
}
},
"visual-tweaks": {
"label": "Візуальні налаштування",
"submenu": {
"like-buttons": {
"default": "За замовчуванням",
"force-show": "Завжди показувати",
"hide": "Приховати",
"label": "Як кнопки"
},
"remove-upgrade-button": "Прибрати кнопку оновлення",
"theme": {
"label": "Тема",
"submenu": {
"import-css-file": "Імпортувати власний CSS файл",
"no-theme": "Без теми"
}
}
}
}
}
},
"plugins": {
"enabled": "Увімкнено",
"label": "Плагіни"
},
"view": {
"label": "Вид",
"submenu": {
"force-reload": "Примусове перезавантаження",
"reload": "Перезавантажити",
"reset-zoom": "Фактичний розмір",
"toggle-fullscreen": "Включити повноекранний режим",
"zoom-in": "Збільшити",
"zoom-out": "Зменшити"
}
}
},
"tray": {
"next": "Далі",
"play-pause": "Відтворення/Пауза",
"previous": "Попередній",
"quit": "Вихід",
"restart": "Перезапустити програму",
"show": "Показати вікно"
}
},
"plugins": {
"adblocker": {
"description": "Блокувати всю рекламу та відстеження з коробки",
"menu": {
"blocker": "Блокувальник"
},
"name": "Блокувальник реклами"
},
"album-color-theme": {
"description": "Застосовує динамічну тему та візуальні ефекти на основі колірної палітри альбому",
"name": "Кольорова тема альбому"
},
"ambient-mode": {
"description": "Застосовує ефект освітлення, накладаючи ніжні кольори з відео на фон екрана.",
"menu": {
"blur-amount": {
"label": "Обсяг розмиття",
"submenu": {
"pixels": "{{blurAmount}} пікселів"
}
},
"buffer": {
"label": "Буфер",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Прозорість",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Якість",
"submenu": {
"pixels": "{{quality}} пікселів"
}
},
"size": {
"label": "Розмір",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Плавність переходу",
"submenu": {
"during": "Протягом {{interpolationTime}} с"
}
},
"use-fullscreen": {
"label": "Повноекранний режим"
}
},
"name": "Режим навколишнього середовища"
},
"audio-compressor": {
"description": "Застосувати стиснення аудіо (зменшити гучність найгучніших фрагментів сигналу та збільшити гучність тихих фрагментів)",
"name": "Аудіокомпресор"
},
"blur-nav-bar": {
"description": "Робить панель навігації прозорою та розмитою",
"name": "Розмиття панелі навігації"
},
"bypass-age-restrictions": {
"description": "Обхід перевірки віку на YouTube",
"name": "Обхід вікових обмежень"
},
"captions-selector": {
"description": "Вибір субтитрів для аудіодоріжок YouTube Music",
"menu": {
"autoload": "Автоматичний вибір останніх використаних субтитрів",
"disable-captions": "За замовчуванням субтитри відсутні"
},
"name": "Вибір субтитрів",
"prompt": {
"selector": {
"label": "Поточна мова субтитрів: {{language}}",
"none": "Нема",
"title": "Виберіть мову субтитрів"
}
},
"templates": {
"title": "Відкрити селектор субтитрів"
}
},
"compact-sidebar": {
"description": "Бічна панель завжди в компактному режимі",
"name": "Компактна бічна панель"
},
"crossfade": {
"description": "Плавний перехід між піснями",
"menu": {
"advanced": "Розширене"
},
"name": "Плавний перехід[бета-версія]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Тривалість згасання (мс)",
"fade-out-duration": "Тривалість згасання (ms)",
"fade-scaling": {
"label": "Згладжування масштабування",
"linear": "Лінійна",
"logarithmic": "Логарифмічна"
},
"seconds-before-end": "Плавний перехід за N секунд до кінця"
},
"title": "Параметри плавного переходу"
}
}
},
"disable-autoplay": {
"description": "Запуск пісні в режимі \"пауза\"",
"menu": {
"apply-once": "Застосовується тільки при запуску"
},
"name": "Вимкнути автовідтворення"
},
"discord": {
"backend": {
"already-connected": "Спроба підключення при активному з'єднанні",
"connected": "Під'єднано до Discord",
"disconnected": "Від'єднано від Discord"
},
"description": "Покажіть друзям, що ви слухаєте за допомогою Rich Presence",
"menu": {
"auto-reconnect": "Автоматичне перепідключення",
"clear-activity": "Очистити активність",
"clear-activity-after-timeout": "Очистити активність після тайм-ауту",
"connected": "Під'єднано",
"disconnected": "Від'єднано",
"hide-duration-left": "Приховати тривалість, яка залишилася",
"hide-github-button": "Приховати посилання на GitHub",
"play-on-youtube-music": "Слухати на YouTube Music",
"set-inactivity-timeout": "Встановити тайм-аут бездіяльності"
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Введіть тайм-аут бездіяльності в секундах:",
"title": "Встановлення тайм-ауту бездіяльності"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Добре"
},
"message": "Йой! При завантаженні виникла помилка…",
"title": "Помилка при завантаженні!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} пісень)",
"message": "Завантаження списку відтворення {{playlistTitle}}",
"title": "Завантаження розпочато"
}
},
"feedback": {
"conversion-progress": "Конвертація: {{percent}}%",
"converting": "Конвертація…",
"done": "Готово: {{filePath}}",
"download-info": "Завантаження {{artist}} - {{title}} [{{videoId}}]",
"download-progress": "Завантажено: {{percent}}%",
"downloading": "Завантаження…",
"downloading-counter": "Завантажено {{current}}/{{total}}…",
"downloading-playlist": "Завантаження плейлисту \"{{playlistTitle}}\" - {{playlistSize}} пісень ({{playlistId}})",
"error-while-downloading": "Помилка завантаження \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Папка {{playlistFolder}} вже існує",
"getting-playlist-info": "Отримання інформації про плейлист…",
"loading": "Завантаження…",
"playlist-has-only-one-song": "Плейлист має лише один елемент, завантаження якого відбувається безпосередньо",
"playlist-id-not-found": "Ідентифікатор плейлиста не знайдено",
"playlist-is-empty": "Плейлист порожній",
"playlist-is-mix-or-private": "Помилка при отриманні інформації про плейлист: переконайтеся, що це не приватний плейлист або плейлист \"Мікс для вас\"\n\n{{error}}",
"preparing-file": "Готуємо файл…",
"saving": "Збереження…",
"trying-to-get-playlist-id": "Спроба отримати ідентифікатора плейлиста: {{playlistId}}",
"video-id-not-found": "Відео не знайдено",
"writing-id3": "Пишемо ID3-теги…"
}
},
"description": "Завантажує MP3 / джерело аудіо безпосередньо з інтерфейсу",
"menu": {
"choose-download-folder": "Оберіть папку для завантаження",
"download-playlist": "Завантажити плейлист",
"presets": "Попередні налаштування",
"skip-existing": "Пропустити наявні файли"
},
"name": "Завантажувач",
"renderer": {
"can-not-update-progress": "Неможливо оновити прогрес"
},
"templates": {
"button": "Завантажити"
}
},
"exponential-volume": {
"description": "Робить регулятор гучності експоненціальним, що полегшує вибір тихих рівнів гучності.",
"name": "Експоненціальний обсяг"
},
"in-app-menu": {
"description": "Надає меню-барам вишуканого, темного або кольору альбому вигляду",
"menu": {
"hide-dom-window-controls": "Сховати елементи керування вікном DOM"
},
"name": "Меню в програмі"
},
"last-fm": {
"description": "Додати підтримку прокрутки для Last.fm",
"name": "Last.fm"
},
"lumiastream": {
"description": "Додано підтримку для Lumia Stream",
"name": "Lumia Stream [бета-версія]"
},
"lyrics-genius": {
"description": "Додає підтримку текстів для більшості пісень",
"menu": {
"romanized-lyrics": "Романізована лірика"
}
},
"navigation": {
"name": "Навігація"
},
"no-google-login": {
"name": "Без входу в Google"
},
"notifications": {
"description": "Відображати сповіщення, коли пісня починає грати (інтерактивні сповіщення доступні в Windows)",
"menu": {
"interactive": "Інтерактивні сповіщення",
"interactive-settings": {
"label": "Інтерактивні налаштування",
"submenu": {
"hide-button-text": "Сховати текст кнопки"
}
}
},
"name": "Сповіщення"
},
"picture-in-picture": {
"description": "Дозволяє перемикати програму в режим «картинка в картинці»",
"menu": {
"always-on-top": "Завжди наверху",
"hotkey": {
"label": "Гаряча клавіша",
"prompt": {
"keybind-options": {
"hotkey": "Гаряча клавіша"
},
"label": "Оберіть гарячу клавішу для перемикання режиму зображення в зображенні",
"title": "Гаряча клавіша для режиму зображення в зображенні"
}
},
"save-window-position": "Зберегти положення вікна",
"save-window-size": "Зберегти розмір вікна"
},
"name": "Зображення в зображенні"
}
}
}

View File

@ -0,0 +1,598 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "执行插件 {{pluginName}}::{{contextName}} 失败",
"executed-at-ms": "插件 {{pluginName}}::{{contextName}} 已在 {{ms}} 毫秒内执行",
"initialize-failed": "初始化插件 “{{pluginName}}” 失败",
"load-all": "正在加载所有插件",
"load-failed": "加载插件 “{{pluginName}}” 失败",
"loaded": "插件 “{{pluginName}}” 已载入",
"unload-failed": "卸载插件 “{{pluginName}}” 失败",
"unloaded": "插件 “{{pluginName}}” 已卸载"
}
}
},
"language": {
"code": "zh-CN",
"local-name": "简体中文",
"name": "Simplified Chinese"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "加载完毕。开发人员工具已启动"
},
"i18n": {
"loaded": "i18n 已载入"
},
"second-instance": {
"receive-command": "已从协议接收到以下指令:“{{command}}”"
},
"theme": {
"css-file-not-found": "CSS 文件 “{{cssFile}}” 不存在,将被忽略"
},
"unresponsive": {
"details": "无响应错误!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "正在清理应用缓存"
},
"window": {
"tried-to-render-offscreen": "窗口试图于屏幕外绘制, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "菜单已隐藏,按下 Alt 键来显示(若使用应用内菜单则按 Esc 键)",
"message": "隐藏菜单已被启用",
"title": "隐藏菜单已启用"
},
"need-to-restart": {
"buttons": {
"later": "稍后",
"restart-now": "现在重启"
},
"detail": "“{{pluginName}}” 插件需要重启程序后生效",
"message": "“{{pluginName}}” 需要重启",
"title": "需要重启"
},
"unresponsive": {
"buttons": {
"quit": "退出",
"relaunch": "重启",
"wait": "等待"
},
"detail": "对产生的不便我们表示抱歉!请选择接下来要做什么:",
"message": "应用程序无响应",
"title": "窗口无响应"
},
"update-available": {
"buttons": {
"disable": "禁用更新",
"download": "下载",
"ok": "好的"
},
"detail": "新版本现已可用,可从 {{downloadLink}} 下载",
"message": "新版本可用",
"title": "更新可用"
}
},
"menu": {
"about": "关于",
"navigation": {
"label": "导航",
"submenu": {
"copy-current-url": "复制当前 URL",
"go-back": "后退",
"go-forward": "前进",
"quit": "退出",
"restart": "重启应用"
}
},
"options": {
"label": "选项",
"submenu": {
"advanced-options": {
"label": "高级选项",
"submenu": {
"auto-reset-app-cache": "启动时重置应用缓存",
"disable-hardware-acceleration": "禁用硬件加速",
"edit-config-json": "编辑 config.json",
"override-user-agent": "覆盖 User-Agent",
"restart-on-config-changes": "配置改变时重启",
"set-proxy": {
"label": "设置代理",
"prompt": {
"label": "输入代理地址(留空以禁用)",
"placeholder": "例如: SOCKS5://127.0.0.1:9999",
"title": "设置代理服务器"
}
},
"toggle-dev-tools": "切换开发人员工具"
}
},
"always-on-top": "保持置顶",
"auto-update": "自动更新",
"hide-menu": {
"dialog": {
"message": "菜单将在下次启动时隐藏,按下 Alt 键以显示(若使用应用内菜单则按 ` 键)",
"title": "隐藏菜单已启用"
},
"label": "隐藏菜单"
},
"language": {
"dialog": {
"message": "语言会在应用重启后更改",
"title": "语言已更改"
},
"label": "语言",
"submenu": {
"to-help-translate": "想要协助翻译?点击此处"
}
},
"resume-on-start": "应用启动时继续上次播放的歌曲",
"single-instance-lock": "单例模式",
"start-at-login": "系统登录时启动",
"starting-page": {
"label": "启动页面",
"unset": "取消设定"
},
"tray": {
"label": "托盘",
"submenu": {
"disabled": "已禁用",
"enabled-and-hide-app": "启用并隐藏应用",
"enabled-and-show-app": "启用并显示应用",
"play-pause-on-click": "点击时播放/暂停"
}
},
"visual-tweaks": {
"label": "视觉调整",
"submenu": {
"like-buttons": {
"default": "默认",
"force-show": "强制显示",
"hide": "隐藏",
"label": "点赞按钮"
},
"remove-upgrade-button": "移除升级按钮",
"theme": {
"label": "主题",
"submenu": {
"import-css-file": "导入自定义 CSS 文件",
"no-theme": "无主题"
}
}
}
}
}
},
"plugins": {
"enabled": "已启用",
"label": "插件"
},
"view": {
"label": "视图",
"submenu": {
"force-reload": "强制刷新",
"reload": "刷新",
"reset-zoom": "实际缩放大小",
"toggle-fullscreen": "切换全屏",
"zoom-in": "放大",
"zoom-out": "缩小"
}
}
},
"tray": {
"next": "下一首",
"play-pause": "播放/暂停",
"previous": "上一首",
"quit": "退出",
"restart": "重启应用",
"show": "显示窗口"
}
},
"plugins": {
"adblocker": {
"description": "屏蔽所有广告与跟踪器",
"menu": {
"blocker": "屏蔽器"
},
"name": "广告屏蔽器"
},
"album-color-theme": {
"description": "根据专辑封面配色动态改变主题与视觉效果",
"name": "专辑配色主题"
},
"ambient-mode": {
"description": "将视频中的浅配色作为光效投射到背景中,以增加沉浸感。",
"menu": {
"blur-amount": {
"label": "模糊等级",
"submenu": {
"pixels": "{{blurAmount}} 像素"
}
},
"buffer": {
"label": "缓冲",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "不透明度",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "质量",
"submenu": {
"pixels": "{{quality}} 像素"
}
},
"size": {
"label": "大小",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "平滑过渡",
"submenu": {
"during": "持续 {{interpolationTime}} 秒"
}
},
"use-fullscreen": {
"label": "使用全屏"
}
},
"name": "沉浸模式"
},
"audio-compressor": {
"description": "对音频应用压缩(压低响亮部分,提升柔和部分)",
"name": "音频压缩器"
},
"blur-nav-bar": {
"description": "让导航栏透明及模糊",
"name": "模糊导航栏"
},
"bypass-age-restrictions": {
"description": "绕过 YouTube 年龄验证",
"name": "绕过年龄验证"
},
"captions-selector": {
"description": "YouTube Music 音轨字幕选择器",
"menu": {
"autoload": "自动选择上次使用的字幕",
"disable-captions": "默认无字幕"
},
"name": "字幕选择器",
"prompt": {
"selector": {
"label": "当前字幕语言: {{language}}",
"none": "无",
"title": "选择字幕语言"
}
},
"templates": {
"title": "开启字幕选择器"
}
},
"compact-sidebar": {
"description": "始终将侧边栏设为紧凑模式",
"name": "紧凑式侧边栏"
},
"crossfade": {
"description": "在歌曲间启用交叉淡化效果",
"menu": {
"advanced": "高级"
},
"name": "交叉淡化 [beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "淡入持续时间(毫秒)",
"fade-out-duration": "淡出持续时间(毫秒)",
"fade-scaling": {
"label": "淡化尺度",
"linear": "线性",
"logarithmic": "对数"
},
"seconds-before-end": "歌曲结束前持续交叉淡化时长"
},
"title": "交叉淡化选项"
}
}
},
"disable-autoplay": {
"description": "让曲目开始时处于 “暂停” 模式",
"menu": {
"apply-once": "仅在程序启动时应用"
},
"name": "禁用自动播放"
},
"discord": {
"backend": {
"already-connected": "已尝试使用活跃网络进行连接",
"connected": "已连接到 Discord",
"disconnected": "已和 Discord 断开连接"
},
"description": "使用 Rich Presence 与好友分享正在收听的音乐",
"menu": {
"auto-reconnect": "自动重连",
"clear-activity": "清除状态",
"clear-activity-after-timeout": "超时后清除状态",
"connected": "已连接",
"disconnected": "已断开连接",
"hide-duration-left": "隐藏剩余时长",
"hide-github-button": "隐藏 GitHub 链接按钮",
"play-on-youtube-music": "转至 YouTube Music 播放",
"set-inactivity-timeout": "设置非活跃时长"
},
"name": "Discord Rich Presence 状态显示",
"prompt": {
"set-inactivity-timeout": {
"label": "设置非活跃状态超时时长,以秒为单位:",
"title": "设置非活跃超时"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "好的"
},
"message": "啊!抱歉,下载失败…",
"title": "下载时发生错误!"
},
"start-download-playlist": {
"buttons": {
"ok": "好的"
},
"detail": "({{playlistSize}} 首歌曲)",
"message": "正在下载播放列表 {{playlistTitle}}",
"title": "下载已开始"
}
},
"feedback": {
"conversion-progress": "转换进度: {{percent}}%",
"converting": "正在转换…",
"done": "完成下载: {{filePath}}",
"download-info": "正在下载 {{artist}} - {{title}} [{{videoId}}",
"download-progress": "下载: {{percent}}%",
"downloading": "下载中…",
"downloading-counter": "当前正下载 {{current}}/{{total}}…",
"downloading-playlist": "当前正在下载播放列表 \"{{playlistTitle}}\" - 共有 {{playlistSize}} 首歌曲 ({{playlistId}})",
"error-while-downloading": "下载 \"{{author}} - {{title}}\" 时出错: {{error}}",
"folder-already-exists": "文件夹 {{playlistFolder}} 已存在",
"getting-playlist-info": "正在获取播放列表信息…",
"loading": "加载中…",
"playlist-has-only-one-song": "播放列表只有一首歌曲,将直接下载",
"playlist-id-not-found": "未找到播放列表 ID",
"playlist-is-empty": "播放列表是空的",
"playlist-is-mix-or-private": "获取播放列表信息时出错:请确认目标不是私有或“为你推荐”列表\n\n{{error}}",
"preparing-file": "正在准备文件…",
"saving": "正在保存…",
"trying-to-get-playlist-id": "正尝试获取播放列表 ID {{playlistId}}",
"video-id-not-found": "视频未找到",
"writing-id3": "正在写入 ID3 标签…"
}
},
"description": "在界面内直接下载 MP3 / 源音频",
"menu": {
"choose-download-folder": "选择下载文件夹",
"download-playlist": "下载播放列表",
"presets": "预设",
"skip-existing": "跳过已存在的文件"
},
"name": "下载器",
"renderer": {
"can-not-update-progress": "无法更新进度"
},
"templates": {
"button": "下载"
}
},
"exponential-volume": {
"description": "让音量滑块指数化以便选择更低的音量。",
"name": "指数化音量"
},
"in-app-menu": {
"description": "为菜单栏启用更精美的外观,可选暗色或专辑配色",
"menu": {
"hide-dom-window-controls": "隐藏 DOM 窗口控件"
},
"name": "应用内菜单"
},
"last-fm": {
"description": "添加 Last.fm 记录支持",
"name": "Last.fm"
},
"lumiastream": {
"description": "添加 Lumia Stream 支持",
"name": "Lumia Stream [测试]"
},
"lyrics-genius": {
"description": "为大多数歌曲添加歌词支持",
"menu": {
"romanized-lyrics": "罗马化字幕"
},
"name": "Genius 歌词",
"renderer": {
"fetched-lyrics": "已从 Genius 获取字幕"
}
},
"navigation": {
"description": "如同浏览器般,在应用界面内直接显示前进/后退导航按钮",
"name": "导航"
},
"no-google-login": {
"description": "从界面内移除 Google 登录按钮和链接",
"name": "无 Google 登录"
},
"notifications": {
"description": "歌曲开始时显示通知(交互式通知仅适用于 Windows",
"menu": {
"interactive": "交互式通知",
"interactive-settings": {
"label": "通知交互设定",
"submenu": {
"hide-button-text": "隐藏按钮文字",
"refresh-on-play-pause": "播放/暂停时刷新",
"tray-controls": "点击托盘时打开/关闭"
}
},
"priority": "通知优先级",
"toast-style": "弹出通知样式",
"unpause-notification": "取消暂停时显示通知"
},
"name": "通知"
},
"picture-in-picture": {
"description": "允许应用切换到画中画模式",
"menu": {
"always-on-top": "保持置顶",
"hotkey": {
"label": "热键",
"prompt": {
"keybind-options": {
"hotkey": "热键"
},
"label": "选择切换画中画模式的热键",
"title": "画中画热键"
}
},
"save-window-position": "记住窗口位置",
"save-window-size": "记住窗口尺寸",
"use-native-pip": "使用浏览器原生画中画功能"
},
"name": "画中画",
"templates": {
"button": "画中画"
}
},
"playback-speed": {
"description": "快慢随心!为歌曲添加速度控制滑块",
"name": "回放速度",
"templates": {
"button": "速度"
}
},
"precise-volume": {
"description": "启用自定义的音量弹窗以及可变步长,以便采用滚轮/热键精确控制音量",
"menu": {
"arrows-shortcuts": "本地方向键控件",
"custom-volume-steps": "设置自定义音量步长",
"global-shortcuts": "全局热键"
},
"name": "精确音量",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "降低音量",
"increase": "提升音量"
},
"label": "选择全局音量键绑定:",
"title": "全局音量键绑定"
},
"volume-steps": {
"label": "选择音量增加/减弱步长",
"title": "音量步长"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "当前质量: {{quality}}",
"message": "选择视频画质:",
"title": "选择视频画质"
}
}
},
"description": "允许在视频上显示切换画质按钮",
"name": "视频画质切换器"
},
"shortcuts": {
"description": "允许为音频回放(播放/暂停/上一曲/下一曲)设置全局热键,兼具覆盖物理键以禁用 OSD、启用 Ctrl/CMD + F 搜索、为物理键启用 Linux MPRIS 支持及自定义热键等高级功能。",
"menu": {
"override-media-keys": "覆盖物理媒体热键",
"set-keybinds": "设置全局歌曲控件"
},
"name": "快捷键(以及 MPRIS",
"prompt": {
"keybind": {
"keybind-options": {
"next": "下一曲",
"play-pause": "播放/暂停",
"previous": "上一曲"
},
"label": "为歌曲控制选择全局按键绑定:",
"title": "全局按键绑定"
}
}
},
"skip-disliked-songs": {
"description": "自动跳过不喜欢的歌曲",
"name": "跳过不喜欢的歌曲"
},
"skip-silences": {
"description": "自动跳过音乐中的无声片段",
"name": "跳过无声片段"
},
"sponsorblock": {
"description": "自动跳过非音乐部分,如 MV 的介绍/结语以及歌曲未开始的部分",
"name": "SponsorBlock"
},
"taskbar-mediacontrol": {
"description": "从 Windows 任务栏控制音乐回放",
"name": "任务栏媒体控件"
},
"touchbar": {
"description": "为 macOS 用户启用 TouchBar 支持",
"name": "TouchBar"
},
"tuna-obs": {
"description": "与 OBS 的 Tuna 插件集成",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "增加视频/音频模式间的切换按钮。兼具移除整个视频页面的功能",
"menu": {
"align": {
"label": "位置对齐",
"submenu": {
"left": "左对齐",
"middle": "居中",
"right": "右对齐"
}
},
"force-hide": "强制移除视频页",
"mode": {
"label": "模式",
"submenu": {
"custom": "自定义开关样式",
"disabled": "禁用",
"native": "原生开关样式"
}
}
},
"name": "视频切换开关",
"templates": {
"button": "歌曲"
}
},
"visualizer": {
"description": "在播放器中添加可视化效果",
"menu": {
"visualizer-type": "可视化类型"
},
"name": "可视化效果"
}
}
}

View File

@ -0,0 +1,598 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "外掛 {{pluginName}} 無法執行::{{contextName}}",
"executed-at-ms": "外掛 {{pluginName}}::{{contextName}} 用了 {{ms}} 毫秒來執行",
"initialize-failed": "初始化外掛「{{pluginName}}」失敗",
"load-all": "正在載入所有外掛",
"load-failed": "載入外掛「{{pluginName}}」失敗",
"loaded": "外掛「{{pluginName}}」已載入",
"unload-failed": "移除外掛「{{pluginName}}」失敗",
"unloaded": "外掛「{{pluginName}}」已移除"
}
}
},
"language": {
"code": "zh-TW",
"local-name": "正體中文",
"name": "Traditional Chinese"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "載入完成。開發人員工具已開啟"
},
"i18n": {
"loaded": "i18n 已載入"
},
"second-instance": {
"receive-command": "使用協定來接收指令:「{{command}}」"
},
"theme": {
"css-file-not-found": "CSS 檔案「{{cssFile}}」不存在,已忽略"
},
"unresponsive": {
"details": "沒有回應錯誤!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "清理程式的快取資料"
},
"window": {
"tried-to-render-offscreen": "視窗正嘗試在螢幕外繪製,視窗大小 = {{windowSize}},螢幕大小 = {{displaySize}},位置 = {{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "選單列已隱藏,使用 Alt 鍵來重新顯示(或是使用 Esc 鍵)",
"message": "隱藏選單列已經啟用",
"title": "隱藏選單列已啟用"
},
"need-to-restart": {
"buttons": {
"later": "稍後",
"restart-now": "立即重啟"
},
"detail": "\"{{pluginName}}\" 外掛需要重啟應用之後才會生效",
"message": "\"{{pluginName}}\" 需要重啟應用",
"title": "需要重啟應用"
},
"unresponsive": {
"buttons": {
"quit": "離開",
"relaunch": "重新啟動",
"wait": "等一下"
},
"detail": "造成不便我們深表歉意!請選擇動作:",
"message": "應用程式沒有回應",
"title": "視窗沒有回應"
},
"update-available": {
"buttons": {
"disable": "停用更新",
"download": "下載",
"ok": "確定"
},
"detail": "新版本已經推出,你可以至 {{downloadLink}} 下載",
"message": "有新版本可用",
"title": "有可用的更新"
}
},
"menu": {
"about": "關於",
"navigation": {
"label": "導覽列",
"submenu": {
"copy-current-url": "複製目前的網址",
"go-back": "回到上一頁",
"go-forward": "回到下一頁",
"quit": "退出",
"restart": "重啟應用"
}
},
"options": {
"label": "選項",
"submenu": {
"advanced-options": {
"label": "進階選項",
"submenu": {
"auto-reset-app-cache": "當程式啟動時重設應用程式快取",
"disable-hardware-acceleration": "關閉硬體加速",
"edit-config-json": "編輯 config.json",
"override-user-agent": "覆寫使用者代理",
"restart-on-config-changes": "在設定檔更動時自動重啟應用程式",
"set-proxy": {
"label": "設定代理伺服器",
"prompt": {
"label": "輸入代理伺服器位置:(留空以停用本設定)",
"placeholder": "範例SOCKS5://127.0.0.1:9999",
"title": "設定代理伺服器"
}
},
"toggle-dev-tools": "切換開發者人員工具"
}
},
"always-on-top": "永遠顯示在最上層",
"auto-update": "自動更新",
"hide-menu": {
"dialog": {
"message": "選單列會在程式下次啟動時隱藏,使用 Alt 鍵來重新顯示(或是使用 ` 鍵)",
"title": "隱藏選單列已啟用"
},
"label": "隱藏選單列"
},
"language": {
"dialog": {
"message": "語言會在下一次重啟應用程式時變更",
"title": "語言已變更"
},
"label": "語言",
"submenu": {
"to-help-translate": "想要協助翻譯?按一下這裡"
}
},
"resume-on-start": "應用啟動時繼續上次播放的歌曲",
"single-instance-lock": "單視窗鎖定",
"start-at-login": "開機時啟動",
"starting-page": {
"label": "啟動頁面",
"unset": "不指定"
},
"tray": {
"label": "系統閘圖式",
"submenu": {
"disabled": "已停用",
"enabled-and-hide-app": "啟用並隱藏應用程式",
"enabled-and-show-app": "啟用並顯示應用程式",
"play-pause-on-click": "點擊時播放/暫停"
}
},
"visual-tweaks": {
"label": "介面設定",
"submenu": {
"like-buttons": {
"default": "預設",
"force-show": "強制顯示",
"hide": "隱藏",
"label": "按讚按鈕"
},
"remove-upgrade-button": "移除升級按鈕",
"theme": {
"label": "主題",
"submenu": {
"import-css-file": "匯入自訂 CSS 檔案",
"no-theme": "無主題"
}
}
}
}
}
},
"plugins": {
"enabled": "啟用",
"label": "外掛功能"
},
"view": {
"label": "視窗",
"submenu": {
"force-reload": "強制重新整理",
"reload": "重新整理",
"reset-zoom": "重設大小",
"toggle-fullscreen": "全螢幕",
"zoom-in": "放大",
"zoom-out": "縮小"
}
}
},
"tray": {
"next": "下一首",
"play-pause": "播放/暫停",
"previous": "上一首",
"quit": "關閉",
"restart": "重啟程式",
"show": "顯示視窗"
}
},
"plugins": {
"adblocker": {
"description": "阻擋所有廣告",
"menu": {
"blocker": "阻擋方式"
},
"name": "廣告阻擋"
},
"album-color-theme": {
"description": "依歌曲色調自動更改應用程式主題",
"name": "隨歌曲色調變更主題"
},
"ambient-mode": {
"description": "將影片內容的淺色畫面投放至螢幕背景, 讓觀眾在觀賞影片時更有臨場感。",
"menu": {
"blur-amount": {
"label": "模糊等級",
"submenu": {
"pixels": "{{blurAmount}} 像素"
}
},
"buffer": {
"label": "緩衝",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "不透明度",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "品質",
"submenu": {
"pixels": "{{quality}} 像素"
}
},
"size": {
"label": "大小",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "微光轉換時長",
"submenu": {
"during": "{{interpolationTime}} 秒"
}
},
"use-fullscreen": {
"label": "使用全螢幕"
}
},
"name": "微光效果"
},
"audio-compressor": {
"description": "使用音效壓縮 (大聲部份的音量降低, 柔和部份的音量提高)",
"name": "音效壓縮器"
},
"blur-nav-bar": {
"description": "使導覽列透明及模糊",
"name": "模糊導覽列"
},
"bypass-age-restrictions": {
"description": "繞過Youtube年齡驗證",
"name": "繞過年齡驗證"
},
"captions-selector": {
"description": "音軌標題選擇",
"menu": {
"autoload": "自動選擇最後使用的標題",
"disable-captions": "預設無標題"
},
"name": "標題選擇器",
"prompt": {
"selector": {
"label": "目前標題語言: {{language}}",
"none": "無",
"title": "選擇標題語言"
}
},
"templates": {
"title": "打開標題選擇器"
}
},
"compact-sidebar": {
"description": "永遠讓側邊欄保持收合狀態",
"name": "收合側邊欄"
},
"crossfade": {
"description": "在歌曲之間使用交叉淡化",
"menu": {
"advanced": "進階"
},
"name": "交叉淡化 [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "淡入時間(毫秒)",
"fade-out-duration": "淡出時間(毫秒)",
"fade-scaling": {
"label": "淡化計算方式",
"linear": "線性",
"logarithmic": "對數"
},
"seconds-before-end": "交叉淡化持續時間 (秒)"
},
"title": "交叉淡化選項"
}
}
},
"disable-autoplay": {
"description": "讓歌曲開始時為暫停模式",
"menu": {
"apply-once": "只在啟動程式時套用"
},
"name": "停用自動播放"
},
"discord": {
"backend": {
"already-connected": "已嘗試可用連接",
"connected": "已連接至Discord",
"disconnected": "與Discord斷開連接"
},
"description": "使用Discord狀態與你的好友分享你正在收聽的音樂",
"menu": {
"auto-reconnect": "自動重新連接",
"clear-activity": "清除狀態",
"clear-activity-after-timeout": "在音樂暫停後清除狀態",
"connected": "已連接",
"disconnected": "已斷開連接",
"hide-duration-left": "隱藏音樂剩餘時間狀態",
"hide-github-button": "隱藏Github頁面按鈕",
"play-on-youtube-music": "顯示Play on YouTube Music按鈕",
"set-inactivity-timeout": "設定閒置狀態時長"
},
"name": "Discord狀態",
"prompt": {
"set-inactivity-timeout": {
"label": "設定多少秒後清除狀態:",
"title": "設定閒置狀態時長"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "完成"
},
"message": "啊!抱歉,下載失敗了…",
"title": "下載出現錯誤!"
},
"start-download-playlist": {
"buttons": {
"ok": "完成"
},
"detail": "({{playlistSize}} 首歌曲)",
"message": "正在下載播放清單 {{playlistTitle}}",
"title": "開始下載"
}
},
"feedback": {
"conversion-progress": "轉檔進度: {{percent}}%",
"converting": "轉檔中…",
"done": "完成下載: {{filePath}}",
"download-info": "正在下載 {{artist}} - {{title}} [{{videoId}}",
"download-progress": "下載進度: {{percent}}%",
"downloading": "下載中…",
"downloading-counter": "正在下載第 {{current}}/{{total}}…",
"downloading-playlist": "正在下載播放清單 \"{{playlistTitle}}\" - 共 {{playlistSize}} 首歌 ({{playlistId}})",
"error-while-downloading": "無法下載 \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "資料夾 {{playlistFolder}} 已經存在",
"getting-playlist-info": "正在獲取播放清單資訊…",
"loading": "載入中…",
"playlist-has-only-one-song": "播放清單內只有一首歌曲, 將直接下載",
"playlist-id-not-found": "沒有找到播放清單 ID",
"playlist-is-empty": "播放清單是空的",
"playlist-is-mix-or-private": "獲取播放清單資訊時發生錯誤: 請確認非私人播放清單或是\"為你推薦的合輯\"\n\n{{error}}",
"preparing-file": "正在準備檔案…",
"saving": "儲存中…",
"trying-to-get-playlist-id": "正在嘗試獲取播放清單 ID: {{playlistId}}",
"video-id-not-found": "未能找到該影片",
"writing-id3": "正在寫入 ID3 標籤…"
}
},
"description": "在應用程式內下載 MP3原始音樂檔",
"menu": {
"choose-download-folder": "選擇下載位置",
"download-playlist": "下載播放清單",
"presets": "預設格式",
"skip-existing": "跳過已存在的檔案"
},
"name": "歌曲下載",
"renderer": {
"can-not-update-progress": "無法更新進度"
},
"templates": {
"button": "下載"
}
},
"exponential-volume": {
"description": "使音量滑桿指數化,以便更容易選擇較低的音量。",
"name": "指數化音量調整"
},
"in-app-menu": {
"description": "使選單列變更為黑色或隨主題變色",
"menu": {
"hide-dom-window-controls": "隱藏DOM視窗控制"
},
"name": "程式內選單列"
},
"last-fm": {
"description": "新增對Last.fm的scrobbling支援",
"name": "Last.fm"
},
"lumiastream": {
"description": "新增對 Lumia Stream 的支援",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "為更多歌曲新增字幕支援",
"menu": {
"romanized-lyrics": "羅馬拼音字幕"
},
"name": "第三方字幕",
"renderer": {
"fetched-lyrics": "為Genius獲取字幕"
}
},
"navigation": {
"description": "將上一頁/下一頁按鈕新增至應用程式上方, 就像你最熟悉的瀏覽器",
"name": "導覽列"
},
"no-google-login": {
"description": "移除Google登入按鈕及連結",
"name": "停用Google登入"
},
"notifications": {
"description": "在歌曲播放時發送一個系統通知 (可互動通知僅限Windows)",
"menu": {
"interactive": "可互動通知",
"interactive-settings": {
"label": "通知互動設定",
"submenu": {
"hide-button-text": "隱藏按鈕文字",
"refresh-on-play-pause": "在播放/暫停時刷新",
"tray-controls": "點擊系統閘圖示時打開/關閉"
}
},
"priority": "通知優先權",
"toast-style": "通知樣式",
"unpause-notification": "在取消暫停時發送通知"
},
"name": "歌曲播放通知"
},
"picture-in-picture": {
"description": "允許應用程式切換至子母畫面模式",
"menu": {
"always-on-top": "永遠顯示在最上層",
"hotkey": {
"label": "快捷鍵",
"prompt": {
"keybind-options": {
"hotkey": "快捷鍵"
},
"label": "選擇一個快捷鍵來切換子母畫面模式",
"title": "子母畫面模式快捷鍵"
}
},
"save-window-position": "記住視窗位置",
"save-window-size": "記住視窗大小",
"use-native-pip": "使用瀏覽器原生子母畫面模式"
},
"name": "子母畫面",
"templates": {
"button": "子母畫面"
}
},
"playback-speed": {
"description": "傷心的人別聽慢歌, 新增一個滑桿控制歌曲速度",
"name": "控制歌曲速度",
"templates": {
"button": "速度"
}
},
"precise-volume": {
"description": "讓你可使用滑鼠滾輪/快捷鍵控制音量,並顯示目前音量大小",
"menu": {
"arrows-shortcuts": "方向鍵音量控制",
"custom-volume-steps": "自訂音量調整時層級",
"global-shortcuts": "全域快捷鍵"
},
"name": "進階音量控制",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "降低音量",
"increase": "增加音量"
},
"label": "選擇全域音量控制快捷鍵:",
"title": "全域音量控制快捷鍵"
},
"volume-steps": {
"label": "設定音量每一次增加/降低的層級",
"title": "自訂音量調整時層級"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "目前畫質: {{quality}}",
"message": "選擇影片畫質:",
"title": "選擇影片畫質"
}
}
},
"description": "允許在影片內進行畫質更改",
"name": "允許變更影片畫質"
},
"shortcuts": {
"description": "使用全域快捷鍵控制音樂 (播放/暫停/下一首/上一首) + 透過覆寫媒體快捷鍵停用媒體OSD + 允許Ctrl/CMD + F來搜尋 + 支援Linux MPRIS媒體快捷鍵 + 更多自訂快捷鍵給進階使用者。",
"menu": {
"override-media-keys": "覆寫媒體快捷鍵",
"set-keybinds": "設定全域歌曲控制"
},
"name": "Shortcuts (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "下一首",
"play-pause": "播放/暫停",
"previous": "上一首"
},
"label": "選擇全域音樂控制快捷鍵:",
"title": "全域快捷鍵"
}
}
},
"skip-disliked-songs": {
"description": "自動跳過不喜歡的歌曲",
"name": "自動跳過不喜歡的歌曲"
},
"skip-silences": {
"description": "自動跳過音樂無聲片段",
"name": "自動跳過無聲片段"
},
"sponsorblock": {
"description": "自動跳過贊助片段",
"name": "贊助阻擋"
},
"taskbar-mediacontrol": {
"description": "透過工作列應用程式圖式控制媒體播放",
"name": "工作列媒體控制"
},
"touchbar": {
"description": "為macOS使用者新增觸控列支援",
"name": "觸控列 (Touchbar) 支援"
},
"tuna-obs": {
"description": "與 OBS 的 Tuna 外掛連接",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "新增一個按鈕可以控制影片/歌曲切換和完全移除整個影片頁面的功能",
"menu": {
"align": {
"label": "按鈕位置",
"submenu": {
"left": "靠左",
"middle": "中間",
"right": "靠右"
}
},
"force-hide": "強制移除整個影片頁面",
"mode": {
"label": "樣式",
"submenu": {
"custom": "自訂樣式",
"disabled": "停用",
"native": "原生樣式"
}
}
},
"name": "歌曲/影片切換",
"templates": {
"button": "歌曲"
}
},
"visualizer": {
"description": "新增一個可視化音樂效果",
"menu": {
"visualizer-type": "樣式"
},
"name": "可視化音樂效果"
}
}
}

View File

@ -1,7 +1,6 @@
import path from 'node:path'; import path from 'node:path';
import url from 'node:url'; import url from 'node:url';
import fs from 'node:fs'; import fs from 'node:fs';
import process from 'node:process';
import { import {
BrowserWindow, BrowserWindow,
@ -300,7 +299,7 @@ async function createMainWindow() {
const { x: windowX, y: windowY } = windowPosition; const { x: windowX, y: windowY } = windowPosition;
const winSize = win.getSize(); const winSize = win.getSize();
const display = screen.getDisplayNearestPoint(windowPosition); const display = screen.getDisplayNearestPoint(windowPosition);
const scaleFactor = display.scaleFactor; const scaleFactor = is.windows() ? display.scaleFactor: 1;
const scaledWidth = Math.floor(windowSize.width / scaleFactor); const scaledWidth = Math.floor(windowSize.width / scaleFactor);
const scaledHeight = Math.floor(windowSize.height / scaleFactor); const scaledHeight = Math.floor(windowSize.height / scaleFactor);
@ -403,55 +402,16 @@ async function createMainWindow() {
removeContentSecurityPolicy(); removeContentSecurityPolicy();
win.webContents.on('dom-ready', async () => { win.webContents.on('dom-ready', () => {
if (useInlineMenu) { if (useInlineMenu && is.windows()) {
win.setTitleBarOverlay({ win.setTitleBarOverlay({
...defaultTitleBarOverlayOptions, ...defaultTitleBarOverlayOptions,
height: Math.floor( height: Math.floor(
defaultTitleBarOverlayOptions.height! * defaultTitleBarOverlayOptions.height! *
win.webContents.getZoomFactor(), win.webContents.getZoomFactor(),
), ),
}); });
} }
// Inject index.html file as string using insertAdjacentHTML
// In dev mode, get string from process.env.VITE_DEV_SERVER_URL, else use fs.readFileSync
if (is.dev() && process.env.ELECTRON_RENDERER_URL) {
// HACK: to make vite work with electron renderer (supports hot reload)
await win.webContents.executeJavaScript(`
console.log('Loading vite from dev server');
const viteScript = document.createElement('script');
viteScript.type = 'module';
viteScript.src = '${process.env.ELECTRON_RENDERER_URL}/@vite/client';
const rendererScript = document.createElement('script');
rendererScript.type = 'module';
rendererScript.src = '${process.env.ELECTRON_RENDERER_URL}/renderer.ts';
document.body.appendChild(viteScript);
document.body.appendChild(rendererScript);
0
`);
} else {
const rendererPath = path.join(__dirname, '..', 'renderer');
const indexHTML = parse(
fs.readFileSync(path.join(rendererPath, 'index.html'), 'utf-8'),
);
const scriptSrc = indexHTML.querySelector('script')!;
const scriptPath = path.join(
rendererPath,
scriptSrc.getAttribute('src')!,
);
const scriptString = fs.readFileSync(scriptPath, 'utf-8');
await win.webContents.executeJavaScriptInIsolatedWorld(
0,
[
{
code: scriptString + ';0',
url: url.pathToFileURL(scriptPath).toString(),
},
],
true,
);
}
}); });
win.webContents.loadURL(urlToLoad); win.webContents.loadURL(urlToLoad);
@ -630,6 +590,48 @@ app.whenReady().then(async () => {
} }
} }
ipcMain.on('get-renderer-script', (event) => {
// Inject index.html file as string using insertAdjacentHTML
// In dev mode, get string from process.env.VITE_DEV_SERVER_URL, else use fs.readFileSync
if (is.dev() && process.env.ELECTRON_RENDERER_URL) {
// HACK: to make vite work with electron renderer (supports hot reload)
event.returnValue = [null, `
console.log('${LoggerPrefix}', 'Loading vite from dev server');
(async () => {
await new Promise((resolve) => {
if (document.readyState === 'loading') {
console.log('${LoggerPrefix}', 'Waiting for DOM to load');
document.addEventListener('DOMContentLoaded', () => resolve(), { once: true });
} else {
resolve();
}
});
const viteScript = document.createElement('script');
viteScript.type = 'module';
viteScript.src = '${process.env.ELECTRON_RENDERER_URL}/@vite/client';
const rendererScript = document.createElement('script');
rendererScript.type = 'module';
rendererScript.src = '${process.env.ELECTRON_RENDERER_URL}/renderer.ts';
document.body.appendChild(viteScript);
document.body.appendChild(rendererScript);
})();
0
`];
} else {
const rendererPath = path.join(__dirname, '..', 'renderer');
const indexHTML = parse(
fs.readFileSync(path.join(rendererPath, 'index.html'), 'utf-8'),
);
const scriptSrc = indexHTML.querySelector('script')!;
const scriptPath = path.join(
rendererPath,
scriptSrc.getAttribute('src')!,
);
const scriptString = fs.readFileSync(scriptPath, 'utf-8');
event.returnValue = [url.pathToFileURL(scriptPath).toString(), scriptString + ';0'];
}
});
mainWindow = await createMainWindow(); mainWindow = await createMainWindow();
await setApplicationMenu(mainWindow); await setApplicationMenu(mainWindow);
await refreshMenu(mainWindow); await refreshMenu(mainWindow);

View File

@ -1,5 +1,3 @@
import process from 'node:process';
import is from 'electron-is'; import is from 'electron-is';
import { import {
app, app,
@ -11,6 +9,7 @@ import {
shell, shell,
} from 'electron'; } from 'electron';
import prompt from 'custom-electron-prompt'; import prompt from 'custom-electron-prompt';
import { satisfies } from 'semver';
import { allPlugins } from 'virtual:plugins'; import { allPlugins } from 'virtual:plugins';
@ -25,6 +24,8 @@ import promptOptions from './providers/prompt-options';
import { getAllMenuTemplate, loadAllMenuPlugins } from './loader/menu'; import { getAllMenuTemplate, loadAllMenuPlugins } from './loader/menu';
import { setLanguage, t } from '@/i18n'; import { setLanguage, t } from '@/i18n';
import packageJson from '../package.json';
export type MenuTemplate = Electron.MenuItemConstructorOptions[]; export type MenuTemplate = Electron.MenuItemConstructorOptions[];
// True only if in-app-menu was loaded on launch // True only if in-app-menu was loaded on launch
@ -33,10 +34,14 @@ const inAppMenuActive = config.plugins.isEnabled('in-app-menu');
const pluginEnabledMenu = ( const pluginEnabledMenu = (
plugin: string, plugin: string,
label = '', label = '',
description: string | undefined = undefined,
isNew = false,
hasSubmenu = false, hasSubmenu = false,
refreshMenu: (() => void) | undefined = undefined, refreshMenu: (() => void) | undefined = undefined,
): Electron.MenuItemConstructorOptions => ({ ): Electron.MenuItemConstructorOptions => ({
label: label || plugin, label: label || plugin,
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
toolTip: description,
type: 'checkbox', type: 'checkbox',
checked: config.plugins.isEnabled(plugin), checked: config.plugins.isEnabled(plugin),
click(item: Electron.MenuItem) { click(item: Electron.MenuItem) {
@ -68,12 +73,15 @@ export const mainMenuTemplate = async (
const menuResult = Object.entries(getAllMenuTemplate()).map( const menuResult = Object.entries(getAllMenuTemplate()).map(
([id, template]) => { ([id, template]) => {
const pluginLabel = allPlugins[id]?.name?.() ?? id; const plugin = allPlugins[id];
const pluginLabel = plugin?.name?.() ?? id;
const pluginDescription = plugin?.description?.() ?? undefined;
const isNew = plugin?.addedVersion ? satisfies(packageJson.version, plugin.addedVersion) : false;
if (!config.plugins.isEnabled(id)) { if (!config.plugins.isEnabled(id)) {
return [ return [
id, id,
pluginEnabledMenu(id, pluginLabel, true, innerRefreshMenu), pluginEnabledMenu(id, pluginLabel, pluginDescription, isNew, true, innerRefreshMenu),
] as const; ] as const;
} }
@ -81,10 +89,14 @@ export const mainMenuTemplate = async (
id, id,
{ {
label: pluginLabel, label: pluginLabel,
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
toolTip: pluginDescription,
submenu: [ submenu: [
pluginEnabledMenu( pluginEnabledMenu(
id, id,
t('main.menu.plugins.enabled'), t('main.menu.plugins.enabled'),
undefined,
false,
true, true,
innerRefreshMenu, innerRefreshMenu,
), ),
@ -108,9 +120,12 @@ export const mainMenuTemplate = async (
const predefinedTemplate = menuResult.find((it) => it[0] === id); const predefinedTemplate = menuResult.find((it) => it[0] === id);
if (predefinedTemplate) return predefinedTemplate[1]; if (predefinedTemplate) return predefinedTemplate[1];
const pluginLabel = allPlugins[id]?.name?.() ?? id; const plugin = allPlugins[id];
const pluginLabel = plugin?.name?.() ?? id;
const pluginDescription = plugin?.description?.() ?? undefined;
const isNew = plugin?.addedVersion ? satisfies(packageJson.version, plugin.addedVersion) : false;
return pluginEnabledMenu(id, pluginLabel, true, innerRefreshMenu); return pluginEnabledMenu(id, pluginLabel, pluginDescription, isNew, true, innerRefreshMenu);
}); });
const availableLanguages = Object.keys(languageResources); const availableLanguages = Object.keys(languageResources);
@ -377,7 +392,7 @@ export const mainMenuTemplate = async (
availableLanguages availableLanguages
.map( .map(
(lang): Electron.MenuItemConstructorOptions => ({ (lang): Electron.MenuItemConstructorOptions => ({
label: `${languageResources[lang].translation.language.name} (${languageResources[lang].translation.language['local-name']})`, label: `${languageResources[lang].translation.language?.name ?? 'Unknown'} (${languageResources[lang].translation.language?.['local-name'] ?? 'Unknown'})`,
type: 'checkbox', type: 'checkbox',
checked: (config.get('options.language') ?? 'en') === lang, checked: (config.get('options.language') ?? 'en') === lang,
click() { click() {

View File

@ -1,3 +1,5 @@
import { contextBridge, webFrame } from 'electron';
import { blockers } from './types'; import { blockers } from './types';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { import {
@ -107,31 +109,25 @@ export default createPlugin({
}, },
}, },
preload: { preload: {
script: 'window.JSON.parse = window._proxyJsonParse; window._proxyJsonParse = undefined; window.Response.prototype.json = window._proxyResponseJson; window._proxyResponseJson = undefined; 0',
async start({ getConfig }) { async start({ getConfig }) {
const config = await getConfig(); const config = await getConfig();
if (config.blocker === blockers.WithBlocklists) { if (config.blocker === blockers.WithBlocklists) {
// Preload adblocker to inject scripts/styles // Preload adblocker to inject scripts/styles
await injectCliqzPreload(); await injectCliqzPreload();
} else if (config.blocker === blockers.InPlayer && !isInjected()) {
inject(contextBridge);
await webFrame.executeJavaScript(this.script);
} }
}, },
async onConfigChange(newConfig) { async onConfigChange(newConfig) {
if (newConfig.blocker === blockers.WithBlocklists) { if (newConfig.blocker === blockers.WithBlocklists) {
await injectCliqzPreload(); await injectCliqzPreload();
} else if (newConfig.blocker === blockers.InPlayer && !isInjected()) {
inject(contextBridge);
await webFrame.executeJavaScript(this.script);
} }
}, },
}, },
renderer: {
async start({ getConfig }) {
const config = await getConfig();
if (config.blocker === blockers.InPlayer && !isInjected()) {
inject();
}
},
onConfigChange(newConfig) {
if (newConfig.blocker === blockers.InPlayer && !isInjected()) {
inject();
}
},
}
}); });

View File

@ -1,3 +1,5 @@
export const inject: () => void; import type { ContextBridge } from 'electron';
export const inject: (contextBridge: ContextBridge) => void;
export const isInjected: () => boolean; export const isInjected: () => boolean;

View File

@ -10,9 +10,13 @@
let injected = false; let injected = false;
export const isInjected = () => isInjected; export const isInjected = () => injected;
export const inject = () => { /**
* @param {Electron.ContextBridge} contextBridge
* @returns {*}
*/
export const inject = (contextBridge) => {
injected = true; injected = true;
{ {
const pruner = function (o) { const pruner = function (o) {
@ -28,17 +32,17 @@ export const inject = () => {
return o; return o;
}; };
JSON.parse = new Proxy(JSON.parse, { contextBridge.exposeInMainWorld('_proxyJsonParse', new Proxy(JSON.parse, {
apply() { apply() {
return pruner(Reflect.apply(...arguments)); return pruner(Reflect.apply(...arguments));
}, },
}); }));
Response.prototype.json = new Proxy(Response.prototype.json, { contextBridge.exposeInMainWorld('_proxyResponseJson', new Proxy(Response.prototype.json, {
apply() { apply() {
return Reflect.apply(...arguments).then((o) => pruner(o)); return Reflect.apply(...arguments).then((o) => pruner(o));
}, },
}); }));
} }
(function () { (function () {

View File

@ -0,0 +1,179 @@
import { t } from '@/i18n';
import { createPlugin } from '@/utils';
import { ElementFromHtml } from '@/plugins/utils/renderer';
import undislikeHTML from './templates/undislike.html?raw';
import dislikeHTML from './templates/dislike.html?raw';
import likeHTML from './templates/like.html?raw';
import unlikeHTML from './templates/unlike.html?raw';
export default createPlugin({
name: () => t('plugins.album-actions.name'),
description: () => t('plugins.album-actions.description'),
restartNeeded: false,
addedVersion: '3.2.0',
config: {
enabled: false,
},
renderer: {
observer: null as MutationObserver | null,
loadObserver: null as MutationObserver | null,
changeObserver: null as MutationObserver | null,
waiting: false as boolean,
start() {
//Waits for pagechange
this.onPageChange();
this.observer = new MutationObserver(() => {
this.onPageChange();
});
this.observer.observe(document.querySelector('#browse-page'), {
attributes: false,
childList: true,
subtree: true,
});
},
onPageChange() {
if (this.waiting) {
return;
} else {
this.waiting = true;
}
this.waitForElem('#continuations').then((continuations: HTMLElement) => {
this.waiting = false;
//Gets the for buttons
let buttons: Array<HTMLElement> = [
ElementFromHtml(undislikeHTML),
ElementFromHtml(dislikeHTML),
ElementFromHtml(likeHTML),
ElementFromHtml(unlikeHTML),
];
//Finds the playlist
const playlist =
document.querySelector('ytmusic-shelf-renderer') ??
document.querySelector('ytmusic-playlist-shelf-renderer');
//Adds an observer for every button so it gets updated when one is clicked
this.changeObserver?.disconnect();
this.changeObserver = new MutationObserver(() => {
this.stop();
this.start();
});
const allButtons = playlist.querySelectorAll(
'yt-button-shape.ytmusic-like-button-renderer',
);
for (const btn of allButtons)
this.changeObserver.observe(btn, {
attributes: true,
childList: false,
subtree: false,
});
//Determine if button is needed and colors the percentage
const listsLength = playlist.querySelectorAll(
'#button-shape-dislike > button',
).length;
if (continuations.children.length == 0 && listsLength > 0) {
const counts = [
playlist?.querySelectorAll(
'#button-shape-dislike[aria-pressed=true] > button',
).length,
playlist?.querySelectorAll(
'#button-shape-dislike[aria-pressed=false] > button',
).length,
playlist?.querySelectorAll(
'#button-shape-like[aria-pressed=false] > button',
).length,
playlist?.querySelectorAll(
'#button-shape-like[aria-pressed=true] > button',
).length,
];
let i = 0;
for (const count of counts) {
if (count == 0) {
buttons.splice(i, 1);
i--;
} else {
buttons[i].children[0].children[0].style.setProperty(
'-webkit-mask-size',
`100% ${100 - (count / listsLength) * 100}%`,
);
}
i++;
}
}
const menu = document.querySelector('.detail-page-menu');
if (menu && !document.querySelector('.like-menu')) {
for (const button of buttons) {
menu.appendChild(button);
button.addEventListener('click', this.loadFullList);
}
}
});
},
loadFullList(event) {
event.stopPropagation();
const id: string = event.currentTarget.id,
loader = document.getElementById('continuations');
this.loadObserver = new MutationObserver(() => {
this.applyToList(id, loader);
});
this.applyToList(id, loader);
this.loadObserver.observe(loader, {
attributes: true,
childList: true,
subtree: true,
});
loader?.style.setProperty('top', '0');
loader?.style.setProperty('left', '50%');
loader?.style.setProperty('position', 'absolute');
},
applyToList(id: string, loader: HTMLElement) {
if (loader.children.length != 0) return;
this.loadObserver?.disconnect();
let playlistbuttons: NodeListOf<Element> | undefined;
const playlist = document.querySelector('ytmusic-shelf-renderer')
? document.querySelector('ytmusic-shelf-renderer')
: document.querySelector('ytmusic-playlist-shelf-renderer');
switch (id) {
case 'allundislike':
playlistbuttons = playlist?.querySelectorAll(
'#button-shape-dislike[aria-pressed=true] > button',
);
break;
case 'alldislike':
playlistbuttons = playlist?.querySelectorAll(
'#button-shape-dislike[aria-pressed=false] > button',
);
break;
case 'alllike':
playlistbuttons = playlist?.querySelectorAll(
'#button-shape-like[aria-pressed=false] > button',
);
break;
case 'allunlike':
playlistbuttons = playlist?.querySelectorAll(
'#button-shape-like[aria-pressed=true] > button',
);
break;
default:
}
playlistButtons?.forEach((elem) => elem.click());
},
stop() {
this.observer?.disconnect();
this.changeObserver?.disconnect();
for (const button of document.querySelectorAll('.like-menu')) {
button.remove();
}
},
waitForElem(selector: string) {
return new Promise((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
});
});
},
},
});

View File

@ -0,0 +1,74 @@
<button
id="alldislike"
data-type="dislike"
data-filled="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Dislike all"
>
<div
class="yt-spec-button-shape-next__icon"
style="color: var(--ytmusic-setting-item-toggle-active)"
aria-hidden="true"
>
<div
class="yt-spec-button-shape-next__icon"
style="
color: white;
-webkit-mask: linear-gradient(grey, grey);
-webkit-mask-size: 100% 50%;
-webkit-mask-repeat: no-repeat;
z-index: 1;
position: absolute;
"
aria-hidden="true"
>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="
pointer-events: none;
display: block;
width: 100%;
height: 100%;
"
>
<g class="style-scope yt-icon">
<path
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%"
>
<g class="style-scope yt-icon">
<path
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<yt-touch-feedback-shape style="border-radius: inherit">
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -0,0 +1,74 @@
<button
id="alllike"
data-type="like"
data-filled="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Like all"
>
<div
class="yt-spec-button-shape-next__icon"
style="color: var(--ytmusic-setting-item-toggle-active)"
aria-hidden="true"
>
<div
class="yt-spec-button-shape-next__icon"
style="
color: white;
-webkit-mask: linear-gradient(grey, grey);
-webkit-mask-size: 100% 50%;
-webkit-mask-repeat: no-repeat;
z-index: 1;
position: absolute;
"
aria-hidden="true"
>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="
pointer-events: none;
display: block;
width: 100%;
height: 100%;
"
>
<g class="style-scope yt-icon">
<path
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%"
>
<g class="style-scope yt-icon">
<path
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<yt-touch-feedback-shape style="border-radius: inherit">
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -0,0 +1,74 @@
<button
id="allundislike"
data-type="dislike"
data-filled="true"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Undislike all"
>
<div
class="yt-spec-button-shape-next__icon"
style="color: var(--ytmusic-setting-item-toggle-active)"
aria-hidden="true"
>
<div
class="yt-spec-button-shape-next__icon"
style="
color: white;
-webkit-mask: linear-gradient(grey, grey);
-webkit-mask-size: 100% 50%;
-webkit-mask-repeat: no-repeat;
z-index: 1;
position: absolute;
"
aria-hidden="true"
>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="
pointer-events: none;
display: block;
width: 100%;
height: 100%;
"
>
<g class="style-scope yt-icon">
<path
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%"
>
<g class="style-scope yt-icon">
<path
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<yt-touch-feedback-shape style="border-radius: inherit">
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -0,0 +1,74 @@
<button
id="allunlike"
data-type="like"
data-filled="true"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Unlike all"
>
<div
class="yt-spec-button-shape-next__icon"
style="color: var(--ytmusic-setting-item-toggle-active)"
aria-hidden="true"
>
<div
class="yt-spec-button-shape-next__icon"
style="
color: white;
-webkit-mask: linear-gradient(grey, grey);
-webkit-mask-size: 100% 50%;
-webkit-mask-repeat: no-repeat;
z-index: 1;
position: absolute;
"
aria-hidden="true"
>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="
pointer-events: none;
display: block;
width: 100%;
height: 100%;
"
>
<g class="style-scope yt-icon">
<path
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<div style="width: 24px; height: 24px">
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%"
>
<g class="style-scope yt-icon">
<path
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
class="style-scope yt-icon"
></path>
</g>
</svg>
</div>
</div>
<yt-touch-feedback-shape style="border-radius: inherit">
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -1,11 +1,13 @@
import { FastAverageColor } from 'fast-average-color'; import { FastAverageColor } from 'fast-average-color';
import Color from 'color';
import style from './style.css?inline'; import style from './style.css?inline';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { t } from '@/i18n'; import { t } from '@/i18n';
import type { VideoDataChanged } from '@/types/video-data-changed'; const COLOR_KEY = '--ytmusic-album-color';
const DARK_COLOR_KEY = '--ytmusic-album-color-dark';
export default createPlugin({ export default createPlugin({
name: () => t('plugins.album-color-theme.name'), name: () => t('plugins.album-color-theme.name'),
@ -16,69 +18,8 @@ export default createPlugin({
}, },
stylesheets: [style], stylesheets: [style],
renderer: { renderer: {
hexToHSL: (H: string) => { color: null as Color | null,
// Convert hex to RGB first darkColor: null as Color | null,
let r = 0;
let g = 0;
let b = 0;
if (H.length == 4) {
r = Number('0x' + H[1] + H[1]);
g = Number('0x' + H[2] + H[2]);
b = Number('0x' + H[3] + H[3]);
} else if (H.length == 7) {
r = Number('0x' + H[1] + H[2]);
g = Number('0x' + H[3] + H[4]);
b = Number('0x' + H[5] + H[6]);
}
// Then to HSL
r /= 255;
g /= 255;
b /= 255;
const cmin = Math.min(r, g, b);
const cmax = Math.max(r, g, b);
const delta = cmax - cmin;
let h: number;
let s: number;
let l: number;
if (delta == 0) {
h = 0;
} else if (cmax == r) {
h = ((g - b) / delta) % 6;
} else if (cmax == g) {
h = ((b - r) / delta) + 2;
} else {
h = ((r - g) / delta) + 4;
}
h = Math.round(h * 60);
if (h < 0) {
h += 360;
}
l = (cmax + cmin) / 2;
s = delta == 0 ? 0 : delta / (1 - Math.abs((2 * l) - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
//return "hsl(" + h + "," + s + "%," + l + "%)";
return [h, s, l];
},
hue: 0,
saturation: 0,
lightness: 0,
changeElementColor: (
element: HTMLElement | null,
hue: number,
saturation: number,
lightness: number,
) => {
if (element) {
element.style.backgroundColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}
},
playerPage: null as HTMLElement | null, playerPage: null as HTMLElement | null,
navBarBackground: null as HTMLElement | null, navBarBackground: null as HTMLElement | null,
@ -103,113 +44,66 @@ export default createPlugin({
'#mini-guide-background', '#mini-guide-background',
); );
this.ytmusicAppLayout = document.querySelector<HTMLElement>('#layout'); this.ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
const isPageOpen =
this.ytmusicAppLayout?.hasAttribute('player-page-open');
if (isPageOpen) {
this.changeElementColor(
this.sidebarSmall,
this.hue,
this.saturation,
this.lightness - 30,
);
} else {
if (this.sidebarSmall) {
this.sidebarSmall.style.backgroundColor = 'black';
}
}
}
}
});
if (this.playerPage) {
observer.observe(this.playerPage, { attributes: true });
}
}, },
onPlayerApiReady(playerApi) { onPlayerApiReady(playerApi) {
const fastAverageColor = new FastAverageColor(); const fastAverageColor = new FastAverageColor();
document.addEventListener( document.addEventListener('videodatachange', async (event) => {
'videodatachange', if (event.detail.name !== 'dataloaded') return;
(event: CustomEvent<VideoDataChanged>) => {
if (event.detail.name === 'dataloaded') { const playerResponse = playerApi.getPlayerResponse();
const playerResponse = playerApi.getPlayerResponse(); const thumbnail = playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
const thumbnail = if (!thumbnail) return;
playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
if (thumbnail) { const albumColor = await fastAverageColor.getColorAsync(thumbnail.url)
fastAverageColor .catch((err) => {
.getColorAsync(thumbnail.url) console.error(err);
.then((albumColor) => { return null;
if (albumColor) { });
const [hue, saturation, lightness] = ([
this.hue, if (albumColor) {
this.saturation, const target = Color(albumColor.hex);
this.lightness,
] = this.hexToHSL(albumColor.hex)); this.darkColor = target.darken(0.3).rgb();
this.changeElementColor( this.color = target.darken(0.15).rgb();
this.playerPage,
hue, while (this.color.luminosity() > 0.5) {
saturation, this.color = this.color?.darken(0.05);
lightness - 30, this.darkColor = this.darkColor?.darken(0.05);
);
this.changeElementColor(
this.navBarBackground,
hue,
saturation,
lightness - 15,
);
this.changeElementColor(
this.ytmusicPlayerBar,
hue,
saturation,
lightness - 15,
);
this.changeElementColor(
this.playerBarBackground,
hue,
saturation,
lightness - 15,
);
this.changeElementColor(
this.sidebarBig,
hue,
saturation,
lightness - 15,
);
if (
this.ytmusicAppLayout?.hasAttribute('player-page-open')
) {
this.changeElementColor(
this.sidebarSmall,
hue,
saturation,
lightness - 30,
);
}
const ytRightClickList =
document.querySelector<HTMLElement>(
'tp-yt-paper-listbox',
);
this.changeElementColor(
ytRightClickList,
hue,
saturation,
lightness - 15,
);
} else {
if (this.playerPage) {
this.playerPage.style.backgroundColor = '#000000';
}
}
})
.catch((e) => console.error(e));
}
} }
},
); document.documentElement.style.setProperty(COLOR_KEY, `${~~this.color.red()}, ${~~this.color.green()}, ${~~this.color.blue()}`);
document.documentElement.style.setProperty(DARK_COLOR_KEY, `${~~this.darkColor.red()}, ${~~this.darkColor.green()}, ${~~this.darkColor.blue()}`);
} else {
document.documentElement.style.setProperty(COLOR_KEY, '0, 0, 0');
document.documentElement.style.setProperty(DARK_COLOR_KEY, '0, 0, 0');
}
this.updateColor();
});
},
getColor(key: string, alpha = 1) {
return `rgba(var(${key}), ${alpha})`;
},
updateColor() {
const change = (element: HTMLElement | null, color: string) => {
if (element) {
element.style.backgroundColor = color;
}
};
change(this.playerPage, this.getColor(DARK_COLOR_KEY));
change(this.navBarBackground, this.getColor(COLOR_KEY));
change(this.ytmusicPlayerBar, this.getColor(COLOR_KEY));
change(this.playerBarBackground, this.getColor(COLOR_KEY));
change(this.sidebarBig, this.getColor(COLOR_KEY));
if (this.ytmusicAppLayout?.hasAttribute('player-page-open')) {
change(this.sidebarSmall, this.getColor(DARK_COLOR_KEY));
}
const ytRightClickList = document.querySelector<HTMLElement>('tp-yt-paper-listbox');
change(ytRightClickList, this.getColor(COLOR_KEY));
}, },
}, },
}); });

View File

@ -4,28 +4,24 @@ yt-page-navigation-progress {
} }
#player-page { #player-page {
transition: transition: transform 300ms,
transform 300ms, background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
} }
#nav-bar-background { #nav-bar-background {
transition: transition: opacity 200ms,
opacity 200ms, background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
} }
#mini-guide-background { #mini-guide-background {
transition: transition: opacity 200ms,
opacity 200ms, background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
border-right: 0px !important; border-right: 0px !important;
} }
#guide-wrapper { #guide-wrapper {
transition: transition: opacity 200ms,
opacity 200ms, background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
} }
#img, #img,
@ -37,3 +33,35 @@ yt-page-navigation-progress {
#items { #items {
border-radius: 10px !important; border-radius: 10px !important;
} }
/* fix blur navigation bar */
ytmusic-app-layout > [slot='player-page'] {
padding-top: 90px;
margin-top: calc(-90px + var(--menu-bar-height, 0px)) !important;
}
/* fix icon color */
.duration.ytmusic-player-queue-item, .byline.ytmusic-player-queue-item {
color: rgba(255, 255, 255, 0.5) !important;
--yt-endpoint-color: rgba(255, 255, 255, 0.5) !important;
--yt-endpoint-hover-color: rgba(255, 255, 255, 0.5) !important;
--yt-endpoint-visited-color: rgba(255, 255, 255, 0.5) !important;
}
.icon.ytmusic-menu-navigation-item-renderer {
color: rgba(255, 255, 255, 0.5) !important;
}
.menu.ytmusic-player-bar {
--iron-icon-fill-color: rgba(255, 255, 255, 0.5) !important;
}
ytmusic-player-bar {
color: rgba(255, 255, 255, 0.5) !important;
}
.time-info.ytmusic-player-bar {
color: rgba(255, 255, 255, 0.5) !important;
}
.volume-slider.ytmusic-player-bar, .expand-volume-slider.ytmusic-player-bar {
--paper-slider-container-color: rgba(255, 255, 255, 0.5) !important;
}

View File

@ -145,6 +145,73 @@ export default createPlugin({
observer: null as MutationObserver | null, observer: null as MutationObserver | null,
start() { start() {
const injectBlurImage = () => {
const songImage = document.querySelector<HTMLImageElement>(
'#song-image',
);
const image = document.querySelector<HTMLImageElement>(
'#song-image yt-img-shadow > img',
);
if (!songImage) return null;
if (!image) return null;
const blurImage = document.createElement('img');
blurImage.classList.add('html5-blur-image');
blurImage.src = image.src;
const applyImageAttribute = () => {
const rect = image.getBoundingClientRect();
const newWidth = Math.floor(image.width || rect.width);
const newHeight = Math.floor(image.height || rect.height);
if (newWidth === 0 || newHeight === 0) return;
if (this.isFullscreen) blurImage.classList.add('fullscreen');
else blurImage.classList.remove('fullscreen');
const leftOffset = (newWidth * (this.sizeRatio - 1)) / 2;
const topOffset = (newHeight * (this.sizeRatio - 1)) / 2;
blurImage.style.setProperty('--left', `${-1 * leftOffset}px`);
blurImage.style.setProperty('--top', `${-1 * topOffset}px`);
blurImage.style.setProperty('--width', `${newWidth * this.sizeRatio}px`);
blurImage.style.setProperty('--height', `${newHeight * this.sizeRatio}px`);
blurImage.style.setProperty('--blur', `${this.blur}px`);
blurImage.style.setProperty('--opacity', `${this.opacity}`);
};
this.update = applyImageAttribute;
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes') {
applyImageAttribute();
}
});
});
const resizeObserver = new ResizeObserver(() => {
applyImageAttribute();
});
applyImageAttribute();
observer.observe(songImage, { attributes: true });
resizeObserver.observe(songImage);
window.addEventListener('resize', applyImageAttribute);
/* injecting */
songImage.prepend(blurImage);
/* cleanup */
return () => {
observer.disconnect();
resizeObserver.disconnect();
window.removeEventListener('resize', applyImageAttribute);
if (blurImage.isConnected) blurImage.remove();
};
};
const injectBlurVideo = (): (() => void) | null => { const injectBlurVideo = (): (() => void) | null => {
const songVideo = document.querySelector<HTMLDivElement>('#song-video'); const songVideo = document.querySelector<HTMLDivElement>('#song-video');
const video = document.querySelector<HTMLVideoElement>( const video = document.querySelector<HTMLVideoElement>(
@ -172,7 +239,6 @@ export default createPlugin({
cancelAnimationFrame(lastEffectWorkId); cancelAnimationFrame(lastEffectWorkId);
lastEffectWorkId = requestAnimationFrame(() => { lastEffectWorkId = requestAnimationFrame(() => {
// console.log('context', context);
if (!context) return; if (!context) return;
const width = this.qualityRatio; const width = this.qualityRatio;
@ -280,13 +346,20 @@ export default createPlugin({
}; };
}; };
const isVideoMode = () => {
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
if (!songVideo) return false;
return getComputedStyle(songVideo).display !== 'none';
};
const playerPage = document.querySelector<HTMLElement>('#player-page'); const playerPage = document.querySelector<HTMLElement>('#player-page');
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout'); const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open'); const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
if (isPageOpen) { if (isPageOpen) {
this.unregister?.(); this.unregister?.();
this.unregister = injectBlurVideo() ?? null; this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
} }
const observer = new MutationObserver((mutationsList) => { const observer = new MutationObserver((mutationsList) => {
@ -296,7 +369,7 @@ export default createPlugin({
ytmusicAppLayout?.hasAttribute('player-page-open'); ytmusicAppLayout?.hasAttribute('player-page-open');
if (isPageOpen) { if (isPageOpen) {
this.unregister?.(); this.unregister?.();
this.unregister = injectBlurVideo() ?? null; this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
} else { } else {
this.unregister?.(); this.unregister?.();
this.unregister = null; this.unregister = null;

View File

@ -24,3 +24,17 @@
#song-video .html5-video-container > video { #song-video .html5-video-container > video {
top: 0 !important; top: 0 !important;
} }
#song-image .html5-blur-image {
position: absolute;
left: var(--left, 0px);
top: var(--top, 0px);
width: var(--width, 100%) !important;
height: var(--height, 100%) !important;
filter: blur(var(--blur, 100px));
opacity: var(--opacity, 1);
pointer-events: none;
}

View File

@ -1,3 +1,5 @@
import { inject } from 'simple-youtube-age-restriction-bypass';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { t } from '@/i18n'; import { t } from '@/i18n';
@ -6,6 +8,6 @@ export default createPlugin({
description: () => t('plugins.bypass-age-restrictions.description'), description: () => t('plugins.bypass-age-restrictions.description'),
restartNeeded: true, restartNeeded: true,
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript // See https://github.com/organization/Simple-YouTube-Age-Restriction-Bypass#userscript
renderer: () => import('simple-youtube-age-restriction-bypass'), renderer: () => inject(),
}); });

View File

@ -1,4 +1,3 @@
declare module 'simple-youtube-age-restriction-bypass' { declare module 'simple-youtube-age-restriction-bypass' {
const nothing: never; export const inject: () => void;
export default nothing;
} }

View File

@ -33,7 +33,7 @@ const menuObserver = new MutationObserver(() => {
} }
const menuUrl = document.querySelector<HTMLAnchorElement>( const menuUrl = document.querySelector<HTMLAnchorElement>(
'tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint', 'tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint',
)?.href; )?.href;
if (!menuUrl?.includes('watch?') && doneFirstLoad) { if (!menuUrl?.includes('watch?') && doneFirstLoad) {
return; return;
@ -42,11 +42,9 @@ const menuObserver = new MutationObserver(() => {
menu.prepend(downloadButton); menu.prepend(downloadButton);
progress = document.querySelector('#ytmcustom-download'); progress = document.querySelector('#ytmcustom-download');
if (doneFirstLoad) { if (!doneFirstLoad) {
return; setTimeout(() => (doneFirstLoad ||= true), 500);
} }
setTimeout(() => (doneFirstLoad ||= true), 500);
}); });
export const onRendererLoad = ({ export const onRendererLoad = ({
@ -56,7 +54,7 @@ export const onRendererLoad = ({
let videoUrl = getSongMenu() let videoUrl = getSongMenu()
// Selector of first button which is always "Start Radio" // Selector of first button which is always "Start Radio"
?.querySelector( ?.querySelector(
'ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint', 'ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint',
) )
?.getAttribute('href'); ?.getAttribute('href');
if (videoUrl) { if (videoUrl) {
@ -77,7 +75,7 @@ export const onRendererLoad = ({
ipc.on('downloader-feedback', (feedback: string) => { ipc.on('downloader-feedback', (feedback: string) => {
if (progress) { if (progress) {
progress.innerHTML = feedback || 'Download'; progress.innerHTML = feedback || t('plugins.downloader.templates.button');
} else { } else {
console.warn( console.warn(
LoggerPrefix, LoggerPrefix,

View File

@ -15,7 +15,7 @@ export const onMenu = async ({
if (is.linux()) { if (is.linux()) {
return [ return [
{ {
label: t('plugins.in-app-menu.hide-dom-window-controls'), label: t('plugins.in-app-menu.menu.hide-dom-window-controls'),
type: 'checkbox', type: 'checkbox',
checked: config.hideDOMWindowControls, checked: config.hideDOMWindowControls,
click(item) { click(item) {

View File

@ -7,13 +7,14 @@ import type { MenuItem } from 'electron';
interface PanelOptions { interface PanelOptions {
placement?: 'bottom' | 'right'; placement?: 'bottom' | 'right';
order?: number; order?: number;
openOnHover?: boolean;
} }
export const createPanel = ( export const createPanel = (
parent: HTMLElement, parent: HTMLElement,
anchor: HTMLElement, anchor: HTMLElement,
items: MenuItem[], items: MenuItem[],
options: PanelOptions = { placement: 'bottom', order: 0 }, options: PanelOptions = { placement: 'bottom', order: 0, openOnHover: false },
) => { ) => {
const childPanels: HTMLElement[] = []; const childPanels: HTMLElement[] = [];
const panel = document.createElement('menu-panel'); const panel = document.createElement('menu-panel');
@ -51,6 +52,29 @@ export const createPanel = (
menu.appendChild(iconWrapper); menu.appendChild(iconWrapper);
menu.append(item.label); menu.append(item.label);
if (item.sublabel) {
menu.classList.add('badge');
const menuBadge = document.createElement('menu-item-badge');
menuBadge.append(item.sublabel);
menu.append(menuBadge);
}
if (item.toolTip) {
const menuTooltip = document.createElement('menu-item-tooltip');
menuTooltip.append(item.toolTip);
menu.addEventListener('mouseenter', () => {
const rect = menu.getBoundingClientRect();
menuTooltip.style.setProperty('max-width', `${rect.width - 8}px`);
menuTooltip.style.setProperty('--x', `${rect.left}px`);
menuTooltip.style.setProperty('--y', `${rect.top + rect.height}px`);
menuTooltip.classList.add('show');
});
menu.addEventListener('mouseleave', () => {
menuTooltip.classList.remove('show');
});
parent.append(menuTooltip);
}
menu.addEventListener('click', async () => { menu.addEventListener('click', async () => {
await window.ipcRenderer.invoke('menu-event', item.commandId); await window.ipcRenderer.invoke('menu-event', item.commandId);
const menuItem = (await window.ipcRenderer.invoke( const menuItem = (await window.ipcRenderer.invoke(
@ -93,11 +117,12 @@ export const createPanel = (
{ {
placement: 'right', placement: 'right',
order: (options?.order ?? 0) + 1, order: (options?.order ?? 0) + 1,
openOnHover: true,
}, },
); );
childPanels.push(child); childPanels.push(child);
children.push(...children); childPanels.push(...children);
} }
return panel.appendChild(menu); return panel.appendChild(menu);
@ -132,6 +157,46 @@ export const createPanel = (
} }
}; };
if (options.openOnHover) {
let timeout: number | null = null;
anchor.addEventListener('mouseenter', () => {
if (timeout) window.clearTimeout(timeout);
timeout = window.setTimeout(() => {
if (!isOpened()) open();
}, 225);
});
anchor.addEventListener('mouseleave', () => {
if (timeout) window.clearTimeout(timeout);
let mouseX = 0, mouseY = 0;
const onMouseMove = (event: MouseEvent) => {
mouseX = event.clientX;
mouseY = event.clientY;
};
document.addEventListener('mousemove', onMouseMove);
timeout = window.setTimeout(() => {
document.removeEventListener('mousemove', onMouseMove);
const now = document.elementFromPoint(mouseX, mouseY);
if (now === panel || panel.contains(now)) {
const onLeave = () => {
document.addEventListener('mousemove', onMouseMove);
timeout = window.setTimeout(() => {
document.removeEventListener('mousemove', onMouseMove);
const now = document.elementFromPoint(mouseX, mouseY);
if (now === panel || panel.contains(now) || childPanels.some((it) => it.contains(now))) return;
if (isOpened()) close();
panel.removeEventListener('mouseleave', onLeave);
}, 225);
};
panel.addEventListener('mouseleave', onLeave);
return;
}
if (isOpened()) close();
}, 225);
});
}
anchor.addEventListener('click', () => { anchor.addEventListener('click', () => {
if (isOpened()) close(); if (isOpened()) close();
else open(); else open();

View File

@ -22,8 +22,7 @@ title-bar {
color: #f1f1f1; color: #f1f1f1;
font-size: 12px; font-size: 12px;
padding: 4px 12px; padding: 4px 12px 4px var(--offset-left, 12px);
padding-left: var(--offset-left, 12px);
background-color: var(--titlebar-background-color, #030303); background-color: var(--titlebar-background-color, #030303);
user-select: none; user-select: none;
@ -97,6 +96,8 @@ menu-panel.position-by-bottom {
} }
menu-item { menu-item {
position: relative;
-webkit-app-region: none; -webkit-app-region: none;
min-height: 32px; min-height: 32px;
height: 32px; height: 32px;
@ -109,6 +110,9 @@ menu-item {
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
} }
menu-item.badge {
grid-template-columns: 32px 1fr auto minmax(32px, auto);
}
menu-item:hover { menu-item:hover {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
} }
@ -128,6 +132,56 @@ menu-separator {
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
} }
menu-item-badge {
display: flex;
justify-content: center;
align-items: center;
min-width: 16px;
height: 16px;
padding: 0 4px;
margin-left: 8px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.2);
color: #f1f1f1;
font-size: 10px;
font-weight: 500;
line-height: 1;
}
menu-item-tooltip {
position: fixed;
left: var(--x, 0);
top: var(--y, 0);
display: flex;
justify-content: center;
align-items: center;
min-width: 32px;
padding: 4px;
border-radius: 4px;
background-color: rgba(25, 25, 25, 0.8);
color: #f1f1f1;
font-size: 10px;
pointer-events: none;
z-index: 1000;
opacity: 0;
scale: 0.9;
transform-origin: 50% 0;
transition: opacity 0.225s ease-out, scale 0.225s ease-out;
}
menu-item-tooltip.show {
opacity: 1;
scale: 1.0;
}
/* classes */ /* classes */
.title-bar-icon { .title-bar-icon {

View File

@ -0,0 +1,149 @@
import { DataConnection, Peer } from 'peerjs';
import type { Permission, Profile, VideoData } from './types';
export type ConnectionEventMap = {
ADD_SONGS: { videoList: VideoData[], index?: number };
REMOVE_SONG: { index: number };
MOVE_SONG: { fromIndex: number; toIndex: number };
IDENTIFY: { profile: Profile } | undefined;
SYNC_PROFILE: { profiles: Record<string, Profile> } | undefined;
SYNC_QUEUE: { videoList: VideoData[] } | undefined;
SYNC_PROGRESS: { progress?: number; state?: number; index?: number; } | undefined;
PERMISSION: Permission | undefined;
};
export type ConnectionEventUnion = {
[Event in keyof ConnectionEventMap]: {
type: Event;
payload: ConnectionEventMap[Event];
after?: ConnectionEventUnion[];
};
}[keyof ConnectionEventMap];
type PromiseUtil<T> = {
promise: Promise<T>;
resolve: (id: T) => void;
reject: (err: unknown) => void;
}
export type ConnectionListener = (event: ConnectionEventUnion, conn: DataConnection) => void;
export type ConnectionMode = 'host' | 'guest' | 'disconnected';
export class Connection {
private peer: Peer;
private _mode: ConnectionMode = 'disconnected';
private connections: Record<string, DataConnection> = {};
private waitOpen: PromiseUtil<string> = {} as PromiseUtil<string>;
private listeners: ConnectionListener[] = [];
private connectionListeners: ((connection?: DataConnection) => void)[] = [];
constructor() {
this.peer = new Peer({ debug: 0 });
this.waitOpen.promise = new Promise<string>((resolve, reject) => {
this.waitOpen.resolve = resolve;
this.waitOpen.reject = reject;
});
this.peer.on('open', (id) => {
this._mode = 'host';
this.waitOpen.resolve(id);
});
this.peer.on('connection', (conn) => {
this._mode = 'host';
this.registerConnection(conn);
});
this.peer.on('error', (err) => {
this._mode = 'disconnected';
this.waitOpen.reject(err);
this.connectionListeners.forEach((listener) => listener());
console.log(err);
});
}
/* public */
async waitForReady() {
return this.waitOpen.promise;
}
async connect(id: string) {
this._mode = 'guest';
const conn = this.peer.connect(id);
await this.registerConnection(conn);
return conn;
}
async disconnect() {
if (this._mode === 'disconnected') throw new Error('Already disconnected');
this._mode = 'disconnected';
this.connections = {};
this.peer.destroy();
}
/* utils */
public get id() {
return this.peer.id;
}
public get mode() {
return this._mode;
}
public getConnections() {
return Object.values(this.connections);
}
public async broadcast<Event extends keyof ConnectionEventMap>(type: Event, payload: ConnectionEventMap[Event]) {
await Promise.all(
this.getConnections().map((conn) => conn.send({ type, payload }))
);
}
public on(listener: ConnectionListener) {
this.listeners.push(listener);
}
public onConnections(listener: (connections?: DataConnection) => void) {
this.connectionListeners.push(listener);
}
/* privates */
private async registerConnection(conn: DataConnection) {
return new Promise<DataConnection>((resolve, reject) => {
this.peer.once('error', (err) => {
this._mode = 'disconnected';
reject(err);
this.connectionListeners.forEach((listener) => listener());
});
conn.on('open', () => {
this.connections[conn.connectionId] = conn;
resolve(conn);
this.connectionListeners.forEach((listener) => listener(conn));
conn.on('data', (data) => {
if (!data || typeof data !== 'object' || !('type' in data) || !('payload' in data) || !data.type) {
console.warn('Music Together: Invalid data', data);
return;
}
for (const listener of this.listeners) {
listener(data as ConnectionEventUnion, conn);
}
});
});
const onClose = (err?: Error) => {
if (err) reject(err);
delete this.connections[conn.connectionId];
this.connectionListeners.forEach((listener) => listener(conn));
};
conn.on('error', onClose);
conn.on('close', onClose);
});
}
}

View File

@ -0,0 +1,138 @@
import { ElementFromHtml } from '@/plugins/utils/renderer';
import itemHTML from './templates/item.html?raw';
import popupHTML from './templates/popup.html?raw';
type Placement =
'top'
| 'bottom'
| 'right'
| 'left'
| 'center'
| 'middle'
| 'center-middle'
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right';
type PopupItem = (ItemRendererProps & { type: 'item'; })
| { type: 'divider'; }
| { type: 'custom'; element: HTMLElement; };
type PopupProps = {
data: PopupItem[];
anchorAt?: Placement;
popupAt?: Placement;
}
export const Popup = (props: PopupProps) => {
const popup = ElementFromHtml(popupHTML);
const container = popup.querySelector<HTMLElement>('.music-together-popup-container')!;
const items = props.data
.map((props) => {
if (props.type === 'item') return {
type: 'item' as const,
...ItemRenderer(props),
};
if (props.type === 'divider') return {
type: 'divider' as const,
element: ElementFromHtml('<div class="music-together-divider horizontal"></div>'),
};
if (props.type === 'custom') return {
type: 'custom' as const,
element: props.element,
};
return null;
})
.filter(Boolean);
container.append(...items.map(({ element }) => element));
popup.style.setProperty('opacity', '0');
popup.style.setProperty('pointer-events', 'none');
document.body.append(popup);
return {
element: popup,
container,
items,
show(x: number, y: number, anchor?: HTMLElement) {
let left = x;
let top = y;
if (anchor) {
if (props.anchorAt?.includes('right')) left += anchor.clientWidth;
if (props.anchorAt?.includes('bottom')) top += anchor.clientHeight;
if (props.anchorAt?.includes('center')) left += anchor.clientWidth / 2;
if (props.anchorAt?.includes('middle')) top += anchor.clientHeight / 2;
}
if (props.popupAt?.includes('right')) left -= popup.clientWidth;
if (props.popupAt?.includes('bottom')) top -= popup.clientHeight;
if (props.popupAt?.includes('center')) left -= popup.clientWidth / 2;
if (props.popupAt?.includes('middle')) top -= popup.clientHeight / 2;
popup.style.setProperty('left', `${left}px`);
popup.style.setProperty('top', `${top}px`);
popup.style.setProperty('opacity', '1');
popup.style.setProperty('pointer-events', 'unset');
setTimeout(() => {
const onClose = (event: MouseEvent) => {
const isPopupClick = event.composedPath().some((element) => element === popup);
if (!isPopupClick) {
this.dismiss();
document.removeEventListener('click', onClose);
}
};
document.addEventListener('click', onClose);
}, 16);
},
showAtAnchor(anchor: HTMLElement) {
const { x, y } = anchor.getBoundingClientRect();
this.show(x, y, anchor);
},
isShowing() {
return popup.style.getPropertyValue('opacity') === '1';
},
dismiss() {
popup.style.setProperty('opacity', '0');
popup.style.setProperty('pointer-events', 'none');
}
};
};
type ItemRendererProps = {
id?: string;
icon?: Element;
text: string;
onClick?: () => void;
};
export const ItemRenderer = (props: ItemRendererProps) => {
const element = ElementFromHtml(itemHTML);
const iconContainer = element.querySelector<HTMLElement>('div.icon')!;
const textContainer = element.querySelector<HTMLElement>('div.text')!;
if (props.icon) iconContainer.appendChild(props.icon);
textContainer.append(props.text);
if (props.onClick) {
element.addEventListener('click', () => {
props.onClick?.();
});
}
if (props.id) element.id = props.id;
return {
element,
setIcon(icon: Element) {
iconContainer.replaceChildren(icon);
},
setText(text: string) {
textContainer.replaceChildren(text);
},
id: props.id
};
};

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M480-640 280-440l56 56 104-103v407h80v-407l104 103 56-56-200-200ZM146-260q-32-49-49-105T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 59-17 115t-49 105l-58-58q22-37 33-78t11-84q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 43 11 84t33 78l-58 58Z"/>
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path
d="M280-400q-33 0-56.5-23.5T200-480q0-33 23.5-56.5T280-560q33 0 56.5 23.5T360-480q0 33-23.5 56.5T280-400Zm0 160q-100 0-170-70T40-480q0-100 70-170t170-70q67 0 121.5 33t86.5 87h352l120 120-180 180-80-60-80 60-85-60h-47q-32 54-86.5 87T280-240Zm0-80q56 0 98.5-34t56.5-86h125l58 41 82-61 71 55 75-75-40-40H435q-14-52-56.5-86T280-640q-66 0-113 47t-47 113q0 66 47 113t113 47Z"/>
</svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M560-160q-66 0-113-47t-47-113q0-66 47-113t113-47q23 0 42.5 5.5T640-458v-342h240v120H720v360q0 66-47 113t-113 47ZM80-320q0-99 38-186.5T221-659q65-65 152.5-103T560-800v80q-82 0-155 31.5t-127.5 86q-54.5 54.5-86 127T160-320H80Zm160 0q0-66 25.5-124.5t69-102Q378-590 436-615t124-25v80q-100 0-170 70t-70 170h-80Z"/>
</svg>

After

Width:  |  Height:  |  Size: 416 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path
d="M792-56 686-160H260q-92 0-156-64T40-380q0-77 47.5-137T210-594q3-8 6-15.5t6-16.5L56-792l56-56 736 736-56 56ZM260-240h346L284-562q-2 11-3 21t-1 21h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm185-161Zm419 191-58-56q17-14 25.5-32.5T840-340q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-27 0-52 6.5T380-693l-58-58q35-24 74.5-36.5T480-800q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 39-15 72.5T864-210ZM593-479Z"/>
</svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M440-120v-240h80v80h320v80H520v80h-80Zm-320-80v-80h240v80H120Zm160-160v-80H120v-80h160v-80h80v240h-80Zm160-80v-80h400v80H440Zm160-160v-240h80v80h160v80H680v80h-80Zm-480-80v-80h400v80H120Z"/>
</svg>

After

Width:  |  Height:  |  Size: 298 B

View File

@ -0,0 +1,681 @@
import prompt from 'custom-electron-prompt';
import { t } from '@/i18n';
import { createPlugin } from '@/utils';
import promptOptions from '@/providers/prompt-options';
import { AppAPI, getDefaultProfile, Permission, Profile, VideoData } from './types';
import { Queue } from './queue';
import { Connection, ConnectionEventUnion } from './connection';
import { createHostPopup } from './ui/host';
import { createGuestPopup } from './ui/guest';
import { createSettingPopup } from './ui/setting';
import settingHTML from './templates/setting.html?raw';
import style from './style.css?inline';
import type { YoutubePlayer } from '@/types/youtube-player';
import type { RendererContext } from '@/types/contexts';
import type { VideoDataChanged } from '@/types/video-data-changed';
import { DataConnection } from 'peerjs';
type RawAccountData = {
accountName: {
runs: { text: string }[];
};
accountPhoto: {
thumbnails: { url: string; width: number; height: number; }[];
};
settingsEndpoint: unknown;
manageAccountTitle: unknown;
trackingParams: string;
channelHandle: {
runs: { text: string }[];
};
};
export default createPlugin({
name: () => t('plugins.music-together.name'),
description: () => t('plugins.music-together.description'),
restartNeeded: false,
addedVersion: '3.2.0',
config: {
enabled: false
},
stylesheets: [style],
backend: {
async start({ ipc }) {
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
title,
label,
type: 'input',
...promptOptions()
}));
}
},
renderer: {
connection: null as Connection | null,
ipc: null as RendererContext<never>['ipc'] | null,
api: null as (HTMLElement & AppAPI) | null,
queue: null as Queue | null,
playerApi: null as YoutubePlayer | null,
showPrompt: (async () => null) as ((title: string, label: string) => Promise<string | null>),
elements: {} as {
setting: HTMLElement;
icon: SVGElement;
spinner: HTMLElement;
},
popups: {} as {
host: ReturnType<typeof createHostPopup>;
guest: ReturnType<typeof createGuestPopup>;
setting: ReturnType<typeof createSettingPopup>;
},
stateInterval: null as number | null,
updateNext: false,
ignoreChange: false,
rollbackInjector: null as (() => void) | null,
me: null as Omit<Profile, 'id'> | null,
profiles: {} as Record<string, Profile>,
permission: 'playlist' as Permission,
/* events */
videoChangeListener(event: CustomEvent<VideoDataChanged>) {
if (event.detail.name === 'dataloaded' || this.updateNext) {
if (this.connection?.mode === 'host') {
const videoList: VideoData[] = this.queue?.flatItems.map((it: any) => ({
videoId: it.videoId,
ownerId: this.connection!.id
} satisfies VideoData)) ?? [];
this.queue?.setVideoList(videoList, false);
this.queue?.syncQueueOwner();
this.connection.broadcast('SYNC_QUEUE', {
videoList
});
this.updateNext = event.detail.name === 'dataloaded';
}
}
},
videoStateChangeListener() {
if (this.connection?.mode !== 'guest') return;
if (this.ignoreChange) return;
if (this.permission !== 'all') return;
const state = this.playerApi?.getPlayerState();
if (state !== 1 && state !== 2) return;
this.connection.broadcast('SYNC_PROGRESS', {
// progress: this.playerApi?.getCurrentTime(),
state: this.playerApi?.getPlayerState()
// index: this.queue?.selectedIndex ?? 0,
});
},
/* connection */
async onHost() {
this.connection = new Connection();
const wait = await this.connection.waitForReady().catch(() => null);
if (!wait) return false;
if (!this.me) this.me = getDefaultProfile(this.connection.id);
const rawItems = this.queue?.flatItems?.map((it: any) => ({
videoId: it.videoId,
ownerId: this.connection!.id
} satisfies VideoData)) ?? [];
this.queue?.setOwner({
id: this.connection.id,
...this.me
});
this.queue?.setVideoList(rawItems, false);
this.queue?.syncQueueOwner();
this.queue?.initQueue();
this.queue?.injection();
this.profiles = {};
this.connection.onConnections((connection) => {
if (!connection) {
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
this.onStop();
return;
}
if (!connection.open) {
this.api?.openToast(t('plugins.music-together.toast.user-disconnected', {
name: this.profiles[connection.peer]?.name
}));
this.putProfile(connection.peer, undefined);
}
});
this.putProfile(this.connection.id, {
id: this.connection.id,
...this.me
});
const listener = async (event: ConnectionEventUnion, conn?: DataConnection) => {
this.ignoreChange = true;
switch (event.type) {
case 'ADD_SONGS': {
if (conn && this.permission === 'host-only') return;
await this.queue?.addVideos(event.payload.videoList, event.payload.index);
await this.connection?.broadcast('ADD_SONGS', event.payload);
break;
}
case 'REMOVE_SONG': {
if (conn && this.permission === 'host-only') return;
await this.queue?.removeVideo(event.payload.index);
await this.connection?.broadcast('REMOVE_SONG', event.payload);
break;
}
case 'MOVE_SONG': {
if (conn && this.permission === 'host-only') {
await this.connection?.broadcast('SYNC_QUEUE', {
videoList: this.queue?.videoList ?? []
});
break;
}
this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
await this.connection?.broadcast('MOVE_SONG', event.payload);
break;
}
case 'IDENTIFY': {
if (!event.payload || !conn) {
console.warn('Music Together [Host]: Received "IDENTIFY" event without payload or connection');
break;
}
this.api?.openToast(t('plugins.music-together.toast.user-connected', { name: event.payload.profile.name }));
this.putProfile(conn.peer, event.payload.profile);
break;
}
case 'SYNC_PROFILE': {
await this.connection?.broadcast('SYNC_PROFILE', { profiles: this.profiles });
break;
}
case 'PERMISSION': {
await this.connection?.broadcast('PERMISSION', this.permission);
this.popups.guest.setPermission(this.permission);
this.popups.host.setPermission(this.permission);
this.popups.setting.setPermission(this.permission);
break;
}
case 'SYNC_QUEUE': {
await this.connection?.broadcast('SYNC_QUEUE', {
videoList: this.queue?.videoList ?? []
});
break;
}
case 'SYNC_PROGRESS': {
let permissionLevel = 0;
if (this.permission === 'all') permissionLevel = 2;
if (this.permission === 'playlist') permissionLevel = 1;
if (this.permission === 'host-only') permissionLevel = 0;
if (!conn) permissionLevel = 3;
if (permissionLevel >= 2) {
if (typeof event.payload?.progress === 'number') {
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
if (Math.abs(event.payload.progress - currentTime) > 3) this.playerApi?.seekTo(event.payload.progress);
}
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
if (event.payload?.state === 1) this.playerApi?.playVideo();
}
}
if (permissionLevel >= 1) {
if (typeof event.payload?.index === 'number') {
const nowIndex = this.queue?.selectedIndex ?? 0;
if (nowIndex !== event.payload.index) {
this.queue?.setIndex(event.payload.index);
}
}
}
break;
}
default: {
console.warn('Music Together [Host]: Unknown Event', event);
break;
}
}
if (event.after) {
const now = event.after.shift();
if (now) {
now.after = event.after;
await listener(now, conn);
}
}
};
this.connection.on(listener);
this.queue?.on(listener);
setTimeout(() => {
this.ignoreChange = false;
}, 16); // wait 1 frame
return true;
},
async onJoin() {
this.connection = new Connection();
const wait = await this.connection.waitForReady().catch(() => null);
if (!wait) return false;
this.profiles = {};
const id = await this.showPrompt(t('plugins.music-together.name'), t('plugins.music-together.dialog.enter-host'));
if (typeof id !== 'string') return false;
const connection = await this.connection.connect(id).catch(() => false);
if (!connection) return false;
this.connection.onConnections((connection) => {
if (!connection?.open) {
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
this.onStop();
}
});
let resolveIgnore: number | null = null;
const listener = async (event: ConnectionEventUnion) => {
this.ignoreChange = true;
switch (event.type) {
case 'ADD_SONGS': {
await this.queue?.addVideos(event.payload.videoList, event.payload.index);
break;
}
case 'REMOVE_SONG': {
await this.queue?.removeVideo(event.payload.index);
break;
}
case 'MOVE_SONG': {
await this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
break;
}
case 'IDENTIFY': {
console.warn('Music Together [Guest]: Received "IDENTIFY" event from guest');
break;
}
case 'SYNC_QUEUE': {
if (Array.isArray(event.payload?.videoList)) {
await this.queue?.setVideoList(event.payload.videoList);
}
break;
}
case 'SYNC_PROFILE': {
if (!event.payload) {
console.warn('Music Together [Guest]: Received "SYNC_PROFILE" event without payload');
break;
}
Object.entries(event.payload.profiles).forEach(([id, profile]) => {
this.putProfile(id, profile);
});
break;
}
case 'SYNC_PROGRESS': {
if (typeof event.payload?.progress === 'number') {
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
if (Math.abs(event.payload.progress - currentTime) > 3) this.playerApi?.seekTo(event.payload.progress);
}
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
if (event.payload?.state === 1) this.playerApi?.playVideo();
}
if (typeof event.payload?.index === 'number') {
const nowIndex = this.queue?.selectedIndex ?? 0;
if (nowIndex !== event.payload.index) {
this.queue?.setIndex(event.payload.index);
}
}
break;
}
case 'PERMISSION': {
if (!event.payload) {
console.warn('Music Together [Guest]: Received "PERMISSION" event without payload');
break;
}
this.permission = event.payload;
this.popups.guest.setPermission(this.permission);
this.popups.host.setPermission(this.permission);
this.popups.setting.setPermission(this.permission);
const permissionLabel = t(`plugins.music-together.menu.permission.${this.permission}`);
this.api?.openToast(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
break;
}
default: {
console.warn('Music Together [Guest]: Unknown Event', event);
break;
}
}
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
resolveIgnore = window.setTimeout(() => {
this.ignoreChange = false;
}, 16); // wait 1 frame
};
this.connection.on(listener);
this.queue?.on(async (event: ConnectionEventUnion) => {
this.ignoreChange = true;
switch (event.type) {
case 'ADD_SONGS': {
await this.connection?.broadcast('ADD_SONGS', event.payload);
await this.connection?.broadcast('SYNC_QUEUE', undefined);
break;
}
case 'REMOVE_SONG': {
await this.connection?.broadcast('REMOVE_SONG', event.payload);
break;
}
case 'MOVE_SONG': {
await this.connection?.broadcast('MOVE_SONG', event.payload);
await this.connection?.broadcast('SYNC_QUEUE', undefined);
break;
}
case 'SYNC_PROGRESS': {
if (this.permission === 'host-only') await this.connection?.broadcast('SYNC_QUEUE', undefined);
else await this.connection?.broadcast('SYNC_PROGRESS', event.payload);
break;
}
}
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
resolveIgnore = window.setTimeout(() => {
this.ignoreChange = false;
}, 16); // wait 1 frame
});
if (!this.me) this.me = getDefaultProfile(this.connection.id);
this.queue?.injection();
this.queue?.setOwner({
id: this.connection.id,
...this.me
});
const progress = Array.from(document.querySelectorAll<HTMLElement & {
_update: (...args: unknown[]) => void
}>('tp-yt-paper-progress'));
const rollbackList = progress.map((progress) => {
const original = progress._update;
progress._update = (...args) => {
const now = args[0];
if (this.permission === 'all' && typeof now === 'number') {
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
if (Math.abs(now - currentTime) > 3) this.connection?.broadcast('SYNC_PROGRESS', {
progress: now,
state: this.playerApi?.getPlayerState()
});
}
original.call(progress, ...args);
};
return () => {
progress._update = original;
};
});
this.rollbackInjector = () => {
rollbackList.forEach((rollback) => rollback());
};
this.connection.broadcast('IDENTIFY', {
profile: {
id: this.connection.id,
handleId: this.me.handleId,
name: this.me.name,
thumbnail: this.me.thumbnail
}
});
this.connection.broadcast('SYNC_PROFILE', undefined);
this.connection.broadcast('PERMISSION', undefined);
this.queue?.clear();
this.queue?.syncQueueOwner();
this.queue?.initQueue();
this.connection.broadcast('SYNC_QUEUE', undefined);
return true;
},
onStop() {
this.connection?.disconnect();
this.queue?.rollbackInjection();
this.queue?.removeQueueOwner();
if (this.rollbackInjector) {
this.rollbackInjector();
this.rollbackInjector = null;
}
this.profiles = {};
this.popups.host.setUsers(Object.values(this.profiles));
this.popups.guest.setUsers(Object.values(this.profiles));
this.popups.host.dismiss();
this.popups.guest.dismiss();
this.popups.setting.dismiss();
},
/* methods */
putProfile(id: string, profile?: Profile) {
if (profile === undefined) {
delete this.profiles[id];
} else {
this.profiles[id] = profile;
}
this.popups.host.setUsers(Object.values(this.profiles));
this.popups.guest.setUsers(Object.values(this.profiles));
},
showSpinner() {
this.elements.icon.style.setProperty('display', 'none');
this.elements.spinner.removeAttribute('hidden');
this.elements.spinner.setAttribute('active', '');
},
hideSpinner() {
this.elements.icon.style.removeProperty('display');
this.elements.spinner.removeAttribute('active');
this.elements.spinner.setAttribute('hidden', '');
},
initMyProfile() {
const accountButton = document.querySelector<HTMLElement & {
onButtonTap: () => void
}>('ytmusic-settings-button');
accountButton?.onButtonTap();
setTimeout(() => {
accountButton?.onButtonTap();
const renderer = document.querySelector<HTMLElement & { data: unknown }>('ytd-active-account-header-renderer');
if (!accountButton || !renderer) {
console.warn('Music Together: Cannot find account');
this.me = getDefaultProfile(this.connection?.id ?? '');
return;
}
const accountData = renderer.data as RawAccountData;
this.me = {
handleId: accountData.channelHandle.runs[0].text,
name: accountData.accountName.runs[0].text,
thumbnail: accountData.accountPhoto.thumbnails[0].url
};
if (this.me.thumbnail) {
this.popups.host.setProfile(this.me.thumbnail);
this.popups.guest.setProfile(this.me.thumbnail);
this.popups.setting.setProfile(this.me.thumbnail);
}
}, 0);
},
/* hooks */
start({ ipc }) {
this.ipc = ipc;
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label);
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
/* setup */
document.querySelector('#right-content > ytmusic-settings-button')?.insertAdjacentHTML('beforebegin', settingHTML);
const setting = document.querySelector<HTMLElement>('#music-together-setting-button');
const icon = document.querySelector<SVGElement>('#music-together-setting-button > svg');
const spinner = document.querySelector<HTMLElement>('#music-together-setting-button > tp-yt-paper-spinner-lite');
if (!setting || !icon || !spinner) {
console.warn('Music Together: Cannot inject html');
console.log(setting, icon, spinner);
return;
}
this.elements = {
setting,
icon,
spinner
};
this.stateInterval = window.setInterval(() => {
if (this.connection?.mode !== 'host') return;
const index = this.queue?.selectedIndex ?? 0;
this.connection.broadcast('SYNC_PROGRESS', {
progress: this.playerApi?.getCurrentTime(),
state: this.playerApi?.getPlayerState(),
index
});
}, 1000);
/* UI */
const hostPopup = createHostPopup({
onItemClick: (id) => {
if (id === 'music-together-close') {
this.onStop();
this.api?.openToast(t('plugins.music-together.toast.closed'));
hostPopup.dismiss();
}
if (id === 'music-together-copy-id') {
navigator.clipboard.writeText(this.connection?.id ?? '');
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
hostPopup.dismiss();
}
if (id === 'music-together-permission') {
if (this.permission === 'all') this.permission = 'host-only';
else if (this.permission === 'playlist') this.permission = 'all';
else if (this.permission === 'host-only') this.permission = 'playlist';
this.connection?.broadcast('PERMISSION', this.permission);
hostPopup.setPermission(this.permission);
guestPopup.setPermission(this.permission);
settingPopup.setPermission(this.permission);
const permissionLabel = t(`plugins.music-together.menu.permission.${this.permission}`);
this.api?.openToast(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
const item = hostPopup.items.find((it) => it?.element.id === id);
if (item?.type === 'item') {
item.setText(t('plugins.music-together.menu.set-permission'));
}
}
}
});
const guestPopup = createGuestPopup({
onItemClick: (id) => {
if (id === 'music-together-disconnect') {
this.onStop();
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
guestPopup.dismiss();
}
}
});
const settingPopup = createSettingPopup({
onItemClick: async (id) => {
if (id === 'music-together-host') {
settingPopup.dismiss();
this.showSpinner();
const result = await this.onHost();
this.hideSpinner();
if (result) {
navigator.clipboard.writeText(this.connection?.id ?? '');
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
hostPopup.showAtAnchor(setting);
} else {
this.api?.openToast(t('plugins.music-together.toast.host-failed'));
}
}
if (id === 'music-together-join') {
settingPopup.dismiss();
this.showSpinner();
const result = await this.onJoin();
this.hideSpinner();
if (result) {
this.api?.openToast(t('plugins.music-together.toast.joined'));
guestPopup.showAtAnchor(setting);
} else {
this.api?.openToast(t('plugins.music-together.toast.join-failed'));
}
}
}
});
this.popups = {
host: hostPopup,
guest: guestPopup,
setting: settingPopup
};
setting.addEventListener('click', async () => {
let popup = settingPopup;
if (this.connection?.mode === 'host') popup = hostPopup;
if (this.connection?.mode === 'guest') popup = guestPopup;
if (popup.isShowing()) popup.dismiss();
else popup.showAtAnchor(setting);
});
/* account data getter */
this.initMyProfile();
},
onPlayerApiReady(playerApi) {
this.queue = new Queue({
owner: {
id: this.connection?.id ?? '',
...this.me!
},
getProfile: (id) => this.profiles[id]
});
this.playerApi = playerApi;
this.playerApi.addEventListener('onStateChange', this.videoStateChangeListener);
document.addEventListener('videodatachange', this.videoChangeListener);
},
stop() {
const dividers = Array.from(document.querySelectorAll('.music-together-divider'));
dividers.forEach((divider) => divider.remove());
this.elements.setting?.remove();
this.onStop();
if (typeof this.stateInterval === 'number') clearInterval(this.stateInterval);
if (this.playerApi) this.playerApi.removeEventListener('onStateChange', this.videoStateChangeListener);
if (this.videoChangeListener) document.removeEventListener('videodatachange', this.videoChangeListener);
}
}
});

View File

@ -0,0 +1,40 @@
import { SHA1Hash } from './sha1hash';
export const extractToken = (cookie = document.cookie) => cookie.match(/SAPISID=([^;]+);/)?.[1] ?? cookie.match(/__Secure\-3PAPISID=([^;]+);/)?.[1];
export const getHash = (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') => {
const hash = SHA1Hash();
hash.update(`${millis} ${papisid} ${origin}`);
return hash.digestString().toLowerCase();
};
export const getAuthorizationHeader = (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') => {
return `SAPISIDHASH ${millis}_${getHash(papisid, millis, origin)}`;
};
export const getClient = () => {
return {
hl: navigator.language.split('-')[0] ?? 'en',
gl: navigator.language.split('-')[1] ?? 'US',
deviceMake: '',
deviceModel: '',
userAgent: navigator.userAgent,
clientName: 'WEB_REMIX',
clientVersion: '1.20231208.05.02',
osName: '',
osVersion: '',
platform: 'DESKTOP',
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
locationInfo: {
locationPermissionAuthorizationStatus: 'LOCATION_PERMISSION_AUTHORIZATION_STATUS_UNSUPPORTED',
},
musicAppInfo: {
pwaInstallabilityStatus: 'PWA_INSTALLABILITY_STATUS_UNKNOWN',
webDisplayMode: 'WEB_DISPLAY_MODE_BROWSER',
storeDigitalGoodsApiSupportStatus: {
playStoreDigitalGoodsApiSupportStatus: 'DIGITAL_GOODS_API_SUPPORT_STATUS_UNSUPPORTED',
},
},
utcOffsetMinutes: -1 * (new Date()).getTimezoneOffset(),
};
};

View File

@ -0,0 +1 @@
export * from './queue';

View File

@ -0,0 +1,429 @@
import { getMusicQueueRenderer } from './song';
import { mapQueueItem } from './utils';
import type { Profile, QueueAPI, VideoData } from '../types';
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
import { t } from '@/i18n';
const getHeaderPayload = (() => {
let payload: unknown = null;
return () => {
if (!payload) {
payload = {
title: {
runs: [
{
text: t('plugins.music-together.internal.track-source')
}
]
},
subtitle: {
runs: [
{
text: t('plugins.music-together.name')
}
]
},
buttons: [
{
chipCloudChipRenderer: {
style: {
styleType: 'STYLE_TRANSPARENT'
},
text: {
runs: [
{
text: t('plugins.music-together.internal.save')
}
]
},
navigationEndpoint: {
saveQueueToPlaylistCommand: {}
},
icon: {
iconType: 'ADD_TO_PLAYLIST'
},
accessibilityData: {
accessibilityData: {
label: t('plugins.music-together.internal.save')
}
},
isSelected: false,
uniqueId: t('plugins.music-together.internal.save')
}
}
]
};
}
return payload;
}
})();
export type QueueOptions = {
videoList?: VideoData[];
owner?: Profile;
queue?: HTMLElement & QueueAPI;
getProfile: (id: string) => Profile | undefined;
}
export type QueueEventListener = (event: ConnectionEventUnion) => void;
export class Queue {
private queue: (HTMLElement & QueueAPI) | null = null;
private originalDispatch: ((obj: {
type: string;
payload?: unknown;
}) => void) | null = null;
private internalDispatch = false;
private ignoreFlag = false;
private listeners: QueueEventListener[] = [];
private owner: Profile | null = null;
private getProfile: (id: string) => Profile | undefined;
constructor(options: QueueOptions) {
this.getProfile = options.getProfile;
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue');
this.owner = options.owner ?? null;
this._videoList = options.videoList ?? [];
}
private _videoList: VideoData[] = [];
/* utils */
get videoList() {
return this._videoList;
}
get selectedIndex() {
return mapQueueItem((it) => it?.selected, this.queue?.store.getState().queue.items).findIndex(Boolean) ?? 0;
}
get rawItems() {
return this.queue?.store.getState().queue.items;
}
get flatItems() {
return mapQueueItem((it) => it, this.rawItems);
}
setOwner(owner: Profile) {
this.owner = owner;
}
/* public */
async setVideoList(videoList: VideoData[], sync = true) {
this._videoList = videoList;
if (sync) await this.syncVideo();
}
async addVideos(videos: VideoData[], index?: number) {
const response = await getMusicQueueRenderer(videos.map((it) => it.videoId));
if (!response) return false;
const items = response.queueDatas.map((it) => it?.content).filter(Boolean);
if (!items) return false;
this.internalDispatch = true;
this._videoList.push(...videos);
this.queue?.dispatch({
type: 'ADD_ITEMS',
payload: {
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
index: index ?? this.queue.store.getState().queue.items.length ?? 0,
items,
shuffleEnabled: false,
shouldAssignIds: true
}
});
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
return true;
}
async removeVideo(index: number) {
this.internalDispatch = true;
this._videoList.splice(index, 1);
this.queue?.dispatch({
type: 'REMOVE_ITEM',
payload: index
});
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
}
setIndex(index: number) {
this.internalDispatch = true;
this.queue?.dispatch({
type: 'SET_INDEX',
payload: index
});
this.internalDispatch = false;
}
moveItem(fromIndex: number, toIndex: number) {
this.internalDispatch = true;
const data = this._videoList.splice(fromIndex, 1)[0];
this._videoList.splice(toIndex, 0, data);
this.queue?.dispatch({
type: 'MOVE_ITEM',
payload: {
fromIndex,
toIndex
}
});
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
}
clear() {
this.internalDispatch = true;
this._videoList = [];
this.queue?.dispatch({
type: 'CLEAR'
});
this.internalDispatch = false;
}
on(listener: QueueEventListener) {
this.listeners.push(listener);
}
off(listener: QueueEventListener) {
this.listeners = this.listeners.filter((it) => it !== listener);
}
rollbackInjection() {
if (!this.queue) {
console.error('Queue is not initialized!');
return;
}
if (this.originalDispatch) this.queue.store.dispatch = this.originalDispatch;
}
injection() {
if (!this.queue) {
console.error('Queue is not initialized!');
return;
}
this.originalDispatch = this.queue.store.dispatch;
this.queue.store.dispatch = (event) => {
if (!this.queue || !this.owner) {
console.error('Queue is not initialized!');
return;
}
if (!this.internalDispatch) {
if (event.type === 'CLEAR') {
this.ignoreFlag = true;
}
if (event.type === 'ADD_ITEMS') {
if (this.ignoreFlag) {
this.ignoreFlag = false;
const videoList = mapQueueItem((it: any) => ({
videoId: it.videoId,
ownerId: this.owner!.id
} satisfies VideoData), (event.payload as any).items);
const index = this._videoList.length + videoList.length - 1;
if (videoList.length > 0) {
this.broadcast({ // play
type: 'ADD_SONGS',
payload: {
videoList
},
after: [
{
type: 'SYNC_PROGRESS',
payload: {
index
}
}
]
});
}
} else if ((event.payload as any).items.length === 1) {
this.broadcast({ // add playlist
type: 'ADD_SONGS',
payload: {
// index: (event.payload as any).index,
videoList: mapQueueItem((it: any) => ({
videoId: it.videoId,
ownerId: this.owner!.id
} satisfies VideoData), (event.payload as any).items)
}
});
}
return;
}
if (event.type === 'MOVE_ITEM') {
this.broadcast({
type: 'MOVE_SONG',
payload: {
fromIndex: (event.payload as any).fromIndex,
toIndex: (event.payload as any).toIndex
}
});
return;
}
if (event.type === 'REMOVE_ITEM') {
this.broadcast({
type: 'REMOVE_SONG',
payload: {
index: event.payload as number
}
});
return;
}
if (event.type === 'SET_INDEX') {
this.broadcast({
type: 'SYNC_PROGRESS',
payload: {
index: event.payload as number
}
});
return;
}
if (event.type === 'SET_HEADER') event.payload = getHeaderPayload();
if (event.type === 'ADD_STEERING_CHIPS') {
event.type = 'CLEAR_STEERING_CHIPS';
event.payload = undefined;
}
if (event.type === 'SET_PLAYER_UI_STATE') {
if (event.payload === 'INACTIVE' && this.videoList.length > 0) {
return;
}
}
if (event.type === 'HAS_SHOWN_AUTOPLAY') return;
if (event.type === 'ADD_AUTOMIX_ITEMS') return;
}
const fakeContext = {
...this.queue,
store: {
...this.queue.store,
dispatch: this.originalDispatch
}
};
this.originalDispatch!.call(fakeContext, event);
};
}
/* sync */
async initQueue() {
if (!this.queue) return;
this.internalDispatch = true;
this.queue.dispatch({
type: 'HAS_SHOWN_AUTOPLAY',
payload: false
});
this.queue.dispatch({
type: 'SET_HEADER',
payload: getHeaderPayload(),
});
this.queue.dispatch({
type: 'CLEAR_STEERING_CHIPS'
});
this.internalDispatch = false;
}
async syncVideo() {
const response = await getMusicQueueRenderer(this._videoList.map((it) => it.videoId));
if (!response) return false;
const items = response.queueDatas.map((it) => it.content);
this.internalDispatch = true;
this.queue?.dispatch({
type: 'UPDATE_ITEMS',
payload: {
items: items,
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
shouldAssignIds: true,
currentIndex: -1
}
});
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
return true;
}
async syncQueueOwner() {
const allQueue = document.querySelectorAll('#queue');
allQueue.forEach((queue) => {
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
list.forEach((item, index) => {
if (typeof index !== 'number') return;
const id = this._videoList[index]?.ownerId;
const data = this.getProfile(id);
const profile = item.querySelector<HTMLImageElement>('.music-together-owner') ?? document.createElement('img');
profile.classList.add('music-together-owner');
profile.dataset.id = id;
profile.dataset.index = index.toString();
const name = item.querySelector<HTMLElement>('.music-together-name') ?? document.createElement('div');
name.classList.add('music-together-name');
name.textContent = data?.name ?? t('plugins.music-together.internal.unknown-user');
if (data) {
profile.dataset.thumbnail = data.thumbnail ?? '';
profile.dataset.name = data.name ?? '';
profile.dataset.handleId = data.handleId ?? '';
profile.dataset.id = data.id ?? '';
profile.src = data.thumbnail ?? '';
profile.title = data.name ?? '';
profile.alt = data.handleId ?? '';
}
if (!profile.isConnected) item.append(profile);
if (!name.isConnected) item.append(name);
});
});
}
removeQueueOwner() {
const allQueue = document.querySelectorAll('#queue');
allQueue.forEach((queue) => {
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
list.forEach((item) => {
const profile = item.querySelector<HTMLImageElement>('.music-together-owner');
const name = item.querySelector<HTMLElement>('.music-together-name');
profile?.remove();
name?.remove();
});
});
}
/* private */
private broadcast(event: ConnectionEventUnion) {
this.listeners.forEach((listener) => listener(event));
}
}

View File

@ -0,0 +1,117 @@
export function SHA1Hash(): {
reset: () => void,
update: (message: string | number[], length?: number) => void,
digest: () => number[],
digestString: () => string
} {
let hash: number[];
function initialize(): void {
hash = [1732584193, 4023233417, 2562383102, 271733878, 3285377520];
totalLength = currentLength = 0;
}
function processBlock(block: number[]): void {
let words: number[] = [];
for (let i = 0; i < 64; i += 4) {
words[i / 4] = block[i] << 24 | block[i + 1] << 16 | block[i + 2] << 8 | block[i + 3];
}
for (let i = 16; i < 80; i++) {
let temp = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
words[i] = (temp << 1 | temp >>> 31) & 4294967295;
}
let a = hash[0],
b = hash[1],
c = hash[2],
d = hash[3],
e = hash[4];
for (let i = 0; i < 80; i++) {
let f, k;
if (i < 20) {
f = d ^ b & (c ^ d);
k = 1518500249;
} else if (i < 40) {
f = b ^ c ^ d;
k = 1859775393;
} else if (i < 60) {
f = b & c | d & (b | c);
k = 2400959708;
} else {
f = b ^ c ^ d;
k = 3395469782;
}
let temp = ((a << 5 | a >>> 27) & 4294967295) + f + e + k + words[i] & 4294967295;
e = d;
d = c;
c = (b << 30 | b >>> 2) & 4294967295;
b = a;
a = temp;
}
hash[0] = hash[0] + a & 4294967295;
hash[1] = hash[1] + b & 4294967295;
hash[2] = hash[2] + c & 4294967295;
hash[3] = hash[3] + d & 4294967295;
hash[4] = hash[4] + e & 4294967295;
}
function update(message: string | number[], length?: number): void {
if ('string' === typeof message) {
message = unescape(encodeURIComponent(message));
let bytes: number[] = [];
for (let i = 0, len = message.length; i < len; ++i)
bytes.push(message.charCodeAt(i));
message = bytes;
}
length || (length = message.length);
let i = 0;
if (0 == currentLength)
for (; i + 64 < length;)
processBlock(message.slice(i, i + 64)),
i += 64,
totalLength += 64;
for (; i < length;)
if (buffer[currentLength++] = message[i++],
totalLength++,
64 == currentLength)
for (currentLength = 0,
processBlock(buffer); i + 64 < length;)
processBlock(message.slice(i, i + 64)),
i += 64,
totalLength += 64;
}
function finalize(): number[] {
let result: number[] = []
, bits = 8 * totalLength;
if (currentLength < 56)
update(padding, 56 - currentLength);
else
update(padding, 64 - (currentLength - 56));
for (let i = 63; i >= 56; i--)
buffer[i] = bits & 255,
bits >>>= 8;
processBlock(buffer);
for (let i = 0; i < 5; i++)
for (let j = 24; j >= 0; j -= 8)
result.push(hash[i] >> j & 255);
return result;
}
let buffer: number[] = [], padding: number[] = [128], totalLength: number, currentLength: number;
for (let i = 1; i < 64; ++i)
padding[i] = 0;
initialize();
return {
reset: initialize,
update: update,
digest: finalize,
digestString: function(): string {
let hash = finalize(), hex = '';
for (let i = 0; i < hash.length; i++)
hex += '0123456789ABCDEF'.charAt(Math.floor(hash[i] / 16)) + '0123456789ABCDEF'.charAt(hash[i] % 16);
return hex;
}
};
}

View File

@ -0,0 +1,48 @@
import { extractToken, getAuthorizationHeader, getClient } from './client';
type QueueRendererResponse = {
queueDatas: {
content: unknown;
}[];
responseContext: unknown;
trackingParams: string;
};
export const getMusicQueueRenderer = async (videoIds: string[]): Promise<QueueRendererResponse | null> => {
const token = extractToken();
if (!token) return null;
const response = await fetch(
'https://music.youtube.com/youtubei/v1/music/get_queue?key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30&prettyPrint=false',
{
method: 'POST',
credentials: 'include',
body: JSON.stringify({
context: {
client: getClient(),
request: {
useSsl: true,
internalExperimentFlags: [],
consistencyTokenJars: [],
},
user: {
lockedSafetyMode: false,
},
},
videoIds,
}),
headers: {
'Content-Type': 'application/json',
Origin: 'https://music.youtube.com',
Authorization: getAuthorizationHeader(token)
}
}
);
const text = await response.text();
try {
return JSON.parse(text) as QueueRendererResponse;
} catch {}
return null;
};

View File

@ -0,0 +1,15 @@
export const mapQueueItem = <T>(map: (item: any | null) => T, array: any[]): T[] => array
.map((item) => {
if ('playlistPanelVideoWrapperRenderer' in item) {
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer.primaryRenderer);
return item.playlistPanelVideoWrapperRenderer.primaryRenderer[keys[0]];
}
if ('playlistPanelVideoRenderer' in item) {
return item.playlistPanelVideoRenderer;
}
console.error('Music Together: Unknown item', item);
return null;
})
.map(map);

View File

@ -0,0 +1,160 @@
.music-together-button {
display: inline-flex;
cursor: pointer;
margin-left: 8px;
& svg {
width: 24px;
height: 24px;
fill: rgba(255, 255, 255, .5);
}
&:hover svg:hover {
fill: #fff;
}
}
#right-content > .music-together-divider {
width: 1px;
height: 26px;
margin-left: 16px;
margin-right: 8px;
}
.music-together-divider {
background-color: rgba(255, 255, 255, .15);
}
.music-together-divider.horizontal {
width: 100%;
height: 1px;
}
.music-together-divider.vertical {
width: 1px;
height: 100%;
}
.music-together-tool {
position: absolute;
display: flex;
align-items: center;
gap: 8px;
opacity: 0;
translate: 50%;
pointer-events: none;
transition: all 0.225s ease-out;
&.open {
position: unset;
opacity: 1;
translate: 0;
pointer-events: all;
}
}
.music-together-spinner {
}
.music-together-popup {
position: fixed;
z-index: 1000;
}
.music-together-popup-container {
border-radius: 10px !important;
}
.music-together-item {
display: flex;
height: 48px;
align-items: center;
padding: 0 8px;
--iron-icon-fill-color: #fff;
&:not([is-disabled]) {
cursor: pointer;
}
&:hover {
background-color: var(--ytmusic-menu-item-hover-background-color, rgba(255,255,255,0.05));
}
}
.music-together-status {
display: flex;
flex-direction: column;
align-items: stretch;
padding: 16px;
}
.music-together-profile {
width: 24px;
height: 24px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
}
.music-together-profile.big {
width: 32px;
height: 32px;
}
.music-together-status-container {
flex: 1;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 16px;
}
.music-together-status-item {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 14px;
font-weight: 400;
}
.music-together-user-container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 8px;
padding-top: 16px;
font-size: 14px;
}
.music-together-empty {
width: 100%;
font-size: 14px;
color: rgba(255, 255, 255, .5);
text-align: center;
}
.music-together-owner {
width: 24px;
height: 24px;
flex-shrink: 0;
border-radius: 50%;
margin-left: 8px;
}
.music-together-name {
display: none;
color: #fff;
font-size: 14px;
margin-left: 8px;
}
ytmusic-player-queue-item:hover .music-together-name {
display: unset;
}

View File

@ -0,0 +1,8 @@
<div class="style-scope music-together-item">
<div class="icon style-scope ytmusic-menu-service-item-renderer">
<!-- icon -->
</div>
<div class="text style-scope ytmusic-menu-service-item-renderer">
<!-- text -->
</div>
</div>

View File

@ -0,0 +1,5 @@
<div class="music-together-popup">
<tp-yt-paper-listbox class="style-scope ytmusic-menu-popup-renderer music-together-popup-container">
</tp-yt-paper-listbox>
</div>

View File

@ -0,0 +1,7 @@
<div id="music-together-setting-button" class="music-together-button style-scope ytmusic-nav-bar">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M0-240v-63q0-43 44-70t116-27q13 0 25 .5t23 2.5q-14 21-21 44t-7 48v65H0Zm240 0v-65q0-32 17.5-58.5T307-410q32-20 76.5-30t96.5-10q53 0 97.5 10t76.5 30q32 20 49 46.5t17 58.5v65H240Zm540 0v-65q0-26-6.5-49T754-397q11-2 22.5-2.5t23.5-.5q72 0 116 26.5t44 70.5v63H780Zm-455-80h311q-10-20-55.5-35T480-370q-55 0-100.5 15T325-320ZM160-440q-33 0-56.5-23.5T80-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T160-440Zm640 0q-33 0-56.5-23.5T720-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T800-440Zm-320-40q-50 0-85-35t-35-85q0-51 35-85.5t85-34.5q51 0 85.5 34.5T600-600q0 50-34.5 85T480-480Zm0-80q17 0 28.5-11.5T520-600q0-17-11.5-28.5T480-640q-17 0-28.5 11.5T440-600q0 17 11.5 28.5T480-560Zm1 240Zm-1-280Z"/>
</svg>
<tp-yt-paper-spinner-lite id="music-together-host-spinner" hidden class="loading-indicator style-scope music-together-spinner"></tp-yt-paper-spinner-lite>
</div>
<div class="music-together-divider"></div>

View File

@ -0,0 +1,23 @@
<div class="music-together-status">
<div class="music-together-status-container">
<img class="music-together-profile big">
<div class="music-together-status-item">
<ytmd-trans key="plugins.music-together.name"></ytmd-trans>
<span id="music-together-status-label">
<ytmd-trans key="plugins.music-together.menu.status.disconnected"></ytmd-trans>
</span>
<span id="music-together-permission-label">
<ytmd-trans key="plugins.music-together.menu.permission.playlist" style="color: rgba(255, 255, 255, 0.75)"></ytmd-trans>
</span>
</div>
</div>
<div class="music-together-divider horizontal" style="margin: 16px 0;"></div>
<div class="music-together-status-item">
<ytmd-trans key="plugins.music-together.menu.connected-users"></ytmd-trans>
</div>
<div class="music-together-user-container">
<span class="music-together-empty">
<ytmd-trans key="plugins.music-together.menu.empty-user"></ytmd-trans>
</span>
</div>
</div>

View File

@ -0,0 +1,54 @@
import { YoutubePlayer } from '@/types/youtube-player';
type StoreState = any;
type Store = {
dispatch: (obj: {
type: string;
payload?: unknown;
}) => void;
getState: () => StoreState;
replaceReducer: (param1: unknown) => unknown;
subscribe: (callback: () => void) => unknown;
};
export type QueueAPI = {
dispatch(obj: {
type: string;
payload?: unknown;
}): void;
getItems(): unknown[];
store: Store;
continuation?: string;
autoPlaying?: boolean;
};
export type AppAPI = {
queue_: QueueAPI;
playerApi_: YoutubePlayer;
openToast: (message: string) => void;
// TODO: Add more
};
export type Profile = {
id: string;
handleId: string;
name: string;
thumbnail: string;
};
export type VideoData = {
videoId: string;
ownerId: string;
};
export type Permission = 'host-only' | 'playlist' | 'all';
export const getDefaultProfile = (connectionID: string, id: string = Date.now().toString()): Profile => {
const name = `Guest ${id.slice(0, 4)}`;
return {
id: connectionID,
handleId: `#music-together:${id}`,
name,
thumbnail: `https://ui-avatars.com/api/?name=${name}&background=random`
};
};

View File

@ -0,0 +1,43 @@
import { ElementFromHtml } from '@/plugins/utils/renderer';
import { t } from '@/i18n';
import { Popup } from '../element';
import { createStatus } from '../ui/status';
import IconOff from '../icons/off.svg?raw';
export type GuestPopupProps = {
onItemClick: (id: string) => void;
};
export const createGuestPopup = (props: GuestPopupProps) => {
const status = createStatus();
status.setStatus('guest');
const result = Popup({
data: [
{
type: 'custom',
element: status.element,
},
{
type: 'divider',
},
{
type: 'item',
id: 'music-together-disconnect',
icon: ElementFromHtml(IconOff),
text: t('plugins.music-together.menu.disconnect'),
onClick: () => props.onItemClick('music-together-disconnect'),
},
],
anchorAt: 'bottom-right',
popupAt: 'top-right'
});
return {
...status,
...result,
};
};

View File

@ -0,0 +1,60 @@
import { t } from '@/i18n';
import { ElementFromHtml } from '@/plugins/utils/renderer';
import { Popup } from '../element';
import { createStatus } from '../ui/status';
import IconKey from '../icons/key.svg?raw';
import IconOff from '../icons/off.svg?raw';
import IconTune from '../icons/tune.svg?raw';
export type HostPopupProps = {
onItemClick: (id: string) => void;
};
export const createHostPopup = (props: HostPopupProps) => {
const status = createStatus();
status.setStatus('host');
const result = Popup({
data: [
{
type: 'custom',
element: status.element,
},
{
type: 'divider'
},
{
id: 'music-together-copy-id',
type: 'item',
icon: ElementFromHtml(IconKey),
text: t('plugins.music-together.menu.click-to-copy-id'),
onClick: () => props.onItemClick('music-together-copy-id'),
},
{
id: 'music-together-permission',
type: 'item',
icon: ElementFromHtml(IconTune),
text: t('plugins.music-together.menu.set-permission', { permission: t('plugins.music-together.menu.permission.host-only') }),
onClick: () => props.onItemClick('music-together-permission'),
},
{
type: 'divider',
},
{
type: 'item',
id: 'music-together-close',
icon: ElementFromHtml(IconOff),
text: t('plugins.music-together.menu.close'),
onClick: () => props.onItemClick('music-together-close'),
},
],
anchorAt: 'bottom-right',
popupAt: 'top-right',
});
return {
...status,
...result,
};
};

View File

@ -0,0 +1,49 @@
import { Popup } from '@/plugins/music-together/element';
import { ElementFromHtml } from '@/plugins/utils/renderer';
import { createStatus } from './status';
import { t } from '@/i18n';
import IconMusicCast from '../icons/music-cast.svg?raw';
import IconConnect from '../icons/connect.svg?raw';
export type SettingPopupProps = {
onItemClick: (id: string) => void;
};
export const createSettingPopup = (props: SettingPopupProps) => {
const status = createStatus();
status.setStatus('disconnected');
const result = Popup({
data: [
{
type: 'custom',
element: status.element,
},
{
type: 'divider',
},
{
id: 'music-together-host',
type: 'item',
icon: ElementFromHtml(IconMusicCast),
text: t('plugins.music-together.menu.host'),
onClick: () => props.onItemClick('music-together-host'),
},
{
type: 'item',
icon: ElementFromHtml(IconConnect),
text: t('plugins.music-together.menu.join'),
onClick: () => props.onItemClick('music-together-join'),
},
],
anchorAt: 'bottom-right',
popupAt: 'top-right'
});
return {
...status,
...result,
};
};

View File

@ -0,0 +1,82 @@
import { ElementFromHtml } from '@/plugins/utils/renderer';
import statusHTML from '../templates/status.html?raw';
import { t } from '@/i18n';
import type { Permission, Profile } from '../types';
export const createStatus = () => {
const element = ElementFromHtml(statusHTML);
const icon = document.querySelector<HTMLImageElement>('ytmusic-settings-button > tp-yt-paper-icon-button > tp-yt-iron-icon#icon img');
const profile = element.querySelector<HTMLImageElement>('.music-together-profile')!;
const statusLabel = element.querySelector<HTMLSpanElement>('#music-together-status-label')!;
const permissionLabel = element.querySelector<HTMLSpanElement>('#music-together-permission-label')!;
profile.src = icon?.src ?? '';
const setStatus = (status: 'disconnected' | 'host' | 'guest') => {
if (status === 'disconnected') {
statusLabel.textContent = t('plugins.music-together.menu.status.disconnected');
statusLabel.style.color = 'rgba(255, 255, 255, 0.5)';
}
if (status === 'host') {
statusLabel.textContent = t('plugins.music-together.menu.status.host');
statusLabel.style.color = 'rgba(255, 0, 0, 1)';
}
if (status === 'guest') {
statusLabel.textContent = t('plugins.music-together.menu.status.guest');
statusLabel.style.color = 'rgba(255, 255, 255, 1)';
}
};
const setPermission = (permission: Permission) => {
if (permission === 'host-only') {
permissionLabel.textContent = t('plugins.music-together.menu.permission.host-only');
permissionLabel.style.color = 'rgba(255, 255, 255, 0.5)';
}
if (permission === 'playlist') {
permissionLabel.textContent = t('plugins.music-together.menu.permission.playlist');
permissionLabel.style.color = 'rgba(255, 255, 255, 0.75)';
}
if (permission === 'all') {
permissionLabel.textContent = t('plugins.music-together.menu.permission.all');
permissionLabel.style.color = 'rgba(255, 255, 255, 1)';
}
};
const setProfile = (src: string) => {
profile.src = src;
};
const setUsers = (users: Profile[]) => {
const container = element.querySelector<HTMLDivElement>('.music-together-user-container')!;
const empty = element.querySelector<HTMLElement>('.music-together-empty')!;
for (const child of Array.from(container.children)) {
if (child !== empty) child.remove();
}
if (users.length === 0) empty.style.display = 'block';
else empty.style.display = 'none';
for (const user of users) {
const img = document.createElement('img');
img.classList.add('music-together-profile');
img.src = user.thumbnail ?? '';
img.title = user.name;
img.alt = `${user.name} (${user.id})`;
container.append(img);
}
};
return {
element,
setStatus,
setUsers,
setProfile,
setPermission,
};
};

View File

@ -10,18 +10,21 @@ import backHTML from './templates/back.html?raw';
export default createPlugin({ export default createPlugin({
name: () => t('plugins.navigation.name'), name: () => t('plugins.navigation.name'),
description: () => t('plugins.navigation.description'), description: () => t('plugins.navigation.description'),
restartNeeded: true, restartNeeded: false,
config: { config: {
enabled: true, enabled: true,
}, },
stylesheets: [style], stylesheets: [style],
renderer() { renderer: {
const forwardButton = ElementFromHtml(forwardHTML); start() {
const backButton = ElementFromHtml(backHTML); const forwardButton = ElementFromHtml(forwardHTML);
const menu = document.querySelector('#right-content'); const backButton = ElementFromHtml(backHTML);
const menu = document.querySelector('#right-content');
if (menu) { menu?.prepend(backButton, forwardButton);
menu.prepend(backButton, forwardButton); },
} stop() {
document.querySelector('[tab-id=FEmusic_back]')?.remove();
document.querySelector('[tab-id=FEmusic_next]')?.remove();
},
}, },
}); });

View File

@ -107,13 +107,15 @@ export const onMainLoad = async ({
window.on('move', () => { window.on('move', () => {
if (config.isInPiP && !config.useNativePiP) { if (config.isInPiP && !config.useNativePiP) {
setConfig({ 'pip-position': window.getPosition() as [number, number] }); const [x, y] = window.getPosition();
setConfig({ 'pip-position': [x, y] });
} }
}); });
window.on('resize', () => { window.on('resize', () => {
if (config.isInPiP && !config.useNativePiP) { if (config.isInPiP && !config.useNativePiP) {
setConfig({ 'pip-size': window.getSize() as [number, number] }); const [width, height] = window.getSize();
setConfig({ 'pip-size': [width, height] });
} }
}); });
}; };

View File

@ -18,6 +18,8 @@ let useNativePiP = false;
let menu: Element | null = null; let menu: Element | null = null;
const pipButton = ElementFromHtml(pipHTML); const pipButton = ElementFromHtml(pipHTML);
let doneFirstLoad = false;
// Will also clone // Will also clone
function replaceButton(query: string, button: Element) { function replaceButton(query: string, button: Element) {
const svg = button.querySelector('#icon svg')?.cloneNode(true); const svg = button.querySelector('#icon svg')?.cloneNode(true);
@ -61,11 +63,15 @@ const observer = new MutationObserver(() => {
const menuUrl = $<HTMLAnchorElement>( const menuUrl = $<HTMLAnchorElement>(
'tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint', 'tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint',
)?.href; )?.href;
if (!menuUrl?.includes('watch?')) { if (!menuUrl?.includes('watch?') && doneFirstLoad) {
return; return;
} }
menu.prepend(pipButton); menu.prepend(pipButton);
if (!doneFirstLoad) {
setTimeout(() => (doneFirstLoad ||= true), 500);
}
}); });
const togglePictureInPicture = async () => { const togglePictureInPicture = async () => {

View File

@ -12,7 +12,7 @@
tabindex="-1" tabindex="-1"
> >
<div <div
class="icon menu-icon style-scope ytmusic-menu-navigation-item-renderer" class="icon ytmd-menu-item style-scope ytmusic-menu-navigation-item-renderer"
> >
<svg <svg
id="Layer_1" id="Layer_1"

View File

@ -0,0 +1,41 @@
import { t } from '@/i18n';
import { createPlugin } from '@/utils';
export default createPlugin({
name: () => t('plugins.skip-disliked-songs.name'),
description: () => t('plugins.skip-disliked-songs.description'),
restartNeeded: false,
renderer: {
observer: null as MutationObserver | null,
start() {
this.waitForElem('#like-button-renderer').then((likeBtn) => {
this.observer = new MutationObserver(() => {
if (likeBtn?.getAttribute('like-status') == 'DISLIKE') {
document
.querySelector('tp-yt-paper-icon-button.next-button')
?.click();
}
});
this.observer.observe(likeBtn, {
attributes: true,
childList: false,
subtree: false,
});
});
},
stop() {
this.observer?.disconnect();
},
waitForElem(selector) {
return new Promise((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
});
});
},
},
});

View File

@ -1,4 +1,8 @@
// Creates a DOM element from an HTML string /**
* Creates a DOM element from an HTML string
* @param html The HTML string
* @returns The DOM element
*/
export const ElementFromHtml = (html: string): HTMLElement => { export const ElementFromHtml = (html: string): HTMLElement => {
const template = document.createElement('template'); const template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result html = html.trim(); // Never return a text node of whitespace as the result
@ -6,3 +10,14 @@ export const ElementFromHtml = (html: string): HTMLElement => {
return template.content.firstElementChild as HTMLElement; return template.content.firstElementChild as HTMLElement;
}; };
/**
* Creates a DOM element from a src string
* @param src The source of the image
* @returns The image element
*/
export const ImageElementFromSrc = (src: string): HTMLImageElement => {
const image = document.createElement('img');
image.src = src;
return image;
};

View File

@ -1,6 +1,4 @@
import process from 'node:process'; import { contextBridge, ipcRenderer, IpcRendererEvent, webFrame } from 'electron';
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
import is from 'electron-is'; import is from 'electron-is';
import config from './config'; import config from './config';
@ -55,3 +53,19 @@ contextBridge.exposeInMainWorld(
'ELECTRON_RENDERER_URL', 'ELECTRON_RENDERER_URL',
process.env.ELECTRON_RENDERER_URL, process.env.ELECTRON_RENDERER_URL,
); );
const [path, script] = ipcRenderer.sendSync('get-renderer-script') as [string | null, string];
let blocked = true;
if (path) {
webFrame.executeJavaScriptInIsolatedWorld(0, [
{
code: script,
url: path,
},
], true, () => blocked = false);
} else {
webFrame.executeJavaScript(script, true, () => blocked = false);
}
// HACK: Wait for the script to be executed
while (blocked);

View File

@ -2,7 +2,7 @@ import { singleton } from './decorators';
import type { YoutubePlayer } from '@/types/youtube-player'; import type { YoutubePlayer } from '@/types/youtube-player';
import type { GetState } from '@/types/datahost-get-state'; import type { GetState } from '@/types/datahost-get-state';
import type { VideoDataChangeValue } from '@/types/player-api-events'; import type { AlbumDetails, VideoDataChangeValue } from '@/types/player-api-events';
import type { SongInfo } from './song-info'; import type { SongInfo } from './song-info';
import type { VideoDataChanged } from '@/types/video-data-changed'; import type { VideoDataChanged } from '@/types/video-data-changed';
@ -155,7 +155,10 @@ export default (api: YoutubePlayer) => {
const data = api.getPlayerResponse(); const data = api.getPlayerResponse();
data.videoDetails.album = data.videoDetails.album =
videoData?.Hd?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album.runs?.at( (
Object.entries(videoData)
.find(([, value]) => value && Object.hasOwn(value, 'playerOverlays')) as [string, AlbumDetails | undefined]
)?.[1]?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album?.runs?.at(
0, 0,
)?.text; )?.text;
data.videoDetails.elapsedSeconds = 0; data.videoDetails.elapsedSeconds = 0;

View File

@ -21,37 +21,6 @@ let isPluginLoaded = false;
let isApiLoaded = false; let isApiLoaded = false;
let firstDataLoaded = false; let firstDataLoaded = false;
const observer = new MutationObserver(() => {
const playerApi = document.querySelector<Element & YoutubePlayer>(
'#movie_player',
);
if (playerApi) {
observer.disconnect();
// Inject song-info provider
setupSongInfo(playerApi);
const dataLoadedListener = (name: string) => {
if (!firstDataLoaded && name === 'dataloaded') {
firstDataLoaded = true;
playerApi.removeEventListener('videodatachange', dataLoadedListener);
}
};
playerApi.addEventListener('videodatachange', dataLoadedListener);
if (isPluginLoaded && !isApiLoaded) {
api = playerApi;
isApiLoaded = true;
onApiLoaded();
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
async function listenForApiLoad() { async function listenForApiLoad() {
if (!isApiLoaded) { if (!isApiLoaded) {
api = document.querySelector('#movie_player'); api = document.querySelector('#movie_player');
@ -176,14 +145,16 @@ const defineYTMDTransElements = () => {
); );
}; };
(async () => { const preload = async () => {
await loadI18n(); await loadI18n();
await setLanguage(window.mainConfig.get('options.language') ?? 'en'); await setLanguage(window.mainConfig.get('options.language') ?? 'en');
window.i18n = { window.i18n = {
t: i18t.bind(i18next), t: i18t.bind(i18next),
}; };
defineYTMDTransElements(); defineYTMDTransElements();
};
const main = async () => {
await loadAllRendererPlugins(); await loadAllRendererPlugins();
isPluginLoaded = true; isPluginLoaded = true;
@ -226,4 +197,48 @@ const defineYTMDTransElements = () => {
console.log(JSON.parse(log)); console.log(JSON.parse(log));
}); });
} }
})(); };
const initObserver = async () => {
// check document.documentElement is ready
await new Promise<void>((resolve) => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => resolve(), { once: true });
} else {
resolve();
}
});
const observer = new MutationObserver(() => {
const playerApi = document.querySelector<Element & YoutubePlayer>(
'#movie_player',
);
if (playerApi) {
observer.disconnect();
// Inject song-info provider
setupSongInfo(playerApi);
const dataLoadedListener = (name: string) => {
if (!firstDataLoaded && name === 'dataloaded') {
firstDataLoaded = true;
playerApi.removeEventListener('videodatachange', dataLoadedListener);
}
};
playerApi.addEventListener('videodatachange', dataLoadedListener);
if (isPluginLoaded && !isApiLoaded) {
api = playerApi;
isApiLoaded = true;
onApiLoaded();
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
};
initObserver().then(preload).then(main);

View File

@ -246,8 +246,6 @@ export type VideoDataChangeValue = Record<string, unknown> & {
title: string; title: string;
author: string; author: string;
Hd?: AlbumDetails;
playlistId: string; playlistId: string;
isUpcoming: boolean; isUpcoming: boolean;
loading: boolean; loading: boolean;
@ -259,4 +257,5 @@ export interface PlayerAPIEvents {
videodatachange: { videodatachange: {
value: VideoDataChangeValue; value: VideoDataChangeValue;
} & ({ name: 'dataloaded' } | { name: 'dataupdated ' }); } & ({ name: 'dataloaded' } | { name: 'dataupdated ' });
onStateChange: number;
} }

View File

@ -47,6 +47,7 @@ export interface PluginDef<
name: () => string; name: () => string;
authors?: Author[]; authors?: Author[];
description?: () => string; description?: () => string;
addedVersion?: string;
config?: Config; config?: Config;
menu?: ( menu?: (

View File

@ -170,17 +170,13 @@ export interface YoutubePlayer {
channelUnsubscribed: <Parameters extends unknown[], Return>( channelUnsubscribed: <Parameters extends unknown[], Return>(
...params: Parameters ...params: Parameters
) => Return; ) => Return;
togglePictureInPicture: <Parameters extends unknown[], Return>( togglePictureInPicture: () => void;
...params: Parameters
) => Return;
supportsGaplessAudio: () => boolean; supportsGaplessAudio: () => boolean;
supportsGaplessShorts: () => boolean; supportsGaplessShorts: () => boolean;
enqueueVideoByPlayerVars: <Parameters extends unknown[], Return>( enqueueVideoByPlayerVars: <Parameters extends unknown[], Return>(
...params: Parameters ...params: Parameters
) => Return; ) => Return;
clearQueue: <Parameters extends unknown[], Return>( clearQueue: () => void;
...params: Parameters
) => Return;
getAudioTrack: <Parameters extends unknown[], Return>( getAudioTrack: <Parameters extends unknown[], Return>(
...params: Parameters ...params: Parameters
) => Return; ) => Return;
@ -266,7 +262,23 @@ export interface YoutubePlayer {
showControls: () => void; showControls: () => void;
hideControls: () => void; hideControls: () => void;
cancelPlayback: () => void; cancelPlayback: () => void;
getProgressState: <Return>() => Return; getProgressState: () => {
airingEnd: number;
airingStart: number;
allowSeeking: boolean;
clipEnd: number;
clipStart: number;
current: number;
displayedStart: number;
duration: number;
ingestionTime: number;
isAtLiveHead: boolean;
loaded: number;
offset: number;
seekableEnd: number;
seekableStart: number;
viewerLivestreamJoinMediaTime: number;
};
isInline: () => boolean; isInline: () => boolean;
setInline: (isInline: boolean) => void; setInline: (isInline: boolean) => void;
setLoopVideo: (value: boolean) => void; setLoopVideo: (value: boolean) => void;
@ -284,14 +296,10 @@ export interface YoutubePlayer {
handleGlobalKeyDown: () => void; handleGlobalKeyDown: () => void;
handleGlobalKeyUp: () => void; handleGlobalKeyUp: () => void;
wakeUpControls: () => void; wakeUpControls: () => void;
cueVideoById: (videoId: string) => void; cueVideoById: (videoId: string, startSeconds: number, suggestedQuality: string) => void;
loadVideoById: (videoId: string) => void; loadVideoById: (videoId: string, startSeconds: number, suggestedQuality: string) => void;
cueVideoByUrl: <Parameters extends unknown[], Return>( cueVideoByUrl: (mediaContentUrl: string, startSeconds: number, suggestedQuality: string, playerType: string) => void;
...params: Parameters loadVideoByUrl: (mediaContentUrl: string, startSeconds: number, suggestedQuality: string, playerType: string) => void;
) => Return;
loadVideoByUrl: <Parameters extends unknown[], Return>(
...params: Parameters
) => Return;
/** /**
* Note: This doesn't resume playback, it plays from the start. * Note: This doesn't resume playback, it plays from the start.
*/ */
@ -328,6 +336,10 @@ export interface YoutubePlayer {
getVolume: () => number; getVolume: () => number;
seekTo: (seconds: number) => void; seekTo: (seconds: number) => void;
getPlayerMode: <Return>() => Return; getPlayerMode: <Return>() => Return;
/**
* 1: playing
* 2: paused
*/
getPlayerState: () => number; getPlayerState: () => number;
getAvailablePlaybackRates: () => number[]; getAvailablePlaybackRates: () => number[];
getPlaybackQuality: () => string; getPlaybackQuality: () => string;
@ -363,9 +375,7 @@ export interface YoutubePlayer {
addCueRange: <Parameters extends unknown[], Return>( addCueRange: <Parameters extends unknown[], Return>(
...params: Parameters ...params: Parameters
) => Return; ) => Return;
removeCueRange: <Parameters extends unknown[], Return>( removeCueRange: (range: unknown[]) => void;
...params: Parameters
) => Return;
setSize: (size: { width: number; height: number }) => void; setSize: (size: { width: number; height: number }) => void;
destroy: <Parameters extends unknown[], Return>( destroy: <Parameters extends unknown[], Return>(
...params: Parameters ...params: Parameters
@ -383,12 +393,8 @@ export interface YoutubePlayer {
getVideoUrl: () => string; getVideoUrl: () => string;
getMediaReferenceTime: () => number; getMediaReferenceTime: () => number;
getSize: () => { width: number; height: number }; getSize: () => { width: number; height: number };
logImaAdEvent: <Parameters extends unknown[], Return>( logImaAdEvent: (eventType: unknown, breakType: unknown) => void;
...params: Parameters preloadVideoById: (videoId: string, startSeconds: number, suggestedQuality: string) => void;
) => Return;
preloadVideoById: <Parameters extends unknown[], Return>(
...params: Parameters
) => Return;
setAccountLinkState: <Parameters extends unknown[], Return>( setAccountLinkState: <Parameters extends unknown[], Return>(
...params: Parameters ...params: Parameters
) => Return; ) => Return;

View File

@ -24,10 +24,6 @@ ytmusic-app-layout {
--ytmusic-nav-bar-height: 90px; --ytmusic-nav-bar-height: 90px;
} }
ytmusic-search-box.ytmusic-nav-bar {
margin-top: 15px;
}
/* Blocking annoying elements */ /* Blocking annoying elements */
ytmusic-mealbar-promo-renderer { ytmusic-mealbar-promo-renderer {
display: none !important; display: none !important;

View File

@ -15,6 +15,11 @@ declare module '*.svg?inline' {
export default base64; export default base64;
} }
declare module '*.svg?raw' {
const html: string;
export default html;
}
declare module '*.png' { declare module '*.png' {
const element: HTMLImageElement; const element: HTMLImageElement;

View File

@ -1,9 +1,9 @@
{ {
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json", "extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "esnext",
"lib": ["dom", "dom.iterable", "es2022"], "lib": ["dom", "dom.iterable", "es2022"],
"module": "CommonJS", "module": "esnext",
"types": ["electron-vite/node"], "types": ["electron-vite/node"],
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"esModuleInterop": true, "esModuleInterop": true,
@ -21,5 +21,8 @@
} }
}, },
"exclude": ["./dist"], "exclude": ["./dist"],
"include": ["electron.vite.config.ts", "./src/**/*"] "include": [
"electron.vite.config.mts",
"./src/**/*"
]
} }

View File

@ -1,4 +1,5 @@
import { basename, relative, resolve, extname } from 'node:path'; import { basename, relative, resolve, extname, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { globSync } from 'glob'; import { globSync } from 'glob';
import { Project } from 'ts-morph'; import { Project } from 'ts-morph';
@ -7,6 +8,7 @@ const snakeToCamel = (text: string) =>
text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase()); text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
export const i18nImporter = () => { export const i18nImporter = () => {
const __dirname = dirname(fileURLToPath(import.meta.url));
const project = new Project({ const project = new Project({
tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'), tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'),
skipAddingFilesFromTsConfig: true, skipAddingFilesFromTsConfig: true,

View File

@ -1,4 +1,5 @@
import { basename, relative, resolve, extname } from 'node:path'; import { basename, relative, resolve, extname, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { globSync } from 'glob'; import { globSync } from 'glob';
import { Project } from 'ts-morph'; import { Project } from 'ts-morph';
@ -9,6 +10,7 @@ const snakeToCamel = (text: string) =>
export const pluginVirtualModuleGenerator = ( export const pluginVirtualModuleGenerator = (
mode: 'main' | 'preload' | 'renderer', mode: 'main' | 'preload' | 'renderer',
) => { ) => {
const __dirname = dirname(fileURLToPath(import.meta.url));
const project = new Project({ const project = new Project({
tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'), tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'),
skipAddingFilesFromTsConfig: true, skipAddingFilesFromTsConfig: true,

View File

@ -1,5 +1,6 @@
import { readFile } from 'node:fs/promises'; import { readFile } from 'node:fs/promises';
import { resolve, basename } from 'node:path'; import { resolve, basename, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createFilter } from 'vite'; import { createFilter } from 'vite';
import { Project, ts, ObjectLiteralExpression, VariableDeclarationKind } from 'ts-morph'; import { Project, ts, ObjectLiteralExpression, VariableDeclarationKind } from 'ts-morph';
@ -17,6 +18,8 @@ export default function (mode: 'backend' | 'preload' | 'renderer' | 'none'): Plu
async load(id) { async load(id) {
if (!pluginFilter(id)) return null; if (!pluginFilter(id)) return null;
const __dirname = dirname(fileURLToPath(import.meta.url));
const project = new Project({ const project = new Project({
tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'), tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'),
skipAddingFilesFromTsConfig: true, skipAddingFilesFromTsConfig: true,

View File

@ -1,28 +0,0 @@
require 'json'
require 'open-uri'
cask "youtube-music" do
desc "YouTube Music Desktop App"
homepage "https://github.com/th-ch/youtube-music"
# Fetch the latest release version from GitHub API
latest_release = JSON.parse(open("https://api.github.com/repos/th-ch/youtube-music/releases/latest").read)['tag_name']
version latest_release
base_url = "https://github.com/th-ch/youtube-music/releases/download/#{latest_release}/YouTube-Music-#{latest_release.delete_prefix('v')}"
file_extension = Hardware::CPU.arm? ? "-arm64.dmg" : ".dmg"
url "#{base_url}#{file_extension}"
# TODO checksum
sha256 :no_check
app "YouTube Music.app"
postflight do
print("Removing quarantine attribute from YouTube Music.app.\n")
system "xattr -cr '/Applications/YouTube Music.app'"
end
auto_updates true
end