Compare commits

...

74 Commits

Author SHA1 Message Date
66816ac42d Bump version to 3.5.1 2024-08-01 20:39:44 +09:00
08b985f2ab chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-08-01 13:38:36 +02:00
747bde2136 fix(synced-lyrics): fix lyric load
fix #2295
2024-08-01 20:19:08 +09:00
eabc28b39f fix(wait-for-element): add 100ms timeout for improve performance 2024-08-01 20:19:08 +09:00
3537dc19ee chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 97.8% (358 of 366 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2024-08-01 10:57:05 +00:00
6afeb60557 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 100.0% (366 of 366 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-08-01 10:57:04 +00:00
71115dedee chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (366 of 366 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-08-01 10:57:04 +00:00
8750b54f76 fix(synced-lyrics): fix i18n 2024-08-01 19:56:32 +09:00
482a1c5073 fix(deps): update dependency youtubei.js to v10.3.0 (#2306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 19:27:58 +09:00
c8d516c40b fix: Window gets stuck offscreen in some instances (#2303)
Improved offscreen detection logic, fixes th-ch#1894
2024-08-01 19:27:01 +09:00
c1ad168c32 fix: Incorrect window size on multi-monitor scaled displays (#2302)
see discussion on th-ch#2258
2024-08-01 19:26:37 +09:00
5f5be5d02f chore(deps): update dependency rollup to v4.19.2 (#2304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 19:25:59 +09:00
61ef56dccc chore: enable sourcemaps in dev 2024-08-01 11:02:21 +03:00
a73b5acc75 chore(deps): update typescript-eslint monorepo to v8 (major) (#2297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 16:37:08 +09:00
877573532c ts-fix: disambiguate ElectronStore typings 2024-08-01 10:10:52 +03:00
7b033b5caf fix(ambient-mode): fix ambient-mode not working for videos after restart (#2294)
* Fix Ambient Mode not working for videos after restart (#2255)

This should fix https://github.com/th-ch/youtube-music/issues/1641

* fix: fix waitForElement

---------

Co-authored-by: craftgeil <80261988+craftgeil@users.noreply.github.com>
2024-07-31 22:08:45 +09:00
8924ec29d3 fix(deps): update dependency @xhayper/discord-rpc to v1.2.0 (#2291)
* fix(deps): update dependency @xhayper/discord-rpc to v1.2.0

* fix: discord-rpc

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2024-07-31 21:57:37 +09:00
23e688aaf8 Update changelog for v3.5.0 2024-07-31 12:04:22 +00:00
9b3cbe8e01 Bump version to 3.5.0 2024-07-31 20:53:02 +09:00
67a89e8ed4 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (366 of 366 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-07-31 13:51:24 +02:00
464a2b94ea fix: bump electron-builder version to 6.3.2 2024-07-31 20:37:38 +09:00
9357a15116 fix(synced-lyrics): fix type error 2024-07-31 20:32:34 +09:00
ee820bb01c Delete test-results directory 2024-07-31 19:55:51 +09:00
6b81735811 chore(i18n): Translated using Weblate (French)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-07-31 12:54:43 +02:00
8ce91b143a plugin: Synced Lyrics (#2207)
* Added Plugin File

* Added Logic

* Known issue

* Finished Backend part

* Before cleanup

* Added Style
Removed log

* Fixed time and visibility issues

* Changed lyrics style

* Changed way lyrics are selected

* Fix

* Added style lyrics options

* Cleanup

* Fix lyrics styling
Changed how lyrics status are changed

* Moved code to make file more readable

* Change Tab Size

* Fixed issue with overlapping lyrics

* Removed debug console.log

* Added style adaptation for music videos

* Changed file indent

* Revered back to original pnpm file

* Removed unnecessary option

* Fix lyrics status bug
Removed leftover logs

* Started to implement fetching for genius lyrics

* feat(synced-lyrics): add `addedVersion` field

* Made changes according to feedbacks

* fix: add a delay of 300ms to the current time

- Since the transition takes 300ms, we need to add a delay of 300ms to the current time

* Removed test about genius.com scraping

* Removed 300ms delay

* chore: cleaned up the code

* Specified path and variable

* chore: always enable lyrics tab

* chore: use SolidJS to render the lyrics

* chore: remove useless signal

* chore: feature-parity with original PR (+some nice stuff)

* recreate lock file

* show json decode error

* feat(synced-lyrics): improve ui
- Change type assertion code
- Replace span to `yt-formatted-string`
- Add refetch button

* chore: make the lyric styling a solidjs effect

* feat: i18n

* chore: apply suggestion

---------

Co-authored-by: Su-Yong <simssy2205@gmail.com>
Co-authored-by: JellyBrick <shlee1503@naver.com>
Co-authored-by: Angelos Bouklis <53124886+ArjixWasTaken@users.noreply.github.com>
2024-07-31 19:54:21 +09:00
116dbad9bc chore(deps): update dependency electron to v31.3.1 (#2290)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-31 19:52:47 +09:00
977af3d617 chore(deps): update typescript-eslint monorepo to v7.18.0 (#2292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-31 19:52:38 +09:00
6da8defc73 chore(i18n): Translated using Weblate (Sinhala)
Currently translated at 4.4% (16 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/si/
2024-07-29 01:09:20 +00:00
0e93a963e1 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-07-29 01:09:19 +00:00
1e98b2e75a fix(deps): update dependency youtubei.js to v10.2.0 (#2285)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-29 01:30:01 +09:00
6f5f13a840 chore(deps): update dependency electron to v31.3.0 (#2282)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:10:07 +00:00
822bcedadf chore(deps): update typescript-eslint monorepo to v7.17.0 (#2283)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:09:48 +00:00
2b6aea82c3 fix(deps): update dependency solid-js to v1.8.19 (#2280)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 19:21:49 +09:00
4f4efb407e fix(deps): update dependency @xhayper/discord-rpc to v1.1.4 (#2279)
* fix(deps): update dependency @xhayper/discord-rpc to v1.1.4

* Update and rename @xhayper__discord-rpc@1.1.2.patch to @xhayper__discord-rpc@1.1.4.patch

* Update package.json

* fix: update pnpm-lock.yaml

* fix: use Listening instead of Playing

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2024-07-28 19:21:35 +09:00
6159e0e652 chore(deps): update dependency @babel/runtime to v7.25.0 (#2281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 19:07:01 +09:00
3957e06174 fix(deps): update dependency @floating-ui/dom to v1.6.8 (#2278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 19:06:26 +09:00
c78f823b9b Fix: Incorrect window size on scaled displays (#2258)
fix th-ch#1716
2024-07-28 19:02:08 +09:00
1be3bb360e chore(deps): update dependency vite-plugin-resolve to v2.5.2 (#2276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 15:06:22 +09:00
ba2afd2652 chore(deps): update playwright monorepo to v1.45.3 (#2277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 15:06:07 +09:00
5e283c9ea5 fix(deps): update dependency deepmerge-ts to v7.1.0 (#2263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:59:23 +09:00
ddb1c56111 chore(deps): update dependency typescript to v5.5.4 (#2274)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:59:13 +09:00
ebd167f3f2 chore(deps): update dependency vite to v5.3.5 (#2275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:55:57 +09:00
178a62b9d3 fix(deps): update dependency i18next to v23.12.2 (#2260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:46:26 +09:00
f98a2cf766 chore(deps): update dependency discord-api-types to v0.37.93 (#2273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:46:06 +09:00
fdbe6f7331 chore(deps): update dependency rollup to v4.19.1 (#2261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 14:45:54 +09:00
57c2cdc91e chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2024-07-26 18:09:17 +02:00
0f5074f8ab chore(i18n): Translated using Weblate (French)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-07-26 18:09:13 +02:00
661396226d fix(deps): update dependency custom-electron-prompt to v1.5.8 (#2262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-26 12:42:17 +09:00
36f27fe2e6 chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-07-25 15:09:16 +02:00
adf1ce4bc7 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-07-22 14:09:21 +02:00
43b4b8df5e chore(i18n): Translated using Weblate (German)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-07-22 14:09:20 +02:00
4a8440c281 chore(i18n): Translated using Weblate (Catalan)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ca/
2024-07-20 18:09:24 +02:00
32fe9fcffe chore(i18n): Translated using Weblate (French)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-07-20 18:09:22 +02:00
a9896845da chore(i18n): Added translation using Weblate (Catalan) 2024-07-19 17:02:32 +02:00
a59aa07334 chore(i18n): Translated using Weblate (Filipino)
Currently translated at 83.7% (299 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fil/
2024-07-19 10:09:32 +02:00
e07d7395e7 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (357 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-07-19 10:09:31 +02:00
9bb6f32ece chore(i18n): Translated using Weblate (Czech)
Currently translated at 96.3% (344 of 357 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-07-19 10:09:30 +02:00
ccb19a0dc9 feat(adblocker): add new option AdSpeedup (#2235)
* Ad speedup code

* And ad-speedup translations

* Update index.ts

* fix error

* Update build.yml

* add AdSpeedup as adBlock option

* remove it as own plugin

* remove console.log

* add semicolons
2024-07-18 13:16:46 +09:00
64fb6c2597 fix: disable multi-plane format for software video (#2254) 2024-07-18 13:15:26 +09:00
73c3e355fe chore(deps): update dependency eslint-plugin-prettier to v5.2.1 (#2253)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 03:42:41 +09:00
fc7a504643 chore(deps): update dependency vite to v5.3.4 (#2243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 03:42:34 +09:00
764dc0f895 chore(deps): update typescript-eslint monorepo to v7.16.1 (#2239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 03:42:26 +09:00
9f33f49ec4 chore(deps): update playwright monorepo to v1.45.2 (#2244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 03:32:19 +09:00
87ae6d29bb chore(deps): update dependency vite-plugin-inspect to v0.8.5 (#2252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 03:31:55 +09:00
093c8e3ca6 fix(deps): update dependency semver to v7.6.3 (#2250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 03:29:19 +09:00
fec26a010d chore(deps): update dependency electron to v31.2.1 (#2241)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-17 18:08:17 +09:00
5d8aaccc55 chore(i18n): Translated using Weblate (Hebrew)
Currently translated at 4.5% (16 of 355 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/he/
2024-07-17 08:09:14 +00:00
cda03078a9 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (355 of 355 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-07-16 02:46:56 +02:00
9c139b96f4 chore(i18n): Translated using Weblate (French)
Currently translated at 98.8% (351 of 355 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-07-16 02:46:55 +02:00
9b2816c156 chore(i18n): Translated using Weblate (Finnish)
Currently translated at 80.8% (287 of 355 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fi/
2024-07-15 19:09:19 +00:00
b1b8847134 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (355 of 355 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-07-15 19:09:19 +00:00
bf9e698288 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (355 of 355 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-07-15 19:09:18 +00:00
28e8a1c5dd chore(i18n): Translated using Weblate (Polish)
Currently translated at 99.7% (354 of 355 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-07-15 19:09:17 +00:00
18e0b1b863 Update changelog for v3.4.1 2024-07-14 15:26:58 +00:00
49 changed files with 2907 additions and 627 deletions

View File

@ -2,8 +2,114 @@
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.5.0](https://github.com/th-ch/youtube-music/compare/v3.4.1...v3.5.0)
- plugin: Synced Lyrics [`#2207`](https://github.com/th-ch/youtube-music/pull/2207)
- chore(deps): update dependency electron to v31.3.1 [`#2290`](https://github.com/th-ch/youtube-music/pull/2290)
- chore(deps): update typescript-eslint monorepo to v7.18.0 [`#2292`](https://github.com/th-ch/youtube-music/pull/2292)
- fix(deps): update dependency youtubei.js to v10.2.0 [`#2285`](https://github.com/th-ch/youtube-music/pull/2285)
- chore(deps): update dependency electron to v31.3.0 [`#2282`](https://github.com/th-ch/youtube-music/pull/2282)
- chore(deps): update typescript-eslint monorepo to v7.17.0 [`#2283`](https://github.com/th-ch/youtube-music/pull/2283)
- fix(deps): update dependency solid-js to v1.8.19 [`#2280`](https://github.com/th-ch/youtube-music/pull/2280)
- fix(deps): update dependency @xhayper/discord-rpc to v1.1.4 [`#2279`](https://github.com/th-ch/youtube-music/pull/2279)
- chore(deps): update dependency @babel/runtime to v7.25.0 [`#2281`](https://github.com/th-ch/youtube-music/pull/2281)
- fix(deps): update dependency @floating-ui/dom to v1.6.8 [`#2278`](https://github.com/th-ch/youtube-music/pull/2278)
- Fix: Incorrect window size on scaled displays [`#2258`](https://github.com/th-ch/youtube-music/pull/2258)
- chore(deps): update dependency vite-plugin-resolve to v2.5.2 [`#2276`](https://github.com/th-ch/youtube-music/pull/2276)
- chore(deps): update playwright monorepo to v1.45.3 [`#2277`](https://github.com/th-ch/youtube-music/pull/2277)
- fix(deps): update dependency deepmerge-ts to v7.1.0 [`#2263`](https://github.com/th-ch/youtube-music/pull/2263)
- chore(deps): update dependency typescript to v5.5.4 [`#2274`](https://github.com/th-ch/youtube-music/pull/2274)
- chore(deps): update dependency vite to v5.3.5 [`#2275`](https://github.com/th-ch/youtube-music/pull/2275)
- fix(deps): update dependency i18next to v23.12.2 [`#2260`](https://github.com/th-ch/youtube-music/pull/2260)
- chore(deps): update dependency discord-api-types to v0.37.93 [`#2273`](https://github.com/th-ch/youtube-music/pull/2273)
- chore(deps): update dependency rollup to v4.19.1 [`#2261`](https://github.com/th-ch/youtube-music/pull/2261)
- fix(deps): update dependency custom-electron-prompt to v1.5.8 [`#2262`](https://github.com/th-ch/youtube-music/pull/2262)
- feat(adblocker): add new option AdSpeedup [`#2235`](https://github.com/th-ch/youtube-music/pull/2235)
- fix: disable multi-plane format for software video [`#2254`](https://github.com/th-ch/youtube-music/pull/2254)
- chore(deps): update dependency eslint-plugin-prettier to v5.2.1 [`#2253`](https://github.com/th-ch/youtube-music/pull/2253)
- chore(deps): update dependency vite to v5.3.4 [`#2243`](https://github.com/th-ch/youtube-music/pull/2243)
- chore(deps): update typescript-eslint monorepo to v7.16.1 [`#2239`](https://github.com/th-ch/youtube-music/pull/2239)
- chore(deps): update playwright monorepo to v1.45.2 [`#2244`](https://github.com/th-ch/youtube-music/pull/2244)
- chore(deps): update dependency vite-plugin-inspect to v0.8.5 [`#2252`](https://github.com/th-ch/youtube-music/pull/2252)
- fix(deps): update dependency semver to v7.6.3 [`#2250`](https://github.com/th-ch/youtube-music/pull/2250)
- chore(deps): update dependency electron to v31.2.1 [`#2241`](https://github.com/th-ch/youtube-music/pull/2241)
- chore(i18n): Translated using Weblate (Catalan) [`4a8440c`](https://github.com/th-ch/youtube-music/commit/4a8440c281c341977ab3687982cec8cbc5af6cf7)
- Update changelog for v3.4.1 [`18e0b1b`](https://github.com/th-ch/youtube-music/commit/18e0b1b86341b13f1cbc713bfbd7b5d7a45ee392)
- fix(synced-lyrics): fix type error [`9357a15`](https://github.com/th-ch/youtube-music/commit/9357a15116a8526d22ba6142c0a02f31688743f2)
#### [v3.4.1](https://github.com/th-ch/youtube-music/compare/v3.4.0...v3.4.1)
> 15 July 2024
- fix(mpris): fix mpris position [`#2225`](https://github.com/th-ch/youtube-music/issues/2225)
- fix(deb): fix depends [`#1983`](https://github.com/th-ch/youtube-music/issues/1983)
- fix: fix touchbar icon [`#2183`](https://github.com/th-ch/youtube-music/issues/2183)
- fix: fix "Starting page" [`#1822`](https://github.com/th-ch/youtube-music/issues/1822)
- fix: fix album actions [`#2202`](https://github.com/th-ch/youtube-music/issues/2202)
- fix: fix playback slider [`#2045`](https://github.com/th-ch/youtube-music/issues/2045)
- chore(i18n): Translated using Weblate (Spanish) [`91bee48`](https://github.com/th-ch/youtube-music/commit/91bee4880ed2c6fdd887814a2620877d89bea311)
- Bump version to 3.4.1 [`02e2fb6`](https://github.com/th-ch/youtube-music/commit/02e2fb6a83844f439f760e72cdcb935b86000df2)
#### [v3.4.0](https://github.com/th-ch/youtube-music/compare/v3.3.12...v3.4.0)
> 14 July 2024
- fix(deps): update dependency i18next to v23.12.1 [`#2230`](https://github.com/th-ch/youtube-music/pull/2230)
- feat(downloader): New option to download on finish [`#1964`](https://github.com/th-ch/youtube-music/pull/1964)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.42 [`#2228`](https://github.com/th-ch/youtube-music/pull/2228)
- chore(deps): update dependency eslint to v9.7.0 [`#2226`](https://github.com/th-ch/youtube-music/pull/2226)
- chore(deps): update dependency @babel/runtime to v7.24.8 [`#2221`](https://github.com/th-ch/youtube-music/pull/2221)
- chore(deps): update dependency node-gyp to v10.2.0 [`#2216`](https://github.com/th-ch/youtube-music/pull/2216)
- chore(deps): update dependency ws to v8.18.0 [`#2217`](https://github.com/th-ch/youtube-music/pull/2217)
- chore(deps): update dependency glob to v11 [`#2219`](https://github.com/th-ch/youtube-music/pull/2219)
- chore(deps): update dependency esbuild to v0.23.0 [`#2215`](https://github.com/th-ch/youtube-music/pull/2215)
- chore(deps): update dependency electron to v31.2.0 [`#2214`](https://github.com/th-ch/youtube-music/pull/2214)
- fix(deps): update dependency youtubei.js to v10.1.0 [`#2218`](https://github.com/th-ch/youtube-music/pull/2218)
- chore(deps): update playwright monorepo to v1.45.1 [`#2212`](https://github.com/th-ch/youtube-music/pull/2212)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.41 [`#2213`](https://github.com/th-ch/youtube-music/pull/2213)
- chore(deps): update dependency rollup to v4.18.1 [`#2210`](https://github.com/th-ch/youtube-music/pull/2210)
- chore(deps): update dependency eslint to v9.6.0 [`#2192`](https://github.com/th-ch/youtube-music/pull/2192)
- chore(deps): update dependency vite to v5.3.3 [`#2211`](https://github.com/th-ch/youtube-music/pull/2211)
- chore(deps): update dependency glob to v10.4.5 [`#2205`](https://github.com/th-ch/youtube-music/pull/2205)
- chore(deps): update dependency discord-api-types to v0.37.92 [`#2204`](https://github.com/th-ch/youtube-music/pull/2204)
- fix(deps): update dependency solid-js to v1.8.18 [`#2189`](https://github.com/th-ch/youtube-music/pull/2189)
- chore(deps): update dependency typescript to v5.5.3 [`#2206`](https://github.com/th-ch/youtube-music/pull/2206)
- chore(deps): update dependency electron to v31.1.0 [`#2190`](https://github.com/th-ch/youtube-music/pull/2190)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.40 [`#2193`](https://github.com/th-ch/youtube-music/pull/2193)
- fix(deps): update dependency @floating-ui/dom to v1.6.7 [`#2196`](https://github.com/th-ch/youtube-music/pull/2196)
- chore(deps): update dependency vite to v5.3.2 [`#2188`](https://github.com/th-ch/youtube-music/pull/2188)
- chore(deps): update dependency discord-api-types to v0.37.91 [`#2187`](https://github.com/th-ch/youtube-music/pull/2187)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.34 [`#2184`](https://github.com/th-ch/youtube-music/pull/2184)
- fix(deps): update dependency @floating-ui/dom to v1.6.6 [`#2182`](https://github.com/th-ch/youtube-music/pull/2182)
- chore(deps): update playwright monorepo to v1.45.0 [`#2181`](https://github.com/th-ch/youtube-music/pull/2181)
- fix(deps): update dependency ts-morph to v23 [`#2180`](https://github.com/th-ch/youtube-music/pull/2180)
- chore(deps): update dependency electron-vite to v2.3.0 [`#2178`](https://github.com/th-ch/youtube-music/pull/2178)
- fix(deps): update dependency conf to v13.0.1 [`#2175`](https://github.com/th-ch/youtube-music/pull/2175)
- chore(deps): update dependency glob to v10.4.2 [`#2168`](https://github.com/th-ch/youtube-music/pull/2168)
- chore(deps): update dependency discord-api-types to v0.37.90 [`#2167`](https://github.com/th-ch/youtube-music/pull/2167)
- chore(deps): update dependency typescript to v5.5.2 [`#2173`](https://github.com/th-ch/youtube-music/pull/2173)
- chore(deps): update dependency electron to v31.0.2 [`#2170`](https://github.com/th-ch/youtube-music/pull/2170)
- chore(deps): update dependency ws to v8.17.1 [`#2164`](https://github.com/th-ch/youtube-music/pull/2164)
- chore(deps): update dependency eslint to v9.5.0 [`#2162`](https://github.com/th-ch/youtube-music/pull/2162)
- fix(deps): update dependency youtubei.js to v10 [`#2136`](https://github.com/th-ch/youtube-music/pull/2136)
- chore(deps): update dependency discord-api-types to v0.37.89 [`#2153`](https://github.com/th-ch/youtube-music/pull/2153)
- chore(deps): update dependency vite to v5.3.1 [`#2154`](https://github.com/th-ch/youtube-music/pull/2154)
- fix(deps): update dependency electron-store to v10 [`#2157`](https://github.com/th-ch/youtube-music/pull/2157)
- fix(deps): update dependency conf to v13 [`#2156`](https://github.com/th-ch/youtube-music/pull/2156)
- chore(deps): update dependency electron to v31.0.1 [`#2148`](https://github.com/th-ch/youtube-music/pull/2148)
- chore(deps): update dependency discord-api-types to v0.37.88 [`#2138`](https://github.com/th-ch/youtube-music/pull/2138)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.30 [`#2139`](https://github.com/th-ch/youtube-music/pull/2139)
- chore(deps): update dependency electron to v31 [`#2141`](https://github.com/th-ch/youtube-music/pull/2141)
- chore(deps): update dependency esbuild to v0.21.5 [`#2135`](https://github.com/th-ch/youtube-music/pull/2135)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.29 [`#2132`](https://github.com/th-ch/youtube-music/pull/2132)
- fix: rollback eslint version to v8 [`45931a2`](https://github.com/th-ch/youtube-music/commit/45931a25b08ab8a406f9e102486585311fd14bf9)
- chore(i18n): Translated using Weblate (Filipino) [`8a20566`](https://github.com/th-ch/youtube-music/commit/8a20566e0f2736f72d46282188ada69df1d7076a)
- chore(i18n): Translated using Weblate (Slovenian) [`40f0b9b`](https://github.com/th-ch/youtube-music/commit/40f0b9b852dcd9146e1c1e6c741b5baaf55ac079)
#### [v3.3.12](https://github.com/th-ch/youtube-music/compare/v3.3.11...v3.3.12) #### [v3.3.12](https://github.com/th-ch/youtube-music/compare/v3.3.11...v3.3.12)
> 8 June 2024
- hotfix: Revert "chore(deps): update dependencies `@cliqz/adblocker-electron`, `@cliqz/adblocker-electron-preload`" [`3c4abc1`](https://github.com/th-ch/youtube-music/commit/3c4abc14187e51f7e47c1ae71b3513f6d8c9912a) - hotfix: Revert "chore(deps): update dependencies `@cliqz/adblocker-electron`, `@cliqz/adblocker-electron-preload`" [`3c4abc1`](https://github.com/th-ch/youtube-music/commit/3c4abc14187e51f7e47c1ae71b3513f6d8c9912a)
- Update changelog for v3.3.11 [`de22444`](https://github.com/th-ch/youtube-music/commit/de224444c2a6d9030aa22a3b263ceacbc4b41914) - Update changelog for v3.3.11 [`de22444`](https://github.com/th-ch/youtube-music/commit/de224444c2a6d9030aa22a3b263ceacbc4b41914)
- Bump version to 3.3.12 [`89ed7d2`](https://github.com/th-ch/youtube-music/commit/89ed7d2345001fea59514944f4c1d56d2b7bd888) - Bump version to 3.3.12 [`89ed7d2`](https://github.com/th-ch/youtube-music/commit/89ed7d2345001fea59514944f4c1d56d2b7bd888)

View File

@ -1,17 +1,17 @@
import { resolve, dirname, join } from 'node:path'; import { resolve, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { UserConfig } from 'vite';
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 solidPlugin from 'vite-plugin-solid';
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs'; import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs';
import pluginLoader from './vite-plugins/plugin-loader.mjs'; import pluginLoader from './vite-plugins/plugin-loader.mjs';
import type { UserConfig } from 'vite';
import { i18nImporter } from './vite-plugins/i18n-importer.mjs'; import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
import solidPlugin from 'vite-plugin-solid';
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
@ -51,8 +51,12 @@ export default defineConfig({
}; };
if (mode === 'development') { if (mode === 'development') {
commonConfig.build!.sourcemap = 'inline';
commonConfig.plugins?.push( commonConfig.plugins?.push(
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/backend') }), Inspect({
build: true,
outputDir: join(__dirname, '.vite-inspect/backend'),
}),
); );
return commonConfig; return commonConfig;
} }
@ -95,8 +99,12 @@ export default defineConfig({
}; };
if (mode === 'development') { if (mode === 'development') {
commonConfig.build!.sourcemap = 'inline';
commonConfig.plugins?.push( commonConfig.plugins?.push(
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/preload') }), Inspect({
build: true,
outputDir: join(__dirname, '.vite-inspect/preload'),
}),
); );
return commonConfig; return commonConfig;
} }
@ -142,8 +150,12 @@ export default defineConfig({
}; };
if (mode === 'development') { if (mode === 'development') {
commonConfig.build!.sourcemap = 'inline';
commonConfig.plugins?.push( commonConfig.plugins?.push(
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/renderer') }), Inspect({
build: true,
outputDir: join(__dirname, '.vite-inspect/renderer'),
}),
); );
return commonConfig; return commonConfig;
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "youtube-music", "name": "youtube-music",
"productName": "YouTube Music", "productName": "YouTube Music",
"version": "3.4.1", "version": "3.5.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",
@ -116,7 +116,7 @@
"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 --watch", "dev": "cross-env NODE_OPTIONS=--enable-source-maps 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 && pnpm electron-builder --win --mac --linux -p never", "dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
@ -145,11 +145,10 @@
"xml2js": "0.6.2", "xml2js": "0.6.2",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"@electron/universal": "2.0.1", "@electron/universal": "2.0.1",
"@babel/runtime": "7.24.8" "@babel/runtime": "7.25.0"
}, },
"patchedDependencies": { "patchedDependencies": {
"vudio@2.1.1": "patches/vudio@2.1.1.patch", "vudio@2.1.1": "patches/vudio@2.1.1.patch",
"@xhayper/discord-rpc@1.1.2": "patches/@xhayper__discord-rpc@1.1.2.patch",
"app-builder-lib@24.13.3": "patches/app-builder-lib@24.13.3.patch" "app-builder-lib@24.13.3": "patches/app-builder-lib@24.13.3.patch"
} }
}, },
@ -160,64 +159,65 @@
"@electron/remote": "2.1.2", "@electron/remote": "2.1.2",
"@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",
"@floating-ui/dom": "1.6.7", "@floating-ui/dom": "1.6.8",
"@foobar404/wave": "2.0.5", "@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.2", "@skyra/jaro-winkler": "^1.1.1",
"@xhayper/discord-rpc": "1.2.0",
"async-mutex": "0.5.0", "async-mutex": "0.5.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", "color": "4.2.3",
"conf": "13.0.1", "conf": "13.0.1",
"custom-electron-prompt": "1.5.7", "custom-electron-prompt": "1.5.8",
"dbus-next": "0.10.2", "dbus-next": "0.10.2",
"deepmerge-ts": "7.0.3", "deepmerge-ts": "7.1.0",
"electron-debug": "4.0.0", "electron-debug": "4.0.0",
"electron-is": "3.0.0", "electron-is": "3.0.0",
"electron-localshortcut": "3.2.1", "electron-localshortcut": "3.2.1",
"electron-store": "10.0.0", "electron-store": "10.0.0",
"electron-unhandled": "4.0.1", "electron-unhandled": "4.0.1",
"electron-updater": "6.2.1", "electron-updater": "6.3.2",
"fast-average-color": "9.4.0", "fast-average-color": "9.4.0",
"fast-equals": "5.0.1", "fast-equals": "5.0.1",
"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.12.1", "i18next": "23.12.2",
"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.13", "node-html-parser": "6.1.13",
"node-id3": "0.2.6", "node-id3": "0.2.6",
"peerjs": "1.5.4", "peerjs": "1.5.4",
"semver": "7.6.2", "semver": "7.6.3",
"serve": "14.2.3", "serve": "14.2.3",
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9", "simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
"solid-floating-ui": "0.3.1", "solid-floating-ui": "0.3.1",
"solid-js": "1.8.18", "solid-js": "1.8.19",
"solid-styled-components": "0.28.5", "solid-styled-components": "0.28.5",
"solid-transition-group": "0.2.3", "solid-transition-group": "0.2.3",
"ts-morph": "23.0.0", "ts-morph": "23.0.0",
"vudio": "2.1.1", "vudio": "2.1.1",
"x11": "2.3.0", "x11": "2.3.0",
"youtubei.js": "10.1.0" "youtubei.js": "10.3.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.45.1", "@playwright/test": "1.45.3",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.5.1",
"@types/color": "3.0.6", "@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",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@typescript-eslint/eslint-plugin": "7.16.0", "@typescript-eslint/eslint-plugin": "8.0.0",
"@typescript-eslint/parser": "7.16.0", "@typescript-eslint/parser": "8.0.0",
"bufferutil": "4.0.8", "bufferutil": "4.0.8",
"builtin-modules": "4.0.0", "builtin-modules": "4.0.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"del-cli": "5.1.0", "del-cli": "5.1.0",
"discord-api-types": "0.37.92", "discord-api-types": "0.37.93",
"electron": "31.2.0", "electron": "31.3.1",
"electron-builder": "24.13.3", "electron-builder": "24.13.3",
"electron-devtools-installer": "3.2.0", "electron-devtools-installer": "3.2.0",
"electron-vite": "2.3.0", "electron-vite": "2.3.0",
@ -226,16 +226,16 @@
"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.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3", "eslint-plugin-prettier": "5.2.1",
"glob": "11.0.0", "glob": "11.0.0",
"node-gyp": "10.2.0", "node-gyp": "10.2.0",
"playwright": "1.45.1", "playwright": "1.45.3",
"rollup": "4.18.1", "rollup": "4.19.2",
"typescript": "5.5.3", "typescript": "5.5.4",
"utf-8-validate": "6.0.4", "utf-8-validate": "6.0.4",
"vite": "5.3.3", "vite": "5.3.5",
"vite-plugin-inspect": "0.8.4", "vite-plugin-inspect": "0.8.5",
"vite-plugin-resolve": "2.5.1", "vite-plugin-resolve": "2.5.2",
"vite-plugin-solid": "2.10.2", "vite-plugin-solid": "2.10.2",
"ws": "8.18.0" "ws": "8.18.0"
}, },

View File

@ -1,17 +0,0 @@
diff --git a/package.json b/package.json
index 40db5dfbd8a4455ce2987d8115eca9882e1f9f14..414fc6986b9c0cc288908eb0107b90c4bfd916b2 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,7 @@
},
"dependencies": {
"axios": "^1.6.2",
- "ws": "^8.15.1"
- },
- "optionalDependencies": {
- "bufferutil": "^4.0.8",
- "utf-8-validate": "^6.0.3"
+ "ws": "^8.16.0"
},
"devDependencies": {
"@types/node": "^14.*",

897
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,8 @@
import Store from 'electron-store';
import { deepmergeCustom } from 'deepmerge-ts'; import { deepmergeCustom } from 'deepmerge-ts';
import defaultConfig from './defaults'; import defaultConfig from './defaults';
import store from './store'; import store, { IStore } from './store';
import plugins from './plugins'; import plugins from './plugins';
import { restart } from '@/providers/app-controls'; import { restart } from '@/providers/app-controls';
@ -62,20 +61,19 @@ type Join<K, P> = K extends string | number
type Paths<T, D extends number = 10> = [D] extends [never] type Paths<T, D extends number = 10> = [D] extends [never]
? never ? never
: T extends object : T extends object
? { ? {
[K in keyof T]-?: K extends string | number [K in keyof T]-?: K extends string | number
? `${K}` | Join<K, Paths<T[K], Prev[D]>> ? `${K}` | Join<K, Paths<T[K], Prev[D]>>
: never; : never;
}[keyof T] }[keyof T]
: ''; : '';
type SplitKey<K> = K extends `${infer A}.${infer B}` ? [A, B] : [K, string]; type SplitKey<K> = K extends `${infer A}.${infer B}` ? [A, B] : [K, string];
type PathValue<T, K extends string> = SplitKey<K> extends [ type PathValue<T, K extends string> =
infer A extends keyof T, SplitKey<K> extends [infer A extends keyof T, infer B extends string]
infer B extends string, ? PathValue<T[A], B>
] : T;
? PathValue<T[A], B>
: T;
const get = <Key extends Paths<typeof defaultConfig>>(key: Key) => const get = <Key extends Paths<typeof defaultConfig>>(key: Key) =>
store.get(key) as PathValue<typeof defaultConfig, typeof key>; store.get(key) as PathValue<typeof defaultConfig, typeof key>;
@ -86,7 +84,7 @@ export default {
setPartial, setPartial,
setMenuOption, setMenuOption,
edit: () => store.openInEditor(), edit: () => store.openInEditor(),
watch(cb: Parameters<Store['onDidAnyChange']>[0]) { watch(cb: Parameters<IStore['onDidAnyChange']>[0]) {
store.onDidAnyChange(cb); store.onDidAnyChange(cb);
}, },
plugins, plugins,

View File

@ -1,12 +1,14 @@
import Store from 'electron-store'; import Store from 'electron-store';
import Conf from 'conf';
import defaults from './defaults'; import defaults from './defaults';
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types'; import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
// prettier-ignore
export type IStore = InstanceType<typeof import('conf/dist/source/index').default<Record<string, unknown>>>;
const migrations = { const migrations = {
'>=3.3.0'(store: Conf<Record<string, unknown>>) { '>=3.3.0'(store: IStore) {
const lastfmConfig = store.get('plugins.lastfm') as { const lastfmConfig = store.get('plugins.lastfm') as {
enabled?: boolean; enabled?: boolean;
token?: string; token?: string;
@ -16,21 +18,21 @@ const migrations = {
secret?: string; secret?: string;
}; };
if (lastfmConfig) { if (lastfmConfig) {
let scrobblerConfig = store.get( let scrobblerConfig = store.get('plugins.scrobbler') as
'plugins.scrobbler', | {
) as {
enabled?: boolean;
scrobblers?: {
lastfm?: {
enabled?: boolean; enabled?: boolean;
token?: string; scrobblers?: {
sessionKey?: string; lastfm?: {
apiRoot?: string; enabled?: boolean;
apiKey?: string; token?: string;
secret?: string; sessionKey?: string;
}; apiRoot?: string;
}; apiKey?: string;
} | undefined; secret?: string;
};
};
}
| undefined;
if (!scrobblerConfig) { if (!scrobblerConfig) {
scrobblerConfig = { scrobblerConfig = {
@ -56,7 +58,7 @@ const migrations = {
store.delete('plugins.lastfm'); store.delete('plugins.lastfm');
} }
}, },
'>=3.0.0'(store: Conf<Record<string, unknown>>) { '>=3.0.0'(store: IStore) {
const discordConfig = store.get('plugins.discord') as Record< const discordConfig = store.get('plugins.discord') as Record<
string, string,
unknown unknown
@ -78,14 +80,14 @@ const migrations = {
} }
} }
}, },
'>=2.1.3'(store: Conf<Record<string, unknown>>) { '>=2.1.3'(store: IStore) {
const listenAlong = store.get('plugins.discord.listenAlong'); const listenAlong = store.get('plugins.discord.listenAlong');
if (listenAlong !== undefined) { if (listenAlong !== undefined) {
store.set('plugins.discord.playOnYouTubeMusic', listenAlong); store.set('plugins.discord.playOnYouTubeMusic', listenAlong);
store.delete('plugins.discord.listenAlong'); store.delete('plugins.discord.listenAlong');
} }
}, },
'>=2.1.0'(store: Conf<Record<string, unknown>>) { '>=2.1.0'(store: IStore) {
const originalPreset = store.get('plugins.downloader.preset') as const originalPreset = store.get('plugins.downloader.preset') as
| string | string
| undefined; | undefined;
@ -110,7 +112,7 @@ const migrations = {
store.delete('plugins.downloader.ffmpegArgs'); store.delete('plugins.downloader.ffmpegArgs');
} }
}, },
'>=1.20.0'(store: Conf<Record<string, unknown>>) { '>=1.20.0'(store: IStore) {
store.delete('plugins.visualizer'); // default value is now in the plugin store.delete('plugins.visualizer'); // default value is now in the plugin
if (store.get('plugins.notifications.toastStyle') === undefined) { if (store.get('plugins.notifications.toastStyle') === undefined) {
@ -125,14 +127,14 @@ const migrations = {
store.set('options.likeButtons', 'force'); store.set('options.likeButtons', 'force');
} }
}, },
'>=1.17.0'(store: Conf<Record<string, unknown>>) { '>=1.17.0'(store: IStore) {
store.delete('plugins.picture-in-picture'); // default value is now in the plugin store.delete('plugins.picture-in-picture'); // default value is now in the plugin
if (store.get('plugins.video-toggle.mode') === undefined) { if (store.get('plugins.video-toggle.mode') === undefined) {
store.set('plugins.video-toggle.mode', 'custom'); store.set('plugins.video-toggle.mode', 'custom');
} }
}, },
'>=1.14.0'(store: Conf<Record<string, unknown>>) { '>=1.14.0'(store: IStore) {
if ( if (
typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object' typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object'
) { ) {
@ -144,12 +146,12 @@ const migrations = {
store.set('plugins.video-toggle.enabled', true); store.set('plugins.video-toggle.enabled', true);
} }
}, },
'>=1.13.0'(store: Conf<Record<string, unknown>>) { '>=1.13.0'(store: IStore) {
if (store.get('plugins.discord.listenAlong') === undefined) { if (store.get('plugins.discord.listenAlong') === undefined) {
store.set('plugins.discord.listenAlong', true); store.set('plugins.discord.listenAlong', true);
} }
}, },
'>=1.12.0'(store: Conf<Record<string, unknown>>) { '>=1.12.0'(store: IStore) {
const options = store.get('plugins.shortcuts') as const options = store.get('plugins.shortcuts') as
| Record< | Record<
string, string,
@ -187,12 +189,12 @@ const migrations = {
} }
} }
}, },
'>=1.11.0'(store: Conf<Record<string, unknown>>) { '>=1.11.0'(store: IStore) {
if (store.get('options.resumeOnStart') === undefined) { if (store.get('options.resumeOnStart') === undefined) {
store.set('options.resumeOnStart', true); store.set('options.resumeOnStart', true);
} }
}, },
'>=1.7.0'(store: Conf<Record<string, unknown>>) { '>=1.7.0'(store: IStore) {
const enabledPlugins = store.get('plugins') as string[]; const enabledPlugins = store.get('plugins') as string[];
if (!Array.isArray(enabledPlugins)) { if (!Array.isArray(enabledPlugins)) {
console.warn('Plugins are not in array format, cannot migrate'); console.warn('Plugins are not in array format, cannot migrate');
@ -233,4 +235,4 @@ export default new Store({
}, },
clearInvalidConfig: false, clearInvalidConfig: false,
migrations, migrations,
}); }) as Store & IStore;

717
src/i18n/resources/ca.json Normal file
View File

@ -0,0 +1,717 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Ha fallat l'execució de l'extensió {{pluginName}}::{{contextName}}",
"executed-at-ms": "L'extensió {{pluginName}}::{{contextName}} s'ha executat als {{ms}}ms",
"initialize-failed": "Ha fallat la inicialització de l'extensió «{{pluginName}}»",
"load-all": "Carregant totes les extensions",
"load-failed": "Error al carregar l'extensió «{{pluginName}}»",
"loaded": "L'extensió «{{pluginName}}» s'ha carregat",
"unload-failed": "Error al deshabilitar l'extensió «{{pluginName}}»",
"unloaded": "Extensió «{{pluginName}}» deshabilitada"
}
}
},
"language": {
"code": "ca",
"local-name": "català",
"name": "Catalan"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Càrrega finalitzada. S'han obert les DevTools"
},
"i18n": {
"loaded": "i18n carregat"
},
"second-instance": {
"receive-command": "Comanda rebuda a través del protocol: «{{command}}»"
},
"theme": {
"css-file-not-found": "L'arxiu CSS «{{cssFile}}» no existeix, s'ha ignorat"
},
"unresponsive": {
"details": "Error sense resposta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Netejant la memòria cau de l'aplicació"
},
"window": {
"tried-to-render-offscreen": "La finestra s'ha intentat mostrar fora de la pantalla, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "El menú es troba amagat, premi «Alt» per mostrar-lo (o «Escapament» si utilitza el menú integrat In-App)",
"message": "S'ha habilitat l'amagament del menú",
"title": "Amagament del menú habilitat"
},
"need-to-restart": {
"buttons": {
"later": "Més tard",
"restart-now": "Reinicia ara"
},
"detail": "L'extensió «{{pluginName}}» requereix reiniciar l'aplicació per fer tenir efecte",
"message": "\"{{pluginName}}\" necessita reiniciar-se",
"title": "Es requereix reiniciar"
},
"unresponsive": {
"buttons": {
"quit": "Marxar",
"relaunch": "Rellançar",
"wait": "Espera"
},
"detail": "Ho sentim per les molèsties! si us plau, tria què fer:",
"message": "L'aplicació ha deixat de respondre",
"title": "La finestra ha deixat de respondre"
},
"update-available": {
"buttons": {
"disable": "Deshabilita les actualitzacions",
"download": "Descarrega",
"ok": "D'acord"
},
"detail": "Hi ha una nova versió disponible i pot ser descarregada a {{downloadLink}}",
"message": "Hi ha una nova versió disponible",
"title": "Actualització disponible"
}
},
"menu": {
"about": "Quant a",
"navigation": {
"label": "Navegació",
"submenu": {
"copy-current-url": "Copia l'URL actual",
"go-back": "Anar enrere",
"go-forward": "Anar endavant",
"quit": "Sortir",
"restart": "Reinicia l'aplicació"
}
},
"options": {
"label": "Opcions",
"submenu": {
"advanced-options": {
"label": "Opcions avançades",
"submenu": {
"auto-reset-app-cache": "Reinicialitza la memòria cau de l'aplicació quan es reiniciï",
"disable-hardware-acceleration": "Deshabilita l'acceleració per hardware",
"edit-config-json": "Edita el config.json",
"override-user-agent": "Sobreescriu l'agent d'usuari (User-Agent)",
"restart-on-config-changes": "Reinicia quan es canviï la configuració",
"set-proxy": {
"label": "Definir proxy",
"prompt": {
"label": "Introduir l'adreça del proxy: (deixar en blanc per deshabilitar)",
"placeholder": "Exemple: SOCKS5://127.0.0.1:9999",
"title": "Definir proxy"
}
},
"toggle-dev-tools": "Commuta les DevTools"
}
},
"always-on-top": "Mostra sempre per sobre",
"auto-update": "Actualitza automàticament",
"hide-menu": {
"dialog": {
"message": "El menú s'amagarà la següent vegada que s'iniciï l'aplicació, prem «Alt» per mostrar-lo (o accent obert « ` » si utilitza el menú integrat In-App)",
"title": "Amagament del menú habilitat"
},
"label": "Amaga el menú"
},
"language": {
"dialog": {
"message": "L'idioma es canviarà un cop es reiniciï",
"title": "Idioma canviat"
},
"label": "Idioma",
"submenu": {
"to-help-translate": "Vols ajudar a traduir? Clica aquí"
}
},
"resume-on-start": "Reprèn l'última cançó quan s'inicia l'aplicació",
"single-instance-lock": "Bloqueja en una única instància",
"start-at-login": "Obre a l'iniciar sessió",
"starting-page": {
"label": "Pàgina d'inici",
"unset": "Sense establir"
},
"tray": {
"label": "Safata d'icones",
"submenu": {
"disabled": "Deshabilitat",
"enabled-and-hide-app": "Mostra la icona i amaga l'aplicació",
"enabled-and-show-app": "Mostra la icona i mostra l'aplicació",
"play-pause-on-click": "Reproduir/Pausar en clicar"
}
},
"visual-tweaks": {
"label": "Configuració visual",
"submenu": {
"like-buttons": {
"default": "Per defecte",
"force-show": "Força que es mostri",
"hide": "Amaga",
"label": "Botons de «m'agrada»"
},
"remove-upgrade-button": "Elimina el botó «Actualitza a Music Premium»",
"theme": {
"dialog": {
"button": {
"cancel": "Cancel·la",
"remove": "Elimina"
},
"remove-theme": "De debó vols eliminar el tema personalitzat?",
"remove-theme-message": "Això eliminarà el tema personalitzat"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importa un arxiu CSS personalitzat",
"no-theme": "Cap tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Habilitat",
"label": "Extensions",
"new": "NOU"
},
"view": {
"label": "Mostra",
"submenu": {
"force-reload": "Força recàrrega",
"reload": "Recarrega",
"reset-zoom": "Mida real",
"toggle-fullscreen": "Commuta la pantalla completa",
"zoom-in": "Apropa el zoom",
"zoom-out": "Allunya el zoom"
}
}
},
"tray": {
"next": "Següent",
"play-pause": "Reprodueix/Pausa",
"previous": "Anterior",
"quit": "Tanca",
"restart": "Reinicia l'aplicació",
"show": "Mostra la finestra",
"tooltip": {
"default": "YouTube Music",
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Si es reprodueix un anunci, silencia l'àudio i el reprodueix a la velocitat 16x",
"name": "Accelera els anuncis"
},
"adblocker": {
"description": "Bloqueja tots els anuncis i el seguiment",
"menu": {
"blocker": "Bloquejador"
},
"name": "Bloquejador d'anuncis"
},
"album-actions": {
"description": "Afegeix botons de «no m'agrada / retirar el no m'agrada» i «m'agrada / retirar el m'agrada» per aplicar-ho a totes les cançons en una llista de reproducció o àlbum",
"name": "Accions de l'àlbum"
},
"album-color-theme": {
"description": "Aplica un tema dinàmic i efectes visuals basats en la paleta de colors de l'àlbum",
"menu": {
"color-mix-ratio": {
"label": "Proporció de la barreja de colors",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Tema de color de l'àlbum"
},
"ambient-mode": {
"description": "Aplica un efecte d'il·luminació que projecta colors difusos del vídeo al fons de la pantalla",
"menu": {
"blur-amount": {
"label": "Quantitat de desenfocament",
"submenu": {
"pixels": "{{blurAmount}} píxels"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacitat",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualitat",
"submenu": {
"pixels": "{{quality}} píxels"
}
},
"size": {
"label": "Mida",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transició suau",
"submenu": {
"during": "Durant {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Utilitza en pantalla completa"
}
},
"name": "Mode ambient"
},
"audio-compressor": {
"description": "Aplica compressió a l'àudio (baixa el volum de les parts més sorolloses de la senyal d'àudio i puja el volum de les parts més fluixes)",
"name": "Compressor d'àudio"
},
"blur-nav-bar": {
"description": "Desenfoca i aplica transparència a la barra de navegació",
"name": "Desenfoca la barra de navegació"
},
"bypass-age-restrictions": {
"description": "Esquiva la verificació d'edat de YouTube",
"name": "Esquiva les restriccions d'edat"
},
"captions-selector": {
"description": "Selector de subtítols per les pistes d'àudio de YouTube Music",
"menu": {
"autoload": "Selecciona automàticament l'últim subtítol emprat",
"disable-captions": "Sense subtítols per defecte"
},
"name": "Selector de subtítols",
"prompt": {
"selector": {
"label": "Idioma actual dels subtítols: {{language}}",
"none": "Cap",
"title": "Selecciona l'idioma dels subtítols"
}
},
"templates": {
"title": "Obra el selector de subtítols"
}
},
"compact-sidebar": {
"description": "Sempre mostrar la barra lateral en mode compacte",
"name": "Barra lateral compacta"
},
"crossfade": {
"description": "Transició creuada (crossfade) entre cançons",
"menu": {
"advanced": "Avançat"
},
"name": "Transició creuada [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Durada de la transició d'entrada (ms)",
"fade-out-duration": "Durada de la transició de sortida (ms)",
"fade-scaling": {
"label": "Escala de la transició",
"linear": "Linear",
"logarithmic": "Logarítmica"
},
"seconds-before-end": "Transiciona N segons abans del final"
},
"title": "Opcions de transició creuada"
}
}
},
"disable-autoplay": {
"description": "Fa que la cançó comenci en mode «pausat»",
"menu": {
"apply-once": "Tan sols s'aplica a l'inici"
},
"name": "Deshabilita la reproducció automàtica"
},
"discord": {
"backend": {
"already-connected": "S'ha intentat connectar amb una connexió activa",
"connected": "Connectat a Discord",
"disconnected": "Desconnectat de Discord"
},
"description": "Mostra als teus amics allò que escoltes a l'estat d'activitat",
"menu": {
"auto-reconnect": "Reconnecta automàticament",
"clear-activity": "Esborra l'activitat",
"clear-activity-after-timeout": "Esborra l'activitat al cap d'un temps",
"connected": "Connectat",
"disconnected": "Desconnectat",
"hide-duration-left": "Amaga la durada restant",
"hide-github-button": "Amaga el botó de l'enllaç a GitHub",
"play-on-youtube-music": "Reprodueix a YouTube Music",
"set-inactivity-timeout": "Estableix temps d'espera d'inactivitat"
},
"name": "Estat d'activitat de Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Introdueix el temps d'espera d'inactivitat en segons:",
"title": "Estableix el temps d'espera d'inactivitat"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "D'acord"
},
"message": "Caram! Ho sentim, ha fallat la descàrrega…",
"title": "Error a la descàrrega!"
},
"start-download-playlist": {
"buttons": {
"ok": "D'acord"
},
"detail": "({{playlistSize}} cançons)",
"message": "Descarregant llista de reproducció {{playlistTitle}}",
"title": "Descàrrega començada"
}
},
"feedback": {
"conversion-progress": "Conversió: {{percent}}%",
"converting": "Convertint…",
"done": "Fet: {{filePath}}",
"download-info": "Descarregant {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Descàrrega: {{percent}}%",
"downloading": "Descarregant…",
"downloading-counter": "Descarregant {{current}}/{{total}}…",
"downloading-playlist": "Descarregant la llista de reproducció «{{playlistTitle}}» - {{playlistSize}} cançons ({{playlistId}})",
"error-while-downloading": "Error al descarregar «{{author}} - {{title}}»: {{error}}",
"folder-already-exists": "La carpeta {{playlistFolder}} ja existeix",
"getting-playlist-info": "Obtenint la informació de la llista de reproducció…",
"loading": "Carregant…",
"playlist-has-only-one-song": "La llista de reproducció té un sol element, descarregant-lo directament",
"playlist-id-not-found": "No s'ha trobat cap ID de llista de reproducció",
"playlist-is-empty": "La llista de reproducció és buida",
"playlist-is-mix-or-private": "Error obtenint la informació de la llista de reproducció: assegura't que no és una llista de reproducció privada o de «Mixos per a tu»\n\n{{error}}",
"preparing-file": "Preparant arxiu…",
"saving": "Desant…",
"trying-to-get-playlist-id": "Intentant obtenir l'ID de la llista de reproducció: {{playlistId}}",
"video-id-not-found": "Vídeo no trobat",
"writing-id3": "Escrivint les etiquetes ID3…"
}
},
"description": "Descarrega el MP3 / àudio d'origen directament des de la interfície",
"menu": {
"choose-download-folder": "Tria la carpeta de descàrrega",
"download-finish-settings": {
"label": "Descarrega en finalitzar",
"prompt": {
"last-percent": "Desprès del x percent",
"last-seconds": "Últims x segons",
"title": "Configura quan descarregar"
},
"submenu": {
"advanced": "Avançat",
"enabled": "Habilitat",
"mode": "Mode de temps",
"percent": "Percentatge",
"seconds": "Segons"
}
},
"download-playlist": "Descarrega la llista de reproducció",
"presets": "Configuracions predefinides",
"skip-existing": "Omet els arxius existents"
},
"name": "Descàrregues",
"renderer": {
"can-not-update-progress": "No es pot actualitzar el progrés"
},
"templates": {
"button": "Descarrega"
}
},
"exponential-volume": {
"description": "Fa que el control lliscant del volum sigui exponencial per que sigui més fàcil seleccionar volums més baixos.",
"name": "Volum exponencial"
},
"in-app-menu": {
"description": "Fa que la barra de menú superior tingui un elegant aspecte fosc o basat en el color de l'àlbum",
"menu": {
"hide-dom-window-controls": "Amaga els controls de la finestra del DOM"
},
"name": "Menú integrat In-App"
},
"lumiastream": {
"description": "Afegeix suport pel Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Afegeix suport per la lletra de la majoria de cançons",
"menu": {
"romanized-lyrics": "Lletra romanitzada"
},
"name": "Lletres de Genius",
"renderer": {
"fetched-lyrics": "S'ha buscat la lletra a Genius"
}
},
"music-together": {
"description": "Comparteix una llista de reproducció amb els demés. Quan l'amfitrió reprodueix una cançó, la resta també sentiran la mateixa",
"dialog": {
"enter-host": "Introdueix l'ID de l'amfitrió"
},
"internal": {
"save": "Desa",
"track-source": "Origen de la pista",
"unknown-user": "Usuari desconegut"
},
"menu": {
"click-to-copy-id": "Copia l'ID d'amfitrió",
"close": "Tanca el Music Together",
"connected-users": "Usuaris connectats",
"disconnect": "Desconnecta el Music Together",
"empty-user": "No hi ha usuaris connectats",
"host": "Amfitrió de Music Together",
"join": "Uneix-te a Music Together",
"permission": {
"all": "Permet que els convidats controlin la llista de reproducció i el reproductor",
"host-only": "Tan sols l'amfitrió pot controlar la llista de reproducció i el reproductor",
"playlist": "Permet que els convidats controlin la llista de reproducció"
},
"set-permission": "Canvia els permisos de control",
"status": {
"disconnected": "Desconnectat",
"guest": "Connectat com a convidat",
"host": "Connectat com amfitrió"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Error al afegir la cançó",
"closed": "Music Together tancat",
"disconnected": "Music Together desconnectat",
"host-failed": "No s'ha pogut començar el Music Together",
"id-copied": "L'ID d'amfitrió s'ha copiat al porta-retalls",
"id-copy-failed": "Error al copiar l'ID d'amfitrió al porta-retalls",
"join-failed": "Error al unir-se al Music Together",
"joined": "T'has unit al Music Together",
"permission-changed": "Els permisos de Music Together han canviat a «{{permission}}»",
"remove-song-failed": "Error al eliminar la cançó",
"user-connected": "{{name}} s'ha unit al Music Together",
"user-disconnected": "{{name}} s'ha desconnectat del Music Together"
}
},
"navigation": {
"description": "Fletxes de navegació Següent / Enrere integrades directament a la interfície, com al teu navegador preferit",
"name": "Navegació"
},
"no-google-login": {
"description": "Elimina els botons d'inici de sessió de Google de la interfície",
"name": "Sense inici de sessió de Google"
},
"notifications": {
"description": "Mostra una notificació quan una cançó es comença a reproduir (les notificacions interactives estan disponibles a Windows)",
"menu": {
"interactive": "Notificacions interactives",
"interactive-settings": {
"label": "Configuració interactiva",
"submenu": {
"hide-button-text": "Amaga text del botó",
"refresh-on-play-pause": "Recarrega al Reproduir/Pausar",
"tray-controls": "Obra/Tanca en clicar a la safata"
}
},
"priority": "Prioritat de les notificacions",
"toast-style": "Estil dels missatges emergents",
"unpause-notification": "Mostra notificació en reprendre la reproducció"
},
"name": "Notificacions"
},
"picture-in-picture": {
"description": "Permet commutar el mode d'imatge en imatge (PiP)",
"menu": {
"always-on-top": "Mostra sempre a sobre",
"hotkey": {
"label": "Drecera del teclat",
"prompt": {
"keybind-options": {
"hotkey": "Drecera del teclat"
},
"label": "Tria una drecera per commutar el mode d'imatge en imatge (PiP)",
"title": "Drecera del mode imatge en imatge (PiP)"
}
},
"save-window-position": "Desa la posició de la finestra",
"save-window-size": "Desa la mida de la finestra",
"use-native-pip": "Utilitza l'imatge en imatge (PiP) nativa del navegador"
},
"name": "Imatge en imatge (PiP)",
"templates": {
"button": "Imatge en imatge (PiP)"
}
},
"playback-speed": {
"description": "Escolta-ho ràpid, escolta-ho lent! Afegeix un control lliscant per canviar la velocitat de la cançó",
"name": "Velocitat de la reproducció",
"templates": {
"button": "Velocitat"
}
},
"precise-volume": {
"description": "Controla el volum de manera precisa a través de la rodeta del ratolí / dreceres del teclat, amb una interfície personalitzada i passos de volum personalitzats",
"menu": {
"arrows-shortcuts": "Controls locals de tecles de fletxa",
"custom-volume-steps": "Estableix passos de volum personalitzats",
"global-shortcuts": "Dreceres de teclat globals"
},
"name": "Volum precís",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Baixa el volum",
"increase": "Puja el volum"
},
"label": "Tria les dreceres globals de volum:",
"title": "Dreceres globals de volum"
},
"volume-steps": {
"label": "Tria els passos d'augment o disminució del volum",
"title": "Passos de volum"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualitat actual: {{quality}}",
"message": "Tria la qualitat del vídeo:",
"title": "Tria la qualitat del vídeo"
}
}
},
"description": "Permet canviar la qualitat del vídeo amb un botó que s'hi mostra a sobre",
"name": "Canvia la qualitat del vídeo"
},
"scrobbler": {
"description": "Afegeix suport per scrobbling (Last.fm, ListenBrainz, etc.)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Error al autenticar amb Last.fm\nAmaga la finestra emergent fins el següent reinici.",
"title": "Error d'autenticació"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Configuració de l'API de Last.fm"
},
"listenbrainz": {
"token": "Introduir token d'usuari de ListenBrainz"
},
"scrobble-other-media": "Scrobble amb altres mitjans"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Clau d'API de Last.fm",
"api-secret": "Clau secreta de l'API de Last.fm"
},
"listenbrainz": {
"token": {
"label": "Introdueix el teu token de ListenBrainz:",
"title": "Token de ListenBrainz"
}
}
}
},
"shortcuts": {
"description": "Permet l'ús de dreceres globals del teclat per la reproducció (reproduir/pausar/següent/anterior) i desactivar l'OSD dels mitjans en sobreescriure les tecles de control multimèdia, habilita el Ctrl/CMD + F per buscar, habilita el suport MPRIS a Linux per tecles de control multimèdia, i dreceres de teclat personalitzades per usuaris avançats",
"menu": {
"override-media-keys": "Sobreescriu les tecles de control multimèdia",
"set-keybinds": "Estableix controls globals de les cançons"
},
"name": "Dreceres i MPRIS",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Següent",
"play-pause": "Reproduir / Pausar",
"previous": "Anterior"
},
"label": "Tria combinacions de tecles per controlar les cançons:",
"title": "Dreceres globals"
}
}
},
"skip-disliked-songs": {
"description": "Omet cançons amb «No m'agrada»",
"name": "Omet cançons amb «No m'agrada»"
},
"skip-silences": {
"description": "Omet automàticament les seccions amb silenci a les cançons",
"name": "Omet els silencis"
},
"sponsorblock": {
"description": "Omet automàticament els segments dels vídeos que no son música, com la intro o el final",
"name": "SponsorBlock"
},
"taskbar-mediacontrol": {
"description": "Controla la reproducció des de la barra de tasques del Windows",
"name": "Control multimèdia a la barra de tasques"
},
"touchbar": {
"description": "Afegeix un giny a la Touch Bar per usuaris de macOS",
"name": "TouchBar"
},
"tuna-obs": {
"description": "Integració amb l'extensió «Tuna» del OBS",
"name": "Tuna OBS"
},
"video-toggle": {
"description": "Afegeix un botó per commutar entre el mode de vídeo o de cançó. Opcionalment, es pot eliminar la pestanya de vídeo per complet",
"menu": {
"align": {
"label": "Alineament",
"submenu": {
"left": "Esquerra",
"middle": "Mig",
"right": "Dreta"
}
},
"force-hide": "Força amagar la pestanya de vídeo",
"mode": {
"label": "Mode",
"submenu": {
"custom": "Commutador personalitzat",
"disabled": "Deshabilitat",
"native": "Commutador nadiu"
}
}
},
"name": "Commutador de vídeo",
"templates": {
"button": "Cançó"
}
},
"visualizer": {
"description": "Afegeix un visualitzador al reproductor",
"menu": {
"visualizer-type": "Tipus de visualitzador"
},
"name": "Visualitzador"
}
}
}

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Pokud se přehraje reklama tak ztlumí zvuk a nastaví rychlost přehrávání na 16x",
"name": "Zrychlovač Reklam"
},
"adblocker": { "adblocker": {
"description": "Blokuje všechny reklamy a sledování ihned od začátku", "description": "Blokuje všechny reklamy a sledování ihned od začátku",
"menu": { "menu": {
@ -410,6 +414,11 @@
"description": "Stahuje MP3 / source audio přímo z rozhraní", "description": "Stahuje MP3 / source audio přímo z rozhraní",
"menu": { "menu": {
"choose-download-folder": "Vybrat složku pro stahování", "choose-download-folder": "Vybrat složku pro stahování",
"download-finish-settings": {
"submenu": {
"advanced": "Pokoročile"
}
},
"download-playlist": "Stáhnout seznam písniček", "download-playlist": "Stáhnout seznam písniček",
"presets": "Předvolby", "presets": "Předvolby",
"skip-existing": "Přeskočit existující soubory" "skip-existing": "Přeskočit existující soubory"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Wenn eine Werbung spielt, stummt es das Audio und setzt die Wiedergabegeschwindigkeit auf 16x",
"name": "Werbungsbeschleunigung"
},
"adblocker": { "adblocker": {
"description": "Blockiere jegliche Werbung und Tracker", "description": "Blockiere jegliche Werbung und Tracker",
"menu": { "menu": {
@ -410,24 +414,24 @@
"description": "Lädt MP3-/Original-Audio direkt von der Schnittstelle herunter", "description": "Lädt MP3-/Original-Audio direkt von der Schnittstelle herunter",
"menu": { "menu": {
"choose-download-folder": "Downloadordner wählen", "choose-download-folder": "Downloadordner wählen",
"download-finish-settings": {
"label": "Song am Ende runterladen",
"prompt": {
"last-percent": "Nach x Prozent",
"last-seconds": "Letzten x Sekunden",
"title": "Konfiguriere wann runtergeladen werden soll"
},
"submenu": {
"advanced": "Erweitert",
"enabled": "Aktiviert",
"mode": "Zeitmodus",
"percent": "Prozent",
"seconds": "Sekunden"
}
},
"download-playlist": "Wiedergabeliste herunterladen", "download-playlist": "Wiedergabeliste herunterladen",
"presets": "Voreinstellungen", "presets": "Voreinstellungen",
"skip-existing": "Vorhandene Dateien überspringen", "skip-existing": "Vorhandene Dateien überspringen"
"download-finish-settings": {
"label": "Song am Ende runterladen",
"submenu": {
"enabled": "Aktiviert",
"mode": "Zeitmodus",
"seconds": "Sekunden",
"percent": "Prozent",
"advanced": "Erweitert"
},
"prompt": {
"title": "Konfiguriere wann runtergeladen werden soll",
"last-seconds": "Letzten x Sekunden",
"last-percent": "Nach x Prozent"
}
}
}, },
"name": "Downloader", "name": "Downloader",
"renderer": { "renderer": {

View File

@ -214,6 +214,10 @@
}, },
"name": "Ad Blocker" "name": "Ad Blocker"
}, },
"ad-speedup": {
"name": "Ad Speedup",
"description": "If an ad play it mutes the audio and sets playback speed to 16x"
},
"album-actions": { "album-actions": {
"description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album", "description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album",
"name": "Album Actions" "name": "Album Actions"
@ -414,19 +418,19 @@
"presets": "Presets", "presets": "Presets",
"skip-existing": "Skip existing files", "skip-existing": "Skip existing files",
"download-finish-settings": { "download-finish-settings": {
"label": "Download on finish", "label": "Download on finish",
"submenu": { "submenu": {
"enabled": "Enabled", "enabled": "Enabled",
"mode": "Time mode", "mode": "Time mode",
"seconds": "Seconds", "seconds": "Seconds",
"percent": "Percent", "percent": "Percent",
"advanced": "Advanced" "advanced": "Advanced"
}, },
"prompt": { "prompt": {
"title": "Configure when to download", "title": "Configure when to download",
"last-seconds": "Last x seconds", "last-seconds": "Last x seconds",
"last-percent": "After x percent" "last-percent": "After x percent"
} }
} }
}, },
"name": "Downloader", "name": "Downloader",
@ -664,6 +668,59 @@
"description": "Automatically Skips non-music parts like intro/outro or parts of music videos where the song isn't playing", "description": "Automatically Skips non-music parts like intro/outro or parts of music videos where the song isn't playing",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Provides synced lyrics to songs, using providers like LRClib.",
"name": "Synced Lyrics",
"errors": {
"fetch": "⚠️ - An error occurred while fetching the lyrics. Please try again later.",
"not-found": "⚠️ - No lyrics found for this song."
},
"warnings": {
"instrumental": "⚠️ - This is an instrumental song",
"inexact": "⚠️ - The lyrics for this song may not be exact",
"duration-mismatch": "⚠️ - The lyrics may be out of sync due to a duration mismatch."
},
"refetch-btn": {
"normal": "Refetch lyrics",
"fetching": "Fetching..."
},
"menu": {
"precise-timing": {
"label": "Make the lyrics perfectly synced",
"tooltip": "Calculate to the milisecond the display of the next line (can have a small impact on performance)"
},
"line-effect": {
"label": "Line effect",
"tooltip": "Choose the effect to apply to the current line",
"submenu": {
"scale": {
"label": "Scale",
"tooltip": "Scale the current line"
},
"offset": {
"label": "Offset",
"tooltip": "Offset on the right the current line"
},
"focus": {
"label": "Focus",
"tooltip": "Make only the current line white"
}
}
},
"default-text-string": {
"label": "Default character between lyrics",
"tooltip": "Choose the default character to use for the gap between lyrics"
},
"show-time-codes": {
"label": "Show time codes",
"tooltip": "Show the time codes next to the lyrics"
},
"show-lyrics-even-if-inexact": {
"label": "Show lyrics even if inexact",
"tooltip": "If the song is not found, the plugin tries again with a different search query.\nThe result from the second attempt may not be exact."
}
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Control playback from your Windows taskbar", "description": "Control playback from your Windows taskbar",
"name": "Taskbar Media Control" "name": "Taskbar Media Control"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Si se reproduce un anuncio, silencia el audio y fija la velocidad de reproducción en 16x",
"name": "Aumento de la velocidad de anuncios"
},
"adblocker": { "adblocker": {
"description": "Bloquear todos los anuncios y el rastreo", "description": "Bloquear todos los anuncios y el rastreo",
"menu": { "menu": {

View File

@ -69,7 +69,7 @@
}, },
"update-available": { "update-available": {
"buttons": { "buttons": {
"disable": "Päivityksen pois päältä", "disable": "Poista päivitykset käytöstä",
"download": "Lataa", "download": "Lataa",
"ok": "Selvä" "ok": "Selvä"
}, },
@ -215,8 +215,8 @@
"name": "Mainos estäjä" "name": "Mainos estäjä"
}, },
"album-actions": { "album-actions": {
"description": "Alapeukuta musiikki/videota, jotta voimme tarjota sinulle parhaimmat MIX:it", "description": "Lisää tykkäysnappulat, joilla voit lisätä tai poistaa tykkäyksiä kerralla kaikille soittolistan tai albumin kappaleille",
"name": "Albumin toiminnot" "name": "Albumin Toiminnot"
}, },
"album-color-theme": { "album-color-theme": {
"description": "Käyttää dynaamista teemaa ja visuaalisia tehosteita albumin väripaletin perusteella", "description": "Käyttää dynaamista teemaa ja visuaalisia tehosteita albumin väripaletin perusteella",
@ -228,7 +228,7 @@
} }
} }
}, },
"name": "Albumin värin teema" "name": "Albumin Värinen Teema"
}, },
"ambient-mode": { "ambient-mode": {
"description": "Antaa valaistustehosteen heittämällä videosta lempeitä värejä näytön taustalle", "description": "Antaa valaistustehosteen heittämällä videosta lempeitä värejä näytön taustalle",
@ -236,11 +236,11 @@
"blur-amount": { "blur-amount": {
"label": "Sumennuksen voimakkuus", "label": "Sumennuksen voimakkuus",
"submenu": { "submenu": {
"pixels": "{{blurAmount}}pikseliä" "pixels": "{{blurAmount}} pikseliä"
} }
}, },
"buffer": { "buffer": {
"label": "Puskuroi", "label": "Puskurointi",
"submenu": { "submenu": {
"buffer": "{{buffer}}" "buffer": "{{buffer}}"
} }
@ -254,7 +254,7 @@
"quality": { "quality": {
"label": "Laatu", "label": "Laatu",
"submenu": { "submenu": {
"pixels": "{{quality}}pikseliä" "pixels": "{{quality}} pikseliä"
} }
}, },
"size": { "size": {
@ -264,7 +264,10 @@
} }
}, },
"smoothness-transition": { "smoothness-transition": {
"label": "sujuvuus siirtymässä" "label": "Siirtymän sujuvuus",
"submenu": {
"during": "Kesto {{interpolationTime}} s"
}
}, },
"use-fullscreen": { "use-fullscreen": {
"label": "Käytetään koko näytön tilaa" "label": "Käytetään koko näytön tilaa"

View File

@ -206,6 +206,9 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Pag mag-play ng ad, I-mute ang audio at i-set ang bilis ng playback ng 16x"
},
"adblocker": { "adblocker": {
"description": "I-block ang lahat ng ad at tracking" "description": "I-block ang lahat ng ad at tracking"
}, },
@ -386,6 +389,17 @@
"description": "Dina-download ang mga MP3 / source audio direkta mula sa interface", "description": "Dina-download ang mga MP3 / source audio direkta mula sa interface",
"menu": { "menu": {
"choose-download-folder": "Pumili ng download folder", "choose-download-folder": "Pumili ng download folder",
"download-finish-settings": {
"label": "Kung natapos ang download",
"prompt": {
"title": "I-configure kung kailan magda-download"
},
"submenu": {
"enabled": "Napagana na",
"percent": "Porsyento",
"seconds": "Segundo"
}
},
"download-playlist": "Dina-download ang playlist", "download-playlist": "Dina-download ang playlist",
"presets": "Mga preset", "presets": "Mga preset",
"skip-existing": "Laktawan ang mga kasalukuyang file" "skip-existing": "Laktawan ang mga kasalukuyang file"
@ -427,6 +441,7 @@
"connected-users": "Nakakonektang (mga) User", "connected-users": "Nakakonektang (mga) User",
"disconnect": "Mag-diskonekta sa Music Together", "disconnect": "Mag-diskonekta sa Music Together",
"empty-user": "Walang naka-konektang user", "empty-user": "Walang naka-konektang user",
"host": "Host ng Music Together",
"join": "Sumali sa Music Together", "join": "Sumali sa Music Together",
"permission": { "permission": {
"all": "Payagan ang mga guest na kontrolin ang playlist at player", "all": "Payagan ang mga guest na kontrolin ang playlist at player",
@ -465,13 +480,16 @@
"notifications": { "notifications": {
"description": "Magpakita ng notification kapag nagsimulang tumugtog ang kanta (magagamit ang mga interactive na notification sa Windows)", "description": "Magpakita ng notification kapag nagsimulang tumugtog ang kanta (magagamit ang mga interactive na notification sa Windows)",
"menu": { "menu": {
"interactive": "Interactive na Notification",
"interactive-settings": { "interactive-settings": {
"label": "Mga Interactive na Setting",
"submenu": { "submenu": {
"hide-button-text": "Itago ang button na texto", "hide-button-text": "Itago ang button na texto",
"refresh-on-play-pause": "I-refresh sa Pag-play/Pag-pause", "refresh-on-play-pause": "I-refresh sa Pag-play/Pag-pause",
"tray-controls": "Buksan/Isara sa pag-click sa tray" "tray-controls": "Buksan/Isara sa pag-click sa tray"
} }
}, },
"priority": "Prioridad ng Notification",
"unpause-notification": "Ipakita ang notification sa pag-unpause" "unpause-notification": "Ipakita ang notification sa pag-unpause"
} }
}, },
@ -486,12 +504,13 @@
} }
}, },
"save-window-position": "I-save ang posisyon ng window", "save-window-position": "I-save ang posisyon ng window",
"save-window-size": "I-save ang laki ng windo", "save-window-size": "I-save ang laki ng window",
"use-native-pip": "Gamitin ang browser native na PiP" "use-native-pip": "Gamitin ang browser native na PiP"
} }
}, },
"playback-speed": { "playback-speed": {
"description": "Makinig na mabilisan, makinig na mabagalan! Nagdaragdag ito ng slider upang makontrol ang bilis ng kanta", "description": "Makinig na mabilisan, makinig na mabagalan! Nagdaragdag ito ng slider upang makontrol ang bilis ng kanta",
"name": "Bilis ng Playback",
"templates": { "templates": {
"button": "Bilis" "button": "Bilis"
} }

View File

@ -105,7 +105,7 @@
"label": "Définir un proxy", "label": "Définir un proxy",
"prompt": { "prompt": {
"label": "Entrez l'adresse proxy : (laissez vide pour désactiver)", "label": "Entrez l'adresse proxy : (laissez vide pour désactiver)",
"placeholder": "Exemple: SOCKS5://127.0.0.1:9999", "placeholder": "Exemple: SOCKS5://127.0.0.1:9999",
"title": "Définir un proxy" "title": "Définir un proxy"
} }
}, },
@ -179,7 +179,7 @@
"plugins": { "plugins": {
"enabled": "Activé", "enabled": "Activé",
"label": "Extensions", "label": "Extensions",
"new": "NOUVELLE" "new": "NOUVEAU"
}, },
"view": { "view": {
"label": "Vue", "label": "Vue",
@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Si une publicité apparaît, le son est coupé et la vitesse de lecture est réglée sur 16x",
"name": "Accélérer les publicités"
},
"adblocker": { "adblocker": {
"description": "Bloquer toutes les annonces et le suivi par défaut", "description": "Bloquer toutes les annonces et le suivi par défaut",
"menu": { "menu": {
@ -410,6 +414,21 @@
"description": "Télécharge les fichiers MP3/source audio directement depuis l'interface", "description": "Télécharge les fichiers MP3/source audio directement depuis l'interface",
"menu": { "menu": {
"choose-download-folder": "Choisissez le dossier de téléchargement", "choose-download-folder": "Choisissez le dossier de téléchargement",
"download-finish-settings": {
"label": "Télécharger une fois terminé",
"prompt": {
"last-percent": "Après x pour cent",
"last-seconds": "Dernières x secondes",
"title": "Configurer quand télécharger"
},
"submenu": {
"advanced": "Avancé",
"enabled": "Activé",
"mode": "Mode de temps",
"percent": "Pourcent",
"seconds": "Secondes"
}
},
"download-playlist": "Télécharger la liste de lecture", "download-playlist": "Télécharger la liste de lecture",
"presets": "Préconfigurations", "presets": "Préconfigurations",
"skip-existing": "Passer les fichiers existants" "skip-existing": "Passer les fichiers existants"

View File

@ -2,7 +2,13 @@
"common": { "common": {
"console": { "console": {
"plugins": { "plugins": {
"execute-failed": "נכשל ביצוע תוסף {{pluginName}}::{{contextName}}" "execute-failed": "נכשל ביצוע תוסף {{pluginName}}::{{contextName}}",
"executed-at-ms": "התוסף {{pluginName}}:{{contextName}} בוצע ב {{ms}}ms",
"initialize-failed": "טעינת התוסף \"{{pluginName}}\" נכשלה",
"load-all": "טוען את כל התוספים",
"load-failed": "טעינת התוסף \"{{pluginName}}\" נכשלה",
"loaded": "התוסף \"{{pluginName}}\" נטען",
"unload-failed": "הסרת התוסף \"{{pluginName}} נכשלה"
} }
} }
}, },
@ -10,5 +16,29 @@
"code": "he", "code": "he",
"local-name": "עברית", "local-name": "עברית",
"name": "Hebrew" "name": "Hebrew"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "הטעינה הסתיימה. הכלים לפמתחים נפתחו"
},
"i18n": {
"loaded": "i18n נטען"
},
"theme": {
"css-file-not-found": "קובץ ה-CSS \"{{cssFile}}\" לא קיים. מדלג"
},
"when-ready": {
"clearing-cache-after-20s": "מוחק קבצי מתמון"
}
},
"dialog": {
"need-to-restart": {
"buttons": {
"later": "אחר כך",
"restart-now": "מתחיל את התוכנה מחדש עכשיו"
}
}
}
} }
} }

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Ha reklám szól, elnémítja a hangot és a lejátszási sebességet 16x-ra állítja",
"name": "Gyorsítás hozzáadása"
},
"adblocker": { "adblocker": {
"description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést", "description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést",
"menu": { "menu": {
@ -407,7 +411,21 @@
"description": "MP3 / forrás hanganyag letöltése közvetlenül az interfészről", "description": "MP3 / forrás hanganyag letöltése közvetlenül az interfészről",
"menu": { "menu": {
"choose-download-folder": "Letöltési mappa kiválasztása", "choose-download-folder": "Letöltési mappa kiválasztása",
"download-finish-settings": {
"prompt": {
"last-percent": "x százalék után",
"last-seconds": "Utolsó x másodperc"
},
"submenu": {
"advanced": "Speciális",
"enabled": "Engedélyezve",
"mode": "Időmód",
"percent": "Százalék",
"seconds": "Másodpercek"
}
},
"download-playlist": "Lejátszási lista letöltése", "download-playlist": "Lejátszási lista letöltése",
"presets": "Előbeállítások",
"skip-existing": "Meglévő fájlok kihagyása" "skip-existing": "Meglévő fájlok kihagyása"
}, },
"name": "Letöltő", "name": "Letöltő",
@ -489,6 +507,7 @@
} }
}, },
"navigation": { "navigation": {
"description": "Következő/Vissza navigációs nyilak közvetlenül az interfészbe integrálva, mint a kedvenc böngésződben",
"name": "Navigáció" "name": "Navigáció"
}, },
"no-google-login": { "no-google-login": {
@ -502,10 +521,14 @@
"interactive-settings": { "interactive-settings": {
"label": "Interaktív beállítások", "label": "Interaktív beállítások",
"submenu": { "submenu": {
"hide-button-text": "Gombok szövegének elrejtése" "hide-button-text": "Gombok szövegének elrejtése",
"refresh-on-play-pause": "Frissítés lejátszás/szünet megnyomásakor",
"tray-controls": "Megnyitás/Bezárás tálca ikonra kattintva"
} }
}, },
"priority": "Értesítési prioritás" "priority": "Értesítési prioritás",
"toast-style": "Értesítés stílusa",
"unpause-notification": "Értesítés megjelenítése a lejátszás folytatásakor"
}, },
"name": "Értesítések" "name": "Értesítések"
}, },
@ -542,6 +565,8 @@
"precise-volume": { "precise-volume": {
"description": "A hangerő precíz szabályozása egérgörgővel/gyorsbillentyűkkel, egy egyedi HUD és testreszabható hangerő csuszka segítségével", "description": "A hangerő precíz szabályozása egérgörgővel/gyorsbillentyűkkel, egy egyedi HUD és testreszabható hangerő csuszka segítségével",
"menu": { "menu": {
"arrows-shortcuts": "Helyi nyíl-billentyűkkel való vezérlés",
"custom-volume-steps": "Egyedi hangerőléptetés beállítása",
"global-shortcuts": "Globális gyorsbillentyűk" "global-shortcuts": "Globális gyorsbillentyűk"
}, },
"name": "Precíz hangerő", "name": "Precíz hangerő",
@ -553,6 +578,10 @@
}, },
"label": "Válaszd ki a globális hangerő gyorsbillentyűket:", "label": "Válaszd ki a globális hangerő gyorsbillentyűket:",
"title": "Globális hangerő gyorsbillentyűk" "title": "Globális hangerő gyorsbillentyűk"
},
"volume-steps": {
"label": "Hangerő növelés/csökkentés léptékének kiválasztása",
"title": "Hangerő lépték"
} }
} }
}, },
@ -566,7 +595,8 @@
} }
} }
}, },
"description": "Lehetővé teszi a videó minőségének megváltoztatását egy gombbal a videó fedvényen" "description": "Lehetővé teszi a videó minőségének megváltoztatását egy gombbal a videó fedvényen",
"name": "Videóminőség modosító"
}, },
"scrobbler": { "scrobbler": {
"description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)", "description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)",
@ -613,6 +643,7 @@
"play-pause": "Lejátszás / Szünet", "play-pause": "Lejátszás / Szünet",
"previous": "Előző" "previous": "Előző"
}, },
"label": "Globális billentyűparancsok választása a dalok vezérléséhez:",
"title": "Globális gyorsbillentyűk" "title": "Globális gyorsbillentyűk"
} }
} }
@ -629,8 +660,26 @@
"description": "Automatikusan kihagyja a nem zenés részeket, mint például az intro/outro vagy a zenei videók olyan részeit, ahol a zene nem szól", "description": "Automatikusan kihagyja a nem zenés részeket, mint például az intro/outro vagy a zenei videók olyan részeit, ahol a zene nem szól",
"name": "SzponzorBlokk" "name": "SzponzorBlokk"
}, },
"synced-lyrics": {
"description": "Szinkronizált dalszövegeket biztosít dalokhoz, LRClib-hez hasonló szolgáltatókat használva.",
"errors": {
"fetch": "⚠️ - Hiba történt a dalszövegek lekérése közben. Kérlek, próbáld újra később.",
"not-found": "⚠️ - Nem található dalszöveg ehhez a zenéhez."
},
"name": "Szinkronizált dalszövegek",
"refetch-btn": {
"fetching": "Lekérés folyamatban...",
"normal": "Dalszöveg újra lekérése"
},
"warnings": {
"duration-mismatch": "⚠️ - A dalszövegek időzítése eltérhet a zene hossza miatt.",
"inexact": "⚠️ - Ennek a zenének a dalszövege pontatlan lehet",
"instrumental": "⚠️ - Ez egy hangszerekkel játszott zene"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Lejátszás vezérlése a Windows tálcáról" "description": "Lejátszás vezérlése a Windows tálcáról",
"name": "Médiavezérlés a tálcán"
}, },
"touchbar": { "touchbar": {
"description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz", "description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz",
@ -667,6 +716,7 @@
} }
}, },
"visualizer": { "visualizer": {
"description": "Vizualizációt ad a lejátszóhoz",
"menu": { "menu": {
"visualizer-type": "Vizualizáció típus" "visualizer-type": "Vizualizáció típus"
}, },

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Se viene riprodotto un annuncio, l'audio viene disattivato e viene impostata la velocità di riproduzione su 16x",
"name": "Accelerazione ad"
},
"adblocker": { "adblocker": {
"description": "Blocca tutti gli annunci e i tracker", "description": "Blocca tutti gli annunci e i tracker",
"menu": { "menu": {
@ -410,6 +414,21 @@
"description": "Download MP3 / sorgenti audio direttamente dall'interfaccia", "description": "Download MP3 / sorgenti audio direttamente dall'interfaccia",
"menu": { "menu": {
"choose-download-folder": "Scegli cartella download", "choose-download-folder": "Scegli cartella download",
"download-finish-settings": {
"label": "Scarica al termine",
"prompt": {
"last-percent": "Dopo x percento",
"last-seconds": "Ultimi x secondi",
"title": "Configura quando scaricare"
},
"submenu": {
"advanced": "Avanzato",
"enabled": "Abilitato",
"mode": "Modalità tempo",
"percent": "Percentuale",
"seconds": "Secondi"
}
},
"download-playlist": "Scarica la playlist", "download-playlist": "Scarica la playlist",
"presets": "Preimpostazioni", "presets": "Preimpostazioni",
"skip-existing": "Salta i file esistenti" "skip-existing": "Salta i file esistenti"
@ -649,6 +668,23 @@
"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", "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" "name": "Blocco sponsor"
}, },
"synced-lyrics": {
"description": "Fornisce testi sincronizzati alle canzoni, utilizzando provider come LRClib.",
"errors": {
"fetch": "⚠️ - Si è verificato un errore nel recuperare il testo. Per favore riprova più tardi.",
"not-found": "⚠️ - Nessun testo trovato per questa canzone."
},
"name": "Testi sincronizzati",
"refetch-btn": {
"fetching": "Sto recuperando...",
"normal": "Recupera i testi"
},
"warnings": {
"duration-mismatch": "⚠️ - I testi potrebbero non essere sincronizzati a causa di una mancata corrispondenza della durata.",
"inexact": "⚠️ - Il testo di questa canzone potrebbe essere inesatto",
"instrumental": "⚠️ - Questo è un brano strumentale"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Controlla riproduzione dalla taskbar di Windows", "description": "Controlla riproduzione dalla taskbar di Windows",
"name": "Controlli multimediali sulla taskbar" "name": "Controlli multimediali sulla taskbar"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "광고가 재생될 때, 오디오가 음소거되고 재생 속도가 16배로 설정됩니다",
"name": "광고 배속"
},
"adblocker": { "adblocker": {
"description": "모든 광고와 트래커를 즉시 차단합니다", "description": "모든 광고와 트래커를 즉시 차단합니다",
"menu": { "menu": {
@ -664,6 +668,59 @@
"description": "인트로/아웃트로와 같은 음악이 아닌 부분이나, 노래가 재생되지 않는 뮤직 비디오의 일부를 자동으로 건너뜁니다", "description": "인트로/아웃트로와 같은 음악이 아닌 부분이나, 노래가 재생되지 않는 뮤직 비디오의 일부를 자동으로 건너뜁니다",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "LRClib등의 가사 제공자에서 싱크 가사를 불러옵니다.",
"errors": {
"fetch": "⚠️ - 가사를 불러오는 동안 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"not-found": "⚠️ - 이 노래의 가사를 찾을 수 없습니다."
},
"menu": {
"default-text-string": {
"label": "가사 사이에 표시할 문자",
"tooltip": "가사 사이의 빈 공간에 사용할 문자를 선택합니다"
},
"line-effect": {
"label": "줄 표시 효과",
"submenu": {
"focus": {
"label": "포커스",
"tooltip": "현재 줄만 하얀색으로 표시"
},
"offset": {
"label": "오프셋",
"tooltip": "현재 줄의 오른쪽에 오프셋 적용"
},
"scale": {
"label": "스케일",
"tooltip": "현재 줄에 스케일 적용"
}
},
"tooltip": "현재 줄에 적용할 효과를 선택합니다"
},
"precise-timing": {
"label": "가사를 최대한 정교하게 동기화",
"tooltip": "다음 줄의 표시를 밀리초 단위로 계산합니다 (성능에 약간의 영향을 미칠 수 있음)"
},
"show-lyrics-even-if-inexact": {
"label": "가사가 정확하지 않더라도 표시",
"tooltip": "노래를 찾을 수 없는 경우, 플러그인이 다른 검색어로 다시 검색합니다.\n두번째 검색 결과는 정확하지 않을 수 있습니다."
},
"show-time-codes": {
"label": "시간 코드 표시",
"tooltip": "가사 옆에 시간 코드 표시"
}
},
"name": "싱크 가사",
"refetch-btn": {
"fetching": "가져오는 중...",
"normal": "가사 다시 가져오기"
},
"warnings": {
"duration-mismatch": "⚠️ - 곡 길이 불일치로 인해 가사가 일치하지 않을 수 있습니다.",
"inexact": "⚠️ - 이 노래의 가사는 정확하지 않을 수 있습니다",
"instrumental": "⚠️ - 연주곡입니다"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Windows 작업 표시줄에서 재생을 제어하세요", "description": "Windows 작업 표시줄에서 재생을 제어하세요",
"name": "작업표시줄 미디어 컨트롤" "name": "작업표시줄 미디어 컨트롤"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Wycisza reklamę i przyśpiesza do 16x",
"name": "Przyśpieszacz reklam"
},
"adblocker": { "adblocker": {
"description": "Blokuj wszystkie reklamy i śledzenie", "description": "Blokuj wszystkie reklamy i śledzenie",
"menu": { "menu": {
@ -410,6 +414,21 @@
"description": "Pobiera MP3/ źródło audio bezpośrednio z interfejsu", "description": "Pobiera MP3/ źródło audio bezpośrednio z interfejsu",
"menu": { "menu": {
"choose-download-folder": "Wybierz folder pobierania", "choose-download-folder": "Wybierz folder pobierania",
"download-finish-settings": {
"label": "Pobierz po zakończeniu",
"prompt": {
"last-percent": "Po x procentach",
"last-seconds": "Ostatnie x sekund",
"title": "Konfiguruj, kiedy pobierać"
},
"submenu": {
"advanced": "Zaawansowane",
"enabled": "Włączone",
"mode": "Tryb czasowy",
"percent": "Procenty",
"seconds": "Sekundy"
}
},
"download-playlist": "Pobierz playlistę", "download-playlist": "Pobierz playlistę",
"presets": "Predefiniowane ustawienia", "presets": "Predefiniowane ustawienia",
"skip-existing": "Pomiń istniejące pliki" "skip-existing": "Pomiń istniejące pliki"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Se um anúncio for reproduzido, ele será silenciado o áudio e será definido a velocidade de reprodução para 16x",
"name": "Acelerar os anúncios"
},
"adblocker": { "adblocker": {
"description": "Bloquear todos os anúncios e rastreamento automaticamente", "description": "Bloquear todos os anúncios e rastreamento automaticamente",
"menu": { "menu": {
@ -222,9 +226,9 @@
"description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum", "description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum",
"menu": { "menu": {
"color-mix-ratio": { "color-mix-ratio": {
"label": "Rácio de mistura das cores", "label": "Relação de mistura de cores",
"submenu": { "submenu": {
"percent": "Proporção" "percent": "{{ratio}}%"
} }
} }
}, },
@ -410,6 +414,21 @@
"description": "Baixa MP3 / fonte de áudio diretamente da interface", "description": "Baixa MP3 / fonte de áudio diretamente da interface",
"menu": { "menu": {
"choose-download-folder": "Escolha a pasta de download", "choose-download-folder": "Escolha a pasta de download",
"download-finish-settings": {
"label": "Baixar ao terminar",
"prompt": {
"last-percent": "Depois de x por cento",
"last-seconds": "Últimos x segundos",
"title": "Configurar quando baixar"
},
"submenu": {
"advanced": "Avançado",
"enabled": "Ativado",
"mode": "Modo de tempo",
"percent": "Porcentagem",
"seconds": "Segundos"
}
},
"download-playlist": "Baixar lista de reprodução", "download-playlist": "Baixar lista de reprodução",
"presets": "Predefinições", "presets": "Predefinições",
"skip-existing": "Ignorar arquivos existentes" "skip-existing": "Ignorar arquivos existentes"
@ -482,15 +501,15 @@
"add-song-failed": "Falha ao adicionar canção", "add-song-failed": "Falha ao adicionar canção",
"closed": "Música Juntos encerrado", "closed": "Música Juntos encerrado",
"disconnected": "Música Juntos foi desconectado", "disconnected": "Música Juntos foi desconectado",
"host-failed": "Falha ao hospedar o Música Juntos", "host-failed": "Falha ao hospedar o Music Together",
"id-copied": "ID de anfitrião copiado para a área de transferência", "id-copied": "ID de anfitrião copiado para a área de transferência",
"id-copy-failed": "Falha ao copiar o ID de anfitrião para a área de transferência", "id-copy-failed": "Falha ao copiar o ID de anfitrião para a área de transferência",
"join-failed": "Falha ao entrar em Música Juntos", "join-failed": "Falha ao entrar em Music Together",
"joined": "Entrou em Música Juntos", "joined": "Entrou em Music Together",
"permission-changed": "A permissão do Música Juntos foi alterada para \"{{permission}}\"", "permission-changed": "A permissão do Music Together foi alterada para \"{{permission}}\"",
"remove-song-failed": "Falha ao remover música", "remove-song-failed": "Falha ao remover música",
"user-connected": "{{name}} entrou em Música Juntos", "user-connected": "{{name}} entrou em Music Together",
"user-disconnected": "{{name}} saiu do Música Juntos" "user-disconnected": "{{name}} saiu do Music Together"
} }
}, },
"navigation": { "navigation": {

View File

@ -7,11 +7,32 @@
"initialize-failed": "\"{{pluginName}}\" ප්ලගිනය ආරම්භ කිරීමට අසමත් විය", "initialize-failed": "\"{{pluginName}}\" ප්ලගිනය ආරම්භ කිරීමට අසමත් විය",
"load-all": "සියලුම ප්ලගින පූරණය කරමින්", "load-all": "සියලුම ප්ලගින පූරණය කරමින්",
"load-failed": "\"{{pluginName}}\" ප්ලගිනය පූරණය කිරීමට අසමත් විය", "load-failed": "\"{{pluginName}}\" ප්ලගිනය පූරණය කිරීමට අසමත් විය",
"loaded": "ප්ලගිනය \"{{pluginName}}\" පූරණය කරන ලදී" "loaded": "ප්ලගිනය \"{{pluginName}}\" පූරණය කරන ලදී",
"unload-failed": "ප්ලගින් \"{{pluginName}}\" ගලවන්න අසාර්ථක වුන",
"unloaded": "ප්ලගින් \"{{pluginName}}\" ගැලෙව්වා"
} }
} }
}, },
"language": {
"code": "si",
"local-name": "සිංහල",
"name": "Sinhala"
},
"main": { "main": {
"console": {
"did-finish-load": {
"dev-tools": "පූරණය සම්පුර්නි. ඩෙව්ටූල්ස් ඇරිලා"
},
"i18n": {
"loaded": "i18n පූරණය කර ඇත"
},
"second-instance": {
"receive-command": "ප්‍රෝටෝකාල් හරහා විධානය ලැබුණි: \"{{command}}\""
},
"theme": {
"css-file-not-found": "සීඑස්එස් ගොනුව \"{{cssFile}}\" නොපවතී, නොසලකා හැරීම"
}
},
"dialog": { "dialog": {
"need-to-restart": { "need-to-restart": {
"title": "නැවත ආරම්භ කිරීම අවශ්‍යයි" "title": "නැවත ආරම්භ කිරීම අවශ්‍යයි"

View File

@ -410,6 +410,21 @@
"description": "MP3 / kaynak sesini doğrudan arayüzden indir", "description": "MP3 / kaynak sesini doğrudan arayüzden indir",
"menu": { "menu": {
"choose-download-folder": "İndirme klasörünü seç", "choose-download-folder": "İndirme klasörünü seç",
"download-finish-settings": {
"label": "Bittiğinde indir",
"prompt": {
"last-percent": "Yüzde x'ten sonra",
"last-seconds": "Son x saniyede",
"title": "Ne zaman indirileceğini ayarla"
},
"submenu": {
"advanced": "Gelişmiş",
"enabled": "Etkin",
"mode": "Zaman türü",
"percent": "Yüzde",
"seconds": "Saniye"
}
},
"download-playlist": "Oynatma listesini indir", "download-playlist": "Oynatma listesini indir",
"presets": "Hazır Ayarlar", "presets": "Hazır Ayarlar",
"skip-existing": "Mevcut dosyaları atla" "skip-existing": "Mevcut dosyaları atla"

View File

@ -2,7 +2,7 @@
"common": { "common": {
"console": { "console": {
"plugins": { "plugins": {
"execute-failed": "Lỗi khi bắt đầu phần mở rộng {{pluginName}}::{{contextName}}", "execute-failed": "Lỗi thực thi plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Phần mở rộng {{pluginName}}::{{contextName}} đã bắt đầu trong {{ms}}ms", "executed-at-ms": "Phần mở rộng {{pluginName}}::{{contextName}} đã bắt đầu trong {{ms}}ms",
"initialize-failed": "Lỗi khi khởi động phần mở rộng \"{{pluginName}}\"", "initialize-failed": "Lỗi khi khởi động phần mở rộng \"{{pluginName}}\"",
"load-all": "Đang tải tất cả phần mở rộng", "load-all": "Đang tải tất cả phần mở rộng",
@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Nếu một quảng cáo được phát thì sẽ bị tắt tiếng và tăng tốc độ phát lên 16x",
"name": "Tăng tốc quảng cáo"
},
"adblocker": { "adblocker": {
"description": "Chặn toàn bộ quảng cáo và trình theo dõi", "description": "Chặn toàn bộ quảng cáo và trình theo dõi",
"menu": { "menu": {
@ -410,6 +414,21 @@
"description": "Tải xuống MP3 / âm thanh nguồn trực tiếp từ giao diện", "description": "Tải xuống MP3 / âm thanh nguồn trực tiếp từ giao diện",
"menu": { "menu": {
"choose-download-folder": "Chọn thư mục tải xuống", "choose-download-folder": "Chọn thư mục tải xuống",
"download-finish-settings": {
"label": "Tải xuống khi hoàn tất",
"prompt": {
"last-percent": "Sau x phần trăm",
"last-seconds": "x giây cuối",
"title": "Định cấu hình thời điểm tải xuống"
},
"submenu": {
"advanced": "Nâng cao",
"enabled": "Đã kích hoạt",
"mode": "Chế độ thời gian",
"percent": "Phần trăm",
"seconds": "Giây"
}
},
"download-playlist": "Tải danh sách phát", "download-playlist": "Tải danh sách phát",
"presets": "Cài đặt sẵn", "presets": "Cài đặt sẵn",
"skip-existing": "Bỏ qua các tập tin hiện có" "skip-existing": "Bỏ qua các tập tin hiện có"
@ -649,6 +668,23 @@
"description": "Tự động bỏ qua các phần không phải âm nhạc như phần giới thiệu/kết thúc hoặc các phần của video nhạc mà bài hát không được phát", "description": "Tự động bỏ qua các phần không phải âm nhạc như phần giới thiệu/kết thúc hoặc các phần của video nhạc mà bài hát không được phát",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Cung cấp lời bài hát được đồng bộ hoá với các bài hát, sử dụng những nhà cung cấp như LRClib.",
"errors": {
"fetch": "⚠️ - Đã xảy ra lỗi khi tìm nạp lời bài hát, Vui lòng thử lại sau.",
"not-found": "⚠️ - Không tìm thấy lời cho bài hát này."
},
"name": "Lời bài hát được đồng bộ hoá",
"refetch-btn": {
"fetching": "Đang tìm nạp...",
"normal": "Tải lại lời bài hát"
},
"warnings": {
"duration-mismatch": "⚠️ - Lời bài hát có thể không đồng bộ do thời lượng không khớp.",
"inexact": "⚠️ - Lời bài hát này có thể không chính xác",
"instrumental": "⚠️ - Đây là một bài hát trình diễn bằng nhạc khí"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Kiểm soát phát lại từ thanh tác vụ Windows của bạn", "description": "Kiểm soát phát lại từ thanh tác vụ Windows của bạn",
"name": "Kiểm soát phương tiện trên thanh tác vụ" "name": "Kiểm soát phương tiện trên thanh tác vụ"

View File

@ -410,6 +410,21 @@
"description": "在界面内直接下载 MP3 / 源音频", "description": "在界面内直接下载 MP3 / 源音频",
"menu": { "menu": {
"choose-download-folder": "选择下载文件夹", "choose-download-folder": "选择下载文件夹",
"download-finish-settings": {
"label": "边播边下",
"prompt": {
"last-percent": "播放超过指定百分比时开始下载",
"last-seconds": "歌曲剩余指定秒数时开始下载",
"title": "配置在何时开始下载"
},
"submenu": {
"advanced": "高级",
"enabled": "已启用",
"mode": "激活时机",
"percent": "按播放百分比",
"seconds": "按播放秒数"
}
},
"download-playlist": "下载播放列表", "download-playlist": "下载播放列表",
"presets": "预设", "presets": "预设",
"skip-existing": "跳过已存在的文件" "skip-existing": "跳过已存在的文件"

View File

@ -96,11 +96,11 @@
"advanced-options": { "advanced-options": {
"label": "進階選項", "label": "進階選項",
"submenu": { "submenu": {
"auto-reset-app-cache": "當程式啟動時重設應用程式快取", "auto-reset-app-cache": "啟動時重設應用快取",
"disable-hardware-acceleration": "關閉硬體加速", "disable-hardware-acceleration": "關閉硬體加速",
"edit-config-json": "編輯 config.json", "edit-config-json": "編輯 config.json",
"override-user-agent": "覆寫使用者代理", "override-user-agent": "覆寫使用者代理",
"restart-on-config-changes": "設定檔更動時自動重啟應用程式", "restart-on-config-changes": "設定變更時自動重啟應用",
"set-proxy": { "set-proxy": {
"label": "設定代理伺服器", "label": "設定代理伺服器",
"prompt": { "prompt": {
@ -123,7 +123,7 @@
}, },
"language": { "language": {
"dialog": { "dialog": {
"message": "語言會在下一次重啟應用程式時變更", "message": "語言會在重啟應用變更",
"title": "語言已變更" "title": "語言已變更"
}, },
"label": "語言", "label": "語言",
@ -131,7 +131,7 @@
"to-help-translate": "想協助翻譯?按一下這裡" "to-help-translate": "想協助翻譯?按一下這裡"
} }
}, },
"resume-on-start": "應用啟時繼續上次播放的歌曲", "resume-on-start": "應用啟時繼續播放上次的歌曲",
"single-instance-lock": "單實例模式", "single-instance-lock": "單實例模式",
"start-at-login": "開機時啟動", "start-at-login": "開機時啟動",
"starting-page": { "starting-page": {
@ -142,8 +142,8 @@
"label": "系統匣", "label": "系統匣",
"submenu": { "submenu": {
"disabled": "已停用", "disabled": "已停用",
"enabled-and-hide-app": "啟用並最小化應用程式", "enabled-and-hide-app": "啟用並最小化應用",
"enabled-and-show-app": "啟用但持續顯示應用程式", "enabled-and-show-app": "啟用顯示應用",
"play-pause-on-click": "點擊時播放/暫停" "play-pause-on-click": "點擊時播放/暫停"
} }
}, },
@ -219,7 +219,7 @@
"name": "進階專輯操作" "name": "進階專輯操作"
}, },
"album-color-theme": { "album-color-theme": {
"description": "依歌曲色調自動更改應用程式主題", "description": "根據專輯封面色調更改應用程式主題顏色",
"menu": { "menu": {
"color-mix-ratio": { "color-mix-ratio": {
"label": "顏色混合程度", "label": "顏色混合程度",
@ -407,9 +407,24 @@
"writing-id3": "正在寫入 ID3 標籤…" "writing-id3": "正在寫入 ID3 標籤…"
} }
}, },
"description": "應用程式內下載 MP3原始音檔", "description": "開啟應用程式內下載 MP3原始音檔功能",
"menu": { "menu": {
"choose-download-folder": "選擇下載位置", "choose-download-folder": "選擇下載位置",
"download-finish-settings": {
"label": "智慧下載",
"prompt": {
"last-percent": "歌曲剩餘多少 % 時下載",
"last-seconds": "歌曲剩餘多少秒時下載",
"title": "智慧下載進階設定"
},
"submenu": {
"advanced": "進階",
"enabled": "啟用",
"mode": "判斷方式",
"percent": "百分比",
"seconds": "秒數"
}
},
"download-playlist": "下載播放清單", "download-playlist": "下載播放清單",
"presets": "預設格式", "presets": "預設格式",
"skip-existing": "跳過已存在的檔案" "skip-existing": "跳過已存在的檔案"
@ -494,7 +509,7 @@
} }
}, },
"navigation": { "navigation": {
"description": "將上一頁/下一頁按鈕新增至應用程式上方, 就像你最熟悉的瀏覽器", "description": "允許應用程式上方顯示上一頁/下一頁按鈕",
"name": "導覽列" "name": "導覽列"
}, },
"no-google-login": { "no-google-login": {
@ -650,7 +665,7 @@
"name": "贊助阻擋" "name": "贊助阻擋"
}, },
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "透過工作列應用程式圖式控制媒體播放", "description": "允許工作列應用程式預覽介面顯示媒體控制相關按鈕",
"name": "工作列媒體控制" "name": "工作列媒體控制"
}, },
"touchbar": { "touchbar": {

View File

@ -100,9 +100,18 @@ if (config.get('options.disableHardwareAcceleration')) {
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
} }
if (is.linux() && config.plugins.isEnabled('shortcuts')) { if (is.linux()) {
const disabledFeatures = [
// Workaround for issue #2248
'UseMultiPlaneFormatForSoftwareVideo',
];
// Stops chromium from launching its own MPRIS service // Stops chromium from launching its own MPRIS service
app.commandLine.appendSwitch('disable-features', 'MediaSessionService'); if (config.plugins.isEnabled('shortcuts')) {
disabledFeatures.push('MediaSessionService');
}
app.commandLine.appendSwitch('disable-features', disabledFeatures.join());
} }
if (config.get('options.proxy')) { if (config.get('options.proxy')) {
@ -312,28 +321,29 @@ 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 = is.windows() ? display.scaleFactor: 1; const primaryDisplay = screen.getPrimaryDisplay();
const scaledWidth = Math.floor(windowSize.width / scaleFactor); const scaleFactor = is.windows() ? primaryDisplay.scaleFactor / display.scaleFactor : 1;
const scaledHeight = Math.floor(windowSize.height / scaleFactor); const scaledWidth = Math.floor(windowSize.width * scaleFactor);
const scaledHeight = Math.floor(windowSize.height * scaleFactor);
const scaledX = windowX; const scaledX = windowX;
const scaledY = windowY; const scaledY = windowY;
if ( if (
scaledX + scaledWidth < display.bounds.x - 8 || scaledX + (scaledWidth / 2) < display.bounds.x - 8 || // Left
scaledX - scaledWidth > display.bounds.x + display.bounds.width || scaledX + (scaledWidth / 2) > display.bounds.x + display.bounds.width || // Right
scaledY < display.bounds.y - 8 || scaledY < display.bounds.y - 8 || // Top
scaledY > display.bounds.y + display.bounds.height scaledY + (scaledHeight / 2) > display.bounds.y + display.bounds.height // Bottom
) { ) {
// Window is offscreen // Window is offscreen
if (is.dev()) { if (is.dev()) {
console.warn( console.warn(
LoggerPrefix, LoggerPrefix,
t('main.console.window.tried-to-render-offscreen', { t('main.console.window.tried-to-render-offscreen', {
winSize: String(winSize), windowSize: String(winSize),
displaySize: String(display.bounds), displaySize: JSON.stringify(display.bounds),
windowPosition: String(windowPosition), position: JSON.stringify(windowPosition),
}), }),
); );
} }

View File

@ -0,0 +1,56 @@
function skipAd(target: Element) {
const skipButton = target.querySelector<HTMLButtonElement>('button.ytp-ad-skip-button-modern');
if (skipButton) {
skipButton.click();
}
}
function speedUpAndMute(player: Element, isAdShowing: boolean) {
const video = player.querySelector<HTMLVideoElement>('video');
if (!video) return;
if (isAdShowing) {
video.playbackRate = 16;
video.muted = true;
} else if (!isAdShowing) {
video.playbackRate = 1;
video.muted = false;
}
}
export const loadAdSpeedup = async () => {
const player = document.querySelector<HTMLVideoElement>('#movie_player');
if (!player) return;
new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (
mutation.type === 'attributes' &&
mutation.attributeName === 'class'
) {
const target = mutation.target as HTMLElement;
const isAdShowing =
target.classList.contains('ad-showing') ||
target.classList.contains('ad-interrupting');
speedUpAndMute(target, isAdShowing);
}
if (
mutation.type === 'childList' &&
mutation.addedNodes.length &&
mutation.target instanceof HTMLElement
) {
skipAd(mutation.target);
}
}
}).observe(player, {
attributes: true,
childList: true,
subtree: true,
});
const isAdShowing =
player.classList.contains('ad-showing') ||
player.classList.contains('ad-interrupting');
speedUpAndMute(player, isAdShowing);
skipAd(player);
}

View File

@ -14,6 +14,7 @@ import { inject, isInjected } from './injectors/inject';
import { t } from '@/i18n'; import { t } from '@/i18n';
import type { BrowserWindow } from 'electron'; import type { BrowserWindow } from 'electron';
import { loadAdSpeedup } from './adSpeedup';
interface AdblockerConfig { interface AdblockerConfig {
/** /**
@ -72,6 +73,14 @@ export default createPlugin({
}, },
]; ];
}, },
renderer: {
async onPlayerApiReady(_, {getConfig}) {
const config = await getConfig();
if (config.blocker === blockers.AdSpeedup) {
await loadAdSpeedup();
}
}
},
backend: { backend: {
mainWindow: null as BrowserWindow | null, mainWindow: null as BrowserWindow | null,
async start({ getConfig, window }) { async start({ getConfig, window }) {

View File

@ -1,4 +1,5 @@
export const blockers = { export const blockers = {
WithBlocklists: 'With blocklists', WithBlocklists: 'With blocklists',
InPlayer: 'In player', InPlayer: 'In player',
AdSpeedup: 'Ad speedup',
} as const; } as const;

View File

@ -1,6 +1,7 @@
import { t } from '@/i18n'; import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { ElementFromHtml } from '@/plugins/utils/renderer'; import { ElementFromHtml } from '@/plugins/utils/renderer';
import { waitForElement } from '@/utils/wait-for-element';
import undislikeHTML from './templates/undislike.html?raw'; import undislikeHTML from './templates/undislike.html?raw';
import dislikeHTML from './templates/dislike.html?raw'; import dislikeHTML from './templates/dislike.html?raw';
@ -16,7 +17,6 @@ export default createPlugin<
changeObserver?: MutationObserver; changeObserver?: MutationObserver;
waiting: boolean; waiting: boolean;
onPageChange(): void; onPageChange(): void;
waitForElem(selector: string): Promise<HTMLElement>;
loadFullList: (event: MouseEvent) => void; loadFullList: (event: MouseEvent) => void;
applyToList(id: string, loader: HTMLElement): void; applyToList(id: string, loader: HTMLElement): void;
start(): void; start(): void;
@ -50,7 +50,7 @@ export default createPlugin<
} else { } else {
this.waiting = true; this.waiting = true;
} }
const continuations = await this.waitForElem('#continuations'); const continuations = await waitForElement<HTMLElement>('#continuations');
this.waiting = false; this.waiting = false;
//Gets the for buttons //Gets the for buttons
const buttons: Array<HTMLElement> = [ const buttons: Array<HTMLElement> = [
@ -183,16 +183,5 @@ export default createPlugin<
button.remove(); button.remove();
} }
}, },
waitForElem(selector: string) {
return new Promise((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector<HTMLElement>(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
});
});
},
}, },
}); });

View File

@ -4,6 +4,7 @@ import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { menu } from './menu'; import { menu } from './menu';
import { AmbientModePluginConfig } from './types'; import { AmbientModePluginConfig } from './types';
import { waitForElement } from '@/utils/wait-for-element';
const defaultConfig: AmbientModePluginConfig = { const defaultConfig: AmbientModePluginConfig = {
enabled: false, enabled: false,
@ -36,7 +37,7 @@ export default createPlugin({
unregister: null as (() => void) | null, unregister: null as (() => void) | null,
update: null as (() => void) | null, update: null as (() => void) | null,
interval: null as NodeJS.Timeout | null, interval: null as NodeJS.Timeout | null,
lastMediaType: null as "video" | "image" | null, lastMediaType: null as 'video' | 'image' | null,
lastVideoSource: null as string | null, lastVideoSource: null as string | null,
lastImageSource: null as string | null, lastImageSource: null as string | null,
@ -53,7 +54,8 @@ export default createPlugin({
const songImage = document.querySelector<HTMLImageElement>('#song-image'); const songImage = document.querySelector<HTMLImageElement>('#song-image');
const songVideo = document.querySelector<HTMLDivElement>('#song-video'); const songVideo = document.querySelector<HTMLDivElement>('#song-video');
const image = songImage?.querySelector<HTMLImageElement>('yt-img-shadow > img'); const image = songImage?.querySelector<HTMLImageElement>('yt-img-shadow > img');
const video = songVideo?.querySelector<HTMLVideoElement>('.html5-video-container > video'); const video = await waitForElement<HTMLVideoElement>('.html5-video-container > video');
const videoWrapper = document.querySelector('#song-video > .player-wrapper'); const videoWrapper = document.querySelector('#song-video > .player-wrapper');
const injectBlurImage = () => { const injectBlurImage = () => {
@ -179,12 +181,12 @@ export default createPlugin({
const isVideoMode = () => { const isVideoMode = () => {
const songVideo = document.querySelector<HTMLDivElement>('#song-video'); const songVideo = document.querySelector<HTMLDivElement>('#song-video');
if (!songVideo) { if (!songVideo) {
this.lastMediaType = "image"; this.lastMediaType = 'image';
return false; return false;
} }
const isVideo = getComputedStyle(songVideo).display !== 'none'; const isVideo = getComputedStyle(songVideo).display !== 'none';
this.lastMediaType = isVideo ? "video" : "image"; this.lastMediaType = isVideo ? 'video' : 'image';
return isVideo; return isVideo;
}; };
@ -196,8 +198,8 @@ export default createPlugin({
if (isPageOpen) { if (isPageOpen) {
const isVideo = isVideoMode(); const isVideo = isVideoMode();
if (!force) { if (!force) {
if (this.lastMediaType === "video" && this.lastVideoSource === video?.src) return false; if (this.lastMediaType === 'video' && this.lastVideoSource === video?.src) return false;
if (this.lastMediaType === "image" && this.lastImageSource === image?.src) return false; if (this.lastMediaType === 'image' && this.lastImageSource === image?.src) return false;
} }
this.unregister?.(); this.unregister?.();
this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null; this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null;
@ -205,7 +207,7 @@ export default createPlugin({
this.unregister?.(); this.unregister?.();
this.unregister = null; this.unregister = null;
} }
} };
/* needed for switching between different views (e.g. miniplayer) */ /* needed for switching between different views (e.g. miniplayer) */
const observer = new MutationObserver((mutationsList) => { const observer = new MutationObserver((mutationsList) => {

View File

@ -2,11 +2,12 @@ import { app, dialog, ipcMain } from 'electron';
import { Client as DiscordClient } from '@xhayper/discord-rpc'; import { Client as DiscordClient } from '@xhayper/discord-rpc';
import { dev } from 'electron-is'; import { dev } from 'electron-is';
import { ActivityType, GatewayActivityButton } from 'discord-api-types/v10';
import registerCallback, { type SongInfo } from '@/providers/song-info'; import registerCallback, { type SongInfo } from '@/providers/song-info';
import { createBackend, LoggerPrefix } from '@/utils'; import { createBackend, LoggerPrefix } from '@/utils';
import { t } from '@/i18n'; import { t } from '@/i18n';
import type { GatewayActivityButton } from 'discord-api-types/v10';
import type { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser'; import type { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
import type { DiscordPluginConfig } from './index'; import type { DiscordPluginConfig } from './index';
@ -180,6 +181,7 @@ export const backend = createBackend<
} }
const activityInfo: SetActivity = { const activityInfo: SetActivity = {
type: ActivityType.Listening,
details: songInfo.title, details: songInfo.title,
state: songInfo.artist, state: songInfo.artist,
largeImageKey: songInfo.imageSrc ?? '', largeImageKey: songInfo.imageSrc ?? '',
@ -207,8 +209,8 @@ export const backend = createBackend<
info.rpc.user?.setActivity(activityInfo).catch(console.error); info.rpc.user?.setActivity(activityInfo).catch(console.error);
}, },
async start({ window: win, getConfig }) { async start(ctx) {
this.config = await getConfig(); this.config = await ctx.getConfig();
info.rpc.on('connected', () => { info.rpc.on('connected', () => {
if (dev()) { if (dev()) {
@ -237,10 +239,10 @@ export const backend = createBackend<
info.autoReconnect = this.config.autoReconnect; info.autoReconnect = this.config.autoReconnect;
window = win; window = ctx.window;
// If the page is ready, register the callback // If the page is ready, register the callback
win.once('ready-to-show', () => { ctx.window.once('ready-to-show', () => {
let lastSongInfo: SongInfo; let lastSongInfo: SongInfo;
registerCallback((songInfo) => { registerCallback((songInfo) => {
lastSongInfo = songInfo; lastSongInfo = songInfo;

View File

@ -77,6 +77,7 @@ export const onRendererLoad = ({
applyLyricsTabState(); applyLyricsTabState();
} }
}; };
const applyLyricsTabState = () => { const applyLyricsTabState = () => {
if (lyrics) { if (lyrics) {
tabs.lyrics.removeAttribute('disabled'); tabs.lyrics.removeAttribute('disabled');
@ -86,6 +87,7 @@ export const onRendererLoad = ({
tabs.lyrics.setAttribute('aria-disabled', ''); tabs.lyrics.setAttribute('aria-disabled', '');
} }
}; };
const lyricsTabHandler = () => { const lyricsTabHandler = () => {
const tabContainer = document.querySelector('ytmusic-tab-renderer'); const tabContainer = document.querySelector('ytmusic-tab-renderer');
if (!tabContainer) return; if (!tabContainer) return;

View File

@ -1,12 +1,12 @@
import { t } from '@/i18n'; import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { waitForElement } from '@/utils/wait-for-element';
export default createPlugin< export default createPlugin<
unknown, unknown,
unknown, unknown,
{ {
observer?: MutationObserver; observer?: MutationObserver;
waitForElem(selector: string): Promise<HTMLElement>;
start(): void; start(): void;
stop(): void; stop(): void;
} }
@ -15,19 +15,8 @@ export default createPlugin<
description: () => t('plugins.skip-disliked-songs.description'), description: () => t('plugins.skip-disliked-songs.description'),
restartNeeded: false, restartNeeded: false,
renderer: { renderer: {
waitForElem(selector: string) {
return new Promise<HTMLElement>((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector<HTMLElement>(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
});
});
},
start() { start() {
this.waitForElem('#dislike-button-renderer').then((dislikeBtn) => { waitForElement<HTMLElement>('#dislike-button-renderer').then((dislikeBtn) => {
this.observer = new MutationObserver(() => { this.observer = new MutationObserver(() => {
if (dislikeBtn?.getAttribute('like-status') == 'DISLIKE') { if (dislikeBtn?.getAttribute('like-status') == 'DISLIKE') {
document document

View File

@ -0,0 +1,28 @@
import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import { menu } from './menu';
import { renderer } from './renderer';
import type { SyncedLyricsPluginConfig } from './types';
export default createPlugin({
name: () => t('plugins.synced-lyrics.name'),
description: () => t('plugins.synced-lyrics.description'),
authors: ['Non0reo', 'ArjixWasTaken'],
restartNeeded: true,
addedVersion: '3.5.X',
config: {
enabled: false,
preciseTiming: true,
showLyricsEvenIfInexact: true,
showTimeCodes: false,
defaultTextString: '♪',
lineEffect: 'scale',
} satisfies SyncedLyricsPluginConfig,
menu,
renderer,
stylesheets: [style],
});

View File

@ -0,0 +1,138 @@
import { MenuItemConstructorOptions } from 'electron';
import { t } from '@/i18n';
import type { MenuContext } from '@/types/contexts';
import type { SyncedLyricsPluginConfig } from './types';
export const menu = async ({
getConfig,
setConfig,
}: MenuContext<SyncedLyricsPluginConfig>): Promise<
MenuItemConstructorOptions[]
> => {
const config = await getConfig();
return [
{
label: t('plugins.synced-lyrics.menu.precise-timing.label'),
toolTip: t('plugins.synced-lyrics.menu.precise-timing.tooltip'),
type: 'checkbox',
checked: config.preciseTiming,
click(item) {
setConfig({
preciseTiming: item.checked,
});
},
},
{
label: t('plugins.synced-lyrics.menu.line-effect.label'),
toolTip: t('plugins.synced-lyrics.menu.line-effect.tooltip'),
type: 'submenu',
submenu: [
{
label: t('plugins.synced-lyrics.menu.line-effect.submenu.scale.label'),
toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.scale.tooltip'),
type: 'radio',
checked: config.lineEffect === 'scale',
click() {
setConfig({
lineEffect: 'scale',
});
},
},
{
label: t('plugins.synced-lyrics.menu.line-effect.submenu.offset.label'),
toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.offset.tooltip'),
type: 'radio',
checked: config.lineEffect === 'offset',
click() {
setConfig({
lineEffect: 'offset',
});
},
},
{
label: t('plugins.synced-lyrics.menu.line-effect.submenu.focus.label'),
toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.focus.tooltip'),
type: 'radio',
checked: config.lineEffect === 'focus',
click() {
setConfig({
lineEffect: 'focus',
});
},
},
],
},
{
label: t('plugins.synced-lyrics.menu.default-text-string.label'),
toolTip: t('plugins.synced-lyrics.menu.default-text-string.tooltip'),
type: 'submenu',
submenu: [
{
label: '♪',
type: 'radio',
checked: config.defaultTextString === '♪',
click() {
setConfig({
defaultTextString: '♪',
});
},
},
{
label: '" "',
type: 'radio',
checked: config.defaultTextString === ' ',
click() {
setConfig({
defaultTextString: ' ',
});
},
},
{
label: '...',
type: 'radio',
checked: config.defaultTextString === '...',
click() {
setConfig({
defaultTextString: '...',
});
},
},
{
label: '———',
type: 'radio',
checked: config.defaultTextString === '———',
click() {
setConfig({
defaultTextString: '———',
});
},
},
],
},
{
label: t('plugins.synced-lyrics.menu.show-time-codes.label'),
toolTip: t('plugins.synced-lyrics.menu.show-time-codes.tooltip'),
type: 'checkbox',
checked: config.showTimeCodes,
click(item) {
setConfig({
showTimeCodes: item.checked,
});
},
},
{
label: t('plugins.synced-lyrics.menu.show-lyrics-even-if-inexact.label'),
toolTip: t('plugins.synced-lyrics.menu.show-lyrics-even-if-inexact.tooltip'),
type: 'checkbox',
checked: config.showLyricsEvenIfInexact,
click(item) {
setConfig({
showLyricsEvenIfInexact: item.checked,
});
},
},
];
};

View File

@ -0,0 +1,146 @@
import { createSignal, For, Match, Show, Switch } from 'solid-js';
import { SyncedLine } from './SyncedLine';
import { t } from '@/i18n';
import { getSongInfo } from '@/providers/song-info-front';
import {
differentDuration,
hadSecondAttempt,
isFetching,
isInstrumental,
makeLyricsRequest,
} from '../lyrics/fetch';
import type { LineLyrics } from '../../types';
export const [debugInfo, setDebugInfo] = createSignal<string>();
export const [lineLyrics, setLineLyrics] = createSignal<LineLyrics[]>([]);
export const [currentTime, setCurrentTime] = createSignal<number>(-1);
export const LyricsContainer = () => {
const [error, setError] = createSignal('');
const onRefetch = async () => {
if (isFetching()) return;
setError('');
const info = getSongInfo();
await makeLyricsRequest(info).catch((err) => {
setError(`${err}`);
});
};
return (
<div class={'lyric-container'}>
<Switch>
<Match when={isFetching()}>
<div style="margin-bottom: 8px;">
<tp-yt-paper-spinner-lite
active
class="loading-indicator style-scope"
/>
</div>
</Match>
<Match when={error()}>
<yt-formatted-string
class="warning-lyrics description ytmusic-description-shelf-renderer"
text={{
runs: [
{
text: t('plugins.synced-lyrics.errors.fetch'),
},
],
}}
/>
</Match>
</Switch>
<Switch>
<Match when={!lineLyrics().length}>
<Show
when={isInstrumental()}
fallback={
<>
<yt-formatted-string
class="warning-lyrics description ytmusic-description-shelf-renderer"
text={{
runs: [
{
text: t('plugins.synced-lyrics.errors.not-found'),
},
],
}}
style={'margin-bottom: 16px;'}
/>
<yt-button-renderer
disabled={isFetching()}
data={{
icon: { iconType: 'REFRESH' },
isDisabled: false,
style: 'STYLE_DEFAULT',
text: {
simpleText: isFetching()
? t('plugins.synced-lyrics.refetch-btn.fetching')
: t('plugins.synced-lyrics.refetch-btn.normal'),
},
}}
onClick={onRefetch}
/>
</>
}
>
<yt-formatted-string
class="warning-lyrics description ytmusic-description-shelf-renderer"
text={{
runs: [
{
text: t('plugins.synced-lyrics.warnings.instrumental'),
},
],
}}
/>
</Show>
</Match>
<Match when={lineLyrics().length && !hadSecondAttempt()}>
<yt-formatted-string
class="warning-lyrics description ytmusic-description-shelf-renderer"
text={{
runs: [
{
text: t('plugins.synced-lyrics.warnings.inexact'),
},
],
}}
/>
</Match>
<Match when={lineLyrics().length && !differentDuration()}>
<yt-formatted-string
class="warning-lyrics description ytmusic-description-shelf-renderer"
text={{
runs: [
{
text: t('plugins.synced-lyrics.warnings.duration-mismatch'),
},
],
}}
/>
</Match>
</Switch>
<For each={lineLyrics()}>{(item) => <SyncedLine line={item} />}</For>
<yt-formatted-string
class="footer style-scope ytmusic-description-shelf-renderer"
text={{
runs: [
{
text: 'Source: LRCLIB',
},
],
}}
/>
</div>
);
};

View File

@ -0,0 +1,53 @@
import { createEffect, createMemo } from 'solid-js';
import { currentTime } from './LyricsContainer';
import { config } from '../renderer';
import { _ytAPI } from '..';
import type { LineLyrics } from '../../types';
interface SyncedLineProps {
line: LineLyrics;
}
export const SyncedLine = ({ line }: SyncedLineProps) => {
const status = createMemo(() => {
const current = currentTime();
if (line.timeInMs >= current) return 'upcoming';
if (current - line.timeInMs >= line.duration) return 'previous';
return 'current';
});
let ref: HTMLDivElement;
createEffect(() => {
if (status() === 'current') {
ref.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
return (
<div
ref={ref!}
class={`synced-line ${status()}`}
onClick={() => {
_ytAPI?.seekTo(line.timeInMs / 1000);
}}
>
<yt-formatted-string
class="text-lyrics description ytmusic-description-shelf-renderer"
text={{
runs: [
{
text: '',
},
{
text: `${config()?.showTimeCodes ? `[${line.time}] ` : ''}${line.text}`,
},
],
}}
/>
</div>
);
};

View File

@ -0,0 +1,93 @@
import { createRenderer } from '@/utils';
import { waitForElement } from '@/utils/wait-for-element';
import { makeLyricsRequest } from './lyrics';
import { selectors, tabStates } from './utils';
import { setConfig } from './renderer';
import { setCurrentTime } from './components/LyricsContainer';
import type { RendererContext } from '@/types/contexts';
import type { YoutubePlayer } from '@/types/youtube-player';
import type { SongInfo } from '@/providers/song-info';
import type { SyncedLyricsPluginConfig } from '../types';
export let _ytAPI: YoutubePlayer | null = null;
export const renderer = createRenderer<{
observerCallback: MutationCallback;
hasAddedEvents: boolean;
observer?: MutationObserver;
videoDataChange: () => Promise<void>;
progressCallback: (evt: Event) => void;
}, SyncedLyricsPluginConfig>({
onConfigChange(newConfig) {
setConfig(newConfig);
},
observerCallback(mutations: MutationRecord[]) {
for (const mutation of mutations) {
const header = mutation.target as HTMLElement;
switch (mutation.attributeName) {
case 'disabled':
header.removeAttribute('disabled');
break;
case 'aria-selected':
tabStates[header.ariaSelected as 'true' | 'false']?.(
_ytAPI?.getVideoData(),
);
break;
}
}
},
async onPlayerApiReady(api: YoutubePlayer) {
_ytAPI = api;
api.addEventListener('videodatachange', this.videoDataChange);
await this.videoDataChange();
},
hasAddedEvents: false,
async videoDataChange() {
if (!this.hasAddedEvents) {
const video = document.querySelector('video');
video?.addEventListener('timeupdate', this.progressCallback);
if (video) this.hasAddedEvents = true;
}
this.observer ??= new MutationObserver(
this.observerCallback,
);
// Force the lyrics tab to be enabled at all times.
this.observer.disconnect();
const header = await waitForElement<HTMLElement>(selectors.head);
this.observer.observe(header, { attributes: true });
header.removeAttribute('disabled');
},
progressCallback(evt: Event) {
switch (evt.type) {
case 'timeupdate': {
const video = evt.target as HTMLVideoElement;
setCurrentTime(video.currentTime * 1000);
break;
}
}
},
async start(ctx: RendererContext<SyncedLyricsPluginConfig>) {
setConfig(await ctx.getConfig());
ctx.ipc.on('ytmd:update-song-info', async (info: SongInfo) => {
await makeLyricsRequest(info);
});
},
});

View File

@ -0,0 +1,198 @@
import { createSignal } from 'solid-js';
import { jaroWinkler } from '@skyra/jaro-winkler';
import { config } from '../renderer';
import { setDebugInfo, setLineLyrics } from '../components/LyricsContainer';
import type { SongInfo } from '@/providers/song-info';
import type { LineLyrics, LRCLIBSearchResponse } from '../../types';
// prettier-ignore
export const [isInstrumental, setIsInstrumental] = createSignal(false);
// prettier-ignore
export const [isFetching, setIsFetching] = createSignal(false);
// prettier-ignore
export const [hadSecondAttempt, setHadSecondAttempt] = createSignal(false);
// prettier-ignore
export const [differentDuration, setDifferentDuration] = createSignal(false);
// eslint-disable-next-line prefer-const
export let foundPlainTextLyrics = false;
export type SongData = {
title: string;
artist: string;
album: string;
songDuration: number;
};
export const extractTimeAndText = (
line: string,
index: number,
): LineLyrics | null => {
const groups = /\[(\d+):(\d+)\.(\d+)\](.+)/.exec(line);
if (!groups) return null;
const [_, rMinutes, rSeconds, rMillis, text] = groups;
const [minutes, seconds, millis] = [
parseInt(rMinutes),
parseInt(rSeconds),
parseInt(rMillis),
];
// prettier-ignore
const timeInMs = (minutes * 60 * 1000) + (seconds * 1000) + millis;
return {
index,
timeInMs,
time: `${minutes}:${seconds}:${millis}`,
text: text?.trim() ?? config()!.defaultTextString,
status: 'upcoming',
duration: 0,
};
};
export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => {
setLineLyrics([]);
const songData: SongData = {
title: `${extractedSongInfo.title}`,
artist: `${extractedSongInfo.artist}`,
album: `${extractedSongInfo.album}`,
songDuration: extractedSongInfo.songDuration,
};
const lyrics = await getLyricsList(songData);
setLineLyrics(lyrics ?? []);
};
export const getLyricsList = async (
songData: SongData,
): Promise<LineLyrics[] | null> => {
setIsFetching(true);
setIsInstrumental(false);
setHadSecondAttempt(false);
setDifferentDuration(false);
setDebugInfo('Searching for lyrics...');
let query = new URLSearchParams({
artist_name: songData.artist,
track_name: songData.title,
});
if (songData.album) {
query.set('album_name', songData.album);
}
let url = `https://lrclib.net/api/search?${query.toString()}`;
let response = await fetch(url);
if (!response.ok) {
setIsFetching(false);
setDebugInfo('Got non-OK response from server.');
return null;
}
let data = (await response.json().catch((e: Error) => {
setDebugInfo(`Error: ${e.message}\n\n${e.stack}`);
return null;
})) as LRCLIBSearchResponse | null;
if (!data || !Array.isArray(data)) {
setIsFetching(false);
setDebugInfo('Unexpected server response.');
return null;
}
// Note: If no lyrics are found, try again with a different search query
if (data.length === 0) {
if (!config()?.showLyricsEvenIfInexact) {
return null;
}
query = new URLSearchParams({ q: songData.title });
url = `https://lrclib.net/api/search?${query.toString()}`;
response = await fetch(url);
if (!response.ok) {
setIsFetching(false);
setDebugInfo('Got non-OK response from server. (2)');
return null;
}
data = (await response.json()) as LRCLIBSearchResponse;
if (!Array.isArray(data)) {
setIsFetching(false);
setDebugInfo('Unexpected server response. (2)');
return null;
}
setHadSecondAttempt(true);
}
const filteredResults = [];
for (const item of data) {
if (!item.syncedLyrics) continue;
const { artist } = songData;
const { artistName } = item;
const ratio = jaroWinkler(artist.toLowerCase(), artistName.toLowerCase());
if (ratio <= 0.9) continue;
filteredResults.push(item);
}
const duration = songData.songDuration;
filteredResults.sort(({ duration: durationA }, { duration: durationB }) => {
const left = Math.abs(durationA - duration);
const right = Math.abs(durationB - duration);
return left - right;
});
const closestResult = filteredResults[0];
if (!closestResult) {
setIsFetching(false);
setDebugInfo('No search result matched the criteria.');
return null;
}
// setDebugInfo(JSON.stringify(closestResult, null, 4));
if (Math.abs(closestResult.duration - duration) > 15) return null;
if (Math.abs(closestResult.duration - duration) > 5) {
// show message that the timings may be wrong
setDifferentDuration(true);
}
setIsInstrumental(closestResult.instrumental);
// Separate the lyrics into lines
const raw = closestResult.syncedLyrics.split('\n');
// Add a blank line at the beginning
raw.unshift('[0:0.0] ');
const syncedLyricList = [];
for (let idx = 0; idx < raw.length; idx++) {
const syncedLine = extractTimeAndText(raw[idx], idx);
if (syncedLine) {
syncedLyricList.push(syncedLine);
}
}
for (const line of syncedLyricList) {
const next = syncedLyricList[line.index + 1];
if (!next) {
line.duration = Infinity;
break;
}
line.duration = next.timeInMs - line.timeInMs;
}
setIsFetching(false);
return syncedLyricList;
};

View File

@ -0,0 +1,44 @@
import { createEffect } from 'solid-js';
import { config } from '../renderer';
export { makeLyricsRequest } from './fetch';
createEffect(() => {
if (!config()?.enabled) return;
const root = document.documentElement;
// Set the line effect
switch (config()?.lineEffect) {
case 'scale':
root.style.setProperty(
'--previous-lyrics',
'var(--ytmusic-text-primary)',
);
root.style.setProperty('--current-lyrics', 'var(--ytmusic-text-primary)');
root.style.setProperty('--size-lyrics', '1.2');
root.style.setProperty('--offset-lyrics', '0');
root.style.setProperty('--lyric-width', '83%');
break;
case 'offset':
root.style.setProperty(
'--previous-lyrics',
'var(--ytmusic-text-primary)',
);
root.style.setProperty('--current-lyrics', 'var(--ytmusic-text-primary)');
root.style.setProperty('--size-lyrics', '1');
root.style.setProperty('--offset-lyrics', '5%');
root.style.setProperty('--lyric-width', '100%');
break;
case 'focus':
root.style.setProperty(
'--previous-lyrics',
'var(--ytmusic-text-secondary)',
);
root.style.setProperty('--current-lyrics', 'var(--ytmusic-text-primary)');
root.style.setProperty('--size-lyrics', '1');
root.style.setProperty('--offset-lyrics', '0');
root.style.setProperty('--lyric-width', '100%');
break;
}
});

View File

@ -0,0 +1,19 @@
import { createSignal, Show } from 'solid-js';
import { LyricsContainer } from './components/LyricsContainer';
import type { VideoDetails } from '@/types/video-details';
import type { SyncedLyricsPluginConfig } from '../types';
export const [isVisible, setIsVisible] = createSignal<boolean>(false);
export const [config, setConfig] = createSignal<SyncedLyricsPluginConfig | null>(null);
export const [playerState, setPlayerState] = createSignal<VideoDetails | null>(null);
export const LyricsRenderer = () => {
return (
<Show when={isVisible()}>
<LyricsContainer />
</Show>
);
};

View File

@ -0,0 +1,39 @@
import { render } from 'solid-js/web';
import { waitForElement } from '@/utils/wait-for-element';
import { LyricsRenderer, setIsVisible, setPlayerState } from './renderer';
import type { VideoDetails } from '@/types/video-details';
export const selectors = {
head: '#tabsContent > .tab-header:nth-of-type(2)',
body: {
tabRenderer: '#tab-renderer[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"]',
root: 'ytmusic-description-shelf-renderer',
},
};
export const tabStates = {
true: async (data?: VideoDetails) => {
setIsVisible(true);
setPlayerState(data ?? null);
let container = document.querySelector('#synced-lyrics-container');
if (container) return;
const tabRenderer = await waitForElement<HTMLElement>(
selectors.body.tabRenderer,
);
container = Object.assign(document.createElement('div'), {
id: 'synced-lyrics-container',
});
tabRenderer.appendChild(container);
render(() => <LyricsRenderer />, container);
},
false: () => {
setIsVisible(false);
},
};

View File

@ -0,0 +1,78 @@
/* Hides the original lyrics, to only show our own. */
#tab-renderer[page-type='MUSIC_PAGE_TYPE_TRACK_LYRICS'] > * {
display: none !important;
}
#tab-renderer[page-type='MUSIC_PAGE_TYPE_TRACK_LYRICS'] > #synced-lyrics-container {
display: block !important;
}
/* :root {
--ytmusic-text-primary: #fff;
--ytmusic-text-secondary: #aaa;
} */
:root {
--global-margin: 0.7rem;
--previous-lyrics: var(--ytmusic-text-primary);
--current-lyrics: var(--ytmusic-text-primary);
--upcoming-lyrics: var(--ytmusic-text-secondary);
--size-lyrics: 1.2em;
--offset-lyrics: 1em;
}
.lyric-container {
padding-top: 16px;
}
.description {
font-size: clamp(1.4rem, 1.1vmax, 3rem) !important;
text-align: left !important;
}
.synced-line {
width: var(--lyric-width, 100%);
}
.synced-line > .text-lyrics {
cursor: pointer;
}
.synced-lyrics {
display: block;
justify-content: left;
text-align: left;
margin: 0.5rem 0;
margin-right: 20px;
transition: all 0.3s ease-in-out;
}
.warning-lyrics {
color: var(--ytmusic-text-secondary) !important;
font-style: italic;
}
.text-lyrics {
display: block;
text-align: left;
margin: var(--global-margin) 0;
transition: scale 0.3s ease-in-out, translate 0.3s ease-in-out, color 0.1s ease-in-out;
transform-origin: 0 50%;
}
.previous > .text-lyrics {
color: var(--previous-lyrics);
font-weight: normal;
}
.current > .text-lyrics {
color: var(--current-lyrics);
font-weight: bold;
scale: var(--size-lyrics);
translate: var(--offset-lyrics) 0;
}
.upcoming > .text-lyrics {
color: var(--upcoming-lyrics);
font-weight: normal;
}

View File

@ -0,0 +1,38 @@
export type SyncedLyricsPluginConfig = {
enabled: boolean;
preciseTiming: boolean;
showTimeCodes: boolean;
defaultTextString: string;
showLyricsEvenIfInexact: boolean;
lineEffect: LineEffect;
};
export type LineLyricsStatus = 'previous' | 'current' | 'upcoming';
export type LineLyrics = {
index: number;
time: string;
timeInMs: number;
text: string;
duration: number;
status: LineLyricsStatus;
};
export type PlayPauseEvent = {
isPaused: boolean;
elapsedSeconds: number;
};
export type LineEffect = 'scale' | 'offset' | 'focus';
export type LRCLIBSearchResponse = {
id: number;
name: string;
trackName: string;
artistName: string;
albumName: string;
duration: number;
instrumental: boolean;
plainLyrics: string;
syncedLyrics: string;
}[];

View File

@ -241,6 +241,7 @@ export interface FlagEndpoint {
flagAction: string; flagAction: string;
} }
// see song-info-front.ts
export type VideoDataChangeValue = Record<string, unknown> & { export type VideoDataChangeValue = Record<string, unknown> & {
videoId: string; videoId: string;
title: string; title: string;

View File

@ -0,0 +1,11 @@
export const waitForElement = <T extends Element>(selector: string): Promise<T> => {
return new Promise<T>((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector<T>(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
}, 100 /* ms */);
});
};

37
src/yt-web-components.d.ts vendored Normal file
View File

@ -0,0 +1,37 @@
import type { ComponentProps } from 'solid-js';
declare module 'solid-js' {
namespace JSX {
interface YtFormattedStringProps {
text?: {
runs: { text: string }[];
};
data?: object;
disabled?: boolean;
hidden?: boolean;
}
interface YtButtonRendererProps {
data?: {
icon?: {
iconType: string;
};
isDisabled?: boolean;
style?: string;
text?: {
simpleText: string;
};
};
}
interface YpYtPaperSpinnerLiteProps {
active?: boolean;
}
interface IntrinsicElements {
'yt-formatted-string': ComponentProps<'span'> & YtFormattedStringProps;
'yt-button-renderer': ComponentProps<'button'> & YtButtonRendererProps;
'tp-yt-paper-spinner-lite': ComponentProps<'div'> & YpYtPaperSpinnerLiteProps;
}
}
}