mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 02:31:45 +00:00
Compare commits
194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c94b22b82c | |||
| cab3cb49f0 | |||
| e42084f008 | |||
| adca273ec3 | |||
| 91dceb3c22 | |||
| 216e76f4a1 | |||
| 178bfa483f | |||
| 10ecf5d2fe | |||
| 7099b81296 | |||
| 1f15376b00 | |||
| 02b7a39753 | |||
| 6edc84a8bd | |||
| 11a0d39064 | |||
| d5a5ed35b6 | |||
| dbb9e95b32 | |||
| d4c8a4320d | |||
| 68d4f38e41 | |||
| 2204784e89 | |||
| c3b995b0a8 | |||
| ed0a344077 | |||
| 199d912823 | |||
| 089eff3152 | |||
| 306ee8dba5 | |||
| a4992bafb2 | |||
| 5b004eedff | |||
| 2a66076d31 | |||
| f48e46d29c | |||
| 20296f5463 | |||
| fe4c89c349 | |||
| 3640527c8c | |||
| 3326582a16 | |||
| 5dcb9fe9ba | |||
| 336fa1e6fd | |||
| 3679a109d6 | |||
| 5290ed3de2 | |||
| fe5195714f | |||
| 8eb846262d | |||
| e9a1c2a91f | |||
| 2d1f78b383 | |||
| 1899064fd3 | |||
| e0280e5fe2 | |||
| d577e0fba6 | |||
| 37577c2f7f | |||
| c880f0a4eb | |||
| 4875955914 | |||
| 5b28c780bd | |||
| 4db2674b15 | |||
| a454a0163f | |||
| 61eb28e780 | |||
| e165e64952 | |||
| f380822e11 | |||
| 6b1995145a | |||
| 9317e99f43 | |||
| 66d05d8683 | |||
| 545a3a4bb6 | |||
| 04a6d16dbe | |||
| 3c36477b1e | |||
| c5c191492e | |||
| b12e2f607c | |||
| 1d77ad6de4 | |||
| 25bd26d7f3 | |||
| d11d0abe73 | |||
| 8a643c465d | |||
| 233673b8d8 | |||
| 5a448fab31 | |||
| 42e8262cda | |||
| f64769b1d3 | |||
| e12998761b | |||
| 2e20fa83b8 | |||
| 5149757af3 | |||
| 655741f108 | |||
| 4e58571ad0 | |||
| 1e4a615b47 | |||
| dedcf0c9ff | |||
| a84a7d236a | |||
| e56b4b21f0 | |||
| 361f9e42bd | |||
| 918736d2ca | |||
| 8f3d5b08ac | |||
| 4ca327d801 | |||
| 8d0aa057ad | |||
| b7ffee089b | |||
| d72b994f66 | |||
| e6dafdb068 | |||
| 14886dd4bd | |||
| c9f2f88bac | |||
| 1eabbc0bbe | |||
| 32a572b35a | |||
| 59a5679cbb | |||
| ac51f798c3 | |||
| 7599cc694a | |||
| 53595654b1 | |||
| 7656c41dbc | |||
| ff0c5b87c9 | |||
| 506c95740a | |||
| 575a42e28a | |||
| dcdc6a825f | |||
| 0a41bb1cd6 | |||
| 72a4736dc9 | |||
| f2b1e6b6bf | |||
| bc0e28ad6d | |||
| 8ebae91c02 | |||
| 954ad90733 | |||
| 5af0643788 | |||
| be633ac1f2 | |||
| 0d047c1fd5 | |||
| e8b1aca629 | |||
| 5b9bacf390 | |||
| ccd16f4a5f | |||
| 02e519dab3 | |||
| 473ea78f12 | |||
| e7f366b770 | |||
| 66816ac42d | |||
| 08b985f2ab | |||
| 747bde2136 | |||
| eabc28b39f | |||
| 3537dc19ee | |||
| 6afeb60557 | |||
| 71115dedee | |||
| 8750b54f76 | |||
| 482a1c5073 | |||
| c8d516c40b | |||
| c1ad168c32 | |||
| 5f5be5d02f | |||
| 61ef56dccc | |||
| a73b5acc75 | |||
| 877573532c | |||
| 7b033b5caf | |||
| 8924ec29d3 | |||
| 23e688aaf8 | |||
| 9b3cbe8e01 | |||
| 67a89e8ed4 | |||
| 464a2b94ea | |||
| 9357a15116 | |||
| ee820bb01c | |||
| 6b81735811 | |||
| 8ce91b143a | |||
| 116dbad9bc | |||
| 977af3d617 | |||
| 6da8defc73 | |||
| 0e93a963e1 | |||
| 1e98b2e75a | |||
| 6f5f13a840 | |||
| 822bcedadf | |||
| 2b6aea82c3 | |||
| 4f4efb407e | |||
| 6159e0e652 | |||
| 3957e06174 | |||
| c78f823b9b | |||
| 1be3bb360e | |||
| ba2afd2652 | |||
| 5e283c9ea5 | |||
| ddb1c56111 | |||
| ebd167f3f2 | |||
| 178a62b9d3 | |||
| f98a2cf766 | |||
| fdbe6f7331 | |||
| 57c2cdc91e | |||
| 0f5074f8ab | |||
| 661396226d | |||
| 36f27fe2e6 | |||
| adf1ce4bc7 | |||
| 43b4b8df5e | |||
| 4a8440c281 | |||
| 32fe9fcffe | |||
| a9896845da | |||
| a59aa07334 | |||
| e07d7395e7 | |||
| 9bb6f32ece | |||
| ccb19a0dc9 | |||
| 64fb6c2597 | |||
| 73c3e355fe | |||
| fc7a504643 | |||
| 764dc0f895 | |||
| 9f33f49ec4 | |||
| 87ae6d29bb | |||
| 093c8e3ca6 | |||
| fec26a010d | |||
| 5d8aaccc55 | |||
| cda03078a9 | |||
| 9c139b96f4 | |||
| 9b2816c156 | |||
| b1b8847134 | |||
| bf9e698288 | |||
| 28e8a1c5dd | |||
| 18e0b1b863 | |||
| 02e2fb6a83 | |||
| 91bee4880e | |||
| 7de7303ebb | |||
| 363d869cff | |||
| 2512af80af | |||
| 887979932c | |||
| eeaaf2f158 | |||
| e91e995b95 |
@ -21,7 +21,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md), [🇨🇱 🇪🇸](./docs/readme/README-es.md)
|
||||
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md), [🇨🇱 🇪🇸](./docs/readme/README-es.md), [🇷🇺](./docs/readme/README-ru.md)
|
||||
|
||||
**Electron wrapper around YouTube Music featuring:**
|
||||
|
||||
@ -141,6 +141,8 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸]
|
||||
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or
|
||||
parts of music videos where the song isn't playing
|
||||
|
||||
- **Synced Lyrics**: Provides synced lyrics to songs, using providers like [LRClib](https://lrclib.net).
|
||||
|
||||
- **Taskbar Media Control**: Control playback from
|
||||
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||
|
||||
@ -159,6 +161,7 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸]
|
||||
|
||||
- **Visualizer**: Different music visualizers
|
||||
|
||||
|
||||
## Translation
|
||||
|
||||
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
||||
|
||||
164
changelog.md
164
changelog.md
@ -2,8 +2,172 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||
|
||||
#### [v3.5.2](https://github.com/th-ch/youtube-music/compare/v3.5.1...v3.5.2)
|
||||
|
||||
- chore(deps): update typescript-eslint monorepo to v8.4.0 [`#2401`](https://github.com/th-ch/youtube-music/pull/2401)
|
||||
- chore(deps): update dependency @total-typescript/ts-reset to v0.6.1 [`#2396`](https://github.com/th-ch/youtube-music/pull/2396)
|
||||
- chore(deps): update dependency electron to v31.5.0 [`#2397`](https://github.com/th-ch/youtube-music/pull/2397)
|
||||
- chore(deps): update dependency eslint-import-resolver-typescript to v3.6.3 [`#2376`](https://github.com/th-ch/youtube-music/pull/2376)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.100 [`#2394`](https://github.com/th-ch/youtube-music/pull/2394)
|
||||
- fix(deps): update dependency electron-updater to v6.3.4 [`#2395`](https://github.com/th-ch/youtube-music/pull/2395)
|
||||
- chore(deps): update dependency @babel/runtime to v7.25.6 [`#2388`](https://github.com/th-ch/youtube-music/pull/2388)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.8.7 [`#2389`](https://github.com/th-ch/youtube-music/pull/2389)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.99 [`#2374`](https://github.com/th-ch/youtube-music/pull/2374)
|
||||
- chore(deps): update dependency vite to v5.4.3 [`#2377`](https://github.com/th-ch/youtube-music/pull/2377)
|
||||
- fix: incorrect regex when splitting artistName [`#2378`](https://github.com/th-ch/youtube-music/pull/2378)
|
||||
- chore(deps): update dependency @babel/runtime to v7.25.4 [`#2373`](https://github.com/th-ch/youtube-music/pull/2373)
|
||||
- synced-lyrics: make the lyrics search more reliable [`#2343`](https://github.com/th-ch/youtube-music/pull/2343)
|
||||
- fix(deps): update dependency solid-js to v1.8.22 [`#2354`](https://github.com/th-ch/youtube-music/pull/2354)
|
||||
- chore(deps): update typescript-eslint monorepo to v8.3.0 [`#2350`](https://github.com/th-ch/youtube-music/pull/2350)
|
||||
- fix(deps): update dependency electron-debug to v4.0.1 [`#2349`](https://github.com/th-ch/youtube-music/pull/2349)
|
||||
- chore(deps): update dependency electron to v31.4.0 [`#2356`](https://github.com/th-ch/youtube-music/pull/2356)
|
||||
- fix: hide native-controls on linux when in-app-menu is used [`#2366`](https://github.com/th-ch/youtube-music/pull/2366)
|
||||
- fix: detect the upgrade btn using the icon [`#2364`](https://github.com/th-ch/youtube-music/pull/2364)
|
||||
- fix: exclude build-id files from rpm [`#2361`](https://github.com/th-ch/youtube-music/pull/2361)
|
||||
- fix(deps): update dependency i18next to v23.12.3 [`#2352`](https://github.com/th-ch/youtube-music/pull/2352)
|
||||
- fix(deps): update dependency @floating-ui/dom to v1.6.10 [`#2340`](https://github.com/th-ch/youtube-music/pull/2340)
|
||||
- fix(deps): update dependency electron-updater to v6.3.3 [`#2347`](https://github.com/th-ch/youtube-music/pull/2347)
|
||||
- fix(deps): update dependency solid-js to v1.8.20 [`#2345`](https://github.com/th-ch/youtube-music/pull/2345)
|
||||
- chore(deps): update dependency vite to v5.4.0 [`#2342`](https://github.com/th-ch/youtube-music/pull/2342)
|
||||
- chore(deps): update typescript-eslint monorepo to v8.0.1 [`#2335`](https://github.com/th-ch/youtube-music/pull/2335)
|
||||
- fix(deps): update dependency @floating-ui/dom to v1.6.9 [`#2337`](https://github.com/th-ch/youtube-music/pull/2337)
|
||||
- chore(deps): update playwright monorepo to v1.46.0 [`#2336`](https://github.com/th-ch/youtube-music/pull/2336)
|
||||
- chore(README): Translation README to Russian and adding Synced Lyrics to main README [`#2338`](https://github.com/th-ch/youtube-music/pull/2338)
|
||||
- chore(deps): update dependency rollup to v4.20.0 [`#2326`](https://github.com/th-ch/youtube-music/pull/2326)
|
||||
- fix(synced-lyric): fix timestamp [`#2323`](https://github.com/th-ch/youtube-music/issues/2323) [`#2379`](https://github.com/th-ch/youtube-music/issues/2379)
|
||||
- Revert "fix(MPRIS): Prevents player to start with invalid MPRIS interface (#1996)" [`#2225`](https://github.com/th-ch/youtube-music/issues/2225)
|
||||
- fix(adblocker/inplayer): fix Response.prototype.json [`#2310`](https://github.com/th-ch/youtube-music/issues/2310)
|
||||
- chore(deps): update dependency eslint-plugin-import to v2.30.0 [`f48e46d`](https://github.com/th-ch/youtube-music/commit/f48e46d29cf09c76c5172fd56d2d0f705616e4e3)
|
||||
- Revert "chore(deps): update dependency electron-builder to v25" [`089eff3`](https://github.com/th-ch/youtube-music/commit/089eff3152903c8b55ad3e5571b944062a647e27)
|
||||
- chore(deps): update dependency electron-builder to v25 [`fe4c89c`](https://github.com/th-ch/youtube-music/commit/fe4c89c349bb9f4f54d95c2018943095ccfdab0c)
|
||||
|
||||
#### [v3.5.1](https://github.com/th-ch/youtube-music/compare/v3.5.0...v3.5.1)
|
||||
|
||||
> 1 August 2024
|
||||
|
||||
- fix(deps): update dependency youtubei.js to v10.3.0 [`#2306`](https://github.com/th-ch/youtube-music/pull/2306)
|
||||
- fix: Window gets stuck offscreen in some instances [`#2303`](https://github.com/th-ch/youtube-music/pull/2303)
|
||||
- fix: Incorrect window size on multi-monitor scaled displays [`#2302`](https://github.com/th-ch/youtube-music/pull/2302)
|
||||
- chore(deps): update dependency rollup to v4.19.2 [`#2304`](https://github.com/th-ch/youtube-music/pull/2304)
|
||||
- chore(deps): update typescript-eslint monorepo to v8 (major) [`#2297`](https://github.com/th-ch/youtube-music/pull/2297)
|
||||
- fix(ambient-mode): fix ambient-mode not working for videos after restart [`#2294`](https://github.com/th-ch/youtube-music/pull/2294)
|
||||
- fix(deps): update dependency @xhayper/discord-rpc to v1.2.0 [`#2291`](https://github.com/th-ch/youtube-music/pull/2291)
|
||||
- fix(synced-lyrics): fix lyric load [`#2295`](https://github.com/th-ch/youtube-music/issues/2295)
|
||||
- fix(ambient-mode): fix ambient-mode not working for videos after restart (#2294) [`#1641`](https://github.com/th-ch/youtube-music/issues/1641)
|
||||
- fix(synced-lyrics): fix i18n [`8750b54`](https://github.com/th-ch/youtube-music/commit/8750b54f766c735ff039c6be454427f17d4737e2)
|
||||
- ts-fix: disambiguate ElectronStore typings [`8775735`](https://github.com/th-ch/youtube-music/commit/877573532c1b68af861a3fdc44d093f3097d36ab)
|
||||
- chore(i18n): Translated using Weblate (Hungarian) [`3537dc1`](https://github.com/th-ch/youtube-music/commit/3537dc19eecce7f7deb2478942f70d3c7b72148d)
|
||||
|
||||
#### [v3.5.0](https://github.com/th-ch/youtube-music/compare/v3.4.1...v3.5.0)
|
||||
|
||||
> 31 July 2024
|
||||
|
||||
- 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)
|
||||
|
||||
> 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)
|
||||
- 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)
|
||||
|
||||
374
docs/readme/README-ru.md
Normal file
374
docs/readme/README-ru.md
Normal file
@ -0,0 +1,374 @@
|
||||
<div align="center">
|
||||
|
||||
# YouTube Music
|
||||
|
||||
[](https://github.com/th-ch/youtube-music/releases/)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://aur.archlinux.org/packages/youtube-music-bin)
|
||||
[](https://snyk.io/test/github/th-ch/youtube-music)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/th-ch/youtube-music/releases/latest">
|
||||
<img src="web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
**Клиент для YouTube Music основанный на Electron с поддержкой:**
|
||||
|
||||
- Нативный вид приложения, нацелен на сохранение оригинального интерфейса
|
||||
- Фреймворк для пользовательских плагинов: изменяйте YouTube Music под ваши нужды (внешний вид, контент, возможности), включайте/выключайте плагины в один клик
|
||||
|
||||
## Демо-изображение
|
||||
|
||||
| Экран плеера (цветовая тема альбома & режим Ambient) |
|
||||
|:---------------------------------------------------------------------------------------------------------:|
|
||||
||
|
||||
|
||||
## Содержание
|
||||
|
||||
- [Возможности](#features)
|
||||
- [Доступные плагины](#available-plugins)
|
||||
- [Перевод](#translation)
|
||||
- [Скачать](#download)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [MacOS](#macos)
|
||||
- [Windows](#windows)
|
||||
- [Как установить без подключения к интернету? (в Windows)](#how-to-install-without-a-network-connection-in-windows)
|
||||
- [Темы](#themes)
|
||||
- [Для разработчиков](#dev)
|
||||
- [Создайте свои собственные плагины](#build-your-own-plugins)
|
||||
- [Создание плагина](#creating-a-plugin)
|
||||
- [Примеры использования](#common-use-cases)
|
||||
- [Сборка](#build)
|
||||
- [Предварительный просмотр](#production-preview)
|
||||
- [Тестирование](#tests)
|
||||
- [Лицензия](#license)
|
||||
- [Часто задаваемые вопросы](#faq)
|
||||
|
||||
## Возможности:
|
||||
|
||||
- **Авто-подтверждение при паузе** (Всегда включено): отключает всплывающие уведомление ["Продолжить просмотр?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png),
|
||||
которое приостанавливает воспроизведение через определённое время
|
||||
|
||||
- И больше ...
|
||||
|
||||
## Доступные плагины:
|
||||
|
||||
- **Блокировщик рекламы**: Блокирует всю рекламу и трекеры
|
||||
|
||||
- **Действия с альбомом**: Добавляет кнопки "Убрать дизлайк", "Дизлайк", "Лайк", "Убрать лайк" и применяет их действия ко всем трекам в плейлисте или альбоме
|
||||
|
||||
- **Цветовая тема альбома**: Применяет динамическую тему и эффекты, основываясь на цветовой палитре альбома
|
||||
|
||||
- **Режим Ambient**: Применяет световой эффект, проецируя нежные цвета из видео на задний фон вашего экрана
|
||||
|
||||
- **Нормализация аудио**: Применяет нормализацию к аудио (уменьшает громкость громких частей трека и повышает громкость тихих частей трека)
|
||||
|
||||
- **Размытие панели навигации**: Делает панель навигации прозрачной и размытой
|
||||
|
||||
- **Обход возрастных ограничений**: Обходит проверку возраста YouTube
|
||||
|
||||
- **Выбор субтитров**: Включить субтитры
|
||||
|
||||
- **Компактная боковая панель**: Всегда показывать боковую панель компактно
|
||||
|
||||
- **Плавный переход**: Плавный переход между треками
|
||||
|
||||
- **Отключить автопроигрыш**: Каждый трек начинается в режиме паузы
|
||||
|
||||
- **[Discord](https://discord.com/) Rich Presence**: Показывает вашим друзьям, что вы слушаете с помощью [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||
|
||||
- **Загрузчик**: Загрузка MP3 [напрямую из интерфейса](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||
|
||||
- **Расширенная громкость**: Делает слайдер громкости [расширенным](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) облегчая выбор громкости
|
||||
|
||||
- **Меню в приложении**: [Придаёт панели меню красивый тёмный вид](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
||||
|
||||
> (посмотрите [этот пост,](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) если у вас есть проблемы с доступом к меню после включения этого плагина и опции "Скрыть меню")
|
||||
|
||||
- **Скробблер**: Добавляет поддержку скробблинга [Last.fm](https://www.last.fm/) и [ListenBrainz](https://listenbrainz.org/)
|
||||
|
||||
- **Lumia Stream**: Добавляет поддержку [Lumia Stream](https://lumiastream.com/)
|
||||
|
||||
- **Тесты песен Genius**: Добавляет поддержку текстов для большинства песен
|
||||
|
||||
- **Music Together**: Делитесь плейлистом с другими. Когда ведущий воспроизводит трек, все остальные будут слушать этот же трек.
|
||||
|
||||
- **Навигация**: Кнопки Назад/Вперед интегрированы в интерфейс, как в вашем любимом браузере
|
||||
|
||||
- **Без входа в систему Google**: Убирает из интерфейса кнопки и ссылки для входа через Google
|
||||
|
||||
- **Уведомления**: Показывает уведомление, когда трек начинает играть ([интерактивные уведомления](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) доступны только для Windows)
|
||||
|
||||
- **Картинка в картинке**: Позволяет переключить приложение в режим "картинка в картинке"
|
||||
|
||||
- **Скорость воспроизведения**: Слушайте быстрее, слушайте медленнее! [Добавляет слайдер для контроля скорости трека](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
||||
|
||||
- **Точная громкость**: Точечно управляйте громкостью с помощью колеса мыши/горячих клавиш, с кастомным интерфейсом и настраиваемыми шагами громкости
|
||||
|
||||
- **Ярлыки (и MPRIS)**: Позволяет настроить глобальные горячие клавиши управления воспроизведением (плей/пауза/следующий/предыдущий) + отключает [отображение медиа на экране,](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png) переопределяя клавиши управления + включает Ctrl/CMD + F для поиска + включает поддержку linux mpris для клавиш управления медиа + [настраиваемые сочетания клавиш](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50) для [продвинутых пользователей](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||
|
||||
- **Пропускать непонравившиеся треки**: Пропускает непонравившиеся треки
|
||||
|
||||
- **Пропуск тишины**: Автоматически пропускает тихие моменты в песнях
|
||||
|
||||
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Автоматически пропускает немузыкальные части, такие как интро/аутро музыкальных видео, где трек не играет
|
||||
|
||||
- **Управление воспроизведением из панели задач**: Управляйте воспроизведением из [панели задач Windows](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||
|
||||
- **TouchBar**: Кастомная раскладка TouchBar для MacOS
|
||||
|
||||
- **Tuna OBS**: Интеграция с [OBS](https://obsproject.com/) плагином [Tuna](https://obsproject.com/forum/resources/tuna.843/)
|
||||
|
||||
- **Изменение качества видео**: Позволяет менять качество видео [кнопкой](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) на медиаплеере видео
|
||||
|
||||
- **Переключатель видео**: Добавляет
|
||||
[кнопку](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) переключения режимов Трек/Видео. Также может удалять вкладку "Видео" полностью
|
||||
|
||||
- **Визуализатор**: Различные визуализаторы музыки
|
||||
|
||||
- **Synced Lyrics**:
|
||||
Предоставляет синхронизированные слова для песен из таких источников, как [LRClib](https://lrclib.net).
|
||||
|
||||
## Перевод
|
||||
|
||||
Вы можете помочь с переводом на ваш язык на [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/youtube-music/">
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="translation status" />
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="translation status 2" />
|
||||
</a>
|
||||
|
||||
## Скачать
|
||||
|
||||
Вы можете посмотреть [latest release,](https://github.com/th-ch/youtube-music/releases/latest) чтобы быстро найти новую версию.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Установите пакет [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) из AUR. Инструкции по установке из AUR можете найти на этой [вики-странице](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||
|
||||
### macOS
|
||||
|
||||
Вы можете установить приложение с помощью Homebrew (сморите [cask definition](https://github.com/th-ch/homebrew-youtube-music)):
|
||||
|
||||
```bash
|
||||
brew install th-ch/youtube-music/youtube-music
|
||||
```
|
||||
|
||||
Если вы устанавливаете приложение вручную и получаете ошибку "is damaged and can’t be opened.", запустите в терминале следующую команду:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Вы можете использовать [пакетный менеджер Scoop](https://scoop.sh) для установки пакета `youtube-music` из [`extras` bucket](https://github.com/ScoopInstaller/Extras).
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install extras/youtube-music
|
||||
```
|
||||
|
||||
Также для установки вы можете использовать [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), официальный пакетный менеджер командной строки Windows 11, для установки пакета `th-ch.YouTubeMusic`.
|
||||
|
||||
*К сведению: SmartScreen защитника Windows может блокировать установку, так как она от "неизвестного издателя". Это также применимо к методу ручной установки, когда вы пытаетесь запустить исполняемый файл(.exe) после загрузки здесь, на GitHub (тот же файл).*
|
||||
|
||||
```bash
|
||||
winget install th-ch.YouTubeMusic
|
||||
```
|
||||
|
||||
#### Установка без подключения к Интернету? (в Windows)
|
||||
|
||||
- Скачайте файл `*.nsis.7z` из _архетиктура вашего устройства_ на [release page](https://github.com/th-ch/youtube-music/releases/latest).
|
||||
- `x64` для 64-bit Windows
|
||||
- `ia32` для 32-bit Windows
|
||||
- `arm64` для ARM64 Windows
|
||||
- Скачайте установщик в release page. (`*-Setup.exe`)
|
||||
- Поместите их в **одной директории**.
|
||||
- Запустите установщик.
|
||||
|
||||
## Темы
|
||||
|
||||
Вы можете загрузить файл CSS для смены внешнего вида приложения (Настройки > Визуальные настройки > Тема).
|
||||
|
||||
Некоторые предустановленные темы доступны здесь: https://github.com/kerichdev/themes-for-ytmdesktop-player.
|
||||
|
||||
## Для разработчиков
|
||||
|
||||
```bash
|
||||
git clone https://github.com/th-ch/youtube-music
|
||||
cd youtube-music
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Создайте свои собственные плагины
|
||||
|
||||
Используя плагины вы можете:
|
||||
|
||||
- Манипулировать приложением - `BrowserWindow` из electron проброшен обработчику плагинов
|
||||
- Изменять внешний вид, манипулируя HTML/CSS
|
||||
|
||||
### Создание плагина
|
||||
|
||||
Создайте директорию в `src/plugins/YOUR-PLUGIN-NAME`:
|
||||
|
||||
- `index.ts`: основной файл плагина
|
||||
```typescript
|
||||
import style from './style.css?inline'; // import style as inline
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // if value is true, ytmusic show restart dialog
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // your custom config
|
||||
stylesheets: [style], // your custom style,
|
||||
menu: async ({ getConfig, setConfig }) => {
|
||||
// All *Config methods are wrapped Promise<T>
|
||||
const config = await getConfig();
|
||||
return [
|
||||
{
|
||||
label: 'menu',
|
||||
submenu: [1, 2, 3].map((value) => ({
|
||||
label: `value ${value}`,
|
||||
type: 'radio',
|
||||
checked: config.value === value,
|
||||
click() {
|
||||
setConfig({ value });
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
},
|
||||
backend: {
|
||||
start({ window, ipc }) {
|
||||
window.maximize();
|
||||
|
||||
// you can communicate with renderer plugin
|
||||
ipc.handle('some-event', () => {
|
||||
return 'hello';
|
||||
});
|
||||
},
|
||||
// it fired when config changed
|
||||
onConfigChange(newConfig) { /* ... */ },
|
||||
// it fired when plugin disabled
|
||||
stop(context) { /* ... */ },
|
||||
},
|
||||
renderer: {
|
||||
async start(context) {
|
||||
console.log(await context.ipc.invoke('some-event'));
|
||||
},
|
||||
// Only renderer available hook
|
||||
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
|
||||
// set plugin config easily
|
||||
context.setConfig({ myConfig: api.getVolume() });
|
||||
},
|
||||
onConfigChange(newConfig) { /* ... */ },
|
||||
stop(_context) { /* ... */ },
|
||||
},
|
||||
preload: {
|
||||
async start({ getConfig }) {
|
||||
const config = await getConfig();
|
||||
},
|
||||
onConfigChange(newConfig) {},
|
||||
stop(_context) {},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Примеры использования
|
||||
|
||||
- Кастомный CSS: создайте файл `style.css` в той же директории, затем:
|
||||
|
||||
```typescript
|
||||
// index.ts
|
||||
import style from './style.css?inline'; // import style as inline
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // if value is true, ytmusic will show a restart dialog
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // your custom config
|
||||
stylesheets: [style], // your custom style
|
||||
renderer() {} // define renderer hook
|
||||
});
|
||||
```
|
||||
|
||||
- Если вы хотите изменить HTML:
|
||||
|
||||
```typescript
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // if value is true, ytmusic will show the restart dialog
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // your custom config
|
||||
renderer() {
|
||||
// Remove the login button
|
||||
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
|
||||
} // define renderer hook
|
||||
});
|
||||
```
|
||||
|
||||
- обмен между фронтом и бэком может быть выполнен с помощью модуля ipcMain из electron. Смотрите файл `index.ts` и
|
||||
пример в плагине `sponsorblock`.
|
||||
|
||||
## Сборка
|
||||
|
||||
1. Склонируйте репозиторий
|
||||
2. Следуйте [этой инструкции,](https://pnpm.io/installation) чтобы установить `pnpm`
|
||||
3. Запустите `pnpm install --frozen-lockfile` для установки зависимостей
|
||||
4. Запустите `pnpm build:OS`
|
||||
|
||||
- `pnpm dist:win` - Windows
|
||||
- `pnpm dist:linux` - Linux (amd64)
|
||||
- `pnpm dist:linux:deb-arm64` - Linux (arm64 for Debian)
|
||||
- `pnpm dist:linux:rpm-arm64` - Linux (arm64 for Fedora)
|
||||
- `pnpm dist:mac` - macOS (amd64)
|
||||
- `pnpm dist:mac:arm64` - macOS (arm64)
|
||||
|
||||
Сборка приложения для macOS, Linux, и Windows,
|
||||
используя [electron-builder](https://github.com/electron-userland/electron-builder).
|
||||
|
||||
## Предварительный просмотр
|
||||
|
||||
```bash
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Тестирование
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Использует [Playwright](https://playwright.dev/) для тестирования приложения.
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||
|
||||
## Часто задаваемые вопросы
|
||||
|
||||
### Почему меня приложения не отображается?
|
||||
|
||||
Если опция `Скрыть меню` включена - вы можете отобразить меню с помощью клавиши <kbd>alt</kbd> (или <kbd>\`</kbd> [обратный апостроф], если используете плагин "Меню в приложении")
|
||||
@ -1,17 +1,17 @@
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { UserConfig } from 'vite';
|
||||
import { defineConfig, defineViteConfig } from 'electron-vite';
|
||||
import builtinModules from 'builtin-modules';
|
||||
import viteResolve from 'vite-plugin-resolve';
|
||||
import Inspect from 'vite-plugin-inspect';
|
||||
import solidPlugin from 'vite-plugin-solid';
|
||||
|
||||
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs';
|
||||
import pluginLoader from './vite-plugins/plugin-loader.mjs';
|
||||
|
||||
import type { UserConfig } from 'vite';
|
||||
import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
|
||||
import solidPlugin from 'vite-plugin-solid';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@ -51,8 +51,12 @@ export default defineConfig({
|
||||
};
|
||||
|
||||
if (mode === 'development') {
|
||||
commonConfig.build!.sourcemap = 'inline';
|
||||
commonConfig.plugins?.push(
|
||||
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/backend') }),
|
||||
Inspect({
|
||||
build: true,
|
||||
outputDir: join(__dirname, '.vite-inspect/backend'),
|
||||
}),
|
||||
);
|
||||
return commonConfig;
|
||||
}
|
||||
@ -95,8 +99,12 @@ export default defineConfig({
|
||||
};
|
||||
|
||||
if (mode === 'development') {
|
||||
commonConfig.build!.sourcemap = 'inline';
|
||||
commonConfig.plugins?.push(
|
||||
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/preload') }),
|
||||
Inspect({
|
||||
build: true,
|
||||
outputDir: join(__dirname, '.vite-inspect/preload'),
|
||||
}),
|
||||
);
|
||||
return commonConfig;
|
||||
}
|
||||
@ -142,8 +150,12 @@ export default defineConfig({
|
||||
};
|
||||
|
||||
if (mode === 'development') {
|
||||
commonConfig.build!.sourcemap = 'inline';
|
||||
commonConfig.plugins?.push(
|
||||
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/renderer') }),
|
||||
Inspect({
|
||||
build: true,
|
||||
outputDir: join(__dirname, '.vite-inspect/renderer'),
|
||||
}),
|
||||
);
|
||||
return commonConfig;
|
||||
}
|
||||
|
||||
81
package.json
81
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "youtube-music",
|
||||
"productName": "YouTube Music",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.3",
|
||||
"description": "YouTube Music Desktop App - including custom plugins",
|
||||
"main": "./dist/main/index.js",
|
||||
"license": "MIT",
|
||||
@ -77,9 +77,27 @@
|
||||
"rpm"
|
||||
]
|
||||
},
|
||||
"deb": {
|
||||
"depends": [
|
||||
"libgtk-3-0",
|
||||
"libnotify4",
|
||||
"libnss3",
|
||||
"libxss1",
|
||||
"libxtst6",
|
||||
"xdg-utils",
|
||||
"libatspi2.0-0",
|
||||
"libuuid1",
|
||||
"libasound2",
|
||||
"libgbm1"
|
||||
]
|
||||
},
|
||||
"rpm": {
|
||||
"depends": [
|
||||
"/usr/lib64/libuuid.so.1"
|
||||
],
|
||||
"fpm": [
|
||||
"--rpm-rpmbuild-define",
|
||||
"_build_id_links none"
|
||||
]
|
||||
},
|
||||
"snap": {
|
||||
@ -102,7 +120,7 @@
|
||||
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
|
||||
"start": "electron-vite preview",
|
||||
"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",
|
||||
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
|
||||
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
|
||||
@ -131,11 +149,10 @@
|
||||
"xml2js": "0.6.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"@electron/universal": "2.0.1",
|
||||
"@babel/runtime": "7.24.8"
|
||||
"@babel/runtime": "7.25.6"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@ -146,82 +163,84 @@
|
||||
"@electron/remote": "2.1.2",
|
||||
"@ffmpeg.wasm/core-mt": "0.12.0",
|
||||
"@ffmpeg.wasm/main": "0.12.0",
|
||||
"@floating-ui/dom": "1.6.7",
|
||||
"@floating-ui/dom": "1.6.11",
|
||||
"@foobar404/wave": "2.0.5",
|
||||
"@jellybrick/electron-better-web-request": "1.0.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",
|
||||
"butterchurn": "3.0.0-beta.4",
|
||||
"butterchurn-presets": "3.0.0-beta.4",
|
||||
"color": "4.2.3",
|
||||
"conf": "13.0.1",
|
||||
"custom-electron-prompt": "1.5.7",
|
||||
"custom-electron-prompt": "1.5.8",
|
||||
"dbus-next": "0.10.2",
|
||||
"deepmerge-ts": "7.0.3",
|
||||
"electron-debug": "4.0.0",
|
||||
"deepmerge-ts": "7.1.0",
|
||||
"electron-debug": "4.0.1",
|
||||
"electron-is": "3.0.0",
|
||||
"electron-localshortcut": "3.2.1",
|
||||
"electron-store": "10.0.0",
|
||||
"electron-unhandled": "4.0.1",
|
||||
"electron-updater": "6.2.1",
|
||||
"electron-updater": "6.3.4",
|
||||
"fast-average-color": "9.4.0",
|
||||
"fast-equals": "5.0.1",
|
||||
"filenamify": "6.0.0",
|
||||
"howler": "2.2.4",
|
||||
"html-to-text": "9.0.5",
|
||||
"i18next": "23.12.1",
|
||||
"i18next": "23.15.1",
|
||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||
"keyboardevents-areequal": "0.2.2",
|
||||
"node-html-parser": "6.1.13",
|
||||
"node-id3": "0.2.6",
|
||||
"peerjs": "1.5.4",
|
||||
"semver": "7.6.2",
|
||||
"semver": "7.6.3",
|
||||
"serve": "14.2.3",
|
||||
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
|
||||
"solid-floating-ui": "0.3.1",
|
||||
"solid-js": "1.8.18",
|
||||
"solid-js": "1.8.22",
|
||||
"solid-styled-components": "0.28.5",
|
||||
"solid-transition-group": "0.2.3",
|
||||
"ts-morph": "23.0.0",
|
||||
"vudio": "2.1.1",
|
||||
"x11": "2.3.0",
|
||||
"youtubei.js": "10.1.0"
|
||||
"youtubei.js": "10.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.45.1",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
"@playwright/test": "1.47.1",
|
||||
"@total-typescript/ts-reset": "0.6.1",
|
||||
"@types/color": "3.0.6",
|
||||
"@types/electron-localshortcut": "3.1.3",
|
||||
"@types/howler": "2.2.11",
|
||||
"@types/html-to-text": "9.0.4",
|
||||
"@types/semver": "7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.16.0",
|
||||
"@typescript-eslint/parser": "7.16.0",
|
||||
"@types/trusted-types": "2.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "8.6.0",
|
||||
"@typescript-eslint/parser": "8.6.0",
|
||||
"bufferutil": "4.0.8",
|
||||
"builtin-modules": "4.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"del-cli": "5.1.0",
|
||||
"discord-api-types": "0.37.92",
|
||||
"electron": "31.2.0",
|
||||
"discord-api-types": "0.37.100",
|
||||
"electron": "32.1.0",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
"electron-vite": "2.3.0",
|
||||
"esbuild": "0.23.0",
|
||||
"eslint": "8.57.0",
|
||||
"esbuild": "0.23.1",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||
"eslint-import-resolver-typescript": "3.6.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-import-resolver-typescript": "3.6.3",
|
||||
"eslint-plugin-import": "2.30.0",
|
||||
"eslint-plugin-prettier": "5.2.1",
|
||||
"glob": "11.0.0",
|
||||
"node-gyp": "10.2.0",
|
||||
"playwright": "1.45.1",
|
||||
"rollup": "4.18.1",
|
||||
"typescript": "5.5.3",
|
||||
"playwright": "1.47.1",
|
||||
"rollup": "4.21.3",
|
||||
"typescript": "5.6.2",
|
||||
"utf-8-validate": "6.0.4",
|
||||
"vite": "5.3.3",
|
||||
"vite-plugin-inspect": "0.8.4",
|
||||
"vite-plugin-resolve": "2.5.1",
|
||||
"vite": "5.4.6",
|
||||
"vite-plugin-inspect": "0.8.7",
|
||||
"vite-plugin-resolve": "2.5.2",
|
||||
"vite-plugin-solid": "2.10.2",
|
||||
"ws": "8.18.0"
|
||||
},
|
||||
|
||||
@ -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.*",
|
||||
1871
pnpm-lock.yaml
generated
1871
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,8 @@
|
||||
import Store from 'electron-store';
|
||||
import { deepmergeCustom } from 'deepmerge-ts';
|
||||
|
||||
import defaultConfig from './defaults';
|
||||
|
||||
import store from './store';
|
||||
import store, { IStore } from './store';
|
||||
import plugins from './plugins';
|
||||
|
||||
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]
|
||||
? never
|
||||
: T extends object
|
||||
? {
|
||||
[K in keyof T]-?: K extends string | number
|
||||
? `${K}` | Join<K, Paths<T[K], Prev[D]>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: '';
|
||||
? {
|
||||
[K in keyof T]-?: K extends string | number
|
||||
? `${K}` | Join<K, Paths<T[K], Prev[D]>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: '';
|
||||
|
||||
type SplitKey<K> = K extends `${infer A}.${infer B}` ? [A, B] : [K, string];
|
||||
type PathValue<T, K extends string> = SplitKey<K> extends [
|
||||
infer A extends keyof T,
|
||||
infer B extends string,
|
||||
]
|
||||
? PathValue<T[A], B>
|
||||
: T;
|
||||
type PathValue<T, K extends string> =
|
||||
SplitKey<K> extends [infer A extends keyof T, infer B extends string]
|
||||
? PathValue<T[A], B>
|
||||
: T;
|
||||
|
||||
const get = <Key extends Paths<typeof defaultConfig>>(key: Key) =>
|
||||
store.get(key) as PathValue<typeof defaultConfig, typeof key>;
|
||||
|
||||
@ -86,7 +84,7 @@ export default {
|
||||
setPartial,
|
||||
setMenuOption,
|
||||
edit: () => store.openInEditor(),
|
||||
watch(cb: Parameters<Store['onDidAnyChange']>[0]) {
|
||||
watch(cb: Parameters<IStore['onDidAnyChange']>[0]) {
|
||||
store.onDidAnyChange(cb);
|
||||
},
|
||||
plugins,
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import Store from 'electron-store';
|
||||
import Conf from 'conf';
|
||||
|
||||
import defaults from './defaults';
|
||||
|
||||
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 = {
|
||||
'>=3.3.0'(store: Conf<Record<string, unknown>>) {
|
||||
'>=3.3.0'(store: IStore) {
|
||||
const lastfmConfig = store.get('plugins.lastfm') as {
|
||||
enabled?: boolean;
|
||||
token?: string;
|
||||
@ -16,21 +18,21 @@ const migrations = {
|
||||
secret?: string;
|
||||
};
|
||||
if (lastfmConfig) {
|
||||
let scrobblerConfig = store.get(
|
||||
'plugins.scrobbler',
|
||||
) as {
|
||||
enabled?: boolean;
|
||||
scrobblers?: {
|
||||
lastfm?: {
|
||||
let scrobblerConfig = store.get('plugins.scrobbler') as
|
||||
| {
|
||||
enabled?: boolean;
|
||||
token?: string;
|
||||
sessionKey?: string;
|
||||
apiRoot?: string;
|
||||
apiKey?: string;
|
||||
secret?: string;
|
||||
};
|
||||
};
|
||||
} | undefined;
|
||||
scrobblers?: {
|
||||
lastfm?: {
|
||||
enabled?: boolean;
|
||||
token?: string;
|
||||
sessionKey?: string;
|
||||
apiRoot?: string;
|
||||
apiKey?: string;
|
||||
secret?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
|
||||
if (!scrobblerConfig) {
|
||||
scrobblerConfig = {
|
||||
@ -56,7 +58,7 @@ const migrations = {
|
||||
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<
|
||||
string,
|
||||
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');
|
||||
if (listenAlong !== undefined) {
|
||||
store.set('plugins.discord.playOnYouTubeMusic', 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
|
||||
| string
|
||||
| undefined;
|
||||
@ -110,7 +112,7 @@ const migrations = {
|
||||
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
|
||||
|
||||
if (store.get('plugins.notifications.toastStyle') === undefined) {
|
||||
@ -125,14 +127,14 @@ const migrations = {
|
||||
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
|
||||
|
||||
if (store.get('plugins.video-toggle.mode') === undefined) {
|
||||
store.set('plugins.video-toggle.mode', 'custom');
|
||||
}
|
||||
},
|
||||
'>=1.14.0'(store: Conf<Record<string, unknown>>) {
|
||||
'>=1.14.0'(store: IStore) {
|
||||
if (
|
||||
typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object'
|
||||
) {
|
||||
@ -144,12 +146,12 @@ const migrations = {
|
||||
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) {
|
||||
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
|
||||
| Record<
|
||||
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) {
|
||||
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[];
|
||||
if (!Array.isArray(enabledPlugins)) {
|
||||
console.warn('Plugins are not in array format, cannot migrate');
|
||||
@ -233,4 +235,4 @@ export default new Store({
|
||||
},
|
||||
clearInvalidConfig: false,
|
||||
migrations,
|
||||
});
|
||||
}) as Store & IStore;
|
||||
|
||||
770
src/i18n/resources/ca.json
Normal file
770
src/i18n/resources/ca.json
Normal file
@ -0,0 +1,770 @@
|
||||
{
|
||||
"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": "Ves enrere",
|
||||
"go-forward": "Ves endavant",
|
||||
"quit": "Surt",
|
||||
"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 servidor intermediari (proxy)",
|
||||
"prompt": {
|
||||
"label": "Introduir l'adreça del servidor intermediari: (deixar en blanc per deshabilitar)",
|
||||
"placeholder": "Exemple: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Definir servidor intermediari (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": "Reprodueix / pausa en clicar"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Opcions visuals",
|
||||
"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": "Veure",
|
||||
"submenu": {
|
||||
"force-reload": "Força la 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 a 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": "Compressió 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": "Amaga l'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": "Botó de 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": "Salta les cançons amb «no m'agrada»",
|
||||
"name": "Salta les cançons que no t'agraden"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Proporciona lletres sincronitzades amb les cançons, a través de proveïdors com LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Se ha produït un error en descarregar la lletra. Si us plau, intenta-ho més tard.",
|
||||
"not-found": "⚠️ - No s'ha trobat la lletra per aquesta cançó."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Caràcter per defecte entre lletres",
|
||||
"tooltip": "Tria el caràcter per defecte que es mostrarà a l'espai entre les lletres"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Efecte de la línia",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Focus",
|
||||
"tooltip": "Mostra tan sols la línia actual en blanc"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Desplaçament",
|
||||
"tooltip": "Desplaçament a la dreta de la línia actual"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Escala",
|
||||
"tooltip": "Redimensiona la línia actual"
|
||||
}
|
||||
},
|
||||
"tooltip": "Tria l'efecte a aplicar a la línia actual"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Fes que les lletres es sincronitzin a la perfecció",
|
||||
"tooltip": "Calcula al mil·lisegon l'aparició de la següent línia (pot tenir un petit impacte en el rendiment)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Mostra la lletra tot i que sigui inexacta",
|
||||
"tooltip": "Si no es troba la cançó, el plugin torna a intentar obtenir la lletra amb una cerca diferent.\nEl resultat d'aquesta segona cerca podria no ser exacte."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Mostra els codis de temps",
|
||||
"tooltip": "Mostra els codis de temps al costat de la lletra"
|
||||
}
|
||||
},
|
||||
"name": "Lletres sincronitzades",
|
||||
"refetch-btn": {
|
||||
"fetching": "Obtenint...",
|
||||
"normal": "Tornar a obtenir la lletra"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - La lletra podria no estar ben sincronitzada, la durada no és coincident.",
|
||||
"inexact": "⚠️ - La lletra d'aquesta cançó podria no ser exacta",
|
||||
"instrumental": "⚠️ - Aquesta cançó és instrumental"
|
||||
}
|
||||
},
|
||||
"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": "Botó de vídeo",
|
||||
"templates": {
|
||||
"button": "Cançó"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Afegeix un visualitzador al reproductor",
|
||||
"menu": {
|
||||
"visualizer-type": "Tipus de visualitzador"
|
||||
},
|
||||
"name": "Visualitzador"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Blokuje všechny reklamy a sledování ihned od začátku",
|
||||
"menu": {
|
||||
@ -410,6 +414,11 @@
|
||||
"description": "Stahuje MP3 / source audio přímo z rozhraní",
|
||||
"menu": {
|
||||
"choose-download-folder": "Vybrat složku pro stahování",
|
||||
"download-finish-settings": {
|
||||
"submenu": {
|
||||
"advanced": "Pokoročile"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Stáhnout seznam písniček",
|
||||
"presets": "Předvolby",
|
||||
"skip-existing": "Přeskočit existující soubory"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Wenn eine Werbung spielt, stummt es das Audio und setzt die Wiedergabegeschwindigkeit auf 16x",
|
||||
"name": "Werbungsbeschleunigung"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blockiere jegliche Werbung und Tracker",
|
||||
"menu": {
|
||||
@ -410,24 +414,24 @@
|
||||
"description": "Lädt MP3-/Original-Audio direkt von der Schnittstelle herunter",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Voreinstellungen",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"skip-existing": "Vorhandene Dateien überspringen"
|
||||
},
|
||||
"name": "Downloader",
|
||||
"renderer": {
|
||||
@ -664,6 +668,54 @@
|
||||
"description": "Überspringt automatisch nicht-musikalische Teile wie Intro/Outro oder Teile von Musikvideos, in denen der Song nicht gespielt wird",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Bietet synchronisierte Liedtexte zu Songs, verwendet Anbieter wie LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Beim Abrufen des Liedtexts ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal.",
|
||||
"not-found": "⚠️ - Kein Text für diesen Song gefunden."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Standardzeichen zwischen Texten",
|
||||
"tooltip": "Standardzeichen für die Lücke zwischen Songtexten auswählen"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Zeileneffekt",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Fokussieren",
|
||||
"tooltip": "Nur aktive Zeile weiß darstellen"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Versatz"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Skalieren",
|
||||
"tooltip": "Aktuelle Zeile skalieren"
|
||||
}
|
||||
},
|
||||
"tooltip": "Effekt für aktive Zeile auswählen"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Den Songtext perfekt synchronisieren",
|
||||
"tooltip": "Auf die Millisekunde genau berechnen, wann die nächste Zeile angezeigt werden soll (Kann Einfluss auf die Leistung haben)"
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Zeitkodierungen anzeigen",
|
||||
"tooltip": "Zeitkodierungen neben Songtext anzeigen"
|
||||
}
|
||||
},
|
||||
"name": "Synchronisierte Texte",
|
||||
"refetch-btn": {
|
||||
"fetching": "Hole Songtext...",
|
||||
"normal": "Songtext neu holen"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Es kann sein, dass die Synchronization nicht stimmt, da die Songdauer nicht übereinstimmt.",
|
||||
"inexact": "⚠️ - Es ist Möglich, dass der Songtext für diesen Song nicht übereinstimmt.",
|
||||
"instrumental": "⚠️ - Das ist ein instrumentales Lied"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Wiedergabe aus der Windows Taskleiste kontrollieren",
|
||||
"name": "Mediensteuerung in der Taskleiste"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Αποτυχία εκτέλεσης προσθέτου {{pluginName}}::{{contextName}}",
|
||||
"execute-failed": "Απέτυχε η εκτέλεση του πρόσθετου {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Το πρόσθετο {{pluginName}}::{{contextName}} εκτελέστηκε σε {{ms}}ms",
|
||||
"initialize-failed": "Απέτυχε η αρχικοποίηση του πρόσθετου \"{{pluginName}}\"",
|
||||
"load-all": "Φόρτωση όλων των πρόσθετων",
|
||||
@ -36,7 +36,7 @@
|
||||
"details": "Σφάλμα ανταπόκρισης!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Εκκαθάριση της cache της εφαρμογής"
|
||||
"clearing-cache-after-20s": "Εκκαθάριση μνήμης cache εφαρμογής"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Το παράθυρο προσπάθησε να απεικονίσει εκτός οθόνης, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||
@ -45,23 +45,23 @@
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Το μενού είναι κρυμμένο, χρησιμοποιήστε το 'Alt' για να το εμφανίσετε (ή το 'Escape' αν χρησιμοποιείτε το μενού εφαρμογής)",
|
||||
"message": "Απόκρυψη μενού είναι ενεργοποιημένο",
|
||||
"title": "Ενεργοποιήθηκε η Απόκρυψη του Μενού"
|
||||
"message": "Η απόκρυψη μενού είναι ενεργοποιημένη",
|
||||
"title": "Η απόκρυψη μενού ενεργοποιήθηκε"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Αργότερα",
|
||||
"restart-now": "Επανεκκίνηση Τώρα"
|
||||
"restart-now": "Επανεκκίνηση τώρα"
|
||||
},
|
||||
"detail": "Το πρόσθετο \"{{pluginName}}\" απαιτεί επανεκκίνηση για να ισχύσει",
|
||||
"message": "Το \"{{pluginName}}\" χρειάζεται επανεκκίνηση",
|
||||
"title": "Απαιτείται Επανεκκίνηση"
|
||||
"title": "Απαιτείται επανεκκίνηση"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Έξοδος",
|
||||
"relaunch": "Επανεκκίνηση",
|
||||
"wait": "Περίμενε"
|
||||
"wait": "Περιμένετε"
|
||||
},
|
||||
"detail": "Λυπούμαστε για την ταλαιπωρία! Παρακαλούμε επιλέξτε τι να κάνετε:",
|
||||
"message": "Η εφαρμογή δεν ανταποκρίνεται",
|
||||
@ -69,13 +69,13 @@
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "Απενεργοποίηση Ενημερώσεων",
|
||||
"disable": "Απενεργοποίηση ενημερώσεων",
|
||||
"download": "Λήψη",
|
||||
"ok": "Εντάξει"
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "Μια νέα έκδοση είναι διαθέσιμη και μπορεί να ληφθεί από τον σύνδεσμο {{downloadLink}}",
|
||||
"message": "Μια νέα έκδοση είναι διαθέσιμη",
|
||||
"title": "Υπάρχει Διαθέσιμη Ενημέρωση"
|
||||
"title": "Υπάρχει διαθέσιμη ενημέρωση"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@ -87,7 +87,7 @@
|
||||
"go-back": "Πήγαινε πίσω",
|
||||
"go-forward": "Πήγαινε μπροστά",
|
||||
"quit": "Έξοδος",
|
||||
"restart": "Επανεκκίνηση Εφαρμογής"
|
||||
"restart": "Επανεκκίνηση εφαρμογής"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@ -106,7 +106,7 @@
|
||||
"prompt": {
|
||||
"label": "Εισαγωγή διεύθυνσης διακομιστή μεσολάβησης (proxy): (αφήστε κενό για απενεργοποίηση)",
|
||||
"placeholder": "Παράδειγμα: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Ορισμός διακομιστή μεσολάβησης (proxy)"
|
||||
"title": "Ορισμός μεσολάβησης"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "Εναλλαγή DevTools"
|
||||
@ -135,8 +135,8 @@
|
||||
"single-instance-lock": "Κλείδωμα Μοναδικής Εκδοχής",
|
||||
"start-at-login": "Έναρξη κατά την σύνδεση",
|
||||
"starting-page": {
|
||||
"label": "Σελίδα Έναρξης",
|
||||
"unset": "Κατάργηση Ορισμού"
|
||||
"label": "Σελίδα έναρξης",
|
||||
"unset": "Κατάργηση ορισμού"
|
||||
},
|
||||
"tray": {
|
||||
"label": "Δίσκος",
|
||||
@ -148,13 +148,27 @@
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Τροποποιήσεις Εμφάνισης",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Default"
|
||||
"default": "Default",
|
||||
"force-show": "Αναγκαστική Εμφάνιση",
|
||||
"hide": "Απόκρυψη",
|
||||
"label": "Μου αρέσει"
|
||||
},
|
||||
"remove-upgrade-button": "Αφαίρεση κουμπιού αναβάθμισης",
|
||||
"theme": {
|
||||
"label": "Theme",
|
||||
"dialog": {
|
||||
"button": {
|
||||
"cancel": "Άκυρο",
|
||||
"remove": "Αφαίρεση"
|
||||
},
|
||||
"remove-theme": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε το προσαρμοσμένο θέμα;",
|
||||
"remove-theme-message": "Αυτό θα αφαιρέσει το προσαρμοσμένο θέμα"
|
||||
},
|
||||
"label": "Θέμα",
|
||||
"submenu": {
|
||||
"import-css-file": "Εισαγωγή προσαρμοσμένου αρχείου CSS",
|
||||
"no-theme": "No theme"
|
||||
}
|
||||
}
|
||||
@ -163,29 +177,68 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"label": "Plugins"
|
||||
"enabled": "Ενεργοποιημένο",
|
||||
"label": "Πρόσθετα",
|
||||
"new": "ΝΕΟ"
|
||||
},
|
||||
"view": {
|
||||
"label": "View"
|
||||
"label": "Προβολή",
|
||||
"submenu": {
|
||||
"force-reload": "Αναγκαστική Eπαναφόρτωση",
|
||||
"reload": "Επαναφόρτωση",
|
||||
"reset-zoom": "Πραγματικό μέγεθος",
|
||||
"toggle-fullscreen": "Εναλλαγή Πλήρους Οθόνης",
|
||||
"zoom-in": "Μεγέθυνση",
|
||||
"zoom-out": "Σμίκρυνση"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "Επόμενο",
|
||||
"play-pause": "Αναπαραγωγή/Παύση",
|
||||
"previous": "Προηγούμενο",
|
||||
"quit": "Έξοδος",
|
||||
"restart": "Επανεκκίνηση εφαρμογής",
|
||||
"show": "Εμφάνιση παραθύρου",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Εαν παίξει διαφήμιση κάνει σίγαση του ήχου και θέτει την ταχύτητα αναπαραγωγής στο 16x",
|
||||
"name": "Γρήγορη Προώθηση Διαφημίσεων"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Αποκλεισμός όλων των διαφημίσεων και tracker",
|
||||
"menu": {
|
||||
"blocker": "Μέθοδος αποκλεισμού"
|
||||
},
|
||||
"name": "Adblocker"
|
||||
"name": "Μπλοκάρισμα Διαφημίσεων"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Προσθέτει κουμπιά Like/Unlike και Dislike/Undislike που δρουν συνολικά σε όλα τα κομμάτια μιας playlist ή ενός άλμπουμ",
|
||||
"name": "Ενέργειες σε Άλμπουμ"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Εφαρμόζει ένα δυναμικό θέμα και εφέ με βάση τη χρωματική παλέτα του άλμπουμ",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Αναλογία μίξης χρώματος",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Album Color Theme"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Εφαρμόζει ένα εφέ φωτισμού ρίχνοντας απαλά χρώματα από το βίντεο, στο φόντο της οθόνης σας.",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Ένταση θαμπώματος",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} pixels"
|
||||
}
|
||||
@ -197,25 +250,30 @@
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "Ποσότητα αδιαφάνειας",
|
||||
"label": "Αδιαφάνεια",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Ποιότητα",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} pixels"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "Μέγεθος",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"submenu": {
|
||||
"during": "Σε {{interpolationTime}} δεύτερα"
|
||||
"during": "Σε {{interpolationTime}} δευτερόλεπτα"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Χρήση πλήρους οθόνης"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -257,11 +315,14 @@
|
||||
"description": "Κάνει τα τραγούδια να είναι αυτόματα σε παύση",
|
||||
"menu": {
|
||||
"apply-once": "Εφαρμόζεται μόνο στο πρώτο τραγούδι"
|
||||
}
|
||||
},
|
||||
"name": "Απενεργοποίηση αυτόματης αναπαραγωγής"
|
||||
},
|
||||
"discord": {
|
||||
"description": "Δείξτε στους φίλους σας τι ακούτε με το Rich Presence",
|
||||
"menu": {
|
||||
"auto-reconnect": "Αυτόματη επανασύνδεση",
|
||||
"clear-activity": "Εκκαθάριση δραστηριότητας",
|
||||
"hide-duration-left": "Απόκρυψη της διάρκειας που απομένει",
|
||||
"hide-github-button": "Απόκρυψη του συνδέσμου προς GitHub",
|
||||
"set-inactivity-timeout": "Ορισμός χρονικού ορίου αδράνειας"
|
||||
@ -280,34 +341,113 @@
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"message": "Λήψη λίστας αναπαραγωγής {{playlistTitle}}",
|
||||
"title": "Λήψη ξεκίνησε"
|
||||
"detail": "{{playlistSize}} τραγούδια)",
|
||||
"message": "Λήψη της λίστας αναπαραγωγής {{playlistTitle}}",
|
||||
"title": "Η λήψη ξεκίνησε"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Μετατροπή: {{percent}}%",
|
||||
"download-progress": "Download: {{percent}}%",
|
||||
"preparing-file": "Προετοιμασία αρχείου…"
|
||||
"converting": "Μετατροπή…",
|
||||
"download-info": "Λήψη του {{artist}} - {{title}} [{{videoId}}",
|
||||
"download-progress": "Λήψη: {{percent}}%",
|
||||
"downloading": "Λήψη…",
|
||||
"downloading-counter": "Λήψη {{current}}/{{total}}…",
|
||||
"downloading-playlist": "Λήψη της λίστας αναπαραγωγής \"{{playlistTitle}}\" - {{playlistSize}} τραγούδια ({{playlistId}})",
|
||||
"folder-already-exists": "Ο φάκελος {{playlistFolder}} υπάρχει ήδη",
|
||||
"loading": "Φόρτωση…",
|
||||
"playlist-is-empty": "Η λίστα αναπραγωγής είναι άδεια",
|
||||
"preparing-file": "Προετοιμασία αρχείου…",
|
||||
"saving": "Αποθήκευση…",
|
||||
"video-id-not-found": "Το βίντεο δεν βρέθηκε"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"download-finish-settings": {
|
||||
"prompt": {
|
||||
"last-seconds": "Τελευταία x δευτερόλεπτα"
|
||||
},
|
||||
"submenu": {
|
||||
"percent": "Ποσοστό",
|
||||
"seconds": "Δευτερόλεπτα"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Λήψη λίστας αναπαραγωγής",
|
||||
"skip-existing": "Παράλειψη υπάρχοντων αρχείων"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Download"
|
||||
"button": "Λήψη"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"internal": {
|
||||
"save": "Αποθήκευση",
|
||||
"unknown-user": "Άγνωστος χρήστης"
|
||||
},
|
||||
"menu": {
|
||||
"connected-users": "Συνδεδεμένοι χρήστες"
|
||||
},
|
||||
"toast": {
|
||||
"add-song-failed": "Απέτυχε η προσθήκη τραγουδιού",
|
||||
"remove-song-failed": "Απέτυχε η αφαίρεση τραγουδιού"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"name": "Navigation"
|
||||
"name": "Πλοήγηση"
|
||||
},
|
||||
"no-google-login": {
|
||||
"name": "No Google Login"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notifications"
|
||||
"name": "Ειδοποιήσεις"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"menu": {
|
||||
"always-on-top": "Πάντα σε πρώτο πλάνο",
|
||||
"hotkey": {
|
||||
"label": "Πλήκτρο πρόσβασης",
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "Πλήκτρο πρόσβασης"
|
||||
}
|
||||
}
|
||||
},
|
||||
"save-window-position": "Αποθήκευση θέσης παραθύρου",
|
||||
"save-window-size": "Αποθήκευση μεγέθους παραθύρου"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"name": "Ταχύτητα αναπαραγωγής",
|
||||
"templates": {
|
||||
"button": "Ταχύτητα"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "Μείωση έντασης",
|
||||
"increase": "Αύξηση έντασης"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"quality-changer": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "Τρέχουσα ποιότητα: {{quality}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Next"
|
||||
"next": "Επόμενο",
|
||||
"play-pause": "Αναπαραγωγή / Παύση",
|
||||
"previous": "Προηγούμενο"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,6 +214,10 @@
|
||||
},
|
||||
"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": {
|
||||
"description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album",
|
||||
"name": "Album Actions"
|
||||
@ -414,19 +418,19 @@
|
||||
"presets": "Presets",
|
||||
"skip-existing": "Skip existing files",
|
||||
"download-finish-settings": {
|
||||
"label": "Download on finish",
|
||||
"submenu": {
|
||||
"enabled": "Enabled",
|
||||
"mode": "Time mode",
|
||||
"seconds": "Seconds",
|
||||
"percent": "Percent",
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"prompt": {
|
||||
"title": "Configure when to download",
|
||||
"last-seconds": "Last x seconds",
|
||||
"last-percent": "After x percent"
|
||||
}
|
||||
"label": "Download on finish",
|
||||
"submenu": {
|
||||
"enabled": "Enabled",
|
||||
"mode": "Time mode",
|
||||
"seconds": "Seconds",
|
||||
"percent": "Percent",
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"prompt": {
|
||||
"title": "Configure when to download",
|
||||
"last-seconds": "Last x seconds",
|
||||
"last-percent": "After x percent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"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": {
|
||||
"description": "Control playback from your Windows taskbar",
|
||||
"name": "Taskbar Media Control"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Bloquear todos los anuncios y el rastreo",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Descarga MP3 / audio fuente directamente desde la interfaz",
|
||||
"menu": {
|
||||
"choose-download-folder": "Elija la carpeta de descarga",
|
||||
"download-finish-settings": {
|
||||
"label": "Descargar al finalizar",
|
||||
"prompt": {
|
||||
"last-percent": "Después del x por ciento",
|
||||
"last-seconds": "Últimos x segundos",
|
||||
"title": "Configurar cuándo descargar"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Avanzado",
|
||||
"enabled": "Activado",
|
||||
"mode": "Modo de tiempo",
|
||||
"percent": "Porcentaje",
|
||||
"seconds": "Segundos"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Descargar lista de reproducción",
|
||||
"presets": "Preajustes",
|
||||
"skip-existing": "Saltar archivos existentes"
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "Salta automáticamente las partes no musicales como la introducción/final o secciones de videos musicales donde la canción no está sonando",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Proporciona letras de canciones sincronizadas, utilizando proveedores como LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Se ha producido un error al descargar la letra. Por favor, vuelve a intentarlo más tarde.",
|
||||
"not-found": "⚠️ - No se ha encontrado ninguna letra para esta canción."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Carácter predeterminado entre letras",
|
||||
"tooltip": "Elige el carácter predeterminado que se utilizará para el espacio entre letras"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Efecto de la línea",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Enfoque",
|
||||
"tooltip": "Mostrar solo la línea actual en blanco"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Desplazamiento",
|
||||
"tooltip": "Desplazamiento a la derecha de la línea actual"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Escala",
|
||||
"tooltip": "Escala de la línea actual"
|
||||
}
|
||||
},
|
||||
"tooltip": "Elige el efecto que deseas aplicar a la línea actual"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Haz que la letra esté perfectamente sincronizada",
|
||||
"tooltip": "Calcular al milisegundo la visualización de la línea siguiente (puede tener un pequeño impacto en el rendimiento)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Mostrar la letra aunque sea inexacta",
|
||||
"tooltip": "Si no se encuentra la canción, el complemento vuelve a intentarlo con una búsqueda diferente.\nEl resultado del segundo intento puede no ser exacto."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Visualización del código de tiempo",
|
||||
"tooltip": "Mostrar los códigos de tiempo junto a la letra"
|
||||
}
|
||||
},
|
||||
"name": "Letras sincronizadas",
|
||||
"refetch-btn": {
|
||||
"fetching": "Recuperando...",
|
||||
"normal": "Volver a buscar letras"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - La letra puede estar desincronizada debido a un desajuste en la duración.",
|
||||
"inexact": "⚠️ - La letra de esta canción puede no ser exacta",
|
||||
"instrumental": "⚠️ - Se trata de una canción instrumental"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Controla la reproducción desde la barra de tareas de Windows",
|
||||
"name": "Control de medios de la barra de tareas"
|
||||
|
||||
182
src/i18n/resources/et.json
Normal file
182
src/i18n/resources/et.json
Normal file
@ -0,0 +1,182 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "{{pluginName}}::{{contextName}} lisamooduli käivitamine ei õnnestunud",
|
||||
"executed-at-ms": "{{pluginName}}::{{contextName}} lisamoodul käivitus {{ms}} millisekundiga",
|
||||
"initialize-failed": "„{{pluginName}}“ lisamooduli töö alustamine ei õnnestunud",
|
||||
"load-all": "Laadime kõiki lisamooduleid",
|
||||
"load-failed": "„{{pluginName}}“ lisamooduli laadimine ei õnnestunud",
|
||||
"loaded": "„{{pluginName}}“ lisamoodul on laaditud",
|
||||
"unload-failed": "„{{pluginName}}“ lisamooduli mälust eemaldamine ei õnnestunud",
|
||||
"unloaded": "„{{pluginName}}“ lisamoodul on mälust eemaldatud"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "et",
|
||||
"local-name": "Eesti",
|
||||
"name": "Estonian"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "Laadimine lõppes, arendaja tarvikud on avatud"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n on laaditud"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "„{{command}}“ käsk on vastu võetud"
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "CSS faili „{{cssFile}}“ pole olemas, seega eirame eelistust"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Menüü on peidetud ja „Alt“ klahviga saad ta nähtavaks (rakenduse-siseses menüüs „Esc“ klahviga)",
|
||||
"message": "Menüü peitmine on sisselülitatud",
|
||||
"title": "Menüü peitmine on sisselülitatud"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Hiljem",
|
||||
"restart-now": "Taaskäivita kohe"
|
||||
},
|
||||
"detail": "„{{pluginName}}“ lisamooduli sisselülitamine eeldab rakenduse taaskäivitamist",
|
||||
"message": "„{{pluginName}}“ lisamoodul eeldab rakenduse taaskäivitamist",
|
||||
"title": "Palun käivita rakendus uuesti"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Välju",
|
||||
"relaunch": "Käivita uuesti",
|
||||
"wait": "Oota"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"navigation": {
|
||||
"label": "Liikumine",
|
||||
"submenu": {
|
||||
"copy-current-url": "Kopeeri esitamisel oleva pala URL",
|
||||
"go-back": "Mine tagasi",
|
||||
"go-forward": "Mine edasi",
|
||||
"quit": "Välju",
|
||||
"restart": "Käivita rakendus uuesti"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"label": "Lisamoodulid",
|
||||
"new": "UUS"
|
||||
},
|
||||
"view": {
|
||||
"submenu": {
|
||||
"zoom-in": "Suumi sisse",
|
||||
"zoom-out": "Suumi välja"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "Edasi",
|
||||
"play-pause": "Esita/Peata esitus",
|
||||
"previous": "Eelmine",
|
||||
"quit": "Välju",
|
||||
"restart": "Käivita rakendus uuesti",
|
||||
"show": "Näita akent",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Reklaami esitamisel summutatakse heli ja keritakse edasi 16-kordse kiirusega",
|
||||
"name": "Reklaamikiirendaja"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blokeeri kõik reklaamid ja jälitajad",
|
||||
"menu": {
|
||||
"blocker": "Blokeerijad"
|
||||
},
|
||||
"name": "Reklaamiblokeerija"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"menu": {
|
||||
"opacity": {
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Kvaliteet",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} pikslit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Muudab navigatsiooniriba läbipaistavaks ja hägusaks",
|
||||
"name": "Hägus navigatsiooniriba"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Lisa enamustele lugudele laulusõnad",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Latiniseeritud laulusõnad"
|
||||
},
|
||||
"name": "Lyrics Genius",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "Leidsime Geeniuse jaoks ühed laulusõnad"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"name": "Liikumine"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Lisa kraasimise tugi (last.fm, Listenbrainz, jne)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Last.fm'i autentimine ei õnnestunud\nPeida hüpikaken järgmise taaskäivituseni.",
|
||||
"title": "Autentimine ei õnnestunud"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API seadistused"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Sisesta ListenBrainz'i kasutaja tunnusluba"
|
||||
},
|
||||
"scrobble-other-media": "Kraasi muud meediat"
|
||||
},
|
||||
"name": "Kraasija",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API võti",
|
||||
"api-secret": "Last.fm API saladus"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Sisesta oma ListenBrainz'i tunnusluba:",
|
||||
"title": "ListenBrainz'i tunnusluba"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"menu": {
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"tooltip": "Kui lugu ei leidu, siis lisamoodul üritab uut otsingut teistsuguse päringuga.\nTeise katse puhul tulemused ei pruugi olla väga täpsed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "Lõimimine OBSi Tuna lisamooduliga"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,7 +69,7 @@
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "Päivityksen pois päältä",
|
||||
"disable": "Poista päivitykset käytöstä",
|
||||
"download": "Lataa",
|
||||
"ok": "Selvä"
|
||||
},
|
||||
@ -215,8 +215,8 @@
|
||||
"name": "Mainos estäjä"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Alapeukuta musiikki/videota, jotta voimme tarjota sinulle parhaimmat MIX:it",
|
||||
"name": "Albumin toiminnot"
|
||||
"description": "Lisää tykkäysnappulat, joilla voit lisätä tai poistaa tykkäyksiä kerralla kaikille soittolistan tai albumin kappaleille",
|
||||
"name": "Albumin Toiminnot"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"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": {
|
||||
"description": "Antaa valaistustehosteen heittämällä videosta lempeitä värejä näytön taustalle",
|
||||
@ -236,11 +236,11 @@
|
||||
"blur-amount": {
|
||||
"label": "Sumennuksen voimakkuus",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}}pikseliä"
|
||||
"pixels": "{{blurAmount}} pikseliä"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "Puskuroi",
|
||||
"label": "Puskurointi",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
@ -254,7 +254,7 @@
|
||||
"quality": {
|
||||
"label": "Laatu",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}}pikseliä"
|
||||
"pixels": "{{quality}} pikseliä"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
@ -264,7 +264,10 @@
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "sujuvuus siirtymässä"
|
||||
"label": "Siirtymän sujuvuus",
|
||||
"submenu": {
|
||||
"during": "Kesto {{interpolationTime}} s"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Käytetään koko näytön tilaa"
|
||||
|
||||
@ -206,8 +206,13 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Pag mag-play ng ad, I-mute ang audio at i-set ang bilis ng playback ng 16x",
|
||||
"name": "Pagbilis ng Ad"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "I-block ang lahat ng ad at tracking"
|
||||
"description": "I-block ang lahat ng ad at tracking",
|
||||
"name": "Pag-block ng Ad"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Idadagdag ang Undislike, Dislike, Like, at Unlike na button para ilapat ito sa lahat ng kanta sa isang playlist o album",
|
||||
@ -386,12 +391,28 @@
|
||||
"description": "Dina-download ang mga MP3 / source audio direkta mula sa interface",
|
||||
"menu": {
|
||||
"choose-download-folder": "Pumili ng download folder",
|
||||
"download-finish-settings": {
|
||||
"label": "Kung natapos ang download",
|
||||
"prompt": {
|
||||
"last-percent": "Tapos ng x na porsyento",
|
||||
"last-seconds": "Huling x na segundo",
|
||||
"title": "I-configure kung kailan magda-download"
|
||||
},
|
||||
"submenu": {
|
||||
"enabled": "Napagana na",
|
||||
"percent": "Porsyento",
|
||||
"seconds": "Segundo"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Dina-download ang playlist",
|
||||
"presets": "Mga preset",
|
||||
"skip-existing": "Laktawan ang mga kasalukuyang file"
|
||||
},
|
||||
"renderer": {
|
||||
"can-not-update-progress": "Hindi ma-update ang progress"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Mag download"
|
||||
}
|
||||
},
|
||||
"exponential-volume": {
|
||||
@ -427,6 +448,7 @@
|
||||
"connected-users": "Nakakonektang (mga) User",
|
||||
"disconnect": "Mag-diskonekta sa Music Together",
|
||||
"empty-user": "Walang naka-konektang user",
|
||||
"host": "Host ng Music Together",
|
||||
"join": "Sumali sa Music Together",
|
||||
"permission": {
|
||||
"all": "Payagan ang mga guest na kontrolin ang playlist at player",
|
||||
@ -460,20 +482,25 @@
|
||||
"name": "Nabigasyon"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "Tanggalin ang mga Google login na button at mga link mula sa interface"
|
||||
"description": "Tanggalin ang mga Google login na button at mga link mula sa interface",
|
||||
"name": "Walang Google na Login"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Magpakita ng notification kapag nagsimulang tumugtog ang kanta (magagamit ang mga interactive na notification sa Windows)",
|
||||
"menu": {
|
||||
"interactive": "Interactive na Notification",
|
||||
"interactive-settings": {
|
||||
"label": "Mga Interactive na Setting",
|
||||
"submenu": {
|
||||
"hide-button-text": "Itago ang button na texto",
|
||||
"refresh-on-play-pause": "I-refresh sa Pag-play/Pag-pause",
|
||||
"tray-controls": "Buksan/Isara sa pag-click sa tray"
|
||||
}
|
||||
},
|
||||
"priority": "Prioridad ng Notification",
|
||||
"unpause-notification": "Ipakita ang notification sa pag-unpause"
|
||||
}
|
||||
},
|
||||
"name": "Mga Abiso"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"description": "Payagan ang pag-palit ng app sa picture-in-picture mode",
|
||||
@ -486,12 +513,13 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"description": "Makinig na mabilisan, makinig na mabagalan! Nagdaragdag ito ng slider upang makontrol ang bilis ng kanta",
|
||||
"name": "Bilis ng Playback",
|
||||
"templates": {
|
||||
"button": "Bilis"
|
||||
}
|
||||
@ -561,6 +589,7 @@
|
||||
"shortcuts": {
|
||||
"description": "Nagbibigay-daan sa pagtatakda ng mga global hotkey para sa playback (play/pause/susunod/nakaraan) at pag-off ng media OSD sa pamamagitan ng pag-override sa mga media key, pag-on sa Ctrl/CMD + F para maghanap, pag-on sa suporta ng Linux MPRIS para sa mga media key, at mga custom na hotkey para sa mga advanced na user",
|
||||
"menu": {
|
||||
"override-media-keys": "I-override ang mga Media Key",
|
||||
"set-keybinds": "I-set ang Global Song Control"
|
||||
},
|
||||
"name": "Mga shortcut (at MPRIS)",
|
||||
@ -568,6 +597,7 @@
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Susunod",
|
||||
"play-pause": "Mag-play / Mag-pause",
|
||||
"previous": "Nakaraan"
|
||||
},
|
||||
"label": "Pumili ng Global na Keybind para sa Songs Control:"
|
||||
@ -575,14 +605,65 @@
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Laktawan ang na-dislike na kanta"
|
||||
"description": "Laktawan ang na-dislike na kanta",
|
||||
"name": "I-skip ang mga Na-dislike na Kanta"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "Automatikong laktawan ang mga tahimik na mga seksyon sa kanta"
|
||||
"description": "Automatikong laktawan ang mga tahimik na mga seksyon sa kanta",
|
||||
"name": "I-skip ang mga Katahimikan"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "Automatikong Laktawan ang di part ng kanta tulad ng intro/outro o part ng mga music video na ang kanta ay di nagple-play"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Nagbibigay ng naka-sync na lyrics sa mga kanta, gamit ang mga provider tulad ng LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Nagkaroon ng error habang kinukuha ang lyrics. Subukang muli mamaya.",
|
||||
"not-found": "⚠️ - Walang nakitang lyrics para sa kantang ito."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Default na character sa pagitan ng lyrics",
|
||||
"tooltip": "Pumili ng default na character na gagamitin sa pagitan ng lyrics"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Effect ng Linya",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"tooltip": "Gawing puti lamang ang kasalukuyang linya"
|
||||
},
|
||||
"offset": {
|
||||
"tooltip": "I-offset sa kanan ang kasalukuyang linya"
|
||||
},
|
||||
"scale": {
|
||||
"tooltip": "I-scale ang kasalukuyang linya"
|
||||
}
|
||||
},
|
||||
"tooltip": "Pumili ng effect na ilalapat sa kasalukuyang linya"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Gawing perpektong naka-sync ang lyrics",
|
||||
"tooltip": "Kalkulahin sa millisecond ang pagpapakita ng susunod na linya (maaaring magkaroon ng maliit na epekto sa performance)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Ipakita ang lyrics kahit di-eksakto",
|
||||
"tooltip": "Kung hindi matagpuan ang kanta, susubukan muli ng plugin gamit ang ibang query sa paghahanap.\nAng resulta mula sa pangalawang pagsubok ay maaaring hindi eksakto."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Ipakita ang mga time code",
|
||||
"tooltip": "Ipakita ang mga time code kasunod sa lyrics"
|
||||
}
|
||||
},
|
||||
"refetch-btn": {
|
||||
"fetching": "Nag-fe-fetch...",
|
||||
"normal": "I-fetch muli ang lyrics"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Maaaring hindi naka-sync ang lyrics dahil sa hindi pagkakatugma ng duration.",
|
||||
"inexact": "⚠️ - Maaaring hindi eksakto ang lyrics para sa kantang ito",
|
||||
"instrumental": "⚠️ - Ito ay isang instrumental na kanta"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Kontrolin ang pag-play mula sa iyong taskbar ng Windows"
|
||||
},
|
||||
|
||||
@ -105,7 +105,7 @@
|
||||
"label": "Définir un proxy",
|
||||
"prompt": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@ -179,7 +179,7 @@
|
||||
"plugins": {
|
||||
"enabled": "Activé",
|
||||
"label": "Extensions",
|
||||
"new": "NOUVELLE"
|
||||
"new": "NOUVEAU"
|
||||
},
|
||||
"view": {
|
||||
"label": "Vue",
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Bloquer toutes les annonces et le suivi par défaut",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Télécharge les fichiers MP3/source audio directement depuis l'interface",
|
||||
"menu": {
|
||||
"choose-download-folder": "Choisissez le dossier de téléchargement",
|
||||
"download-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",
|
||||
"presets": "Préconfigurations",
|
||||
"skip-existing": "Passer les fichiers existants"
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "Saute automatiquement les parties non musicales comme l'intro/outro ou les parties de clips vidéo où la chanson n'est pas lue",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Ajoute des paroles synchronisées aux chansons, grâce à LRClib par exemple.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Une erreur s'est produite en allant chercher les paroles. Merci de réessayer plus tard.",
|
||||
"not-found": "⚠️ - Aucune paroles trouvées pour cette musique."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Caractère par défaut entre les paroles",
|
||||
"tooltip": "Choisi le caractère par défaut à utiliser pour l'espace entre les paroles"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Effet de ligne",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Focus",
|
||||
"tooltip": "Rend seulement la ligne actuelle blanche"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Décalage",
|
||||
"tooltip": "Décale sur la droite la ligne actuelle"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Grossissement",
|
||||
"tooltip": "Agrandis la ligne actuelle"
|
||||
}
|
||||
},
|
||||
"tooltip": "Choisi l'effet à appliquer sur la ligne actuelle"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Rend les paroles parfaitement synchronisées",
|
||||
"tooltip": "Calcul à la milliseconde près l'affichage de la ligne suivante (peut avoir un faible impact sur les performances)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Afficher les paroles même si inexactes",
|
||||
"tooltip": "Si la musique n'est pas trouvé, le plugin essaye à nouveau avec une différence requête.\nLe résultat du deuxième essais peut ne pas être exacte."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Afficher les timecodes",
|
||||
"tooltip": "Affiche à côté de chaque paroles son timecode"
|
||||
}
|
||||
},
|
||||
"name": "Paroles Synchronisées",
|
||||
"refetch-btn": {
|
||||
"fetching": "Chargement...",
|
||||
"normal": "Rafraîchir les paroles"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Les paroles peuvent ne pas être synchronisées à cause d'une différence de durée.",
|
||||
"inexact": "⚠️ - Les paroles de cette chanson peuvent ne pas être exactes",
|
||||
"instrumental": "⚠️ - Cette musique n'a pas de paroles"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Contrôlez la lecture depuis votre barre des tâches Windows",
|
||||
"name": "Contrôle multimédia de la barre des tâches"
|
||||
|
||||
@ -2,7 +2,14 @@
|
||||
"common": {
|
||||
"console": {
|
||||
"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}} נכשלה",
|
||||
"unloaded": "תוסף {{pluginName}} הורד"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -10,5 +17,43 @@
|
||||
"code": "he",
|
||||
"local-name": "עברית",
|
||||
"name": "Hebrew"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "הטעינה הסתיימה. הכלים לפמתחים נפתחו"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n נטען"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "התקבלה פקודה מעבר פרוטוקל: {{command}}"
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "קובץ ה-CSS \"{{cssFile}}\" לא קיים. מדלג"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "שגיאה ללא תגובה\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "מוחק קבצי מתמון"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "ווינדוס ניסה להציג תוכן מחוץ למסך, גודל חלון={{windowSize}}, גודל מסך={{displaySize}}, מיקום={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "התפריט מוחבא, השתמש \"Alt\" על להציג אותו (או \"Esacpe\" אם משתמשים בתפריט בתוך האפליקציה)",
|
||||
"message": "הסתרת התפריט מופעלת",
|
||||
"title": "הסתרת התפריט הופעלה"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "אחר כך",
|
||||
"restart-now": "מתחיל את התוכנה מחדש עכשיו"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
"initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" plugint",
|
||||
"load-all": "Összes bővítmény betöltése",
|
||||
"load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" plugint",
|
||||
"loaded": "\"{{pluginName}}\" nevű plugin betöltve",
|
||||
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt letölteni",
|
||||
"loaded": "\"{{pluginName}}\" plugin betöltve",
|
||||
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt kikapcsolni",
|
||||
"unloaded": "A \"{{pluginName}}\" bővítmény kikapcsolva"
|
||||
}
|
||||
}
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést",
|
||||
"menu": {
|
||||
@ -368,6 +372,9 @@
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"error": {
|
||||
"buttons": {
|
||||
"ok": "Rendben"
|
||||
},
|
||||
"message": "Hoppá! Elnézést, a letöltés sikertelen volt…",
|
||||
"title": "A letöltés során hiba történt!"
|
||||
},
|
||||
@ -407,7 +414,22 @@
|
||||
"description": "MP3 / forrás hanganyag letöltése közvetlenül az interfészről",
|
||||
"menu": {
|
||||
"choose-download-folder": "Letöltési mappa kiválasztása",
|
||||
"download-finish-settings": {
|
||||
"label": "Letöltés befejezéskor",
|
||||
"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",
|
||||
"presets": "Sablonok",
|
||||
"skip-existing": "Meglévő fájlok kihagyása"
|
||||
},
|
||||
"name": "Letöltő",
|
||||
@ -489,6 +511,7 @@
|
||||
}
|
||||
},
|
||||
"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ó"
|
||||
},
|
||||
"no-google-login": {
|
||||
@ -502,10 +525,14 @@
|
||||
"interactive-settings": {
|
||||
"label": "Interaktív beállítások",
|
||||
"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"
|
||||
},
|
||||
@ -542,7 +569,9 @@
|
||||
"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",
|
||||
"menu": {
|
||||
"global-shortcuts": "Globális gyorsbillentyűk"
|
||||
"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"
|
||||
},
|
||||
"name": "Precíz hangerő",
|
||||
"prompt": {
|
||||
@ -553,6 +582,10 @@
|
||||
},
|
||||
"label": "Válaszd ki a globális hangerő gyorsbillentyűket:",
|
||||
"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 +599,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": {
|
||||
"description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)",
|
||||
@ -613,6 +647,7 @@
|
||||
"play-pause": "Lejátszás / Szünet",
|
||||
"previous": "Előző"
|
||||
},
|
||||
"label": "Globális billentyűparancsok választása a dalok vezérléséhez:",
|
||||
"title": "Globális gyorsbillentyűk"
|
||||
}
|
||||
}
|
||||
@ -629,8 +664,38 @@
|
||||
"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"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"menu": {
|
||||
"line-effect": {
|
||||
"submenu": {
|
||||
"scale": {
|
||||
"label": "Mérték"
|
||||
}
|
||||
}
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Dalszöveg tökéletes szinkronizálása"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"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": {
|
||||
"description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz",
|
||||
@ -667,6 +732,7 @@
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Vizualizációt ad a lejátszóhoz",
|
||||
"menu": {
|
||||
"visualizer-type": "Vizualizáció típus"
|
||||
},
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Jika iklan diputar, audio akan dimatikan dan kecepatan pemutaran akan diatur ke 16x",
|
||||
"name": "Percepatan Iklan"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blokir semua iklan dan pelacakan di luar kotak",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Unduh MP3 / sumber suara secara langsung via antarmuka",
|
||||
"menu": {
|
||||
"choose-download-folder": "Pilih folder unduhan",
|
||||
"download-finish-settings": {
|
||||
"label": "Unduh setelah selesai",
|
||||
"prompt": {
|
||||
"last-percent": "x persen terakhir",
|
||||
"last-seconds": "x detik terakhir",
|
||||
"title": "Konfigurasikan kapan akan mengunduh"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Lanjutan",
|
||||
"enabled": "Diaktifkan",
|
||||
"mode": "Mode waktu",
|
||||
"percent": "Persen",
|
||||
"seconds": "Detik"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Unduh daftar putar",
|
||||
"presets": "Prasetel",
|
||||
"skip-existing": "Lewati berkas yang sudah ada"
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "Otomatis Melewati bagian yang bukan musik seperti intro/outro atau bagian dari video musik di mana lagu tidak dimainkan",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Menyediakan lirik lagu yang disinkronkan, menggunakan penyedia seperti LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Terjadi kesalahan saat mengambil lirik. Coba lagi nanti.",
|
||||
"not-found": "⚠️ - Tidak ada lirik yang ditemukan untuk lagu ini."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Karakter default antara lirik",
|
||||
"tooltip": "Pilih karakter default yang akan digunakan untuk celah antar lirik"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Efek garis",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Fokus",
|
||||
"tooltip": "Jadikan hanya baris saat ini berwarna putih"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Offset",
|
||||
"tooltip": "Mengimbangi garis saat ini di sebelah kanan"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Skala",
|
||||
"tooltip": "Skala garis saat ini"
|
||||
}
|
||||
},
|
||||
"tooltip": "Pilih efek yang akan diterapkan ke baris saat ini"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Buat liriknya tersinkronisasi dengan sempurna",
|
||||
"tooltip": "Hitung hingga milidetik tampilan baris berikutnya (dapat berdampak kecil pada kinerja)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Tampilkan lirik meskipun tidak tepat",
|
||||
"tooltip": "Jika lagu tidak ditemukan, plugin akan mencoba lagi dengan kueri pencarian yang berbeda.\nHasil dari percobaan kedua mungkin tidak tepat."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Tampilkan kode waktu",
|
||||
"tooltip": "Tampilkan kode waktu di samping lirik"
|
||||
}
|
||||
},
|
||||
"name": "Lirik yang Disinkronkan",
|
||||
"refetch-btn": {
|
||||
"fetching": "Mengambil...",
|
||||
"normal": "Ambil ulang lirik"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Liriknya mungkin tidak sinkron karena ketidakcocokan durasi.",
|
||||
"inexact": "⚠️ - Lirik lagu ini mungkin tidak tepat",
|
||||
"instrumental": "⚠️ - Ini adalah lagu instrumental"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Kendalikan pemutaran dari bilah alat Windows",
|
||||
"name": "Pengendali Media di Bilah Alat"
|
||||
|
||||
@ -158,6 +158,14 @@
|
||||
},
|
||||
"remove-upgrade-button": "Fjarlægja uppgræðartakkan",
|
||||
"theme": {
|
||||
"dialog": {
|
||||
"button": {
|
||||
"cancel": "Hætta við",
|
||||
"remove": "Fjarlægja"
|
||||
},
|
||||
"remove-theme": "Ertu viss um að þú viljir fjarlægja þetta sérsniðna þema?",
|
||||
"remove-theme-message": "Þetta mun fjarlægja sérsniðna þema"
|
||||
},
|
||||
"label": "Þema",
|
||||
"submenu": {
|
||||
"import-css-file": "Flytja inn sérsniðna CSS skrá",
|
||||
@ -199,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Ef auglýsing spilar slökknar hún á hljóðinu og stillir spilunarhraðann á 16x",
|
||||
"name": "Auglýsingahraða"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Lokaðu fyrir allar auglýsingar og rakningar úr kassanum",
|
||||
"menu": {
|
||||
@ -402,6 +414,21 @@
|
||||
"description": "Niðurhalar MP3 / upprunahljóði beint úr viðmótinu",
|
||||
"menu": {
|
||||
"choose-download-folder": "Veldu niðurhalsmöppu",
|
||||
"download-finish-settings": {
|
||||
"label": "Sækja þegar lokið",
|
||||
"prompt": {
|
||||
"last-percent": "Eftir x sekúndur",
|
||||
"last-seconds": "Síðustu x sekúndur",
|
||||
"title": "Stilla hvenær á að hlaða niður"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Ítarlegri",
|
||||
"enabled": "Virkt",
|
||||
"mode": "Tímastilling",
|
||||
"percent": "Hlutfall",
|
||||
"seconds": "Sekúndur"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Sækja spilunarlista",
|
||||
"presets": "Forstillingar",
|
||||
"skip-existing": "Slepptu núverandi skrám"
|
||||
@ -641,6 +668,10 @@
|
||||
"description": "Sleppur sjálfkrafa hlutum sem ekki eru tónlist, eins og inngangur/lok eða hlutar af tónlistarmyndböndum þar sem lag er ekki að spila",
|
||||
"name": "Styrktarblokk"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Veitir samstillta texta við lög, með því að nota veitur eins og LRClib.",
|
||||
"name": "Samstilltur texti"
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Stjórnaðu spilun frá Windows verkefnastikunni þinni",
|
||||
"name": "Miðlunarstýringarverkefnastikunnar"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Blocca tutti gli annunci e i tracker",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Download MP3 / sorgenti audio direttamente dall'interfaccia",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Preimpostazioni",
|
||||
"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",
|
||||
"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": {
|
||||
"description": "Controlla riproduzione dalla taskbar di Windows",
|
||||
"name": "Controlli multimediali sulla taskbar"
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
},
|
||||
"detail": "ご不便をおかけして申し訳ございません! 何をするか選んでください:",
|
||||
"message": "アプリケーションは応答していません",
|
||||
"title": "ウィンドウが応答しません"
|
||||
"title": "ウィンドウが応答していません"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "広告が再生されると、自動的にミュートされ、再生速度が16倍に設定されます",
|
||||
"name": "広告のスピードを上げる"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "すべての広告とトラッカーをブロックj",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "UIから直にMP3・ソースオーディオをダウンロードします",
|
||||
"menu": {
|
||||
"choose-download-folder": "ダウンロードフォルダ",
|
||||
"download-finish-settings": {
|
||||
"label": "完了時にダウンロード",
|
||||
"prompt": {
|
||||
"last-percent": "x パーセント後",
|
||||
"last-seconds": "最後の x 秒",
|
||||
"title": "保存するタイミング"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "高度な設定",
|
||||
"enabled": "有効",
|
||||
"mode": "時間モード",
|
||||
"percent": "パーセント",
|
||||
"seconds": "秒"
|
||||
}
|
||||
},
|
||||
"download-playlist": "プレイリストをダウンロード",
|
||||
"presets": "プリセット",
|
||||
"skip-existing": "存在するファイルをスキップ"
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "イントロ/アウトロなどの音楽以外の部分や、曲が再生されていないミュージック ビデオの部分を自動的にスキップします",
|
||||
"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": {
|
||||
"description": "Windowsタスクバーから再生をコントロール",
|
||||
"name": "タスクバーメディアコントロール"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "광고가 재생될 때, 오디오가 음소거되고 재생 속도가 16배로 설정됩니다",
|
||||
"name": "광고 배속"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "모든 광고와 트래커를 즉시 차단합니다",
|
||||
"menu": {
|
||||
@ -664,6 +668,59 @@
|
||||
"description": "인트로/아웃트로와 같은 음악이 아닌 부분이나, 노래가 재생되지 않는 뮤직 비디오의 일부를 자동으로 건너뜁니다",
|
||||
"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": {
|
||||
"description": "Windows 작업 표시줄에서 재생을 제어하세요",
|
||||
"name": "작업표시줄 미디어 컨트롤"
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"execute-failed": "Pelaksaan plugin gagal {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dilaksanakan pada {{ms}}ms",
|
||||
"initialize-failed": "Gagal untuk memulakan plugin \"{{pluginName}}\"",
|
||||
"load-all": "Memuatkan semua plugin",
|
||||
"loaded": "Plugin \"{{pluginName}}\" dimuatkan",
|
||||
"unload-failed": "Gagal untuk memunggah plugin \"{{pluginName}}\"",
|
||||
"unloaded": "Plugin \"{{pluginName}}\" dipunggahkan"
|
||||
@ -39,15 +40,22 @@
|
||||
"detail": "Menu telah disembunyikan, guna 'Alt' untuk menunjukkannya (atau 'Escape' jika menggunakan In-App Menu)"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Nanti",
|
||||
"restart-now": "Restart Sekarang"
|
||||
},
|
||||
"message": "\"{{pluginName}}\" perlu dimulakan semula",
|
||||
"title": "Mulakan Semula Diperlukan"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Berhenti",
|
||||
"relaunch": "Lancar Semula",
|
||||
"wait": "Tunggu"
|
||||
},
|
||||
"detail": "Kami memohon maaf atas kesulitan! sila pilih apa yang perlu dilakukan:"
|
||||
"detail": "Kami memohon maaf atas kesulitan! sila pilih apa yang perlu dilakukan:",
|
||||
"message": "Aplikasi Tidak Responsif",
|
||||
"title": "Window Tidak Responsif"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
@ -58,16 +66,137 @@
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "Mengenai",
|
||||
"navigation": {
|
||||
"label": "Navigasi",
|
||||
"submenu": {
|
||||
"copy-current-url": "Salin URL semasa",
|
||||
"go-back": "Pulang",
|
||||
"go-back": "Belakang",
|
||||
"go-forward": "Depan",
|
||||
"quit": "Keluar"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "Pilihan"
|
||||
"label": "Tetapan",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "Tetapan Lanjutan",
|
||||
"submenu": {
|
||||
"set-proxy": {
|
||||
"prompt": {
|
||||
"placeholder": "Contoh: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Set proksi"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"always-on-top": "Sentiasa di atas",
|
||||
"auto-update": "Kemas Kini Automatik",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "Menu akan disembunyikan pada pelancaran seterusnya, gunakan [Alt] untuk menunjukkannya (atau backtick [`] jika menggunakan dalam aplikasi-menu)"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "Bahasa akan ditukar selepas dimulakan semula",
|
||||
"title": "Bahasa Berubah"
|
||||
},
|
||||
"label": "Bahasa",
|
||||
"submenu": {
|
||||
"to-help-translate": "Ingin membantu menterjemah? Klik di sini"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "Mulakan semula lagu terakhir apabila aplikasi dimulakan",
|
||||
"start-at-login": "Mulakan semasa log masuk",
|
||||
"starting-page": {
|
||||
"label": "Halaman Permulaan"
|
||||
},
|
||||
"tray": {
|
||||
"submenu": {
|
||||
"play-pause-on-click": "Main / Hentikan pada klik"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Pembaikan Visual",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Lalai",
|
||||
"hide": "Sembunyi"
|
||||
},
|
||||
"theme": {
|
||||
"dialog": {
|
||||
"button": {
|
||||
"cancel": "Batalkan",
|
||||
"remove": "Padam"
|
||||
}
|
||||
},
|
||||
"label": "Tema"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "Seterusnya",
|
||||
"play-pause": "Main / Jeda",
|
||||
"previous": "Sebelumnya",
|
||||
"quit": "Keluar",
|
||||
"restart": "Mulakan Semula Aplikasi"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ambient-mode": {
|
||||
"menu": {
|
||||
"quality": {
|
||||
"label": "Kualiti"
|
||||
},
|
||||
"size": {
|
||||
"label": "Saiz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"captions-selector": {
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"title": "Pilih bahasa kapsyen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"menu": {
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Tunjukkan lirik walaupun tidak tepat",
|
||||
"tooltip": "Jika lagu tidak ditemui, plugin cuba lagi dengan pertanyaan carian yang berbeza. \nHasil dari percubaan kedua mungkin tidak tepat."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"tooltip": "Tunjukkan kod masa di sebelah lirik"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Kawalan main balik dari bar tugas Windows anda",
|
||||
"name": "Kawalan Media Bar Tugas"
|
||||
},
|
||||
"video-toggle": {
|
||||
"menu": {
|
||||
"align": {
|
||||
"submenu": {
|
||||
"left": "Kiri",
|
||||
"middle": "Tengah",
|
||||
"right": "Kanan"
|
||||
}
|
||||
},
|
||||
"force-hide": "Alih Keluar Tab Video",
|
||||
"mode": {
|
||||
"submenu": {
|
||||
"disabled": "Tidak Aktif"
|
||||
}
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"button": "Lagu"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,6 +158,14 @@
|
||||
},
|
||||
"remove-upgrade-button": "Upgrade-knop verwijderen",
|
||||
"theme": {
|
||||
"dialog": {
|
||||
"button": {
|
||||
"cancel": "Annuleren",
|
||||
"remove": "Verwijderen"
|
||||
},
|
||||
"remove-theme": "Weet je zeker dat je het aangepaste thema wilt verwijderen?",
|
||||
"remove-theme-message": "Dit verwijderd het aangepaste thema"
|
||||
},
|
||||
"label": "Thema",
|
||||
"submenu": {
|
||||
"import-css-file": "Aangepast CSS-bestand importeren",
|
||||
@ -199,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Wanneer een advertentie afspeelt, dempt het geluid en versnelt de playback naar 16x",
|
||||
"name": "Snellere advertenties"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blokkeer alle advertenties en tracking vanuit de doos",
|
||||
"menu": {
|
||||
@ -402,6 +414,21 @@
|
||||
"description": "Download MP3 / bron-audio rechtstreeks vanuit de interface",
|
||||
"menu": {
|
||||
"choose-download-folder": "Kies de downloadmap",
|
||||
"download-finish-settings": {
|
||||
"label": "Downloaden bij voltooiing",
|
||||
"prompt": {
|
||||
"last-percent": "Na x procent",
|
||||
"last-seconds": "Laatste x seconden",
|
||||
"title": "Configureren wanneer te downloaden"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Geavanceerd",
|
||||
"enabled": "Ingeschakeld",
|
||||
"mode": "Tijd-modus",
|
||||
"percent": "Procent",
|
||||
"seconds": "Seconden"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Afspeellijst downloaden",
|
||||
"presets": "Voorinstellingen",
|
||||
"skip-existing": "Bestaande bestanden overslaan"
|
||||
@ -523,8 +550,20 @@
|
||||
"save-window-size": "Sla schermgrootte op"
|
||||
}
|
||||
},
|
||||
"video-toggle": {
|
||||
"menu": {
|
||||
"mode": {
|
||||
"submenu": {
|
||||
"disabled": "Uitgeschakeld"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Voeg een visuele equalizer toe",
|
||||
"menu": {
|
||||
"visualizer-type": "Type visualisator"
|
||||
},
|
||||
"name": "Visualisator"
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Wycisza reklamę i przyśpiesza do 16x",
|
||||
"name": "Przyśpieszacz reklam"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blokuj wszystkie reklamy i śledzenie",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Pobiera MP3/ źródło audio bezpośrednio z interfejsu",
|
||||
"menu": {
|
||||
"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ę",
|
||||
"presets": "Predefiniowane ustawienia",
|
||||
"skip-existing": "Pomiń istniejące pliki"
|
||||
@ -649,6 +668,22 @@
|
||||
"description": "Automatycznie pomija fragmenty niebędące muzyką, takie jak wstęp/zakończenie lub fragmenty teledysków, w których utwór nie jest odtwarzany",
|
||||
"name": "Pomiń nieistotne fragmenty"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Dodaje zsynchronizowane napisy do utworów używając między innymi LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Wystąpił błąd podczas pobierania tekstu utworu. Spróbuj ponownie później.",
|
||||
"not-found": "⚠️ - Nie znaleziono napisów dla tego utworu."
|
||||
},
|
||||
"name": "Napisy zsynchronizowane",
|
||||
"refetch-btn": {
|
||||
"fetching": "Pobieranie napisów...",
|
||||
"normal": "Odśwież napisy"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Napisy mogą nie być zsynchronizowane z powodu różnicy w czasie trwania.",
|
||||
"instrumental": "⚠️ - To jest utwór instrumentalny"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Steruj odtwarzaniem z paska zadań systemu Windows",
|
||||
"name": "Kontroler odtwarzania z paska zadań"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Bloquear todos os anúncios e rastreamento automaticamente",
|
||||
"menu": {
|
||||
@ -222,9 +226,9 @@
|
||||
"description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Rácio de mistura das cores",
|
||||
"label": "Relação de mistura de cores",
|
||||
"submenu": {
|
||||
"percent": "Proporção"
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Baixa MP3 / fonte de áudio diretamente da interface",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Predefinições",
|
||||
"skip-existing": "Ignorar arquivos existentes"
|
||||
@ -482,15 +501,15 @@
|
||||
"add-song-failed": "Falha ao adicionar canção",
|
||||
"closed": "Música Juntos encerrado",
|
||||
"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-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",
|
||||
"joined": "Entrou em Música Juntos",
|
||||
"permission-changed": "A permissão do Música Juntos foi alterada para \"{{permission}}\"",
|
||||
"join-failed": "Falha ao entrar em Music Together",
|
||||
"joined": "Entrou em Music Together",
|
||||
"permission-changed": "A permissão do Music Together foi alterada para \"{{permission}}\"",
|
||||
"remove-song-failed": "Falha ao remover música",
|
||||
"user-connected": "{{name}} entrou em Música Juntos",
|
||||
"user-disconnected": "{{name}} saiu do Música Juntos"
|
||||
"user-connected": "{{name}} entrou em Music Together",
|
||||
"user-disconnected": "{{name}} saiu do Music Together"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "Ignora automaticamente partes não musicais, como introdução/final ou partes de videoclipes onde a música não está tocando",
|
||||
"name": "SponsorBlock (bloqueador de patrocínios)"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Fornece letras sincronizadas de músicas, utilizando fornecedores como o LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Ocorreu um erro ao obter as letras da música. Por favor, tenta novamente mais tarde.",
|
||||
"not-found": "⚠️ - Não foram encontradas letras para esta música."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Caractere padrão entre as letras",
|
||||
"tooltip": "Escolha o caractere padrão para usar no espaço entre as letras"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Efeito de linha",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Foco",
|
||||
"tooltip": "Deixe apenas a linha atual branca"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Deslocamento",
|
||||
"tooltip": "Desloque a linha atual para a direita"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Escala",
|
||||
"tooltip": "Escalar a linha atual"
|
||||
}
|
||||
},
|
||||
"tooltip": "Escolha o efeito a ser aplicado à linha atual"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Sincronize perfeitamente as letras",
|
||||
"tooltip": "Calcule até o milissegundo a exibição da próxima linha (pode ter um pequeno impacto no desempenho)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Mostrar letras mesmo que imprecisas",
|
||||
"tooltip": "Se a música não for encontrada, o plugin tenta novamente com uma consulta de pesquisa diferente.\nO resultado da segunda tentativa pode não ser exato."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Mostrar códigos de tempo",
|
||||
"tooltip": "Mostrar os códigos de tempo ao lado das letras"
|
||||
}
|
||||
},
|
||||
"name": "Letras Sincronizadas",
|
||||
"refetch-btn": {
|
||||
"fetching": "Buscando...",
|
||||
"normal": "Buscar as letras novamente"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - As letras da música pode estar dessincronizada devido a um erro de duração.",
|
||||
"inexact": "⚠️ - As letras desta música podem não ser exactas.",
|
||||
"instrumental": "⚠️ - Esta é uma música instrumental."
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Controle a reprodução na barra de tarefas do Windows",
|
||||
"name": "Controle de mídia da barra de tarefas"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Если воспроизводится реклама, аудио заглушается и скорость воспроизведения устанавливается на 16х",
|
||||
"name": "Ускоренная перемотка"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Блокируйте всю рекламу и трекинг сразу после установки",
|
||||
"menu": {
|
||||
@ -277,7 +281,7 @@
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Применяет компрессию к аудио (уменьшает громкость самых громких частей сигнала и повышает громкость самых тихих частей)",
|
||||
"name": "Аудио компрессор"
|
||||
"name": "Нормализация аудио"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Делает панель навигации прозрачной и размытой",
|
||||
@ -649,6 +653,58 @@
|
||||
"description": "Автоматически пропускает не музыкальные фрагменты, например интро/аутро или фрагменты музыкальных клипов, в которых песня не звучит (тишина)",
|
||||
"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": "Показывает временные метки рядом со словами"
|
||||
}
|
||||
},
|
||||
"refetch-btn": {
|
||||
"fetching": "Сбор данных...",
|
||||
"normal": "Обновить слова"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Слова могут быть неточно синхронизированы из-за несовпадения длины трека.",
|
||||
"inexact": "⚠️ - Слова для этой песни могут быть неточными",
|
||||
"instrumental": "⚠️ - Это инструментальная музыка"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Управляйте воспроизведением с панели задач Windows",
|
||||
"name": "Управление мультимедиа на панели задач"
|
||||
@ -692,7 +748,7 @@
|
||||
"menu": {
|
||||
"visualizer-type": "Вид визуализации"
|
||||
},
|
||||
"name": "Визуалайзер"
|
||||
"name": "Визуализатор"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,11 +7,32 @@
|
||||
"initialize-failed": "\"{{pluginName}}\" ප්ලගිනය ආරම්භ කිරීමට අසමත් විය",
|
||||
"load-all": "සියලුම ප්ලගින පූරණය කරමින්",
|
||||
"load-failed": "\"{{pluginName}}\" ප්ලගිනය පූරණය කිරීමට අසමත් විය",
|
||||
"loaded": "ප්ලගිනය \"{{pluginName}}\" පූරණය කරන ලදී"
|
||||
"loaded": "ප්ලගිනය \"{{pluginName}}\" පූරණය කරන ලදී",
|
||||
"unload-failed": "ප්ලගින් \"{{pluginName}}\" ගලවන්න අසාර්ථක වුන",
|
||||
"unloaded": "ප්ලගින් \"{{pluginName}}\" ගැලෙව්වා"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "si",
|
||||
"local-name": "සිංහල",
|
||||
"name": "Sinhala"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "පූරණය සම්පුර්නි. ඩෙව්ටූල්ස් ඇරිලා"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n පූරණය කර ඇත"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "ප්රෝටෝකාල් හරහා විධානය ලැබුණි: \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "සීඑස්එස් ගොනුව \"{{cssFile}}\" නොපවතී, නොසලකා හැරීම"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"need-to-restart": {
|
||||
"title": "නැවත ආරම්භ කිරීම අවශ්යයි"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Bir reklam oynatılırsa sesi kapatır ve oynatma hızını 16x olarak ayarlar",
|
||||
"name": "Hızlandırma"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Tüm reklamları ve izleyicileri engelle",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "MP3 / kaynak sesini doğrudan arayüzden indir",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Hazır Ayarlar",
|
||||
"skip-existing": "Mevcut dosyaları atla"
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "Giriş/Çıkış gibi müzik olmayan kısımları veya müzik videolarında şarkının çalmadığı kısımları otomatik olarak atlar",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "LRClib gibi sağlayıcıları kullanarak şarkılara senkronize şarkı sözleri sağlar.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Şarkı sözleri getirilirken bir hata oluştu. Lütfen daha sonra tekrar deneyin.",
|
||||
"not-found": "⚠️ - Bu şarkı için şarkı sözü bulunamadı."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Şarkı sözleri arasında varsayılan karakter",
|
||||
"tooltip": "Şarkı sözleri arasındaki boşluk için kullanılacak varsayılan karakteri seçin"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Çizgi etkisi",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "odak",
|
||||
"tooltip": "Yalnızca geçerli satırı beyaz yapın"
|
||||
},
|
||||
"offset": {
|
||||
"label": "telafi etmek,ofset",
|
||||
"tooltip": "Geçerli satırın sağındaki ofset"
|
||||
},
|
||||
"scale": {
|
||||
"label": "ölçek",
|
||||
"tooltip": "Geçerli satırı ölçeklendirir"
|
||||
}
|
||||
},
|
||||
"tooltip": "Geçerli satıra uygulanacak efekti seçin"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Şarkı sözlerini mükemmel şekilde senkronize edin",
|
||||
"tooltip": "Bir sonraki satırın görüntülenmesini milisaniyesine kadar hesaplayın (performans üzerinde küçük bir etkisi olabilir)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Kesin olmasa bile şarkı sözlerini gösterin",
|
||||
"tooltip": "Şarkı bulunamazsa, eklenti farklı bir arama sorgusuyla tekrar dener. \nİkinci denemenin sonucu tam olmayabilir."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Zaman kodlarını göster",
|
||||
"tooltip": "Şarkı sözlerinin yanında zaman kodlarını gösterin"
|
||||
}
|
||||
},
|
||||
"name": "Senkronize Şarkı Sözleri",
|
||||
"refetch-btn": {
|
||||
"fetching": "Getiriliyor...",
|
||||
"normal": "Refetch şarkı sözleri"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Süre uyuşmazlığı nedeniyle şarkı sözleri senkronize olmayabilir.",
|
||||
"inexact": "⚠️ - Bu şarkının sözleri tam olmayabilir",
|
||||
"instrumental": "⚠️ - Bu enstrümantal bir şarkıdır"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Windows görev çubuğu üzerinden oynatmayı kontrol edebilmenize olanak sağlar",
|
||||
"name": "Görev Çubuğu Medya Kontrolü"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"common": {
|
||||
"console": {
|
||||
"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",
|
||||
"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",
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Chặn toàn bộ quảng cáo và trình theo dõi",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Tải xuống MP3 / âm thanh nguồn trực tiếp từ giao diện",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Cài đặt sẵn",
|
||||
"skip-existing": "Bỏ qua các tập tin hiện có"
|
||||
@ -649,6 +668,55 @@
|
||||
"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"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Kí tự mặc định giữa các lời bài hát",
|
||||
"tooltip": "Chọn kí tự mặc định cho khoảng trống giữa các lời bài hát"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Kiểu đường thẳng",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Tập trung",
|
||||
"tooltip": "Chỉ làm cho dòng hiện tại có màu trắng"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Tỉ lệ",
|
||||
"tooltip": "Áp dụng tỉ lệ cho dòng hiện tại"
|
||||
}
|
||||
},
|
||||
"tooltip": "Chọn kiểu để áp dụng cho dòng hiện tại"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Làm cho lời bài hát được đồng bộ hoàn hảo",
|
||||
"tooltip": "Tính toán chính xác đến mili giây thời gian hiển thị dòng tiếp theo (có thể có tác động nhỏ đến hiệu suất)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Hiển thị lời bài hát ngay cả khi không chính xác",
|
||||
"tooltip": "Nếu không tìm thấy bài hát, plugin sẽ thử lại bằng truy vấn tìm kiếm khác.\nKết quả từ lần thử thứ hai có thể không chính xác."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Hiện mốc thời gian",
|
||||
"tooltip": "Hiện mốc thời gian bên cạnh lời bài hát"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"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ụ"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "使用静音以及 16 倍速播放跳过广告片段",
|
||||
"name": "广告加速跳过"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "屏蔽所有广告与跟踪器",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "在界面内直接下载 MP3 / 源音频",
|
||||
"menu": {
|
||||
"choose-download-folder": "选择下载文件夹",
|
||||
"download-finish-settings": {
|
||||
"label": "边播边下",
|
||||
"prompt": {
|
||||
"last-percent": "播放超过指定百分比时开始下载",
|
||||
"last-seconds": "歌曲剩余指定秒数时开始下载",
|
||||
"title": "配置在何时开始下载"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "高级",
|
||||
"enabled": "已启用",
|
||||
"mode": "激活时机",
|
||||
"percent": "按播放百分比",
|
||||
"seconds": "按播放秒数"
|
||||
}
|
||||
},
|
||||
"download-playlist": "下载播放列表",
|
||||
"presets": "预设",
|
||||
"skip-existing": "跳过已存在的文件"
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "自动跳过非音乐部分,如 MV 的介绍/结语以及歌曲未开始的部分",
|
||||
"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": {
|
||||
"description": "从 Windows 任务栏控制音乐回放",
|
||||
"name": "任务栏媒体控件"
|
||||
|
||||
@ -96,11 +96,11 @@
|
||||
"advanced-options": {
|
||||
"label": "進階選項",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "當程式啟動時重設應用程式快取",
|
||||
"auto-reset-app-cache": "啟動時重設應用快取",
|
||||
"disable-hardware-acceleration": "關閉硬體加速",
|
||||
"edit-config-json": "編輯 config.json",
|
||||
"override-user-agent": "覆寫使用者代理",
|
||||
"restart-on-config-changes": "在設定檔更動時自動重啟應用程式",
|
||||
"restart-on-config-changes": "設定變更時自動重啟應用",
|
||||
"set-proxy": {
|
||||
"label": "設定代理伺服器",
|
||||
"prompt": {
|
||||
@ -123,7 +123,7 @@
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "語言會在下一次重啟應用程式時變更",
|
||||
"message": "語言會在重啟應用後變更",
|
||||
"title": "語言已變更"
|
||||
},
|
||||
"label": "語言",
|
||||
@ -131,7 +131,7 @@
|
||||
"to-help-translate": "想協助翻譯?按一下這裡"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "應用啟動時繼續上次播放的歌曲",
|
||||
"resume-on-start": "應用開啟時繼續播放上次的歌曲",
|
||||
"single-instance-lock": "單實例模式",
|
||||
"start-at-login": "開機時啟動",
|
||||
"starting-page": {
|
||||
@ -142,8 +142,8 @@
|
||||
"label": "系統匣",
|
||||
"submenu": {
|
||||
"disabled": "已停用",
|
||||
"enabled-and-hide-app": "啟用並最小化應用程式",
|
||||
"enabled-and-show-app": "啟用但持續顯示應用程式",
|
||||
"enabled-and-hide-app": "啟用並最小化應用",
|
||||
"enabled-and-show-app": "啟用並顯示應用",
|
||||
"play-pause-on-click": "點擊時播放/暫停"
|
||||
}
|
||||
},
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "使用 16 倍速播放及靜音來跳過廣告片段",
|
||||
"name": "加速略過"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "阻擋所有廣告",
|
||||
"menu": {
|
||||
@ -219,7 +223,7 @@
|
||||
"name": "進階專輯操作"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "依歌曲色調自動更改應用程式主題",
|
||||
"description": "根據專輯封面色調更改應用程式主題顏色",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "顏色混合程度",
|
||||
@ -341,10 +345,10 @@
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "已嘗試可用連接",
|
||||
"connected": "已連接至Discord",
|
||||
"disconnected": "與Discord斷開連接"
|
||||
"connected": "已連接至 Discord",
|
||||
"disconnected": "與 Discord 斷開連接"
|
||||
},
|
||||
"description": "使用Discord狀態與你的好友分享你正在收聽的音樂",
|
||||
"description": "使用 Discord 狀態與你的好友分享你正在收聽的音樂",
|
||||
"menu": {
|
||||
"auto-reconnect": "自動重新連接",
|
||||
"clear-activity": "清除狀態",
|
||||
@ -352,11 +356,11 @@
|
||||
"connected": "已連接",
|
||||
"disconnected": "已斷開連接",
|
||||
"hide-duration-left": "隱藏音樂剩餘時間狀態",
|
||||
"hide-github-button": "隱藏Github頁面按鈕",
|
||||
"play-on-youtube-music": "顯示Play on YouTube Music按鈕",
|
||||
"hide-github-button": "隱藏 Github 頁面按鈕",
|
||||
"play-on-youtube-music": "顯示 Play on YouTube Music 按鈕",
|
||||
"set-inactivity-timeout": "設定閒置狀態時長"
|
||||
},
|
||||
"name": "Discord狀態",
|
||||
"name": "Discord 狀態",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "設定多少秒後清除狀態:",
|
||||
@ -407,9 +411,24 @@
|
||||
"writing-id3": "正在寫入 ID3 標籤…"
|
||||
}
|
||||
},
|
||||
"description": "在應用程式內下載 MP3/原始音樂檔",
|
||||
"description": "開啟應用程式內下載 MP3/原始音檔功能",
|
||||
"menu": {
|
||||
"choose-download-folder": "選擇下載位置",
|
||||
"download-finish-settings": {
|
||||
"label": "智慧下載",
|
||||
"prompt": {
|
||||
"last-percent": "歌曲剩餘多少 % 時下載",
|
||||
"last-seconds": "歌曲剩餘多少秒時下載",
|
||||
"title": "智慧下載進階設定"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "進階",
|
||||
"enabled": "啟用",
|
||||
"mode": "判斷方式",
|
||||
"percent": "百分比",
|
||||
"seconds": "秒數"
|
||||
}
|
||||
},
|
||||
"download-playlist": "下載播放清單",
|
||||
"presets": "預設格式",
|
||||
"skip-existing": "跳過已存在的檔案"
|
||||
@ -494,12 +513,12 @@
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "將上一頁/下一頁按鈕新增至應用程式上方, 就像你最熟悉的瀏覽器",
|
||||
"description": "允許應用程式上方顯示上一頁/下一頁按鈕",
|
||||
"name": "導覽列"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "移除Google登入按鈕及連結",
|
||||
"name": "停用Google登入"
|
||||
"description": "移除 Google 登入按鈕及連結",
|
||||
"name": "停用 Google 登入"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "在歌曲播放時發送一個系統通知 (可互動通知僅限Windows)",
|
||||
@ -649,8 +668,61 @@
|
||||
"description": "自動跳過贊助片段",
|
||||
"name": "贊助阻擋"
|
||||
},
|
||||
"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": {
|
||||
"description": "透過工作列應用程式圖式控制媒體播放",
|
||||
"description": "允許工作列應用程式預覽介面顯示媒體控制相關按鈕",
|
||||
"name": "工作列媒體控制"
|
||||
},
|
||||
"touchbar": {
|
||||
|
||||
62
src/index.ts
62
src/index.ts
@ -11,6 +11,7 @@ import {
|
||||
shell,
|
||||
dialog,
|
||||
ipcMain,
|
||||
type BrowserWindowConstructorOptions,
|
||||
} from 'electron';
|
||||
import enhanceWebRequest, {
|
||||
BetterSession,
|
||||
@ -100,9 +101,18 @@ if (config.get('options.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
|
||||
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')) {
|
||||
@ -277,6 +287,23 @@ async function createMainWindow() {
|
||||
height: 32,
|
||||
};
|
||||
|
||||
const decorations: Partial<BrowserWindowConstructorOptions> = {
|
||||
frame: !is.macOS() && !useInlineMenu,
|
||||
titleBarOverlay: defaultTitleBarOverlayOptions,
|
||||
titleBarStyle: useInlineMenu
|
||||
? 'hidden'
|
||||
: is.macOS()
|
||||
? 'hiddenInset'
|
||||
: 'default',
|
||||
autoHideMenuBar: config.get('options.hideMenu'),
|
||||
};
|
||||
|
||||
// Note: on linux, for some weird reason, having these extra properties with 'frame: false' does not work
|
||||
if (is.linux() && useInlineMenu) {
|
||||
delete decorations.titleBarOverlay;
|
||||
delete decorations.titleBarStyle;
|
||||
}
|
||||
|
||||
const win = new BrowserWindow({
|
||||
icon,
|
||||
width: windowSize.width,
|
||||
@ -294,14 +321,7 @@ async function createMainWindow() {
|
||||
sandbox: false,
|
||||
}),
|
||||
},
|
||||
frame: !is.macOS() && !useInlineMenu,
|
||||
titleBarOverlay: defaultTitleBarOverlayOptions,
|
||||
titleBarStyle: useInlineMenu
|
||||
? 'hidden'
|
||||
: is.macOS()
|
||||
? 'hiddenInset'
|
||||
: 'default',
|
||||
autoHideMenuBar: config.get('options.hideMenu'),
|
||||
...decorations,
|
||||
});
|
||||
initHook(win);
|
||||
initTheme(win);
|
||||
@ -312,28 +332,29 @@ async function createMainWindow() {
|
||||
const { x: windowX, y: windowY } = windowPosition;
|
||||
const winSize = win.getSize();
|
||||
const display = screen.getDisplayNearestPoint(windowPosition);
|
||||
const scaleFactor = is.windows() ? display.scaleFactor: 1;
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
|
||||
const scaledWidth = Math.floor(windowSize.width / scaleFactor);
|
||||
const scaledHeight = Math.floor(windowSize.height / scaleFactor);
|
||||
const scaleFactor = is.windows() ? primaryDisplay.scaleFactor / display.scaleFactor : 1;
|
||||
const scaledWidth = Math.floor(windowSize.width * scaleFactor);
|
||||
const scaledHeight = Math.floor(windowSize.height * scaleFactor);
|
||||
|
||||
const scaledX = windowX;
|
||||
const scaledY = windowY;
|
||||
|
||||
if (
|
||||
scaledX + scaledWidth < display.bounds.x - 8 ||
|
||||
scaledX - scaledWidth > display.bounds.x + display.bounds.width ||
|
||||
scaledY < display.bounds.y - 8 ||
|
||||
scaledY > display.bounds.y + display.bounds.height
|
||||
scaledX + (scaledWidth / 2) < display.bounds.x - 8 || // Left
|
||||
scaledX + (scaledWidth / 2) > display.bounds.x + display.bounds.width || // Right
|
||||
scaledY < display.bounds.y - 8 || // Top
|
||||
scaledY + (scaledHeight / 2) > display.bounds.y + display.bounds.height // Bottom
|
||||
) {
|
||||
// Window is offscreen
|
||||
if (is.dev()) {
|
||||
console.warn(
|
||||
LoggerPrefix,
|
||||
t('main.console.window.tried-to-render-offscreen', {
|
||||
winSize: String(winSize),
|
||||
displaySize: String(display.bounds),
|
||||
windowPosition: String(windowPosition),
|
||||
windowSize: String(winSize),
|
||||
displaySize: JSON.stringify(display.bounds),
|
||||
position: JSON.stringify(windowPosition),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -597,6 +618,7 @@ app.whenReady().then(async () => {
|
||||
shortcutDetails.target !== appLocation ||
|
||||
shortcutDetails.appUserModelId !== appID
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
||||
throw 'needUpdate';
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
56
src/plugins/adblocker/adSpeedup.ts
Normal file
56
src/plugins/adblocker/adSpeedup.ts
Normal 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);
|
||||
}
|
||||
@ -10,6 +10,7 @@ import {
|
||||
|
||||
import injectCliqzPreload from './injectors/inject-cliqz-preload';
|
||||
import { inject, isInjected } from './injectors/inject';
|
||||
import { loadAdSpeedup } from './adSpeedup';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
@ -72,6 +73,14 @@ export default createPlugin({
|
||||
},
|
||||
];
|
||||
},
|
||||
renderer: {
|
||||
async onPlayerApiReady(_, { getConfig }) {
|
||||
const config = await getConfig();
|
||||
if (config.blocker === blockers.AdSpeedup) {
|
||||
await loadAdSpeedup();
|
||||
}
|
||||
}
|
||||
},
|
||||
backend: {
|
||||
mainWindow: null as BrowserWindow | null,
|
||||
async start({ getConfig, window }) {
|
||||
@ -109,7 +118,19 @@ export default createPlugin({
|
||||
},
|
||||
},
|
||||
preload: {
|
||||
script: 'window.JSON.parse = window._proxyJsonParse; window._proxyJsonParse = undefined; window.Response.prototype.json = window._proxyResponseJson; window._proxyResponseJson = undefined; 0',
|
||||
// see #1478
|
||||
script: `const _prunerFn = window._pruner;
|
||||
window._pruner = undefined;
|
||||
JSON.parse = new Proxy(JSON.parse, {
|
||||
apply() {
|
||||
return _prunerFn(Reflect.apply(...arguments));
|
||||
},
|
||||
});
|
||||
Response.prototype.json = new Proxy(Response.prototype.json, {
|
||||
apply() {
|
||||
return Reflect.apply(...arguments).then((o) => _prunerFn(o));
|
||||
},
|
||||
}); 0`,
|
||||
async start({ getConfig }) {
|
||||
const config = await getConfig();
|
||||
|
||||
|
||||
@ -37,19 +37,9 @@ export const inject = (contextBridge) => {
|
||||
|
||||
//
|
||||
return o;
|
||||
};
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('_proxyJsonParse', new Proxy(JSON.parse, {
|
||||
apply() {
|
||||
return pruner(Reflect.apply(...arguments));
|
||||
},
|
||||
}));
|
||||
|
||||
contextBridge.exposeInMainWorld('_proxyResponseJson', new Proxy(Response.prototype.json, {
|
||||
apply() {
|
||||
return Reflect.apply(...arguments).then((o) => pruner(o));
|
||||
},
|
||||
}));
|
||||
contextBridge.exposeInMainWorld('_pruner', pruner);
|
||||
}
|
||||
|
||||
const chains = [
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export const blockers = {
|
||||
WithBlocklists: 'With blocklists',
|
||||
InPlayer: 'In player',
|
||||
AdSpeedup: 'Ad speedup',
|
||||
} as const;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
import undislikeHTML from './templates/undislike.html?raw';
|
||||
import dislikeHTML from './templates/dislike.html?raw';
|
||||
@ -16,7 +17,6 @@ export default createPlugin<
|
||||
changeObserver?: MutationObserver;
|
||||
waiting: boolean;
|
||||
onPageChange(): void;
|
||||
waitForElem(selector: string): Promise<HTMLElement>;
|
||||
loadFullList: (event: MouseEvent) => void;
|
||||
applyToList(id: string, loader: HTMLElement): void;
|
||||
start(): void;
|
||||
@ -50,7 +50,7 @@ export default createPlugin<
|
||||
} else {
|
||||
this.waiting = true;
|
||||
}
|
||||
const continuations = await this.waitForElem('#continuations');
|
||||
const continuations = await waitForElement<HTMLElement>('#continuations');
|
||||
this.waiting = false;
|
||||
//Gets the for buttons
|
||||
const buttons: Array<HTMLElement> = [
|
||||
@ -112,8 +112,13 @@ export default createPlugin<
|
||||
i++;
|
||||
}
|
||||
}
|
||||
const menu = document.querySelector('.detail-page-menu');
|
||||
if (menu && !document.querySelector('.like-menu')) {
|
||||
const menuParent = document.querySelector('#action-buttons')?.parentElement;
|
||||
if (menuParent && !document.querySelector('.like-menu')) {
|
||||
const menu = document.createElement('div');
|
||||
menu.id = 'ytmd-album-action-buttons';
|
||||
menu.className = 'action-buttons style-scope ytmusic-responsive-header-renderer';
|
||||
|
||||
menuParent.insertBefore(menu, menuParent.children[menuParent.children.length - 1]);
|
||||
for (const button of buttons) {
|
||||
menu.appendChild(button);
|
||||
button.addEventListener('click', this.loadFullList);
|
||||
@ -178,16 +183,5 @@ export default createPlugin<
|
||||
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);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,40 +1,58 @@
|
||||
<button
|
||||
id="alldislike"
|
||||
data-type="dislike"
|
||||
data-filled="false"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Dislike all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
<div class="style-scope">
|
||||
<button
|
||||
id="alldislike"
|
||||
data-type="dislike"
|
||||
data-filled="false"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Dislike all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
@ -45,30 +63,14 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,40 +1,58 @@
|
||||
<button
|
||||
id="alllike"
|
||||
data-type="like"
|
||||
data-filled="false"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Like all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
<div class="style-scope">
|
||||
<button
|
||||
id="alllike"
|
||||
data-type="like"
|
||||
data-filled="false"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Like all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
@ -45,30 +63,14 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,40 +1,58 @@
|
||||
<button
|
||||
id="allundislike"
|
||||
data-type="dislike"
|
||||
data-filled="true"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Undislike all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
<div class="style-scope">
|
||||
<button
|
||||
id="allundislike"
|
||||
data-type="dislike"
|
||||
data-filled="true"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Undislike all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
@ -45,30 +63,14 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,40 +1,58 @@
|
||||
<button
|
||||
id="allunlike"
|
||||
data-type="like"
|
||||
data-filled="true"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Unlike all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
<div class="style-scope">
|
||||
<button
|
||||
id="allunlike"
|
||||
data-type="like"
|
||||
data-filled="true"
|
||||
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||
aria-pressed="false"
|
||||
aria-label="Unlike all"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="yt-spec-button-shape-next__icon"
|
||||
style="
|
||||
color: white;
|
||||
-webkit-mask: linear-gradient(grey, grey);
|
||||
-webkit-mask-size: 100% 50%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
@ -45,30 +63,14 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 24px; height: 24px">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
|
||||
class="style-scope yt-icon"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||
<div
|
||||
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||
</div>
|
||||
</yt-touch-feedback-shape>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,7 @@ import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { menu } from './menu';
|
||||
import { AmbientModePluginConfig } from './types';
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
const defaultConfig: AmbientModePluginConfig = {
|
||||
enabled: false,
|
||||
@ -36,7 +37,7 @@ export default createPlugin({
|
||||
unregister: null as (() => void) | null,
|
||||
update: null as (() => void) | null,
|
||||
interval: null as NodeJS.Timeout | null,
|
||||
lastMediaType: null as "video" | "image" | null,
|
||||
lastMediaType: null as 'video' | 'image' | null,
|
||||
lastVideoSource: null as string | null,
|
||||
lastImageSource: null as string | null,
|
||||
|
||||
@ -53,7 +54,8 @@ export default createPlugin({
|
||||
const songImage = document.querySelector<HTMLImageElement>('#song-image');
|
||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||
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 injectBlurImage = () => {
|
||||
@ -179,12 +181,12 @@ export default createPlugin({
|
||||
const isVideoMode = () => {
|
||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||
if (!songVideo) {
|
||||
this.lastMediaType = "image";
|
||||
this.lastMediaType = 'image';
|
||||
return false;
|
||||
}
|
||||
|
||||
const isVideo = getComputedStyle(songVideo).display !== 'none';
|
||||
this.lastMediaType = isVideo ? "video" : "image";
|
||||
this.lastMediaType = isVideo ? 'video' : 'image';
|
||||
return isVideo;
|
||||
};
|
||||
|
||||
@ -196,8 +198,8 @@ export default createPlugin({
|
||||
if (isPageOpen) {
|
||||
const isVideo = isVideoMode();
|
||||
if (!force) {
|
||||
if (this.lastMediaType === "video" && this.lastVideoSource === video?.src) return false;
|
||||
if (this.lastMediaType === "image" && this.lastImageSource === image?.src) return false;
|
||||
if (this.lastMediaType === 'video' && this.lastVideoSource === video?.src) return false;
|
||||
if (this.lastMediaType === 'image' && this.lastImageSource === image?.src) return false;
|
||||
}
|
||||
this.unregister?.();
|
||||
this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||
@ -205,7 +207,7 @@ export default createPlugin({
|
||||
this.unregister?.();
|
||||
this.unregister = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* needed for switching between different views (e.g. miniplayer) */
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
|
||||
@ -2,11 +2,12 @@ import { app, dialog, ipcMain } from 'electron';
|
||||
import { Client as DiscordClient } from '@xhayper/discord-rpc';
|
||||
import { dev } from 'electron-is';
|
||||
|
||||
import { ActivityType, GatewayActivityButton } from 'discord-api-types/v10';
|
||||
|
||||
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
||||
import { createBackend, LoggerPrefix } from '@/utils';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { GatewayActivityButton } from 'discord-api-types/v10';
|
||||
import type { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
|
||||
import type { DiscordPluginConfig } from './index';
|
||||
|
||||
@ -180,6 +181,7 @@ export const backend = createBackend<
|
||||
}
|
||||
|
||||
const activityInfo: SetActivity = {
|
||||
type: ActivityType.Listening,
|
||||
details: songInfo.title,
|
||||
state: songInfo.artist,
|
||||
largeImageKey: songInfo.imageSrc ?? '',
|
||||
@ -207,8 +209,8 @@ export const backend = createBackend<
|
||||
|
||||
info.rpc.user?.setActivity(activityInfo).catch(console.error);
|
||||
},
|
||||
async start({ window: win, getConfig }) {
|
||||
this.config = await getConfig();
|
||||
async start(ctx) {
|
||||
this.config = await ctx.getConfig();
|
||||
|
||||
info.rpc.on('connected', () => {
|
||||
if (dev()) {
|
||||
@ -237,10 +239,10 @@ export const backend = createBackend<
|
||||
|
||||
info.autoReconnect = this.config.autoReconnect;
|
||||
|
||||
window = win;
|
||||
window = ctx.window;
|
||||
|
||||
// If the page is ready, register the callback
|
||||
win.once('ready-to-show', () => {
|
||||
ctx.window.once('ready-to-show', () => {
|
||||
let lastSongInfo: SongInfo;
|
||||
registerCallback((songInfo) => {
|
||||
lastSongInfo = songInfo;
|
||||
|
||||
@ -261,12 +261,12 @@ async function downloadSongUnsafe(
|
||||
|
||||
let playabilityStatus = info.playability_status;
|
||||
let bypassedResult = null;
|
||||
if (playabilityStatus.status === 'LOGIN_REQUIRED') {
|
||||
if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
|
||||
// Try to bypass the age restriction
|
||||
bypassedResult = await getAndroidTvInfo(id);
|
||||
playabilityStatus = bypassedResult.playability_status;
|
||||
|
||||
if (playabilityStatus.status === 'LOGIN_REQUIRED') {
|
||||
if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
|
||||
throw new Error(
|
||||
`[${playabilityStatus.status}] ${playabilityStatus.reason}`,
|
||||
);
|
||||
@ -275,7 +275,7 @@ async function downloadSongUnsafe(
|
||||
info = bypassedResult;
|
||||
}
|
||||
|
||||
if (playabilityStatus.status === 'UNPLAYABLE') {
|
||||
if (playabilityStatus?.status === 'UNPLAYABLE') {
|
||||
const errorScreen =
|
||||
playabilityStatus.error_screen as PlayerErrorMessage | null;
|
||||
throw new Error(
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { JSX } from 'solid-js';
|
||||
import { css } from 'solid-styled-components';
|
||||
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const iconButton = cache(() => css`
|
||||
const iconButton = cacheNoArgs(() => css`
|
||||
-webkit-app-region: none;
|
||||
|
||||
background: transparent;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { JSX, splitProps } from 'solid-js';
|
||||
import { css } from 'solid-styled-components';
|
||||
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const menuStyle = cache(() => css`
|
||||
const menuStyle = cacheNoArgs(() => css`
|
||||
-webkit-app-region: none;
|
||||
|
||||
display: flex;
|
||||
|
||||
@ -5,9 +5,9 @@ import { Transition } from 'solid-transition-group';
|
||||
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
|
||||
import { useFloating } from 'solid-floating-ui';
|
||||
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const panelStyle = cache(() => css`
|
||||
const panelStyle = cacheNoArgs(() => css`
|
||||
position: fixed;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
@ -36,7 +36,7 @@ const panelStyle = cache(() => css`
|
||||
transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
|
||||
`);
|
||||
|
||||
const animationStyle = cache(() => ({
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
|
||||
@ -8,9 +8,9 @@ import { useFloating } from 'solid-floating-ui';
|
||||
import { autoUpdate, offset, size } from '@floating-ui/dom';
|
||||
|
||||
import { Panel } from './Panel';
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const itemStyle = cache(() => css`
|
||||
const itemStyle = cacheNoArgs(() => css`
|
||||
position: relative;
|
||||
|
||||
-webkit-app-region: none;
|
||||
@ -47,18 +47,18 @@ const itemStyle = cache(() => css`
|
||||
}
|
||||
`);
|
||||
|
||||
const itemIconStyle = cache(() => css`
|
||||
const itemIconStyle = cacheNoArgs(() => css`
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
color: white;
|
||||
`);
|
||||
|
||||
const itemLabelStyle = cache(() => css`
|
||||
const itemLabelStyle = cacheNoArgs(() => css`
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
`);
|
||||
|
||||
const itemChipStyle = cache(() => css`
|
||||
const itemChipStyle = cacheNoArgs(() => css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -76,7 +76,7 @@ const itemChipStyle = cache(() => css`
|
||||
line-height: 1;
|
||||
`);
|
||||
|
||||
const toolTipStyle = cache(() => css`
|
||||
const toolTipStyle = cacheNoArgs(() => css`
|
||||
min-width: 32px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -92,7 +92,7 @@ const toolTipStyle = cache(() => css`
|
||||
font-size: 10px;
|
||||
`);
|
||||
|
||||
const popupStyle = cache(() => css`
|
||||
const popupStyle = cacheNoArgs(() => css`
|
||||
position: fixed;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
@ -105,7 +105,7 @@ const popupStyle = cache(() => css`
|
||||
|
||||
`);
|
||||
|
||||
const animationStyle = cache(() => ({
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
|
||||
@ -9,12 +9,12 @@ import { PanelItem } from './PanelItem';
|
||||
import { IconButton } from './IconButton';
|
||||
import { WindowController } from './WindowController';
|
||||
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
import type { InAppMenuConfig } from '../constants';
|
||||
|
||||
const titleStyle = cache(() => css`
|
||||
const titleStyle = cacheNoArgs(() => css`
|
||||
-webkit-app-region: drag;
|
||||
box-sizing: border-box;
|
||||
|
||||
@ -50,7 +50,7 @@ const titleStyle = cache(() => css`
|
||||
}
|
||||
`);
|
||||
|
||||
const separatorStyle = cache(() => css`
|
||||
const separatorStyle = cacheNoArgs(() => css`
|
||||
min-height: 1px;
|
||||
height: 1px;
|
||||
margin: 4px 0;
|
||||
@ -58,7 +58,7 @@ const separatorStyle = cache(() => css`
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
`);
|
||||
|
||||
const animationStyle = cache(() => ({
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) scale(0.8);
|
||||
@ -271,19 +271,18 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
// tracking mouse position
|
||||
window.addEventListener('mousemove', listener);
|
||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
ytmusicAppLayout?.addEventListener("scroll",()=>{
|
||||
ytmusicAppLayout?.addEventListener('scroll', () => {
|
||||
const scrollValue = ytmusicAppLayout.scrollTop;
|
||||
if (scrollValue > 20){
|
||||
ytmusicAppLayout.classList.add("content-scrolled");
|
||||
ytmusicAppLayout.classList.add('content-scrolled');
|
||||
}
|
||||
else{
|
||||
ytmusicAppLayout.classList.remove("content-scrolled");
|
||||
ytmusicAppLayout.classList.remove('content-scrolled');
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
createEffect(() => {
|
||||
if (!menu() && data()) {
|
||||
|
||||
@ -2,9 +2,9 @@ import { css } from 'solid-styled-components';
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
import { IconButton } from './IconButton';
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const containerStyle = cache(() => css`
|
||||
const containerStyle = cacheNoArgs(() => css`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
@ -77,6 +77,7 @@ export const onRendererLoad = ({
|
||||
applyLyricsTabState();
|
||||
}
|
||||
};
|
||||
|
||||
const applyLyricsTabState = () => {
|
||||
if (lyrics) {
|
||||
tabs.lyrics.removeAttribute('disabled');
|
||||
@ -86,6 +87,7 @@ export const onRendererLoad = ({
|
||||
tabs.lyrics.setAttribute('aria-disabled', '');
|
||||
}
|
||||
};
|
||||
|
||||
const lyricsTabHandler = () => {
|
||||
const tabContainer = document.querySelector('ytmusic-tab-renderer');
|
||||
if (!tabContainer) return;
|
||||
|
||||
@ -166,13 +166,13 @@ export default createPlugin<
|
||||
this.profiles = {};
|
||||
this.connection.onConnections((connection) => {
|
||||
if (!connection) {
|
||||
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.disconnected'));
|
||||
this.onStop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connection.open) {
|
||||
this.api?.openToast(t('plugins.music-together.toast.user-disconnected', {
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.user-disconnected', {
|
||||
name: this.profiles[connection.peer]?.name
|
||||
}));
|
||||
this.putProfile(connection.peer, undefined);
|
||||
@ -219,7 +219,7 @@ export default createPlugin<
|
||||
break;
|
||||
}
|
||||
|
||||
this.api?.openToast(t('plugins.music-together.toast.user-connected', { name: event.payload.profile.name }));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.user-connected', { name: event.payload.profile.name }));
|
||||
this.putProfile(conn.peer, event.payload.profile);
|
||||
break;
|
||||
}
|
||||
@ -307,7 +307,7 @@ export default createPlugin<
|
||||
if (!connection) return false;
|
||||
this.connection.onConnections((connection) => {
|
||||
if (!connection?.open) {
|
||||
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.disconnected'));
|
||||
this.onStop();
|
||||
}
|
||||
});
|
||||
@ -381,7 +381,7 @@ export default createPlugin<
|
||||
|
||||
const permissionLabel = t(`plugins.music-together.menu.permission.${this.permission}`);
|
||||
|
||||
this.api?.openToast(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -593,18 +593,18 @@ export default createPlugin<
|
||||
onItemClick: (id) => {
|
||||
if (id === 'music-together-close') {
|
||||
this.onStop();
|
||||
this.api?.openToast(t('plugins.music-together.toast.closed'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.closed'));
|
||||
hostPopup.dismiss();
|
||||
}
|
||||
|
||||
if (id === 'music-together-copy-id') {
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||
.then(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.dismiss();
|
||||
})
|
||||
.catch(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.id-copy-failed'));
|
||||
hostPopup.dismiss();
|
||||
});
|
||||
}
|
||||
@ -620,7 +620,7 @@ export default createPlugin<
|
||||
settingPopup.setPermission(this.permission);
|
||||
|
||||
const permissionLabel = t(`plugins.music-together.menu.permission.${this.permission}`);
|
||||
this.api?.openToast(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
|
||||
const item = hostPopup.items.find((it) => it?.element.id === id);
|
||||
if (item?.type === 'item') {
|
||||
item.setText(t('plugins.music-together.menu.set-permission'));
|
||||
@ -632,7 +632,7 @@ export default createPlugin<
|
||||
onItemClick: (id) => {
|
||||
if (id === 'music-together-disconnect') {
|
||||
this.onStop();
|
||||
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.disconnected'));
|
||||
guestPopup.dismiss();
|
||||
}
|
||||
}
|
||||
@ -648,14 +648,14 @@ export default createPlugin<
|
||||
if (result) {
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||
.then(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.showAtAnchor(setting);
|
||||
}).catch(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.id-copy-failed'));
|
||||
hostPopup.showAtAnchor(setting);
|
||||
});
|
||||
} else {
|
||||
this.api?.openToast(t('plugins.music-together.toast.host-failed'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.host-failed'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -666,10 +666,10 @@ export default createPlugin<
|
||||
this.hideSpinner();
|
||||
|
||||
if (result) {
|
||||
this.api?.openToast(t('plugins.music-together.toast.joined'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.joined'));
|
||||
guestPopup.showAtAnchor(setting);
|
||||
} else {
|
||||
this.api?.openToast(t('plugins.music-together.toast.join-failed'));
|
||||
this.api?.toastService?.show(t('plugins.music-together.toast.join-failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,9 +55,6 @@ const observePopupContainer = () => {
|
||||
|
||||
if (
|
||||
menu &&
|
||||
(
|
||||
menu.parentElement as HTMLElement & { eventSink_: Element | null }
|
||||
)?.eventSink_?.matches('ytmusic-menu-renderer.ytmusic-player-bar') &&
|
||||
!menu.contains(slider)
|
||||
) {
|
||||
menu.prepend(slider);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BrowserWindow, ipcMain, globalShortcut } from 'electron';
|
||||
import { BrowserWindow, globalShortcut } from 'electron';
|
||||
import is from 'electron-is';
|
||||
import { register as registerElectronLocalShortcut } from 'electron-localshortcut';
|
||||
|
||||
@ -48,9 +48,7 @@ export const onMainLoad = async ({
|
||||
_registerLocalShortcut(window, 'CommandOrControl+L', search);
|
||||
|
||||
if (is.linux()) {
|
||||
ipcMain.once('ytmd:video-src-changed', (_) => {
|
||||
registerMPRIS(window);
|
||||
});
|
||||
registerMPRIS(window);
|
||||
}
|
||||
|
||||
const { global, local } = config;
|
||||
|
||||
@ -102,18 +102,21 @@ function registerMPRIS(win: BrowserWindow) {
|
||||
return videoId.replace(/-/g, '_MINUS_');
|
||||
};
|
||||
|
||||
const player = setupMPRIS();
|
||||
|
||||
const seekTo = (event: Position) => {
|
||||
if (
|
||||
currentSongInfo?.videoId &&
|
||||
event.trackId.endsWith(correctId(currentSongInfo.videoId))
|
||||
) {
|
||||
win.webContents.send('ytmd:seek-to', microToSec(event.position ?? 0));
|
||||
player.setPosition(event.position ?? 0);
|
||||
}
|
||||
};
|
||||
const seekBy = (offset: number) =>
|
||||
const seekBy = (offset: number) => {
|
||||
win.webContents.send('ytmd:seek-by', microToSec(offset));
|
||||
|
||||
const player = setupMPRIS();
|
||||
player.setPosition(player.getPosition() + offset);
|
||||
};
|
||||
|
||||
ipcMain.on('ytmd:player-api-loaded', () => {
|
||||
win.webContents.send('ytmd:setup-seeked-listener', 'mpris');
|
||||
@ -126,7 +129,10 @@ function registerMPRIS(win: BrowserWindow) {
|
||||
requestQueueInformation();
|
||||
});
|
||||
|
||||
ipcMain.on('ytmd:seeked', (_, t: number) => player.seeked(secToMicro(t)));
|
||||
ipcMain.on('ytmd:seeked', (_, t: number) => {
|
||||
player.setPosition(secToMicro(t));
|
||||
player.seeked(secToMicro(t));
|
||||
});
|
||||
|
||||
ipcMain.on('ytmd:time-changed', (_, t: number) => {
|
||||
player.setPosition(secToMicro(t));
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
export default createPlugin<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
observer?: MutationObserver;
|
||||
waitForElem(selector: string): Promise<HTMLElement>;
|
||||
start(): void;
|
||||
stop(): void;
|
||||
}
|
||||
@ -15,19 +15,8 @@ export default createPlugin<
|
||||
description: () => t('plugins.skip-disliked-songs.description'),
|
||||
restartNeeded: false,
|
||||
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() {
|
||||
this.waitForElem('#dislike-button-renderer').then((dislikeBtn) => {
|
||||
waitForElement<HTMLElement>('#dislike-button-renderer').then((dislikeBtn) => {
|
||||
this.observer = new MutationObserver(() => {
|
||||
if (dislikeBtn?.getAttribute('like-status') == 'DISLIKE') {
|
||||
document
|
||||
|
||||
28
src/plugins/synced-lyrics/index.ts
Normal file
28
src/plugins/synced-lyrics/index.ts
Normal 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],
|
||||
});
|
||||
138
src/plugins/synced-lyrics/menu.ts
Normal file
138
src/plugins/synced-lyrics/menu.ts
Normal 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,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
53
src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx
Normal file
53
src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
79
src/plugins/synced-lyrics/renderer/index.ts
Normal file
79
src/plugins/synced-lyrics/renderer/index.ts
Normal file
@ -0,0 +1,79 @@
|
||||
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;
|
||||
observer?: MutationObserver;
|
||||
videoDataChange: () => Promise<void>;
|
||||
updateTimestampInterval?: NodeJS.Timeout | string | number;
|
||||
}, 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();
|
||||
},
|
||||
|
||||
async videoDataChange() {
|
||||
if (!this.updateTimestampInterval) {
|
||||
this.updateTimestampInterval = setInterval(
|
||||
() => setCurrentTime((_ytAPI?.getCurrentTime() ?? 0) * 1000),
|
||||
100,
|
||||
);
|
||||
}
|
||||
|
||||
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');
|
||||
},
|
||||
|
||||
async start(ctx: RendererContext<SyncedLyricsPluginConfig>) {
|
||||
setConfig(await ctx.getConfig());
|
||||
|
||||
ctx.ipc.on('ytmd:update-song-info', async (info: SongInfo) => {
|
||||
await makeLyricsRequest(info);
|
||||
});
|
||||
},
|
||||
});
|
||||
202
src/plugins/synced-lyrics/renderer/lyrics/fetch.ts
Normal file
202
src/plugins/synced-lyrics/renderer/lyrics/fetch.ts
Normal file
@ -0,0 +1,202 @@
|
||||
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 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: `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${millis}`,
|
||||
text: text?.trim() ?? config()!.defaultTextString,
|
||||
status: 'upcoming',
|
||||
duration: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => {
|
||||
setIsFetching(true);
|
||||
setLineLyrics([]);
|
||||
|
||||
const songData: Parameters<typeof getLyricsList>[0] = {
|
||||
title: `${extractedSongInfo.title}`,
|
||||
artist: `${extractedSongInfo.artist}`,
|
||||
songDuration: extractedSongInfo.songDuration,
|
||||
};
|
||||
|
||||
if (extractedSongInfo.album) {
|
||||
songData.album = extractedSongInfo.album;
|
||||
}
|
||||
|
||||
let lyrics;
|
||||
try {
|
||||
lyrics = await getLyricsList(songData);
|
||||
} catch {}
|
||||
|
||||
setLineLyrics(lyrics ?? []);
|
||||
setIsFetching(false);
|
||||
};
|
||||
|
||||
export const getLyricsList = async (
|
||||
songData: Pick<SongInfo, 'title' | 'artist' | 'album' | 'songDuration'>,
|
||||
): Promise<LineLyrics[] | null> => {
|
||||
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) {
|
||||
setDebugInfo('Got non-OK response from server.');
|
||||
return null;
|
||||
}
|
||||
|
||||
let data = await response.json() as LRCLIBSearchResponse;
|
||||
if (!data || !Array.isArray(data)) {
|
||||
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) {
|
||||
setDebugInfo('Got non-OK response from server. (2)');
|
||||
return null;
|
||||
}
|
||||
|
||||
data = (await response.json()) as LRCLIBSearchResponse;
|
||||
if (!Array.isArray(data)) {
|
||||
setDebugInfo('Unexpected server response. (2)');
|
||||
return null;
|
||||
}
|
||||
|
||||
setHadSecondAttempt(true);
|
||||
}
|
||||
|
||||
const filteredResults = [];
|
||||
for (const item of data) {
|
||||
const { artist } = songData;
|
||||
const { artistName } = item;
|
||||
|
||||
const artists = artist.split(/[&,]/g).map((i) => i.trim());
|
||||
const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim());
|
||||
|
||||
const permutations = artists.flatMap((artistA) =>
|
||||
itemArtists.map((artistB) => [artistA.toLowerCase(), artistB.toLowerCase()])
|
||||
);
|
||||
|
||||
const ratio = Math.max(...permutations.map(([x, y]) => jaroWinkler(x, y)));
|
||||
if (ratio > 0.9) 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) {
|
||||
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);
|
||||
if (closestResult.instrumental) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Separate the lyrics into lines
|
||||
const raw = closestResult.syncedLyrics?.split('\n') ?? [];
|
||||
if (!raw.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add a blank line at the beginning
|
||||
raw.unshift('[0:0.0] ');
|
||||
|
||||
const syncedLyricList = raw.reduce<LineLyrics[]>((acc, line, index) => {
|
||||
const syncedLine = extractTimeAndText(line, index);
|
||||
if (syncedLine) {
|
||||
acc.push(syncedLine);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
for (const line of syncedLyricList) {
|
||||
const next = syncedLyricList[line.index + 1];
|
||||
if (!next) {
|
||||
line.duration = Infinity;
|
||||
break;
|
||||
}
|
||||
|
||||
line.duration = next.timeInMs - line.timeInMs;
|
||||
}
|
||||
|
||||
return syncedLyricList;
|
||||
};
|
||||
44
src/plugins/synced-lyrics/renderer/lyrics/index.ts
Normal file
44
src/plugins/synced-lyrics/renderer/lyrics/index.ts
Normal 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;
|
||||
}
|
||||
});
|
||||
19
src/plugins/synced-lyrics/renderer/renderer.tsx
Normal file
19
src/plugins/synced-lyrics/renderer/renderer.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
39
src/plugins/synced-lyrics/renderer/utils.tsx
Normal file
39
src/plugins/synced-lyrics/renderer/utils.tsx
Normal 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);
|
||||
},
|
||||
};
|
||||
78
src/plugins/synced-lyrics/style.css
Normal file
78
src/plugins/synced-lyrics/style.css
Normal 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;
|
||||
}
|
||||
38
src/plugins/synced-lyrics/types.ts
Normal file
38
src/plugins/synced-lyrics/types.ts
Normal 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;
|
||||
}[];
|
||||
@ -1,10 +1,12 @@
|
||||
import { type NativeImage, TouchBar } from 'electron';
|
||||
import { nativeImage, type NativeImage, TouchBar } from 'electron';
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
import getSongControls from '@/providers/song-controls';
|
||||
import registerCallback from '@/providers/song-info';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import youtubeMusicIcon from '@assets/youtube-music.png?asset&asarUnpack';
|
||||
|
||||
export default createPlugin({
|
||||
name: () => t('plugins.touchbar.name'),
|
||||
description: () => t('plugins.touchbar.description'),
|
||||
@ -89,9 +91,9 @@ export default createPlugin({
|
||||
pausePlayButton.label = songInfo.isPaused ? '▶️' : '⏸';
|
||||
|
||||
// Get image source
|
||||
songImage.icon = songInfo.image
|
||||
? songInfo.image.resize({ height: 23 })
|
||||
: undefined;
|
||||
songImage.icon = (
|
||||
songInfo.image ? songInfo.image : nativeImage.createFromPath(youtubeMusicIcon)
|
||||
).resize({ height: 23 });
|
||||
|
||||
window.setTouchBar(touchBar);
|
||||
});
|
||||
|
||||
@ -40,6 +40,16 @@ export function cache<T extends (...params: P) => R, P extends never[], R>(
|
||||
}) as T;
|
||||
}
|
||||
|
||||
export function cacheNoArgs<R>(fn: () => R): () => R {
|
||||
let cached: R;
|
||||
return () => {
|
||||
if (cached === undefined) {
|
||||
cached = fn();
|
||||
}
|
||||
return cached;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
The following are currently unused, but potentially useful in the future
|
||||
*/
|
||||
|
||||
@ -17,7 +17,3 @@ export const startingPages: Record<string, string> = {
|
||||
'Uploaded Albums': 'FEmusic_library_privately_owned_releases',
|
||||
'Uploaded Artists': 'FEmusic_library_privately_owned_artists',
|
||||
};
|
||||
|
||||
export default {
|
||||
startingPages,
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import { singleton } from './decorators';
|
||||
|
||||
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||
import type { GetState } from '@/types/datahost-get-state';
|
||||
import type { AlbumDetails, VideoDataChangeValue } from '@/types/player-api-events';
|
||||
import type { AlbumDetails, PlayerOverlays, VideoDataChangeValue } from '@/types/player-api-events';
|
||||
|
||||
import type { SongInfo } from './song-info';
|
||||
import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||
@ -200,16 +200,43 @@ export default (api: YoutubePlayer) => {
|
||||
for (const status of ['playing', 'pause'] as const) {
|
||||
video.addEventListener(status, playPausedHandlers[status]);
|
||||
}
|
||||
|
||||
if (!isNaN(video.duration)) {
|
||||
const {
|
||||
title, author,
|
||||
video_id: videoId,
|
||||
list: playlistId
|
||||
} = api.getVideoData();
|
||||
|
||||
const watchNextResponse = api.getWatchNextResponse();
|
||||
|
||||
sendSongInfo({
|
||||
title, author, videoId, playlistId,
|
||||
|
||||
isUpcoming: false,
|
||||
lengthSeconds: video.duration,
|
||||
loading: true,
|
||||
|
||||
ytmdWatchNextResponse: watchNextResponse,
|
||||
} satisfies VideoDataChangeValue);
|
||||
}
|
||||
}
|
||||
|
||||
function sendSongInfo(videoData: VideoDataChangeValue) {
|
||||
const data = api.getPlayerResponse();
|
||||
|
||||
data.videoDetails.album = (
|
||||
Object.entries(videoData).find(
|
||||
([, value]) => value && Object.hasOwn(value, 'playerOverlays'),
|
||||
) as [string, AlbumDetails | undefined]
|
||||
)?.[1]?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album?.runs?.at(
|
||||
let playerOverlay: PlayerOverlays | undefined;
|
||||
|
||||
if (!videoData.ytmdWatchNextResponse) {
|
||||
playerOverlay = (
|
||||
Object.entries(videoData).find(
|
||||
([, value]) => value && Object.hasOwn(value, 'playerOverlays'),
|
||||
) as [string, AlbumDetails | undefined]
|
||||
)?.[1]?.playerOverlays;
|
||||
} else {
|
||||
playerOverlay = videoData.ytmdWatchNextResponse?.playerOverlays;
|
||||
}
|
||||
data.videoDetails.album = playerOverlay?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album?.runs?.at(
|
||||
0,
|
||||
)?.text;
|
||||
data.videoDetails.elapsedSeconds = 0;
|
||||
|
||||
@ -214,14 +214,14 @@ const suffixesToRemove = [
|
||||
/\s*vevo$/i,
|
||||
|
||||
// Video titles
|
||||
/\s*[(|\[]official(.*?)[)|\]]/i, // (Official Music Video), [Official Visualizer], etc...
|
||||
/\s*[(|\[]((lyrics?|visualizer|audio)\s*(video)?)[)|\]]/i,
|
||||
/\s*[(|\[](performance video)[)|\]]/i,
|
||||
/\s*[(|\[](clip official)[)|\]]/i,
|
||||
/\s*[(|\[](video version)[)|\]]/i,
|
||||
/\s*[(|\[](HD|HQ)\s*?(?:audio)?[)|\]]$/i,
|
||||
/\s*[(|\[](live)[)|\]]$/i,
|
||||
/\s*[(|\[]4K\s*?(?:upgrade)?[)|\]]$/i,
|
||||
/\s*[(|[]official(.*?)[)|\]]/i, // (Official Music Video), [Official Visualizer], etc...
|
||||
/\s*[(|[]((lyrics?|visualizer|audio)\s*(video)?)[)|\]]/i,
|
||||
/\s*[(|[](performance video)[)|\]]/i,
|
||||
/\s*[(|[](clip official)[)|\]]/i,
|
||||
/\s*[(|[](video version)[)|\]]/i,
|
||||
/\s*[(|[](HD|HQ)\s*?(?:audio)?[)|\]]$/i,
|
||||
/\s*[(|[](live)[)|\]]$/i,
|
||||
/\s*[(|[]4K\s*?(?:upgrade)?[)|\]]$/i,
|
||||
];
|
||||
|
||||
export function cleanupName(name: string): string {
|
||||
|
||||
@ -23,6 +23,14 @@ let isPluginLoaded = false;
|
||||
let isApiLoaded = false;
|
||||
let firstDataLoaded = false;
|
||||
|
||||
if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {
|
||||
window.trustedTypes.createPolicy('default', {
|
||||
createHTML: (input) => input,
|
||||
createScriptURL: (input) => input,
|
||||
createScript: (input) => input,
|
||||
});
|
||||
}
|
||||
|
||||
async function listenForApiLoad() {
|
||||
if (!isApiLoaded) {
|
||||
api = document.querySelector('#movie_player');
|
||||
@ -35,7 +43,7 @@ async function listenForApiLoad() {
|
||||
}
|
||||
|
||||
interface YouTubeMusicAppElement extends HTMLElement {
|
||||
navigate_(page: string): void;
|
||||
navigate(page: string): void;
|
||||
}
|
||||
|
||||
async function onApiLoaded() {
|
||||
@ -167,15 +175,24 @@ async function onApiLoaded() {
|
||||
if (startingPage && startingPages[startingPage]) {
|
||||
document
|
||||
.querySelector<YouTubeMusicAppElement>('ytmusic-app')
|
||||
?.navigate_(startingPages[startingPage]);
|
||||
?.navigate(startingPages[startingPage]);
|
||||
}
|
||||
|
||||
// Remove upgrade button
|
||||
if (window.mainConfig.get('options.removeUpgradeButton')) {
|
||||
const itemsSelector = 'ytmusic-guide-section-renderer #items';
|
||||
let selector = 'ytmusic-guide-entry-renderer:last-child';
|
||||
|
||||
const upgradeBtnIcon = document.querySelector<SVGGElement>('iron-iconset-svg[name="yt-sys-icons"] #youtube_music_monochrome');
|
||||
if (upgradeBtnIcon) {
|
||||
const path = upgradeBtnIcon.firstChild as SVGPathElement;
|
||||
const data = path.getAttribute('d')!.substring(0, 15);
|
||||
selector = `ytmusic-guide-entry-renderer:has(> tp-yt-paper-item > yt-icon path[d^="${data}"])`;
|
||||
}
|
||||
|
||||
const styles = document.createElement('style');
|
||||
styles.innerHTML = `ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer:last-child {
|
||||
display: none;
|
||||
}`;
|
||||
styles.textContent = `${itemsSelector} ${selector} { display: none; }`;
|
||||
|
||||
document.head.appendChild(styles);
|
||||
}
|
||||
|
||||
|
||||
2
src/reset.d.ts
vendored
2
src/reset.d.ts
vendored
@ -6,6 +6,7 @@ import type is from 'electron-is';
|
||||
import type config from './config';
|
||||
import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||
import type { t } from '@/i18n';
|
||||
import type { trustedTypes } from 'trusted-types';
|
||||
|
||||
declare global {
|
||||
interface Compressor {
|
||||
@ -19,6 +20,7 @@ declare global {
|
||||
}
|
||||
|
||||
interface Window {
|
||||
trustedTypes?: typeof trustedTypes;
|
||||
ipcRenderer: typeof electronIpcRenderer;
|
||||
mainConfig: typeof config;
|
||||
electronIs: typeof is;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { WatchNextResponse } from '@/types/youtube-music-desktop-internal';
|
||||
|
||||
export interface AlbumDetails {
|
||||
responseContext: ResponseContext;
|
||||
contents: Contents;
|
||||
@ -241,6 +243,7 @@ export interface FlagEndpoint {
|
||||
flagAction: string;
|
||||
}
|
||||
|
||||
// see song-info-front.ts
|
||||
export type VideoDataChangeValue = Record<string, unknown> & {
|
||||
videoId: string;
|
||||
title: string;
|
||||
@ -251,6 +254,11 @@ export type VideoDataChangeValue = Record<string, unknown> & {
|
||||
loading: boolean;
|
||||
|
||||
lengthSeconds: number;
|
||||
|
||||
/**
|
||||
* YouTube Music Desktop internal variable (for album data)
|
||||
**/
|
||||
ytmdWatchNextResponse?: WatchNextResponse;
|
||||
};
|
||||
|
||||
export interface PlayerAPIEvents {
|
||||
|
||||
@ -30,11 +30,39 @@ export type QueueAPI = {
|
||||
continuation?: string;
|
||||
autoPlaying?: boolean;
|
||||
};
|
||||
|
||||
export type ToastElement = HTMLElement & {
|
||||
autoFitOnAttach: boolean;
|
||||
duration: number;
|
||||
expandSizingTargetForScrollbars: boolean;
|
||||
horizontalAlign: 'left' | 'right' | 'center';
|
||||
importPath?: unknown;
|
||||
label: string;
|
||||
noAutoFocus: boolean;
|
||||
noCancelOnEscKey: boolean;
|
||||
noCancelOnOutsideClick: boolean;
|
||||
noIronAnnounce: boolean;
|
||||
restoreFocusOnClose: boolean;
|
||||
root: ToastElement;
|
||||
rootPath: string;
|
||||
sizingTarget: ToastElement;
|
||||
verticalAlign: 'bottom' | 'top' | 'center';
|
||||
};
|
||||
|
||||
export interface ToastService {
|
||||
attached: boolean;
|
||||
displaying: boolean;
|
||||
messageQueue: string[];
|
||||
toastElement: ToastElement;
|
||||
show: (message: string) => void;
|
||||
}
|
||||
|
||||
export type AppElement = HTMLElement & AppAPI;
|
||||
export type AppAPI = {
|
||||
queue_: QueueAPI;
|
||||
playerApi_: YoutubePlayer;
|
||||
openToast: (message: string) => void;
|
||||
queue: QueueAPI;
|
||||
playerApi: YoutubePlayer;
|
||||
|
||||
toastService: ToastService;
|
||||
|
||||
// TODO: Add more
|
||||
};
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import type { QueueItem } from '@/types/datahost-get-state';
|
||||
import type { PlayerOverlays } from '@/types/player-api-events';
|
||||
|
||||
export interface QueueResponse {
|
||||
items?: QueueItem[];
|
||||
autoPlaying?: boolean;
|
||||
continuation?: string;
|
||||
}
|
||||
|
||||
export interface WatchNextResponse {
|
||||
playerOverlays?: PlayerOverlays;
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { VideoDetails } from './video-details';
|
||||
import { GetPlayerResponse } from './get-player-response';
|
||||
import { PlayerAPIEvents } from './player-api-events';
|
||||
import { WatchNextResponse } from '@/types/youtube-music-desktop-internal';
|
||||
|
||||
export interface YoutubePlayer {
|
||||
getInternalApiInterface: <Parameters extends unknown[], Return>(
|
||||
@ -427,4 +428,6 @@ export interface YoutubePlayer {
|
||||
addEmbedsConversionTrackingParams: <Parameters extends unknown[], Return>(
|
||||
...params: Parameters
|
||||
) => Return;
|
||||
|
||||
getWatchNextResponse(): WatchNextResponse;
|
||||
}
|
||||
|
||||
11
src/utils/wait-for-element.ts
Normal file
11
src/utils/wait-for-element.ts
Normal 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
37
src/yt-web-components.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user