mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
Compare commits
369 Commits
feat/chrom
...
v3.3.12
| Author | SHA1 | Date | |
|---|---|---|---|
| 89ed7d2345 | |||
| 3c4abc1418 | |||
| de224444c2 | |||
| e9ae2d44c9 | |||
| 680f4143f5 | |||
| 23b553ea4b | |||
| e2a91022fd | |||
| d97aa1a8a0 | |||
| ede11307ef | |||
| b74c1a0207 | |||
| 104c1284f6 | |||
| 8af1b36551 | |||
| ce5421ffce | |||
| 98b1fd8787 | |||
| ed5f1ecde3 | |||
| efbd9922fd | |||
| 463bc2c976 | |||
| e71a70d25c | |||
| 4ae9a2820e | |||
| 3ac1cc9204 | |||
| 2938c93803 | |||
| 7e8d31172c | |||
| e0353a88ce | |||
| 635f3334a6 | |||
| 7800e106cb | |||
| e436e6eae0 | |||
| 0c24b70f23 | |||
| 2693a1598a | |||
| 7a87e90edf | |||
| d333fc1075 | |||
| 1f99db3217 | |||
| 4fa9762a50 | |||
| 1e5bea85b3 | |||
| 739518a6fd | |||
| 24b0ae2c6b | |||
| dae6fc9149 | |||
| 25958a7bb1 | |||
| 8b901789dd | |||
| 09a582192f | |||
| 8735107eb0 | |||
| 5b9e947b8f | |||
| 1f1efac466 | |||
| ee9c5a149b | |||
| 79151cb3aa | |||
| 328530ea2c | |||
| 9e809b002d | |||
| 200226f42d | |||
| 5d99a854e2 | |||
| cd4f0ccad7 | |||
| b572623442 | |||
| ef02fdcf45 | |||
| 25d5c16af0 | |||
| 6f49313f03 | |||
| 55c7456c69 | |||
| 78c435b3c4 | |||
| 5e43f38348 | |||
| 24000acda0 | |||
| 24becf0337 | |||
| 8c80922b6b | |||
| 813a089f0d | |||
| 197bead857 | |||
| 5f7a705394 | |||
| 646c0d79a3 | |||
| 5a1313397e | |||
| 4bc70ac2b8 | |||
| dc5b2f96be | |||
| d10b297d75 | |||
| 933b4cc8f0 | |||
| 4557aff9b6 | |||
| 3a42d700fe | |||
| 1a142a8a39 | |||
| 922b04cd54 | |||
| bdfae8ce24 | |||
| 08a537e509 | |||
| 51d8145f13 | |||
| 7e74f33030 | |||
| 0bb8d9bcd9 | |||
| 19a4cb901b | |||
| c497dff69b | |||
| cd0164b665 | |||
| ab35cd3049 | |||
| 13b2ff3a2e | |||
| e00c1b51c7 | |||
| 0a2a289939 | |||
| 919b6ba7cb | |||
| f0683177d8 | |||
| 13450580d0 | |||
| 2375067d19 | |||
| b2fe0f21cb | |||
| 3e0257ba07 | |||
| 4f078284f3 | |||
| e87fa12fdc | |||
| 354c44d717 | |||
| f55faa0a8a | |||
| eaf9d310aa | |||
| bbd10b657d | |||
| 8600b5558f | |||
| 9c7eb5dc26 | |||
| ac63a6a200 | |||
| 3389994ff9 | |||
| adaee80913 | |||
| 4e467d9308 | |||
| a85fc609cb | |||
| 96f69953f2 | |||
| 9095b46a15 | |||
| 4415927465 | |||
| e6b25119cd | |||
| 09e02aeac8 | |||
| f3de17112a | |||
| 91392c0c7e | |||
| 54683a233f | |||
| 8d12eeb033 | |||
| 0ba35890b1 | |||
| 4783ca5942 | |||
| 1517a60215 | |||
| 4ca3c8b7e2 | |||
| 93081c89c8 | |||
| 09255b626b | |||
| 33fe008b5c | |||
| e72ac3d9d0 | |||
| 14a926aa88 | |||
| 99311dba6d | |||
| 5e9f545e4e | |||
| 0cc8fdf564 | |||
| 27e0e7173a | |||
| 2710c62b82 | |||
| a50de65a66 | |||
| c21758f8e6 | |||
| 1a5f6c2a8f | |||
| d521a84f85 | |||
| aa29a0fa65 | |||
| a8469d7d8d | |||
| d09858cbec | |||
| 855f67bb1e | |||
| 8508620e53 | |||
| e9fbfe36cc | |||
| f158a7865a | |||
| 74860edc6e | |||
| 1712b70fb5 | |||
| 4a57cc5ee9 | |||
| 4db0f72864 | |||
| bfe624dc57 | |||
| 994fdaf436 | |||
| 9ac9146d78 | |||
| fbbfc540c2 | |||
| ac3f42d507 | |||
| 993655fdee | |||
| eff2f550c6 | |||
| aef03ab9fd | |||
| f822373c30 | |||
| 19313f9cc9 | |||
| c3b64b097f | |||
| 6668d735a0 | |||
| e2d801168e | |||
| 86f5223350 | |||
| 9ee6940856 | |||
| bffea06343 | |||
| e0ab14b4ea | |||
| 1cb5f628c8 | |||
| 1ac9704cf4 | |||
| 7ebcc51646 | |||
| f4ccde2734 | |||
| e6d7c5cdfc | |||
| 9e3f32a233 | |||
| 8ed813427f | |||
| 2db0d79af6 | |||
| 28ba662d6f | |||
| e041a83121 | |||
| 42185e59d5 | |||
| 975e9719ad | |||
| 31e51a67db | |||
| d5f829d8d0 | |||
| 0dbf0295b8 | |||
| daaf48f453 | |||
| 1d9e021681 | |||
| 6dd36c74e0 | |||
| b933218762 | |||
| 26f8814a97 | |||
| 236ba7536e | |||
| faaf996b16 | |||
| 5a637fd6e7 | |||
| aca1d30d2f | |||
| 5c3eecb3fd | |||
| 9da3ad2fb7 | |||
| d45d597136 | |||
| 2495d5da99 | |||
| 33aeafd19c | |||
| 374d0ce5e7 | |||
| 371805334b | |||
| 47dbeff0d0 | |||
| 17652b5b77 | |||
| 9608c2a7fc | |||
| 8abe2823d7 | |||
| dbc7f23ab8 | |||
| 357bd935e4 | |||
| f99ca53a6d | |||
| 8700c1a110 | |||
| c5e37b791c | |||
| 307f6387ab | |||
| 652a150a0a | |||
| 2c59badb46 | |||
| 69087bbf1f | |||
| af78f1596a | |||
| fca936a698 | |||
| 54b70f6b3e | |||
| 62f7d440fa | |||
| 752ccbf482 | |||
| a8bc53912d | |||
| ed700c2916 | |||
| 97695444af | |||
| 85e5e1814a | |||
| 88c84a50d0 | |||
| 0004fb3fc8 | |||
| 9cbaf5797b | |||
| 1df75ae82f | |||
| 4d86af5437 | |||
| ba0876fd8b | |||
| a3a3fca694 | |||
| de4396936d | |||
| 164575296f | |||
| 7e8cbfc4c0 | |||
| 679938ccf7 | |||
| 5a6d681bf4 | |||
| 62304b723e | |||
| c89bb4606f | |||
| 14c50e0d57 | |||
| f7e9cf9a29 | |||
| 4bb3f41828 | |||
| e4759ebe25 | |||
| ce33a92f02 | |||
| dd44c07450 | |||
| 15a5b7a820 | |||
| 41b7e095eb | |||
| f34a297fcf | |||
| 9b2c1a320b | |||
| 70ed6f8e6c | |||
| 5a2489f0bf | |||
| a7d035022a | |||
| b879a70b24 | |||
| ec4e9a1d47 | |||
| d9c52c0a7f | |||
| 81ecf18231 | |||
| b0b12a075d | |||
| 54c428083c | |||
| 60228a387a | |||
| 3e04baef00 | |||
| 573bcba1a0 | |||
| ae2fad5db3 | |||
| c8fc12569c | |||
| df8efe3fa4 | |||
| 251131b9b5 | |||
| 35fb61087a | |||
| cbec88ff47 | |||
| f4319616a6 | |||
| 1534b7a67f | |||
| cbfcc9d140 | |||
| 60d10a9222 | |||
| 17e090d5c5 | |||
| fa6b4fa83b | |||
| e4b5244d95 | |||
| 04dc5d6314 | |||
| 33ccb03f90 | |||
| 80011ed3aa | |||
| 3e43cf5959 | |||
| bbd590dde8 | |||
| 0975a951e4 | |||
| 8b78f227a7 | |||
| 5a93a04b61 | |||
| b971eb4191 | |||
| 403e825b8d | |||
| 9164eba88c | |||
| bb83bbac38 | |||
| 651a641b22 | |||
| 441b5fc8dd | |||
| 4fba9445d1 | |||
| 3fb5e01ca5 | |||
| 09b2b0d507 | |||
| a9be35481a | |||
| c871506a69 | |||
| 8e6790d366 | |||
| 46620c5ec9 | |||
| 5f090169da | |||
| 2205150b86 | |||
| 3c4bb8a8fc | |||
| 77d0e71529 | |||
| cf974e2d62 | |||
| ee03db4745 | |||
| efd2061058 | |||
| e7ed20f62f | |||
| 36d4c08a56 | |||
| 2a939e615c | |||
| 9825165286 | |||
| 55c934ac7c | |||
| c7715115ee | |||
| e93b5e8135 | |||
| fd6ba1eda1 | |||
| 34f5411aec | |||
| 2b74ec2ef8 | |||
| 14dd0e8e03 | |||
| b0156261b7 | |||
| 05d520f1c1 | |||
| ccd44c79e8 | |||
| 7be48ab05e | |||
| 2d40b410b5 | |||
| f54df86eec | |||
| 1be476de54 | |||
| 82fa8719a9 | |||
| 7600620c4a | |||
| c5217f3e1e | |||
| 706279852a | |||
| 3a6274504f | |||
| 3aa9398481 | |||
| 7cca435b1d | |||
| 241b5800d1 | |||
| 8abb8acdd3 | |||
| ef8226c091 | |||
| 7484e1bf9a | |||
| 2919fd54b7 | |||
| 9eeb1c986a | |||
| d37cd2418c | |||
| 8bd05f525d | |||
| 47b23b414c | |||
| 6f70d179c7 | |||
| 62a86e9267 | |||
| 6358a2d0b1 | |||
| 273633c2ce | |||
| 8b1209ef73 | |||
| 47505e9748 | |||
| 5178cc6bd8 | |||
| d9a27fff42 | |||
| 9e6560b814 | |||
| afdb19a742 | |||
| 0ae5b668f5 | |||
| 10533e28fa | |||
| 6189e67819 | |||
| f9ad505e40 | |||
| 9b011101ed | |||
| a6ed8bf3aa | |||
| 87acf4cf04 | |||
| b6fe2afd75 | |||
| 6d9bb8eb1c | |||
| 192fd0620f | |||
| 00d0b31980 | |||
| 5edb2131d2 | |||
| 4657aeca45 | |||
| 9f5651a8ba | |||
| 570dcfee29 | |||
| 2c130ce80d | |||
| 8457115105 | |||
| fbc02a494a | |||
| 7e2c254ecf | |||
| 11936a889e | |||
| f33970addd | |||
| 2c02b7193d | |||
| 3f80b598ae | |||
| 477068ed49 | |||
| b4083874ac | |||
| c5d0673b2f | |||
| 9ccc44474b | |||
| 98e341f122 | |||
| b23eba51dd | |||
| 980005a58c | |||
| aba58b1d34 | |||
| bb6a127d22 | |||
| ac6e30a6b6 | |||
| 3f23282eed | |||
| 199a77819c | |||
| 89a53b2854 | |||
| c57bf79b08 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -29,7 +29,7 @@ body:
|
|||||||
label: Checklists
|
label: Checklists
|
||||||
options:
|
options:
|
||||||
- label: I use the portable version of the YouTube Music Application.
|
- label: I use the portable version of the YouTube Music Application.
|
||||||
- label: I can reproduce this issue in the [official YTM web version](https://music.youtube.com).
|
- label: I can reproduce this issue in the [official version of (WEB) YTM](https://music.youtube.com).
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: What operating system are you using?
|
label: What operating system are you using?
|
||||||
|
|||||||
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Setup NodeJS
|
- name: Setup NodeJS
|
||||||
@ -61,6 +61,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
sudo snap install snapcraft --classic
|
||||||
pnpm release:linux
|
pnpm release:linux
|
||||||
|
|
||||||
- name: Build and release on Windows
|
- name: Build and release on Windows
|
||||||
@ -91,7 +92,7 @@ jobs:
|
|||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Setup NodeJS
|
- name: Setup NodeJS
|
||||||
|
|||||||
162
README.md
162
README.md
@ -1,7 +1,7 @@
|
|||||||
# YouTube Music
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|
# YouTube Music
|
||||||
|
|
||||||
[](https://github.com/th-ch/youtube-music/releases/)
|
[](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/LICENSE)
|
||||||
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md)
|
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md), [🇨🇱 🇪🇸](./docs/readme/README-es.md)
|
||||||
|
|
||||||
**Electron wrapper around YouTube Music featuring:**
|
**Electron wrapper around YouTube Music featuring:**
|
||||||
|
|
||||||
@ -33,71 +33,28 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md)
|
|||||||
|
|
||||||
| Player Screen (album color theme & ambient light) |
|
| Player Screen (album color theme & ambient light) |
|
||||||
|:---------------------------------------------------------------------------------------------------------:|
|
|:---------------------------------------------------------------------------------------------------------:|
|
||||||
||
|
||
|
||||||
|
|
||||||
## Translation
|
## Content
|
||||||
|
|
||||||
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
- [Features](#features)
|
||||||
|
- [Available plugins](#available-plugins)
|
||||||
<a href="https://hosted.weblate.org/engage/youtube-music/">
|
- [Translation](#translation)
|
||||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="translation status" />
|
- [Download](#download)
|
||||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="translation status 2" />
|
- [Arch Linux](#arch-linux)
|
||||||
</a>
|
- [MacOS](#macos)
|
||||||
|
- [Windows](#windows)
|
||||||
## Download
|
- [How to install without a network connection? (in Windows)](#how-to-install-without-a-network-connection-in-windows)
|
||||||
|
- [Themes](#themes)
|
||||||
You can check out the [latest release](https://github.com/th-ch/youtube-music/releases/latest) to quickly find the
|
- [Dev](#dev)
|
||||||
latest version.
|
- [Build your own plugins](#build-your-own-plugins)
|
||||||
|
- [Creating a plugin](#creating-a-plugin)
|
||||||
### Arch Linux
|
- [Common use cases](#common-use-cases)
|
||||||
|
- [Build](#build)
|
||||||
Install the `youtube-music-bin` package from the AUR. For AUR installation instructions, take a look at
|
- [Production Preview](#production-preview)
|
||||||
this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
- [Tests](#tests)
|
||||||
|
- [License](#license)
|
||||||
### MacOS
|
- [FAQ](#faq)
|
||||||
|
|
||||||
You can install the app using Homebrew (see the [cask definition](https://github.com/th-ch/homebrew-youtube-music)):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew install th-ch/youtube-music/youtube-music
|
|
||||||
```
|
|
||||||
|
|
||||||
If you install the app manually and get an error "is damaged and can’t be opened." when launching the app, run the following in the Terminal:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
xattr -cr /Applications/YouTube\ Music.app
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
You can use the [Scoop package manager](https://scoop.sh) to install the `youtube-music` package from
|
|
||||||
the [`extras` bucket](https://github.com/ScoopInstaller/Extras).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scoop bucket add extras
|
|
||||||
scoop install extras/youtube-music
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternately you can use [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), Windows 11s
|
|
||||||
official CLI package manager to install the `th-ch.YouTubeMusic` package.
|
|
||||||
|
|
||||||
*Note: Microsoft Defender SmartScreen might block the installation since it is from an "unknown publisher". This is also
|
|
||||||
true for the manual installation when trying to run the executable(.exe) after a manual download here on github (same
|
|
||||||
file).*
|
|
||||||
|
|
||||||
```bash
|
|
||||||
winget install th-ch.YouTubeMusic
|
|
||||||
```
|
|
||||||
|
|
||||||
#### How to install without a network connection? (in Windows)
|
|
||||||
|
|
||||||
- Download the `*.nsis.7z` file for _your device architecture_ in [release page](https://github.com/th-ch/youtube-music/releases/latest).
|
|
||||||
- `x64` for 64-bit Windows
|
|
||||||
- `ia32` for 32-bit Windows
|
|
||||||
- `arm64` for ARM64 Windows
|
|
||||||
- Download installer in release page. (`*-Setup.exe`)
|
|
||||||
- Place them in the **same directory**.
|
|
||||||
- Run the installer.
|
|
||||||
|
|
||||||
## Features:
|
## Features:
|
||||||
|
|
||||||
@ -202,6 +159,70 @@ winget install th-ch.YouTubeMusic
|
|||||||
|
|
||||||
- **Visualizer**: Different music visualizers
|
- **Visualizer**: Different music visualizers
|
||||||
|
|
||||||
|
## Translation
|
||||||
|
|
||||||
|
You can help with translation on [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>
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
You can check out the [latest release](https://github.com/th-ch/youtube-music/releases/latest) to quickly find the
|
||||||
|
latest version.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
Install the [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) package from the AUR. For AUR installation instructions, take a look at
|
||||||
|
this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
You can install the app using Homebrew (see the [cask definition](https://github.com/th-ch/homebrew-youtube-music)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install th-ch/youtube-music/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
If you install the app manually and get an error "is damaged and can’t be opened." when launching the app, run the following in the Terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xattr -cr /Applications/YouTube\ Music.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
You can use the [Scoop package manager](https://scoop.sh) to install the `youtube-music` package from
|
||||||
|
the [`extras` bucket](https://github.com/ScoopInstaller/Extras).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install extras/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternately you can use [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), Windows 11s
|
||||||
|
official CLI package manager to install the `th-ch.YouTubeMusic` package.
|
||||||
|
|
||||||
|
*Note: Microsoft Defender SmartScreen might block the installation since it is from an "unknown publisher". This is also
|
||||||
|
true for the manual installation when trying to run the executable(.exe) after a manual download here on github (same
|
||||||
|
file).*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
winget install th-ch.YouTubeMusic
|
||||||
|
```
|
||||||
|
|
||||||
|
#### How to install without a network connection? (in Windows)
|
||||||
|
|
||||||
|
- Download the `*.nsis.7z` file for _your device architecture_ in [release page](https://github.com/th-ch/youtube-music/releases/latest).
|
||||||
|
- `x64` for 64-bit Windows
|
||||||
|
- `ia32` for 32-bit Windows
|
||||||
|
- `arm64` for ARM64 Windows
|
||||||
|
- Download installer in release page. (`*-Setup.exe`)
|
||||||
|
- Place them in the **same directory**.
|
||||||
|
- Run the installer.
|
||||||
|
|
||||||
## Themes
|
## Themes
|
||||||
|
|
||||||
You can load CSS files to change the look of the application (Options > Visual Tweaks > Themes).
|
You can load CSS files to change the look of the application (Options > Visual Tweaks > Themes).
|
||||||
@ -344,8 +365,11 @@ export default createPlugin({
|
|||||||
4. Run `pnpm build:OS`
|
4. Run `pnpm build:OS`
|
||||||
|
|
||||||
- `pnpm dist:win` - Windows
|
- `pnpm dist:win` - Windows
|
||||||
- `pnpm dist:linux` - Linux
|
- `pnpm dist:linux` - Linux (amd64)
|
||||||
- `pnpm dist:mac` - MacOS
|
- `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)
|
||||||
|
|
||||||
Builds the app for macOS, Linux, and Windows,
|
Builds the app for macOS, Linux, and Windows,
|
||||||
using [electron-builder](https://github.com/electron-userland/electron-builder).
|
using [electron-builder](https://github.com/electron-userland/electron-builder).
|
||||||
@ -368,7 +392,7 @@ Uses [Playwright](https://playwright.dev/) to test the app.
|
|||||||
|
|
||||||
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||||
|
|
||||||
## Most asked questions
|
## FAQ
|
||||||
|
|
||||||
### Why apps menu isn't showing up?
|
### Why apps menu isn't showing up?
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.1 KiB |
353
changelog.md
353
changelog.md
@ -2,8 +2,361 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||||
|
|
||||||
|
#### [v3.3.11](https://github.com/th-ch/youtube-music/compare/v3.3.10...v3.3.11)
|
||||||
|
|
||||||
|
- Revert "fix(deps): update dependency @cliqz/adblocker-electron to v1.27.10" [`#2129`](https://github.com/th-ch/youtube-music/pull/2129)
|
||||||
|
- chore(deps): update dependency vite to v5.2.13 [`#2127`](https://github.com/th-ch/youtube-music/pull/2127)
|
||||||
|
- chore(deps): update dependency electron to v30.1.0 [`#2126`](https://github.com/th-ch/youtube-music/pull/2126)
|
||||||
|
- fix(deps): update dependency deepmerge-ts to v7.0.3 [`#2125`](https://github.com/th-ch/youtube-music/pull/2125)
|
||||||
|
- chore(deps): update dependency @babel/runtime to v7.24.7 [`#2124`](https://github.com/th-ch/youtube-music/pull/2124)
|
||||||
|
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.28 [`#2121`](https://github.com/th-ch/youtube-music/pull/2121)
|
||||||
|
- fix(deps): update dependency electron-updater to v6.2.1 [`#2120`](https://github.com/th-ch/youtube-music/pull/2120)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.87 [`#2119`](https://github.com/th-ch/youtube-music/pull/2119)
|
||||||
|
- fix(deps): update dependency deepmerge-ts to v7.0.2 [`#2118`](https://github.com/th-ch/youtube-music/pull/2118)
|
||||||
|
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.25 [`#2114`](https://github.com/th-ch/youtube-music/pull/2114)
|
||||||
|
- fix(menu): fix menubar items doesn't rendered [`#2113`](https://github.com/th-ch/youtube-music/issues/2113)
|
||||||
|
- chore(i18n): Translated using Weblate (Nepali) [`4ae9a28`](https://github.com/th-ch/youtube-music/commit/4ae9a2820e9d453635094956264dd8b42c4997f7)
|
||||||
|
- chore(i18n): Translated using Weblate (Nepali) [`7e8d311`](https://github.com/th-ch/youtube-music/commit/7e8d31172ceb175ba07f307d248fc1246265a4c0)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.10 [`d97aa1a`](https://github.com/th-ch/youtube-music/commit/d97aa1a8a003f15eea63c8cb2dabd0f215e885f1)
|
||||||
|
|
||||||
|
#### [v3.3.10](https://github.com/th-ch/youtube-music/compare/v3.3.9...v3.3.10)
|
||||||
|
|
||||||
|
> 2 June 2024
|
||||||
|
|
||||||
|
- fix(adblocker): fix blank screen [`#2103`](https://github.com/th-ch/youtube-music/issues/2103) [`#2105`](https://github.com/th-ch/youtube-music/issues/2105)
|
||||||
|
- chore(i18n): Translated using Weblate (Hungarian) [`25958a7`](https://github.com/th-ch/youtube-music/commit/25958a7bb1fea20e59676e7821f3dd8819602b68)
|
||||||
|
- fix(deps): bump deps [`4fa9762`](https://github.com/th-ch/youtube-music/commit/4fa9762a506544ce453894ce2df13033225e6c7d)
|
||||||
|
- fix(deps): bump `@typescript-eslint/eslint-plugin` version to 8.0.0-alpha.24 [`1e5bea8`](https://github.com/th-ch/youtube-music/commit/1e5bea85b31da5de868d9eff8758e5d2d888c2c8)
|
||||||
|
|
||||||
|
#### [v3.3.9](https://github.com/th-ch/youtube-music/compare/v3.3.8...v3.3.9)
|
||||||
|
|
||||||
|
> 1 June 2024
|
||||||
|
|
||||||
|
- chore(deps): update dependency eslint to v9.4.0 [`#2106`](https://github.com/th-ch/youtube-music/pull/2106)
|
||||||
|
- fix(adblocker): fix In-Player adblocker [`#1817`](https://github.com/th-ch/youtube-music/issues/1817)
|
||||||
|
- feat(adblocker): improve In-Player adblocker [`5b9e947`](https://github.com/th-ch/youtube-music/commit/5b9e947b8feebb57d9a2122ae7b7ab2ff7c37c06)
|
||||||
|
- chore(i18n): Translated using Weblate (French) [`9e809b0`](https://github.com/th-ch/youtube-music/commit/9e809b002d10f6ec0202a7d56d3d0b73f8093012)
|
||||||
|
- chore(i18n): Translated using Weblate (Malay) [`79151cb`](https://github.com/th-ch/youtube-music/commit/79151cb3aa6c087b8d8bb500322f505797b822bd)
|
||||||
|
|
||||||
|
#### [v3.3.8](https://github.com/th-ch/youtube-music/compare/v3.3.7...v3.3.8)
|
||||||
|
|
||||||
|
> 1 June 2024
|
||||||
|
|
||||||
|
- fix(adblocker): fix blank screen [`#1942`](https://github.com/th-ch/youtube-music/issues/1942) [`#2100`](https://github.com/th-ch/youtube-music/issues/2100) [`#2103`](https://github.com/th-ch/youtube-music/issues/2103)
|
||||||
|
- Update changelog for v3.3.7 [`b572623`](https://github.com/th-ch/youtube-music/commit/b572623442fc8b45b593dc0c91623fbf814115b4)
|
||||||
|
- Bump version to 3.3.8 [`5d99a85`](https://github.com/th-ch/youtube-music/commit/5d99a854e2f29bdb6682beeffa4e6b9b8be0f60f)
|
||||||
|
|
||||||
|
#### [v3.3.7](https://github.com/th-ch/youtube-music/compare/v3.3.6...v3.3.7)
|
||||||
|
|
||||||
|
> 1 June 2024
|
||||||
|
|
||||||
|
- chore(deps): update dependency electron to v30.0.9 [`#2098`](https://github.com/th-ch/youtube-music/pull/2098)
|
||||||
|
- Revert "fix(deps): update dependency @cliqz/adblocker-electron to v1.27.6" [`#2101`](https://github.com/th-ch/youtube-music/pull/2101)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.6 [`#2096`](https://github.com/th-ch/youtube-music/pull/2096)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.86 [`#2092`](https://github.com/th-ch/youtube-music/pull/2092)
|
||||||
|
- chore(deps): update dependency vite to v5.2.12 [`#2094`](https://github.com/th-ch/youtube-music/pull/2094)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.11.0 [`#2093`](https://github.com/th-ch/youtube-music/pull/2093)
|
||||||
|
- chore(docs): Added README-es.md and linked to README.md [`#2090`](https://github.com/th-ch/youtube-music/pull/2090)
|
||||||
|
- fix(deps): update dependency deepmerge-ts to v7 [`#2085`](https://github.com/th-ch/youtube-music/pull/2085)
|
||||||
|
- chore(deps): update dependency builtin-modules to v4 [`#2084`](https://github.com/th-ch/youtube-music/pull/2084)
|
||||||
|
- fix(deps): update dependency electron-debug to v4 [`#2086`](https://github.com/th-ch/youtube-music/pull/2086)
|
||||||
|
- fix(deps): update dependency electron-store to v9 [`#2087`](https://github.com/th-ch/youtube-music/pull/2087)
|
||||||
|
- fix(deps): update dependency conf to v12 [`#1463`](https://github.com/th-ch/youtube-music/pull/1463)
|
||||||
|
- fix(deps): update dependency youtubei.js to v9.4.0 [`#2083`](https://github.com/th-ch/youtube-music/pull/2083)
|
||||||
|
- chore(deps): update playwright monorepo to v1.44.1 [`#2082`](https://github.com/th-ch/youtube-music/pull/2082)
|
||||||
|
- chore(deps): update dependency ws to v8.17.0 [`#2081`](https://github.com/th-ch/youtube-music/pull/2081)
|
||||||
|
- chore(deps): update dependency glob to v10.4.1 [`#2080`](https://github.com/th-ch/youtube-music/pull/2080)
|
||||||
|
- chore(deps): update dependency eslint to v9.3.0 [`#2079`](https://github.com/th-ch/youtube-music/pull/2079)
|
||||||
|
- fix(deps): update dependency peerjs to v1.5.4 [`#2075`](https://github.com/th-ch/youtube-music/pull/2075)
|
||||||
|
- chore(deps): update dependency esbuild to v0.21.4 [`#2078`](https://github.com/th-ch/youtube-music/pull/2078)
|
||||||
|
- fix(deps): update dependency semver to v7.6.2 [`#2076`](https://github.com/th-ch/youtube-music/pull/2076)
|
||||||
|
- chore(deps): update dependency electron-vite to v2.2.0 [`#2077`](https://github.com/th-ch/youtube-music/pull/2077)
|
||||||
|
- fix(deps): update dependency i18next to v23.11.5 [`#2074`](https://github.com/th-ch/youtube-music/pull/2074)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.3 [`#2071`](https://github.com/th-ch/youtube-music/pull/2071)
|
||||||
|
- chore(deps): update dependency vite to v5.2.11 [`#2070`](https://github.com/th-ch/youtube-music/pull/2070)
|
||||||
|
- fix(deps): update dependency @floating-ui/dom to v1.6.5 [`#2073`](https://github.com/th-ch/youtube-music/pull/2073)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.27.3 [`#2072`](https://github.com/th-ch/youtube-music/pull/2072)
|
||||||
|
- chore(deps): update pnpm to v9 [`#1980`](https://github.com/th-ch/youtube-music/pull/1980)
|
||||||
|
- chore(deps): update dependency electron to v30.0.8 [`#2068`](https://github.com/th-ch/youtube-music/pull/2068)
|
||||||
|
- chore(deps-dev): bump ejs from 3.1.9 to 3.1.10 [`#2023`](https://github.com/th-ch/youtube-music/pull/2023)
|
||||||
|
- chore(deps): update dependency utf-8-validate to v6.0.4 [`#2069`](https://github.com/th-ch/youtube-music/pull/2069)
|
||||||
|
- fix(MPRIS): Prevents player to start with invalid MPRIS interface [`#1996`](https://github.com/th-ch/youtube-music/pull/1996)
|
||||||
|
- fix(deps): update dependency solid-js to v1.8.17 [`#2002`](https://github.com/th-ch/youtube-music/pull/2002)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.10.0 [`#2000`](https://github.com/th-ch/youtube-music/pull/2000)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.85 [`#1998`](https://github.com/th-ch/youtube-music/pull/1998)
|
||||||
|
- fix(deps): update dependency serve to v14.2.3 [`#1997`](https://github.com/th-ch/youtube-music/pull/1997)
|
||||||
|
- chore(deps): update dependency rollup to v4.18.0 [`#1990`](https://github.com/th-ch/youtube-music/pull/1990)
|
||||||
|
- feat: Enable arm64 for deb and rpm [`#2033`](https://github.com/th-ch/youtube-music/pull/2033)
|
||||||
|
- chore (README-is.md): Replace viðbót with tengiforrit [`#2004`](https://github.com/th-ch/youtube-music/pull/2004)
|
||||||
|
- chore(docs): readme file translated to french [`#2049`](https://github.com/th-ch/youtube-music/pull/2049)
|
||||||
|
- chore(deps): update dependency @babel/runtime to v7.24.6 [`#2039`](https://github.com/th-ch/youtube-music/pull/2039)
|
||||||
|
- Fix substract `margin-top` in fullscreen mode [`#2015`](https://github.com/th-ch/youtube-music/pull/2015)
|
||||||
|
- chore(deps): update pnpm to v8.15.7 [`#1970`](https://github.com/th-ch/youtube-music/pull/1970)
|
||||||
|
- fix(renderer): fix macos traffic lights gap [`#2035`](https://github.com/th-ch/youtube-music/issues/2035)
|
||||||
|
- Fix substract `margin-top` in fullscreen mode [`#2013`](https://github.com/th-ch/youtube-music/issues/2013)
|
||||||
|
- chore(i18n): Translated using Weblate (Hungarian) [`f3de171`](https://github.com/th-ch/youtube-music/commit/f3de17112af787437362f31b5c4e2d4149ba1436)
|
||||||
|
- feat(menu): add theme list in menu [`933b4cc`](https://github.com/th-ch/youtube-music/commit/933b4cc8f062b3442afd4516a40eb2938db98fc6)
|
||||||
|
- chore(i18n): Translated using Weblate (Filipino) [`91392c0`](https://github.com/th-ch/youtube-music/commit/91392c0c7efaf3b33da4be4aaa7946af7108d676)
|
||||||
|
|
||||||
|
#### [v3.3.6](https://github.com/th-ch/youtube-music/compare/v3.3.5...v3.3.6)
|
||||||
|
|
||||||
|
> 13 April 2024
|
||||||
|
|
||||||
|
- fix: add AdGuard as blocklist sources [`#1966`](https://github.com/th-ch/youtube-music/pull/1966)
|
||||||
|
- chore(deps): update dependency rollup to v4.14.2 [`#1968`](https://github.com/th-ch/youtube-music/pull/1968)
|
||||||
|
- fix(deps): update dependency youtubei.js to v9.3.0 [`#1967`](https://github.com/th-ch/youtube-music/pull/1967)
|
||||||
|
- chore(deps): update playwright monorepo to v1.43.1 [`#1969`](https://github.com/th-ch/youtube-music/pull/1969)
|
||||||
|
- chore(deps): update dependency electron to v29.3.0 [`#1961`](https://github.com/th-ch/youtube-music/pull/1961)
|
||||||
|
- fix(mpris): use global regex to replace minus in the video ID [`#1963`](https://github.com/th-ch/youtube-music/pull/1963)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.27.1 [`#1954`](https://github.com/th-ch/youtube-music/pull/1954)
|
||||||
|
- chore(deps): update dependency typescript to v5.4.5 [`#1958`](https://github.com/th-ch/youtube-music/pull/1958)
|
||||||
|
- fix(deps): update dependency youtubei.js to v9.2.1 [`#1957`](https://github.com/th-ch/youtube-music/pull/1957)
|
||||||
|
- fix(deps): update dependency i18next to v23.11.1 [`#1956`](https://github.com/th-ch/youtube-music/pull/1956)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.1 [`#1953`](https://github.com/th-ch/youtube-music/pull/1953)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.6.0 [`#1947`](https://github.com/th-ch/youtube-music/pull/1947)
|
||||||
|
- fix(deps): update dependency i18next to v23.11.0 [`#1946`](https://github.com/th-ch/youtube-music/pull/1946)
|
||||||
|
- chore(deps): update dependency node-gyp to v10.1.0 [`#1941`](https://github.com/th-ch/youtube-music/pull/1941)
|
||||||
|
- chore(deps): update dependency eslint to v9 [`#1940`](https://github.com/th-ch/youtube-music/pull/1940)
|
||||||
|
- chore(deps): update dependency rollup to v4.14.1 [`#1944`](https://github.com/th-ch/youtube-music/pull/1944)
|
||||||
|
- chore(deps): update dependency node-gyp to v10.1.0 [`#1937`](https://github.com/th-ch/youtube-music/pull/1937)
|
||||||
|
- chore(deps): update dependency typescript to v5.4.4 [`#1936`](https://github.com/th-ch/youtube-music/pull/1936)
|
||||||
|
- chore(deps): update playwright monorepo to v1.43.0 [`#1938`](https://github.com/th-ch/youtube-music/pull/1938)
|
||||||
|
- chore(deps): bump undici from 5.28.3 to 5.28.4 [`#1935`](https://github.com/th-ch/youtube-music/pull/1935)
|
||||||
|
- chore(deps): update dependency vite to v5.2.8 [`#1930`](https://github.com/th-ch/youtube-music/pull/1930)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.79 [`#1933`](https://github.com/th-ch/youtube-music/pull/1933)
|
||||||
|
- chore(deps): update dependency node-gyp to v10.1.0 [`#1910`](https://github.com/th-ch/youtube-music/pull/1910)
|
||||||
|
- chore(deps): update dependency node-gyp to v10.1.0 [`#1908`](https://github.com/th-ch/youtube-music/pull/1908)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.0 [`#1906`](https://github.com/th-ch/youtube-music/pull/1906)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.27.0 [`#1907`](https://github.com/th-ch/youtube-music/pull/1907)
|
||||||
|
- chore(deps): update dependency rollup to v4.13.2 [`#1901`](https://github.com/th-ch/youtube-music/pull/1901)
|
||||||
|
- chore(deps): update dependency glob to v10.3.12 [`#1900`](https://github.com/th-ch/youtube-music/pull/1900)
|
||||||
|
- chore(deps): update dependency vite to v5.2.7 [`#1905`](https://github.com/th-ch/youtube-music/pull/1905)
|
||||||
|
- fix(deps): update dependency node-html-parser to v6.1.13 [`#1903`](https://github.com/th-ch/youtube-music/pull/1903)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.77 [`#1899`](https://github.com/th-ch/youtube-music/pull/1899)
|
||||||
|
- chore(deps): update dependency electron to v29.1.6 [`#1898`](https://github.com/th-ch/youtube-music/pull/1898)
|
||||||
|
- Improve video title filters [`#1667`](https://github.com/th-ch/youtube-music/pull/1667)
|
||||||
|
- chore(deps): update dependency rollup to v4.13.1 [`#1896`](https://github.com/th-ch/youtube-music/pull/1896)
|
||||||
|
- chore(deps): update dependency node-gyp to v10.1.0 [`#1890`](https://github.com/th-ch/youtube-music/pull/1890)
|
||||||
|
- chore(deps): update dependency node-gyp to v10.1.0 [`#1889`](https://github.com/th-ch/youtube-music/pull/1889)
|
||||||
|
- fix: fix `switch-repeat` [`#1810`](https://github.com/th-ch/youtube-music/issues/1810)
|
||||||
|
- i18n Translation to Dutch/nl [`0dbf029`](https://github.com/th-ch/youtube-music/commit/0dbf0295b805f9883522ee00983b338060fbddbe)
|
||||||
|
- fix: rollback electron-builder version to 24.9.4 [`4a57cc5`](https://github.com/th-ch/youtube-music/commit/4a57cc5ee9ab2ad6835cff75b8b3aead75d9e564)
|
||||||
|
- chore: update electron-builder to 25.0.0-alpha.6 [`aef03ab`](https://github.com/th-ch/youtube-music/commit/aef03ab9fd440fe19c41e315cffab27e976c723d)
|
||||||
|
|
||||||
|
#### [v3.3.5](https://github.com/th-ch/youtube-music/compare/v3.3.4...v3.3.5)
|
||||||
|
|
||||||
|
> 26 March 2024
|
||||||
|
|
||||||
|
- chore(deps): update dependency node-gyp to v10.1.0 [`#1885`](https://github.com/th-ch/youtube-music/pull/1885)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.4.0 [`#1886`](https://github.com/th-ch/youtube-music/pull/1886)
|
||||||
|
- chore(deps): update dependency vite to v5.2.6 [`#1883`](https://github.com/th-ch/youtube-music/pull/1883)
|
||||||
|
- fix(style): resolve #1887 [`#1887`](https://github.com/th-ch/youtube-music/issues/1887)
|
||||||
|
- chore(i18n): Translated using Weblate (Swedish) [`69087bb`](https://github.com/th-ch/youtube-music/commit/69087bbf1fac1ba58e992146deb1d6f1706b1e3c)
|
||||||
|
- chore(i18n): Translated using Weblate (French) [`af78f15`](https://github.com/th-ch/youtube-music/commit/af78f1596ab8db2fa7069fdb1c4f078099ce4446)
|
||||||
|
- Update changelog for v3.3.4 [`62f7d44`](https://github.com/th-ch/youtube-music/commit/62f7d440fab5bdbe9f49a3a5f8c32e7aaf2f28f6)
|
||||||
|
|
||||||
|
#### [v3.3.4](https://github.com/th-ch/youtube-music/compare/v3.3.3...v3.3.4)
|
||||||
|
|
||||||
|
> 24 March 2024
|
||||||
|
|
||||||
|
- Update changelog for v3.3.3 [`9769544`](https://github.com/th-ch/youtube-music/commit/97695444affbacb71dd73ae7107d4c987e285a37)
|
||||||
|
- fix(style): fix fullscreen style and in-app-menu [`ed700c2`](https://github.com/th-ch/youtube-music/commit/ed700c2916cc7e6ccd2010d0c552364af116eb4f)
|
||||||
|
- fix(style): fix miniplayer style [`a8bc539`](https://github.com/th-ch/youtube-music/commit/a8bc53912d1f4137008ecb2d9d5d9d9eb06ee2a8)
|
||||||
|
|
||||||
|
#### [v3.3.3](https://github.com/th-ch/youtube-music/compare/v3.3.2...v3.3.3)
|
||||||
|
|
||||||
|
> 24 March 2024
|
||||||
|
|
||||||
|
- chore(deps): update dependency electron to v29.1.5 [`#1876`](https://github.com/th-ch/youtube-music/pull/1876)
|
||||||
|
- chore(deps): update dependency typescript to v5.4.3 [`#1877`](https://github.com/th-ch/youtube-music/pull/1877)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.76 [`#1878`](https://github.com/th-ch/youtube-music/pull/1878)
|
||||||
|
- chore(deps): update dependency vite to v5.2.4 [`#1881`](https://github.com/th-ch/youtube-music/pull/1881)
|
||||||
|
- Ambient Plugin cleanup [`#1880`](https://github.com/th-ch/youtube-music/pull/1880)
|
||||||
|
- chore(deps): update dependency vite to v5.2.2 [`#1875`](https://github.com/th-ch/youtube-music/pull/1875)
|
||||||
|
- fix(deps): update dependency solid-js to v1.8.16 [`#1873`](https://github.com/th-ch/youtube-music/pull/1873)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.3.1 [`#1868`](https://github.com/th-ch/youtube-music/pull/1868)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.75 [`#1867`](https://github.com/th-ch/youtube-music/pull/1867)
|
||||||
|
- chore(deps): update pnpm to v8.15.5 [`#1865`](https://github.com/th-ch/youtube-music/pull/1865)
|
||||||
|
- fix: Fix Miniplayer image size [`#1863`](https://github.com/th-ch/youtube-music/pull/1863)
|
||||||
|
- fix(style): fixed image/video alignment when toggle is active [`#1862`](https://github.com/th-ch/youtube-music/pull/1862)
|
||||||
|
- chore: Update README-is.md [`#1858`](https://github.com/th-ch/youtube-music/pull/1858)
|
||||||
|
- chore(deps): update dependency vite-plugin-solid to v2.10.2 [`#1859`](https://github.com/th-ch/youtube-music/pull/1859)
|
||||||
|
- fix: Ambient Mode intialization improvement [`#1857`](https://github.com/th-ch/youtube-music/pull/1857)
|
||||||
|
- chore(deps): bump follow-redirects from 1.15.5 to 1.15.6 [`#1856`](https://github.com/th-ch/youtube-music/pull/1856)
|
||||||
|
- chore(README): Nicer Readme 2.0 [`#1833`](https://github.com/th-ch/youtube-music/pull/1833)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.74 [`#1854`](https://github.com/th-ch/youtube-music/pull/1854)
|
||||||
|
- chore(deps): update dependency esbuild to v0.20.2 [`#1855`](https://github.com/th-ch/youtube-music/pull/1855)
|
||||||
|
- Improve ambient mode [`#1853`](https://github.com/th-ch/youtube-music/pull/1853)
|
||||||
|
- chore(deps): update dependency electron to v29.1.4 [`#1852`](https://github.com/th-ch/youtube-music/pull/1852)
|
||||||
|
- chore(deps): update dependency electron to v29.1.3 [`#1851`](https://github.com/th-ch/youtube-music/pull/1851)
|
||||||
|
- chore(deps): update dependency rollup to v4.13.0 [`#1850`](https://github.com/th-ch/youtube-music/pull/1850)
|
||||||
|
- fix(deps): update dependency electron-store to v8.2.0 [`#1843`](https://github.com/th-ch/youtube-music/pull/1843)
|
||||||
|
- chore(deps): update dependency electron to v29.1.1 [`#1841`](https://github.com/th-ch/youtube-music/pull/1841)
|
||||||
|
- fix(deps): update dependency i18next to v23.10.1 [`#1842`](https://github.com/th-ch/youtube-music/pull/1842)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.2.0 [`#1848`](https://github.com/th-ch/youtube-music/pull/1848)
|
||||||
|
- chore(deps): update dependency vite to v5.1.6 [`#1847`](https://github.com/th-ch/youtube-music/pull/1847)
|
||||||
|
- fix(deps): update dependency async-mutex to v0.5.0 [`#1849`](https://github.com/th-ch/youtube-music/pull/1849)
|
||||||
|
- fix(deps): update dependency ts-morph to v22 [`#1846`](https://github.com/th-ch/youtube-music/pull/1846)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.73 [`#1840`](https://github.com/th-ch/youtube-music/pull/1840)
|
||||||
|
- chore(deps): update dependency rollup to v4.12.1 [`#1837`](https://github.com/th-ch/youtube-music/pull/1837)
|
||||||
|
- chore: Changed a single word (README-is.md) [`#1836`](https://github.com/th-ch/youtube-music/pull/1836)
|
||||||
|
- chore(deps): update dependency typescript to v5.4.2 [`#1838`](https://github.com/th-ch/youtube-music/pull/1838)
|
||||||
|
- chore(deps): update dependency electron-vite to v2.1.0 [`#1823`](https://github.com/th-ch/youtube-music/pull/1823)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.1 [`#1829`](https://github.com/th-ch/youtube-music/pull/1829)
|
||||||
|
- chore(deps): update dependency vite to v5.1.5 [`#1831`](https://github.com/th-ch/youtube-music/pull/1831)
|
||||||
|
- Revert "chore(deps): update dependency electron-builder to v24.13.3" [`#1818`](https://github.com/th-ch/youtube-music/pull/1818)
|
||||||
|
- chore(deps): update dependency electron-builder to v24.13.3 [`#1774`](https://github.com/th-ch/youtube-music/pull/1774)
|
||||||
|
- chore(deps): update playwright monorepo to v1.42.1 [`#1816`](https://github.com/th-ch/youtube-music/pull/1816)
|
||||||
|
- fix: Add scale ratio for tray icons [`#1811`](https://github.com/th-ch/youtube-music/pull/1811)
|
||||||
|
- Icelandic translation of the readme file [`#1806`](https://github.com/th-ch/youtube-music/pull/1806)
|
||||||
|
- chore(deps): update dependency electron to v29.1.0 [`#1808`](https://github.com/th-ch/youtube-music/pull/1808)
|
||||||
|
- chore(deps): update playwright monorepo to v1.42.0 [`#1805`](https://github.com/th-ch/youtube-music/pull/1805)
|
||||||
|
- chore(deps): update dependency eslint to v8.57.0 [`#1793`](https://github.com/th-ch/youtube-music/pull/1793)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.0 [`#1800`](https://github.com/th-ch/youtube-music/pull/1800)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.71 [`#1799`](https://github.com/th-ch/youtube-music/pull/1799)
|
||||||
|
- chore(deps): update pnpm to v8.15.4 [`#1795`](https://github.com/th-ch/youtube-music/pull/1795)
|
||||||
|
- chore(deps): update dependency @types/semver to v7.5.8 [`#1797`](https://github.com/th-ch/youtube-music/pull/1797)
|
||||||
|
- fix: center the pause icon [`#1786`](https://github.com/th-ch/youtube-music/pull/1786)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.16 [`#1788`](https://github.com/th-ch/youtube-music/pull/1788)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.16 [`#1789`](https://github.com/th-ch/youtube-music/pull/1789)
|
||||||
|
- fix(deps): update dependency youtubei.js to v9.1.0 [`#1790`](https://github.com/th-ch/youtube-music/pull/1790)
|
||||||
|
- fix(deps): update dependency i18next to v23.10.0 [`#1785`](https://github.com/th-ch/youtube-music/pull/1785)
|
||||||
|
- chore(deps): update dependency electron to v29 [`#1773`](https://github.com/th-ch/youtube-music/pull/1773)
|
||||||
|
- chore(deps): update dependency vite to v5.1.4 [`#1778`](https://github.com/th-ch/youtube-music/pull/1778)
|
||||||
|
- chore(deps): bump ip from 2.0.0 to 2.0.1 [`#1777`](https://github.com/th-ch/youtube-music/pull/1777)
|
||||||
|
- fix: add support for Wayland [`#1864`](https://github.com/th-ch/youtube-music/issues/1864)
|
||||||
|
- fix(style): fix navigation bar items are not working [`#1381`](https://github.com/th-ch/youtube-music/issues/1381) [`#1396`](https://github.com/th-ch/youtube-music/issues/1396) [`#1649`](https://github.com/th-ch/youtube-music/issues/1649)
|
||||||
|
- fix(ytm-bugs): fixed a `scrollbar-color` bug that affected Chromium 121 and later [`#1737`](https://github.com/th-ch/youtube-music/issues/1737)
|
||||||
|
- chore(i18n): Translated using Weblate (Icelandic) [`82fa871`](https://github.com/th-ch/youtube-music/commit/82fa8719a96abdfaaa8548a0077f4db2164ec09b)
|
||||||
|
- chore(i18n): Translated using Weblate (Romanian) [`c871506`](https://github.com/th-ch/youtube-music/commit/c871506a69180308ab4fc587b6e8a33f193087e8)
|
||||||
|
- chore(i18n): Translated using Weblate (Thai) [`a7d0350`](https://github.com/th-ch/youtube-music/commit/a7d035022a229f0b245694d1fc7a484befe1c269)
|
||||||
|
|
||||||
|
#### [v3.3.2](https://github.com/th-ch/youtube-music/compare/v3.3.1...v3.3.2)
|
||||||
|
|
||||||
|
> 20 February 2024
|
||||||
|
|
||||||
|
- fix: fix bugs in MPRIS, and improve MPRIS [`#1760`](https://github.com/th-ch/youtube-music/pull/1760)
|
||||||
|
- fix(deps): update dependency electron-updater to v6.1.8 [`#1770`](https://github.com/th-ch/youtube-music/pull/1770)
|
||||||
|
- chore(deps): update dependency electron-builder to v24.12.0 [`#1771`](https://github.com/th-ch/youtube-music/pull/1771)
|
||||||
|
- feat(scrobblers): use `BrowserWindow` instead of `shell.openExternal` [`#1758`](https://github.com/th-ch/youtube-music/pull/1758)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.0.2 [`#1763`](https://github.com/th-ch/youtube-music/pull/1763)
|
||||||
|
- chore(deps): update dependency esbuild to v0.20.1 [`#1759`](https://github.com/th-ch/youtube-music/pull/1759)
|
||||||
|
- fix(deps): update dependency i18next to v23.9.0 [`#1754`](https://github.com/th-ch/youtube-music/pull/1754)
|
||||||
|
- fix: fixed an issue that caused infinite loops when using Music Together [`#1752`](https://github.com/th-ch/youtube-music/issues/1752)
|
||||||
|
- chore(deps): rollback dependency electron-builder to v24.9.1 [`8bd05f5`](https://github.com/th-ch/youtube-music/commit/8bd05f525df98671f0a516b159cccab302b7ae99)
|
||||||
|
- chore(deps): update dependency electron-builder to v24.13.1 [`47b23b4`](https://github.com/th-ch/youtube-music/commit/47b23b414c8feb25c4d9a23d6adb7cbf1ac818fb)
|
||||||
|
- chore(i18n): Translated using Weblate (German) [`47505e9`](https://github.com/th-ch/youtube-music/commit/47505e97482f9e953ee451b968d0950585616ffa)
|
||||||
|
|
||||||
|
#### [v3.3.1](https://github.com/th-ch/youtube-music/compare/v3.3.0...v3.3.1)
|
||||||
|
|
||||||
|
> 18 February 2024
|
||||||
|
|
||||||
|
- Update changelog for v3.3.0 [`6d9bb8e`](https://github.com/th-ch/youtube-music/commit/6d9bb8eb1cc2d892a5552ffb1f7c20859aa80f67)
|
||||||
|
- hotfix: in-app-menu position issue [`87acf4c`](https://github.com/th-ch/youtube-music/commit/87acf4cf042ba32a000a4aeaec5c17c93501d333)
|
||||||
|
- release 3.3.1 (HOTFIX) [`a6ed8bf`](https://github.com/th-ch/youtube-music/commit/a6ed8bf3aa20ca8e950e85d88f981ccf9edc7498)
|
||||||
|
|
||||||
|
#### [v3.3.0](https://github.com/th-ch/youtube-music/compare/v3.2.2...v3.3.0)
|
||||||
|
|
||||||
|
> 18 February 2024
|
||||||
|
|
||||||
|
- fix(deps): update dependency i18next to v23.8.3 [`#1751`](https://github.com/th-ch/youtube-music/pull/1751)
|
||||||
|
- import fixed ./constants [`#1748`](https://github.com/th-ch/youtube-music/pull/1748)
|
||||||
|
- chore(deps): update dependency rollup to v4.12.0 [`#1743`](https://github.com/th-ch/youtube-music/pull/1743)
|
||||||
|
- chore(deps): bump undici from 5.28.2 to 5.28.3 [`#1747`](https://github.com/th-ch/youtube-music/pull/1747)
|
||||||
|
- chore(deps): update dependency vite to v5.1.3 [`#1742`](https://github.com/th-ch/youtube-music/pull/1742)
|
||||||
|
- chore(deps): update dependency vite-plugin-solid to v2.10.1 [`#1734`](https://github.com/th-ch/youtube-music/pull/1734)
|
||||||
|
- chore(deps): update dependency discord-api-types to v0.37.70 [`#1740`](https://github.com/th-ch/youtube-music/pull/1740)
|
||||||
|
- chore(deps): update dependency electron to v28.2.3 [`#1736`](https://github.com/th-ch/youtube-music/pull/1736)
|
||||||
|
- chore(deps): update pnpm to v8.15.3 [`#1739`](https://github.com/th-ch/youtube-music/pull/1739)
|
||||||
|
- chore(deps): update dependency rollup to v4.11.0 [`#1738`](https://github.com/th-ch/youtube-music/pull/1738)
|
||||||
|
- fix(deps): update dependency solid-js to v1.8.15 [`#1735`](https://github.com/th-ch/youtube-music/pull/1735)
|
||||||
|
- chore(deps): update dependency vite to v5.1.2 [`#1733`](https://github.com/th-ch/youtube-music/pull/1733)
|
||||||
|
- chore(deps): update dependency vite-plugin-solid to v2.10.0 [`#1732`](https://github.com/th-ch/youtube-music/pull/1732)
|
||||||
|
- chore(deps): update pnpm to v8.15.2 [`#1729`](https://github.com/th-ch/youtube-music/pull/1729)
|
||||||
|
- Update Copyright - 2024 [`#1730`](https://github.com/th-ch/youtube-music/pull/1730)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7 [`#1728`](https://github.com/th-ch/youtube-music/pull/1728)
|
||||||
|
- fix(deps): update dependency @floating-ui/dom to v1.6.3 [`#1727`](https://github.com/th-ch/youtube-music/pull/1727)
|
||||||
|
- chore(deps): update dependency electron to v28.2.2 [`#1717`](https://github.com/th-ch/youtube-music/pull/1717)
|
||||||
|
- chore(deps): update dependency vite to v5.1.1 [`#1718`](https://github.com/th-ch/youtube-music/pull/1718)
|
||||||
|
- chore(deps): update dependency @types/semver to v7.5.7 [`#1724`](https://github.com/th-ch/youtube-music/pull/1724)
|
||||||
|
- fix(deps): update dependency @floating-ui/dom to v1.6.2 [`#1725`](https://github.com/th-ch/youtube-music/pull/1725)
|
||||||
|
- chore(deps): update dependency rollup to v4.10.0 [`#1719`](https://github.com/th-ch/youtube-music/pull/1719)
|
||||||
|
- fix(deps): update dependency solid-js to v1.8.14 [`#1713`](https://github.com/th-ch/youtube-music/pull/1713)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.21.0 [`#1711`](https://github.com/th-ch/youtube-music/pull/1711)
|
||||||
|
- fix(deps): update dependency semver to v7.6.0 [`#1712`](https://github.com/th-ch/youtube-music/pull/1712)
|
||||||
|
- refactor(in-app-menu): refactor `in-app-menu` plugin [`#1710`](https://github.com/th-ch/youtube-music/pull/1710)
|
||||||
|
- chore(deps): update playwright monorepo to v1.41.2 [`#1706`](https://github.com/th-ch/youtube-music/pull/1706)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-beta.5 [`#1707`](https://github.com/th-ch/youtube-music/pull/1707)
|
||||||
|
- feat(album-color-theme): support album color theme in all pages [`#1685`](https://github.com/th-ch/youtube-music/pull/1685)
|
||||||
|
- fix(deps): update dependency youtubei.js to v9.0.2 [`#1704`](https://github.com/th-ch/youtube-music/pull/1704)
|
||||||
|
- fix(deps): update dependency i18next to v23.8.2 [`#1702`](https://github.com/th-ch/youtube-music/pull/1702)
|
||||||
|
- feat: Support disabling scrobbling for non-music content [`#1665`](https://github.com/th-ch/youtube-music/pull/1665)
|
||||||
|
- fix(deps): update dependency youtubei.js to v9 [`#1682`](https://github.com/th-ch/youtube-music/pull/1682)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-beta.4 [`#1698`](https://github.com/th-ch/youtube-music/pull/1698)
|
||||||
|
- fix(deps): update dependency i18next to v23.8.1 [`#1694`](https://github.com/th-ch/youtube-music/pull/1694)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.20.0 [`#1700`](https://github.com/th-ch/youtube-music/pull/1700)
|
||||||
|
- chore(deps): update pnpm to v8.15.1 [`#1699`](https://github.com/th-ch/youtube-music/pull/1699)
|
||||||
|
- chore(deps): update dependency esbuild to v0.20.0 [`#1691`](https://github.com/th-ch/youtube-music/pull/1691)
|
||||||
|
- chore(deps): update pnpm to v8.15.0 [`#1692`](https://github.com/th-ch/youtube-music/pull/1692)
|
||||||
|
- fix(deps): update dependency i18next to v23.7.20 [`#1684`](https://github.com/th-ch/youtube-music/pull/1684)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-beta.3 [`#1683`](https://github.com/th-ch/youtube-music/pull/1683)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-beta.2 [`#1681`](https://github.com/th-ch/youtube-music/pull/1681)
|
||||||
|
- chore(deps): update dependency rollup to v4.9.6 [`#1663`](https://github.com/th-ch/youtube-music/pull/1663)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-beta.1 [`#1670`](https://github.com/th-ch/youtube-music/pull/1670)
|
||||||
|
- fix(deps): update dependency i18next to v23.7.19 [`#1680`](https://github.com/th-ch/youtube-music/pull/1680)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.1 [`#1669`](https://github.com/th-ch/youtube-music/pull/1669)
|
||||||
|
- chore(deps): update pnpm to v8.14.3 [`#1668`](https://github.com/th-ch/youtube-music/pull/1668)
|
||||||
|
- chore(deps): update dependency vite-plugin-inspect to v0.8.3 [`#1672`](https://github.com/th-ch/youtube-music/pull/1672)
|
||||||
|
- chore(deps): update dependency esbuild to v0.19.12 [`#1673`](https://github.com/th-ch/youtube-music/pull/1673)
|
||||||
|
- fix(deps): update dependency @electron/remote to v2.1.2 [`#1676`](https://github.com/th-ch/youtube-music/pull/1676)
|
||||||
|
- chore: Update issue templates [`#1661`](https://github.com/th-ch/youtube-music/pull/1661)
|
||||||
|
- chore(deps): update playwright monorepo to v1.41.1 [`#1660`](https://github.com/th-ch/youtube-music/pull/1660)
|
||||||
|
- fix(deps): update dependency i18next to v23.7.18 [`#1662`](https://github.com/th-ch/youtube-music/pull/1662)
|
||||||
|
- chore(deps): update actions/dependency-review-action action to v4 [`#1654`](https://github.com/th-ch/youtube-music/pull/1654)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-alpha.11 [`#1656`](https://github.com/th-ch/youtube-music/pull/1656)
|
||||||
|
- chore(deps): update dependency vite to v5.0.12 [security] [`#1659`](https://github.com/th-ch/youtube-music/pull/1659)
|
||||||
|
- fix(deps): update dependency async-mutex to v0.4.1 [`#1653`](https://github.com/th-ch/youtube-music/pull/1653)
|
||||||
|
- chore(deps): update playwright monorepo to v1.41.0 [`#1651`](https://github.com/th-ch/youtube-music/pull/1651)
|
||||||
|
- feat: Better Scrobbler Plugin [`#1640`](https://github.com/th-ch/youtube-music/pull/1640)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-alpha.10 [`#1645`](https://github.com/th-ch/youtube-music/pull/1645)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.0 [`#1643`](https://github.com/th-ch/youtube-music/pull/1643)
|
||||||
|
- chore(README): Fix plugins names and add plugins in/to Readme (in menu too) [`#1624`](https://github.com/th-ch/youtube-music/pull/1624)
|
||||||
|
- fix(album-actions): Fixed album actions [`#1639`](https://github.com/th-ch/youtube-music/pull/1639)
|
||||||
|
- chore(deps): update playwright monorepo to v1.41.0-beta-1705101589000 [`#1638`](https://github.com/th-ch/youtube-music/pull/1638)
|
||||||
|
- fix(#1543): fix song control doesn't work [`#1637`](https://github.com/th-ch/youtube-music/pull/1637)
|
||||||
|
- chore(deps): update playwright monorepo to v1.41.0-beta-1705092460000 [`#1635`](https://github.com/th-ch/youtube-music/pull/1635)
|
||||||
|
- chore(deps): update dependency rollup to v4.9.5 [`#1629`](https://github.com/th-ch/youtube-music/pull/1629)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-alpha.9 [`#1627`](https://github.com/th-ch/youtube-music/pull/1627)
|
||||||
|
- chore(deps): update dependency electron to v29.0.0-alpha.8 [`#1608`](https://github.com/th-ch/youtube-music/pull/1608)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.15 [`#1615`](https://github.com/th-ch/youtube-music/pull/1615)
|
||||||
|
- chore(deps): update dependency rollup to v4.9.4 [`#1591`](https://github.com/th-ch/youtube-music/pull/1591)
|
||||||
|
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.15 [`#1616`](https://github.com/th-ch/youtube-music/pull/1616)
|
||||||
|
- chore(deps): update pnpm to v8.14.1 [`#1619`](https://github.com/th-ch/youtube-music/pull/1619)
|
||||||
|
- chore(deps): update dependency eslint-plugin-prettier to v5.1.3 [`#1618`](https://github.com/th-ch/youtube-music/pull/1618)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.1 [`#1612`](https://github.com/th-ch/youtube-music/pull/1612)
|
||||||
|
- fix(deps): update dependency youtubei.js to v8.2.0 [`#1614`](https://github.com/th-ch/youtube-music/pull/1614)
|
||||||
|
- chore(deps): update dependency electron-vite to v2.0.0 [`#1609`](https://github.com/th-ch/youtube-music/pull/1609)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.0 [`#1603`](https://github.com/th-ch/youtube-music/pull/1603)
|
||||||
|
- chore(deps): update dependency electron-vite to v2.0.0-beta.4 [`#1602`](https://github.com/th-ch/youtube-music/pull/1602)
|
||||||
|
- fix: fix upgrade button [`#1199`](https://github.com/th-ch/youtube-music/issues/1199)
|
||||||
|
- fix(mpris): fix mpris invalid position [`#1726`](https://github.com/th-ch/youtube-music/issues/1726)
|
||||||
|
- fix: discord RPC (fix #1664) [`#1664`](https://github.com/th-ch/youtube-music/issues/1664)
|
||||||
|
- fix: remove sign-in button (fix #1199) [`#1199`](https://github.com/th-ch/youtube-music/issues/1199)
|
||||||
|
- Fix #1617 [`#1617`](https://github.com/th-ch/youtube-music/issues/1617)
|
||||||
|
- fix(crossfade): fix #1633 [`#1633`](https://github.com/th-ch/youtube-music/issues/1633)
|
||||||
|
- fix: fix #1621 [`#1621`](https://github.com/th-ch/youtube-music/issues/1621)
|
||||||
|
- fix(tuna-obs): partially fix #1596 [`#1596`](https://github.com/th-ch/youtube-music/issues/1596)
|
||||||
|
- fix(discord): fix hide duration button [`#1644`](https://github.com/th-ch/youtube-music/issues/1644)
|
||||||
|
- fix(in-app-menu): fix invalid `margin-top` [`#1597`](https://github.com/th-ch/youtube-music/issues/1597)
|
||||||
|
- fix(README): fix `plugins` path [`#1598`](https://github.com/th-ch/youtube-music/issues/1598)
|
||||||
|
- chore(i18n): Translated using Weblate (Vietnamese) [`0528637`](https://github.com/th-ch/youtube-music/commit/05286371353e8b4c36a5b9fe9011ae5dfdc7ee82)
|
||||||
|
- chore: update pnpm-lock [`fd8d59b`](https://github.com/th-ch/youtube-music/commit/fd8d59bada56dab4e156d22394fe0c5efec5abc4)
|
||||||
|
- fix(in-app-menu): fix app crash in production [`febc63e`](https://github.com/th-ch/youtube-music/commit/febc63edef375bd82db48b7fb460ec5a601ab872)
|
||||||
|
|
||||||
#### [v3.2.2](https://github.com/th-ch/youtube-music/compare/v3.2.1...v3.2.2)
|
#### [v3.2.2](https://github.com/th-ch/youtube-music/compare/v3.2.1...v3.2.2)
|
||||||
|
|
||||||
|
> 5 January 2024
|
||||||
|
|
||||||
- feat(tray): Add song info and paused icon [`#1592`](https://github.com/th-ch/youtube-music/pull/1592)
|
- feat(tray): Add song info and paused icon [`#1592`](https://github.com/th-ch/youtube-music/pull/1592)
|
||||||
- fix(skip-silences): fix audio distorted [`#1141`](https://github.com/th-ch/youtube-music/issues/1141)
|
- fix(skip-silences): fix audio distorted [`#1141`](https://github.com/th-ch/youtube-music/issues/1141)
|
||||||
- chore(deps): update dependency rollup to v4.9.3 [`0c3c380`](https://github.com/th-ch/youtube-music/commit/0c3c3805918adf2a185a7f1dc67ea3af8135863d)
|
- chore(deps): update dependency rollup to v4.9.3 [`0c3c380`](https://github.com/th-ch/youtube-music/commit/0c3c3805918adf2a185a7f1dc67ea3af8135863d)
|
||||||
|
|||||||
@ -478,7 +478,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="footer-copyright">© 2021 th-ch</div>
|
<div class="footer-copyright">© 2024 th-ch</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
390
docs/readme/README-es.md
Normal file
390
docs/readme/README-es.md
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
**Electron wrapper de YouTube Music con las siguientes características:**
|
||||||
|
|
||||||
|
- Apariencia y sensación nativa, tiene como objetivo mantener la interfaz original
|
||||||
|
- Framework para plugins personalizados: cambia YouTube Music según tus necesidades (estilo, contenido, funciones), habilita/deshabilita plugins con un solo clic
|
||||||
|
|
||||||
|
## Imagen de demostración
|
||||||
|
|
||||||
|
| Pantalla del reproductor (color del álbum como tema y luz ambiental) |
|
||||||
|
|:---------------------------------------------------------------------------------------------------------:|
|
||||||
|
||
|
||||||
|
|
||||||
|
## Contenido
|
||||||
|
|
||||||
|
- [Características](#características)
|
||||||
|
- [Plugins disponibles](#plugins-disponibles)
|
||||||
|
- [Traducción](#traducción)
|
||||||
|
- [Descarga](#descarga)
|
||||||
|
- [Arch Linux](#arch-linux)
|
||||||
|
- [macOS](#macos)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Cómo instalar sin conexión a internet? (en Windows)](#cómo-instalar-sin-conexión-a-internet-en-windows)
|
||||||
|
- [Temas](#temas)
|
||||||
|
- [Dev](#dev)
|
||||||
|
- [Crea tus propios plugins](#crea-tus-propios-plugins)
|
||||||
|
- [Creación de un plugin](#creación-de-un-plugin)
|
||||||
|
- [Casos de uso comunes](#casos-de-uso-comunes)
|
||||||
|
- [Compilar](#compilar)
|
||||||
|
- [Vista previa de producción](#vista-previa-de-producción)
|
||||||
|
- [Tests](#tests)
|
||||||
|
- [Licencia](#licencia)
|
||||||
|
- [Preguntas frecuentes](#preguntas-frecuentes)
|
||||||
|
|
||||||
|
## Características:
|
||||||
|
|
||||||
|
- **Confirmación automática al pausar** (Siempre habilitado): desactiva
|
||||||
|
el mensaje emergente ["¿Continuar reproduciendo?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
|
||||||
|
que pausa la música después de cierto tiempo
|
||||||
|
|
||||||
|
- Y más ...
|
||||||
|
|
||||||
|
## Plugins disponibles:
|
||||||
|
|
||||||
|
- **Bloqueador de Anuncios**: Bloquea todos los anuncios y rastreadores de forma predeterminada
|
||||||
|
|
||||||
|
- **Acciones de Álbum**: Agrega botones de deshacer No me gusta, No me gusta, Me gusta, y Deshacer me gusta a todas las canciones de una lista de reproducción o álbum
|
||||||
|
|
||||||
|
- **Tema de Color del Álbum**: Aplica un tema dinámico y efectos visuales basados en la paleta de colores del álbum
|
||||||
|
|
||||||
|
- **Modo Ambiente**: Aplica un efecto de iluminación proyectando colores suaves del video en el fondo de tu pantalla
|
||||||
|
|
||||||
|
- **Compresor de Audio**: Aplica compresión al audio (reduce el volumen de las partes más fuertes de la señal y aumenta el
|
||||||
|
volumen de las partes más suaves)
|
||||||
|
|
||||||
|
- **Barra de Navegación Difuminada**: hace que la barra de navegación sea transparente y borrosa
|
||||||
|
|
||||||
|
- **Omitir Restricciones de Edades**: omite la verificación de edad de YouTube
|
||||||
|
|
||||||
|
- **Selector de Subtítulos**: Habilita los subtítulos
|
||||||
|
|
||||||
|
- **Barra Lateral Compacta**: Siempre muestra la barra lateral en modo compacto
|
||||||
|
|
||||||
|
- **Crossfade**: Transición suave entre canciones
|
||||||
|
|
||||||
|
- **Desactivar Reproducción Automática**: Hace que cada canción comience en modo "pausado"
|
||||||
|
|
||||||
|
- **[Discord](https://discord.com/) Rich Presence**: Muestra a tus amigos lo que estás escuchando
|
||||||
|
con [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||||
|
|
||||||
|
- **Descargador**: Descarga
|
||||||
|
MP3 [directamente desde la interfaz](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||||
|
|
||||||
|
- **Volumen Exponencial**: Hace que el control de volumen
|
||||||
|
sea [exponencial](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) para facilitar la
|
||||||
|
selección de volúmenes más bajos
|
||||||
|
|
||||||
|
- **Menú en la Aplicación**: [da a las barras un aspecto elegante y oscuro](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
||||||
|
|
||||||
|
> (consulta [esta publicación](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) si tienes problemas
|
||||||
|
para acceder al menú después de habilitar este plugin y la opción hide-menu)
|
||||||
|
|
||||||
|
- **Scrobbler**: Agrega soporte para scrobbling en [Last.fm](https://www.last.fm/) y [ListenBrainz](https://listenbrainz.org/)
|
||||||
|
|
||||||
|
- **Lumia Stream**: Agrega soporte para [Lumia Stream](https://lumiastream.com/)
|
||||||
|
|
||||||
|
- **Letras Genius**: Agrega soporte de letras para la mayoría de las canciones
|
||||||
|
|
||||||
|
- **Music Together**: Comparte una lista de reproducción con otros. Cuando el anfitrión reproduce una canción, todos los demás escucharán la misma canción
|
||||||
|
|
||||||
|
- **Navegación**: Flechas de siguiente/anterior integradas directamente en la interfaz, como en tu navegador favorito
|
||||||
|
|
||||||
|
- **Sin Inicio de Sesión de Google**: Elimina los botones y enlaces de inicio de sesión de Google de la interfaz
|
||||||
|
|
||||||
|
- **Notificaciones**: Muestra una notificación cuando comienza una canción
|
||||||
|
a reproducirse ([notificaciones interactivas](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
|
||||||
|
están disponibles en Windows)
|
||||||
|
|
||||||
|
- **Picture-in-picture**: permite cambiar la aplicación al modo picture-in-picture
|
||||||
|
|
||||||
|
- **Velocidad de Reproducción**: Escucha rápido, escucha
|
||||||
|
lento! [Agrega un deslizador que controla la velocidad de reproducción de las canciones](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
||||||
|
|
||||||
|
- **Volumen Preciso**: Controla el volumen de forma precisa utilizando la rueda del mouse/atajos de teclado, con un HUD personalizado y pasos de volumen personalizables
|
||||||
|
|
||||||
|
- **Atajos (& MPRIS)**: Permite configurar atajos globales para la reproducción (reproducir/pausar/siguiente/anterior) +
|
||||||
|
desactivar [osd multimedia](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
|
||||||
|
al anular las teclas multimedia + habilitar Ctrl/CMD + F para buscar + habilitar el soporte mpris de Linux para
|
||||||
|
teclas multimedia + [atajos personalizados](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
|
||||||
|
para [usuarios avanzados](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||||
|
|
||||||
|
- **Saltar Canción no Gustada**: Salta las canciones que no te gustan
|
||||||
|
|
||||||
|
- **Saltar Silencios**: Salta automáticamente las secciones de silencio
|
||||||
|
|
||||||
|
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Salta automáticamente las partes que no son de música, como la introducción/final o
|
||||||
|
partes de videos musicales donde no se reproduce la canción
|
||||||
|
|
||||||
|
- **Control Multimedia en la Barra de Tareas**: Controla la reproducción desde
|
||||||
|
la [barra de tareas de Windows](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||||
|
|
||||||
|
- **TouchBar**: Diseño personalizado de TouchBar para macOS
|
||||||
|
|
||||||
|
- **Tuna OBS**: Integración con el complemento [Tuna](https://obsproject.com/forum/resources/tuna.843/) de [OBS](https://obsproject.com/)
|
||||||
|
|
||||||
|
- **Cambiador de Calidad de Video**: Permite cambiar la calidad del video con
|
||||||
|
un [botón](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) en
|
||||||
|
la superposición de video
|
||||||
|
|
||||||
|
- **Alternar Video**: Agrega
|
||||||
|
un [botón](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) para
|
||||||
|
alternar entre el modo de video/canción. también puede eliminar opcionalmente toda la pestaña de video
|
||||||
|
|
||||||
|
- **Visualizador**: Diferentes visualizadores de música
|
||||||
|
|
||||||
|
## Traducción
|
||||||
|
|
||||||
|
Puedes ayudar con la traducción en [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="estado de traducción" />
|
||||||
|
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="estado de traducción 2" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Descarga
|
||||||
|
|
||||||
|
Puedes consultar la [última versión](https://github.com/th-ch/youtube-music/releases/latest) para encontrar rápidamente la versión más reciente.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
Instala el paquete [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) desde AUR. Para obtener instrucciones de instalación de AUR, consulta esta [página del wiki](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
Puedes instalar la aplicación usando Homebrew (consulta la [definición de cask](https://github.com/th-ch/homebrew-youtube-music)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install th-ch/youtube-music/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
Si instalas la aplicación manualmente y obtienes un error "está dañado y no se puede abrir" al iniciar la aplicación, ejecuta lo siguiente en la Terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xattr -cr /Applications/YouTube\ Music.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Puedes usar el [administrador de paquetes Scoop](https://scoop.sh) para instalar el paquete `youtube-music` desde
|
||||||
|
el [`extras` bucket](https://github.com/ScoopInstaller/Extras).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install extras/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternativamente, puedes usar [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), el administrador de paquetes CLI oficial de Windows 11 para instalar el paquete `th-ch.YouTubeMusic`.
|
||||||
|
|
||||||
|
*Nota: Microsoft Defender SmartScreen podría bloquear la instalación ya que proviene de un "editor desconocido". Esto también esválido para la instalación manual al intentar ejecutar el ejecutable (.exe) después de una descarga manual aquí en GitHub (mismo archivo).*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
winget install th-ch.YouTubeMusic
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Cómo instalar sin conexión a Internet? (en Windows)
|
||||||
|
|
||||||
|
- Descarga el archivo `*.nsis.7z` para _la arquitectura de tu dispositivo_ en la [página de lanzamientos](https://github.com/th-ch/youtube-music/releases/latest).
|
||||||
|
- `x64` para Windows de 64 bits
|
||||||
|
- `ia32` para Windows de 32 bits
|
||||||
|
- `arm64` para Windows ARM64
|
||||||
|
- Descarga el instalador en la página de lanzamientos. (`*-Setup.exe`)
|
||||||
|
- Colócalos en el **mismo directorio**.
|
||||||
|
- Ejecuta el instalador.
|
||||||
|
|
||||||
|
## Temas
|
||||||
|
|
||||||
|
Puedes cargar archivos CSS para cambiar la apariencia de la aplicación (Opciones > Ajustes visuales > Tema).
|
||||||
|
|
||||||
|
Algunos temas predefinidos están disponibles en https://github.com/kerichdev/themes-for-ytmdesktop-player.
|
||||||
|
|
||||||
|
## Dev
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/th-ch/youtube-music
|
||||||
|
cd youtube-music
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Crea tus propios plugins
|
||||||
|
|
||||||
|
Usando plugins, puedes:
|
||||||
|
|
||||||
|
- manipular la aplicación - se pasa el `BrowserWindow` de electron al controlador del plugin
|
||||||
|
- cambiar la interfaz manipulando el HTML/CSS
|
||||||
|
|
||||||
|
### Creación de un plugin
|
||||||
|
|
||||||
|
Crea una carpeta en `src/plugins/NOMBRE-DEL-PLUGIN`:
|
||||||
|
|
||||||
|
- `index.ts`: el archivo principal del plugin
|
||||||
|
```typescript
|
||||||
|
import style from './style.css?inline'; // importar estilo como inline
|
||||||
|
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: "Plugin Label",
|
||||||
|
restartNeeded: true, // si el valor es true, ytmusic muestra el diálogo de reinicio
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // tu configuración personalizada
|
||||||
|
stylesheets: [style], // tu estilo personalizado,
|
||||||
|
menu: async ({ getConfig, setConfig }) => {
|
||||||
|
// Todos los métodos *Config están envueltos en 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();
|
||||||
|
|
||||||
|
// puedes comunicarte con el plugin de renderizado
|
||||||
|
ipc.handle("some-event", () => {
|
||||||
|
return "hello";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// se activa cuando cambia la configuración
|
||||||
|
onConfigChange(newConfig) { /* ... */ },
|
||||||
|
// se activa cuando se desactiva el plugin
|
||||||
|
stop(context) { /* ... */ },
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
async start(context) {
|
||||||
|
console.log(await context.ipc.invoke("some-event"));
|
||||||
|
},
|
||||||
|
// Solo disponible en el plugin de renderizado
|
||||||
|
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
|
||||||
|
// establecer la configuración del plugin fácilmente
|
||||||
|
context.setConfig({ myConfig: api.getVolume() });
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) { /* ... */ },
|
||||||
|
stop(_context) { /* ... */ },
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
async start({ getConfig }) {
|
||||||
|
const config = await getConfig();
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) {},
|
||||||
|
stop(_context) {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Casos de uso comunes
|
||||||
|
|
||||||
|
- inyectar CSS personalizado: crea un archivo `style.css` en la misma carpeta y luego:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// index.ts
|
||||||
|
import style from './style.css?inline'; // importar estilo como inline
|
||||||
|
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Plugin Label',
|
||||||
|
restartNeeded: true, // si el valor es true, ytmusic mostrará el diálogo de reinicio
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // tu configuración personalizada
|
||||||
|
stylesheets: [style], // tu estilo personalizado
|
||||||
|
renderer() {} // define el hook del renderizador
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- Si quieres cambiar el HTML:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Plugin Label',
|
||||||
|
restartNeeded: true, // si el valor es true, ytmusic mostrará el diálogo de reinicio
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // tu configuración personalizada
|
||||||
|
renderer() {
|
||||||
|
// Elimina el botón de inicio de sesión
|
||||||
|
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
|
||||||
|
} // define el hook del renderizador
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- comunicación entre el front y el back: se puede hacer utilizando el módulo ipcMain de electron. Ver archivo `index.ts` y
|
||||||
|
ejemplo en el plugin `sponsorblock`.
|
||||||
|
|
||||||
|
## Compilar
|
||||||
|
|
||||||
|
1. Clonar el repositorio
|
||||||
|
2. Seguir [esta guía](https://pnpm.io/es/installation) para instalar `pnpm`
|
||||||
|
3. Ejecutar `pnpm install --frozen-lockfile` para instalar las dependencias
|
||||||
|
4. Ejecutar `pnpm build:OS`
|
||||||
|
|
||||||
|
- `pnpm dist:win` - Windows
|
||||||
|
- `pnpm dist:linux` - Linux (amd64)
|
||||||
|
- `pnpm dist:linux:deb-arm64` - Linux (arm64 para Debian)
|
||||||
|
- `pnpm dist:linux:rpm-arm64` - Linux (arm64 para Fedora)
|
||||||
|
- `pnpm dist:mac` - macOS (amd64)
|
||||||
|
- `pnpm dist:mac:arm64` - macOS (arm64)
|
||||||
|
|
||||||
|
Construye la aplicación para macOS, Linux y Windows,
|
||||||
|
utilizando [electron-builder](https://github.com/electron-userland/electron-builder).
|
||||||
|
|
||||||
|
## Vista previa de producción
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Utiliza [Playwright](https://playwright.dev/) para probar la aplicación.
|
||||||
|
|
||||||
|
## Licencia
|
||||||
|
|
||||||
|
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||||
|
|
||||||
|
## Preguntas frecuentes
|
||||||
|
|
||||||
|
### ¿Por qué no se muestra el menú de aplicaciones?
|
||||||
|
|
||||||
|
Si la opción `Ocultar menú` está activada - puedes mostrar el menú con la tecla <kbd>alt</kbd> (o <kbd>\`</kbd> [acento grave] si estás utilizando el plugin in-app-menu)
|
||||||
388
docs/readme/README-fr.md
Normal file
388
docs/readme/README-fr.md
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
<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="https://github.com/th-ch/youtube-music/raw/master/web/youtube-music.svg" width="400" height="100" alt="SVG YouTube Music">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
**Enveloppe Electron autour de YouTube Music offrant :**
|
||||||
|
|
||||||
|
- Aspect & sensation naturels, vise à conserver l'interface originale
|
||||||
|
- Cadre pour les plugins personnalisés : modifiez YouTube Music selon vos besoins (style, contenu, fonctionnalités), activez/désactivez les plugins en
|
||||||
|
un clic
|
||||||
|
|
||||||
|
## Image de démonstration
|
||||||
|
|
||||||
|
| Écran du lecteur (thème de couleur de l'album & lumière ambiante) |
|
||||||
|
|:---------------------------------------------------------------------------------------------------------:|
|
||||||
|
||
|
||||||
|
|
||||||
|
## Contenu
|
||||||
|
|
||||||
|
- [Fonctionnalités](#fonctionnalités)
|
||||||
|
- [Plugins disponibles](#plugins-disponibles)
|
||||||
|
- [Traduction](#traduction)
|
||||||
|
- [Téléchargement](#téléchargement)
|
||||||
|
- [Arch Linux](#arch-linux)
|
||||||
|
- [MacOS](#macos)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Comment installer sans connexion réseau ? (sous Windows)](#comment-installer-sans-connexion-réseau-sous-windows)
|
||||||
|
- [Thèmes](#thèmes)
|
||||||
|
- [Dev](#dev)
|
||||||
|
- [Créez vos propres plugins](#créez-vos-propres-plugins)
|
||||||
|
- [Créer un plugin](#créer-un-plugin)
|
||||||
|
- [Cas d'utilisation courants](#cas-dutilisation-courants)
|
||||||
|
- [Construction](#construction)
|
||||||
|
- [Aperçu de la production](#aperçu-de-la-production)
|
||||||
|
- [Tests](#tests)
|
||||||
|
- [Licence](#licence)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
|
||||||
|
## Fonctionnalités :
|
||||||
|
|
||||||
|
- **Confirmation automatique lors de la pause** (Toujours activé) : désactiver
|
||||||
|
la pop-up ["Continuer à regarder ?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
|
||||||
|
qui pause la musique après un certain temps
|
||||||
|
|
||||||
|
- Et plus encore ...
|
||||||
|
|
||||||
|
## Plugins disponibles :
|
||||||
|
|
||||||
|
- **Bloqueur de publicités** : Bloquez toutes les publicités et le suivi dès le départ
|
||||||
|
|
||||||
|
- **Actions d'album** : Ajoute des boutons Je n'aime pas, Dislike, J'aime, et Unlike pour appliquer cela à toutes les chansons dans une playlist ou un album
|
||||||
|
|
||||||
|
- **Thème de couleur d'album** : Applique un thème dynamique et des effets visuels basés sur la palette de couleurs de l'album
|
||||||
|
|
||||||
|
- **Mode Ambiant** : Applique un effet d'éclairage en projetant des couleurs douces de la vidéo, sur l'arrière-plan de votre écran
|
||||||
|
|
||||||
|
- **Compresseur Audio** : Appliquer une compression audio (diminue le volume des parties les plus fortes du signal et augmente le
|
||||||
|
volume des parties les plus douces)
|
||||||
|
|
||||||
|
- **Barre de navigation floue** : rend la barre de navigation transparente et floue
|
||||||
|
|
||||||
|
- **Contournement des restrictions d'âge** : contourner la vérification d'âge de YouTube
|
||||||
|
|
||||||
|
- **Sélecteur de sous-titres** : Activer les sous-titres
|
||||||
|
|
||||||
|
- **Barre latérale compacte** : Toujours définir la barre latérale en mode compact
|
||||||
|
|
||||||
|
- **Fondu enchaîné** : Fondu enchaîné entre les chansons
|
||||||
|
|
||||||
|
- **Désactiver la lecture automatique** : Fait démarrer chaque chanson en mode "pause"
|
||||||
|
|
||||||
|
- **[Discord](https://discord.com/) Présence riche** : Montrez à vos amis ce que vous écoutez
|
||||||
|
avec [Présence riche](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||||
|
|
||||||
|
- **Téléchargeur** : télécharge des
|
||||||
|
MP3 [directement depuis l'interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||||
|
|
||||||
|
- **Volume exponentiel** : Rend le curseur de volume
|
||||||
|
[exponentiel](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) afin qu'il soit plus facile de
|
||||||
|
sélectionner des volumes plus bas
|
||||||
|
|
||||||
|
- **Menu In-App** : [donne aux barres un aspect chic et sombre](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
||||||
|
|
||||||
|
> (voir [ce poste](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) si vous avez des problèmes
|
||||||
|
pour accéder au menu après avoir activé ce plugin et l'option masquer-menu)
|
||||||
|
|
||||||
|
- **Scrobbler** : Ajoute le support de scrobbling pour [Last.fm](https://www.last.fm/) et [ListenBrainz](https://listenbrainz.org/)
|
||||||
|
|
||||||
|
- **Lumia Stream** : Ajoute le support de [Lumia Stream](https://lumiastream.com/)
|
||||||
|
|
||||||
|
- **Lyrics Genius** : Ajoute le support des paroles pour la plupart des chansons
|
||||||
|
|
||||||
|
- **Musique Ensemble** : Partagez une playlist avec d'autres. Lorsque l'hôte joue une chanson, tout le monde entendra la même chanson
|
||||||
|
|
||||||
|
- **Navigation** : Flèches de navigation Suivant/Retour directement intégrées dans l'interface, comme dans votre navigateur préféré
|
||||||
|
|
||||||
|
- **Pas de connexion Google** : Supprime les boutons et les liens de connexion Google de l'interface
|
||||||
|
|
||||||
|
- **Notifications** : Affiche une notification lorsqu'une chanson commence à jouer ([notifications interactives](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
|
||||||
|
sont disponibles sur Windows)
|
||||||
|
|
||||||
|
- **Image dans l'image** : permet de passer l'application en mode image dans l'image
|
||||||
|
|
||||||
|
- **Vitesse de lecture** : Écoutez rapidement, écoutez lentement ! [Ajoute un curseur qui contrôle la vitesse des chansons](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
||||||
|
|
||||||
|
- **Volume précis** : Contrôlez le volume précisément en utilisant la molette de la souris/raccourcis clavier, avec un hud personnalisé et des étapes de volume personnalisables
|
||||||
|
|
||||||
|
- **Raccourcis (& MPRIS)** : Permet de définir des raccourcis globaux pour la lecture (lecture/pause/suivant/précédent) +
|
||||||
|
désactive [osd média](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
|
||||||
|
en remplaçant les touches multimédias + activer Ctrl/CMD + F pour rechercher + activer le support mpris linux pour
|
||||||
|
les touches multimédias + [raccourcis personnalisés](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
|
||||||
|
pour [utilisateurs avancés](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||||
|
|
||||||
|
- **Passer la chanson non aimée** : passe les chansons non aimées
|
||||||
|
|
||||||
|
- **Passer les silences** : passe automatiquement les sections silencieuses
|
||||||
|
|
||||||
|
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock) : Saute automatiquement les parties non musicales comme les intros/outros ou
|
||||||
|
les parties des clips vidéo où la chanson n'est pas jouée
|
||||||
|
|
||||||
|
- **Contrôle multimédia de la barre des tâches** : Contrôlez la lecture depuis
|
||||||
|
votre [barre des tâches Windows](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||||
|
|
||||||
|
- **TouchBar** : Disposition personnalisée de la TouchBar pour macOS
|
||||||
|
|
||||||
|
- **Tuna OBS** : Intégration avec le
|
||||||
|
plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/) d'[OBS](https://obsproject.com/)
|
||||||
|
|
||||||
|
- **Changeur de qualité vidéo** : Permet de changer la qualité vidéo avec
|
||||||
|
un [bouton](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) sur
|
||||||
|
l'overlay vidéo
|
||||||
|
|
||||||
|
- **Bascule vidéo** : Ajoute
|
||||||
|
un [bouton](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) pour
|
||||||
|
basculer entre le mode Vidéo/Chanson. peut également supprimer l'onglet vidéo entier
|
||||||
|
|
||||||
|
- **Visualiseur** : Différents visualiseurs musicaux
|
||||||
|
|
||||||
|
## Traduction
|
||||||
|
|
||||||
|
Vous pouvez aider à la traduction sur [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="statut de la traduction" />
|
||||||
|
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="statut de la traduction 2" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Téléchargement
|
||||||
|
|
||||||
|
Vous pouvez consulter la [dernière sortie](https://github.com/th-ch/youtube-music/releases/latest) pour trouver rapidement la
|
||||||
|
dernière version.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
Installez le paquet [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) depuis l'AUR. Pour les instructions d'installation de l'AUR, consultez
|
||||||
|
cette [page wiki](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
|
||||||
|
Vous pouvez installer l'application en utilisant Homebrew (voir la [définition du fût](https://github.com/th-ch/homebrew-youtube-music)) :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install th-ch/youtube-music/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
Si vous installez l'application manuellement et obtenez une erreur "est endommagé et ne peut pas être ouvert." lors du lancement de l'application, exécutez ce qui suit dans le Terminal :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xattr -cr /Applications/YouTube\ Music.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Vous pouvez utiliser le [gestionnaire de paquets Scoop](https://scoop.sh) pour installer le paquet `youtube-music` depuis le [seau `extras`](https://github.com/ScoopInstaller/Extras).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install extras/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternativement, vous pouvez utiliser [Winget](https://learn.microsoft.com/fr-fr/windows/package-manager/winget/), le gestionnaire de paquets CLI officiel de Windows 11, pour installer le paquet `th-ch.YouTubeMusic`.
|
||||||
|
|
||||||
|
*Note : Microsoft Defender SmartScreen pourrait bloquer l'installation car elle provient d'un "éditeur inconnu". Ceci est également vrai pour l'installation manuelle lors de l'essai d'exécution de l'exécutable (.exe) après un téléchargement manuel ici sur GitHub (même fichier).*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
winget install th-ch.YouTubeMusic
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Comment installer sans connexion réseau ? (sous Windows)
|
||||||
|
|
||||||
|
- Téléchargez le fichier `*.nsis.7z` pour _l'architecture de votre appareil_ sur la [page des versions](https://github.com/th-ch/youtube-music/releases/latest).
|
||||||
|
- `x64` pour Windows 64 bits
|
||||||
|
- `ia32` pour Windows 32 bits
|
||||||
|
- `arm64` pour Windows ARM64
|
||||||
|
- Téléchargez l'installeur sur la page des versions. (`*-Setup.exe`)
|
||||||
|
- Placez-les dans le **même dossier**.
|
||||||
|
- Exécutez l'installeur.
|
||||||
|
|
||||||
|
## Thèmes
|
||||||
|
|
||||||
|
Vous pouvez charger des fichiers CSS pour changer l'apparence de l'application (Options > Ajustements visuels > Thèmes).
|
||||||
|
|
||||||
|
Certains thèmes prédéfinis sont disponibles sur [https://github.com/kerichdev/themes-for-ytmdesktop-player](https://github.com/kerichdev/themes-for-ytmdesktop-player).
|
||||||
|
|
||||||
|
## Dev
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/th-ch/youtube-music
|
||||||
|
cd youtube-music
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
## Créez vos propres plugins
|
||||||
|
|
||||||
|
En utilisant des plugins, vous pouvez :
|
||||||
|
|
||||||
|
- manipuler l'application - la `BrowserWindow` d'Electron est passée au gestionnaire de plugin
|
||||||
|
- changer le front en manipulant le HTML/CSS
|
||||||
|
|
||||||
|
### Créer un plugin
|
||||||
|
|
||||||
|
Créez un dossier dans `src/plugins/NOM-DE-VOTRE-PLUGIN` :
|
||||||
|
|
||||||
|
- `index.ts` : le fichier principal du plugin
|
||||||
|
```typescript
|
||||||
|
import style from './style.css?inline'; // importez le style comme inline
|
||||||
|
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Étiquette du plugin',
|
||||||
|
restartNeeded: true, // si la valeur est vraie, ytmusic affichera la boîte de dialogue de redémarrage
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // votre configuration personnalisée
|
||||||
|
stylesheets: [style], // votre style personnalisé,
|
||||||
|
menu: async ({ getConfig, setConfig }) => {
|
||||||
|
// Toutes les méthodes *Config sont des promesses encapsulées <T>
|
||||||
|
const config = await getConfig();
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'menu',
|
||||||
|
submenu: [1, 2, 3].map((value) => ({
|
||||||
|
label: `valeur ${value}`,
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.value === value,
|
||||||
|
click() {
|
||||||
|
setConfig({ value });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
start({ window, ipc }) {
|
||||||
|
window.maximize();
|
||||||
|
|
||||||
|
// vous pouvez communiquer avec le plugin du rendu
|
||||||
|
ipc.handle('un événement', () => {
|
||||||
|
return 'bonjour';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// il est déclenché lorsque la configuration change
|
||||||
|
onConfigChange(newConfig) { /* ... */ },
|
||||||
|
// il est déclenché lorsque le plugin est désactivé
|
||||||
|
stop(context) { /* ... */ },
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
async start(context) {
|
||||||
|
console.log(await context.ipc.invoke('un événement'));
|
||||||
|
},
|
||||||
|
// Seul le crochet disponible pour le rendu
|
||||||
|
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
|
||||||
|
// définir facilement la configuration du plugin
|
||||||
|
context.setConfig({ myConfig: api.getVolume() });
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) { /* ... */ },
|
||||||
|
stop(_context) { /* ... */ },
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
async start({ getConfig }) {
|
||||||
|
const config is obtained by `getConfig` method.
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) {},
|
||||||
|
stop(_context) {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cas d'utilisation courants
|
||||||
|
|
||||||
|
- **Injection de CSS personnalisé** : créez un fichier `style.css` dans le même dossier puis :
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// index.ts
|
||||||
|
import style from './style.css?inline'; // importez le style comme en ligne
|
||||||
|
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Étiquette du plugin',
|
||||||
|
restartNeeded: true, // si la valeur est vraie, ytmusic affichera la boîte de dialogue de redémarrage
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // votre configuration personnalisée
|
||||||
|
stylesheets: [style], // votre style personnalisé
|
||||||
|
renderer() {} // définissez le crochet de rendu
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Si vous voulez modifier le HTML** :
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Étiquette du plugin',
|
||||||
|
restartNeeded: true, // si la valeur est vraie, ytmusic affichera la boîte de dialogue de redémarrage
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // votre configuration personnalisée
|
||||||
|
renderer() {
|
||||||
|
// Supprimez le bouton de connexion
|
||||||
|
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
|
||||||
|
} // définissez le crochet de rendu
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Communication entre le front et le back** : cela peut se faire en utilisant le module ipcMain d'Electron. Voir le fichier `index.ts` et l'exemple dans le plugin `sponsorblock`.
|
||||||
|
|
||||||
|
## Construction
|
||||||
|
|
||||||
|
1. Clonez le dépôt
|
||||||
|
2. Suivez [ce guide](https://pnpm.io/installation) pour installer `pnpm`
|
||||||
|
3. Exécutez `pnpm install --frozen-lockfile` pour installer les dépendances
|
||||||
|
4. Exécutez `pnpm build:OS`
|
||||||
|
|
||||||
|
- `pnpm dist:win` - pour Windows
|
||||||
|
- `pnpm dist:linux` - pour Linux
|
||||||
|
- `pnpm dist:mac` - pour MacOS
|
||||||
|
|
||||||
|
Construit l'application pour macOS, Linux et Windows,
|
||||||
|
en utilisant [electron-builder](https://github.com/electron-userland/electron-builder).
|
||||||
|
|
||||||
|
## Aperçu de la production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Utilise [Playwright](https://playwright.dev/) pour tester l'application.
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Pourquoi le menu de l'application ne s'affiche-t-il pas ?
|
||||||
|
|
||||||
|
Si l'option `Masquer le menu` est activée - vous pouvez afficher le menu avec la touche <kbd>alt</kbd> (ou <kbd>\`</kbd> [backtick] si vous utilisez le plugin du menu intégré)
|
||||||
388
docs/readme/README-is.md
Normal file
388
docs/readme/README-is.md
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
# YouTube Tónlist
|
||||||
|
|
||||||
|
[](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>
|
||||||
|
|
||||||
|
**Electron umbúðir utan um YouTube Tónlist sem inniheldur:**
|
||||||
|
|
||||||
|
- Innfæddur útlit og tilfinning, miðar að því að halda upprunalegu viðmótinu
|
||||||
|
- Rammi fyrir sérsniðnar tengiforrit: breyttu YouTube Tónlist að þínum þörfum (stíl, efni, eiginleikar), virkjaðu/slökktu á viðbætur í
|
||||||
|
einn smellur
|
||||||
|
|
||||||
|
## Sýnishornsmynd
|
||||||
|
|
||||||
|
| Spilaraskjár (albúmslitaþema & umhverfisljós) |
|
||||||
|
|:---------------------------------------------------------------------------------------------------------:|
|
||||||
|
||
|
||||||
|
|
||||||
|
## Efni
|
||||||
|
|
||||||
|
- [Eiginleikar](#eiginleikar)
|
||||||
|
- [Tiltæk tengiforrit](#tiltæk-tengiforrit)
|
||||||
|
- [Þýðing](#þýðing)
|
||||||
|
- [Sækja](#sækja)
|
||||||
|
- [Arch Linux](#arch-linux)
|
||||||
|
- [MacOS](#macos)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Hvernig á að setja upp án nettengingar? (í Windows)](#hvernig-á-að-setja-upp-án-nettengingar-í-windows)
|
||||||
|
- [Þemu](#þemu)
|
||||||
|
- [Þróun](#þróun)
|
||||||
|
- [Búðu til þín eigin viðbætur](#búðu-til-þín-eigin-viðbætur)
|
||||||
|
- [Er að búa til viðbót](#er-að-búa-til-viðbót)
|
||||||
|
- [Algeng notkunartilvik](#algeng-notkunartilvik)
|
||||||
|
- [Byggja](#byggja)
|
||||||
|
- [Framleiðsluforskoðun](#framleiðsluforskoðun)
|
||||||
|
- [Prófanir](#prófanir)
|
||||||
|
- [Leyfi](#leyfi)
|
||||||
|
- [Algengustu spurningar](#algengustu-spurningar)
|
||||||
|
|
||||||
|
## Eiginleikar:
|
||||||
|
|
||||||
|
- **Sjálfvirk staðfesting þegar gert er hlé** (Alltaf virkt): slökkva á
|
||||||
|
["Halda áfram að horfa?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
|
||||||
|
popup sem gerir hlé á tónlist eftir ákveðinn tíma
|
||||||
|
|
||||||
|
- Og meira...
|
||||||
|
|
||||||
|
## Tiltæk tengiforrit:
|
||||||
|
|
||||||
|
- **Auglýsingablokkari**: Lokaðu fyrir allar auglýsingar og rakningar úr kassanum
|
||||||
|
|
||||||
|
- **Albúmsaðgerðir**: Bætir Ódíslika, Mislíkt, Líkt, og Ólíkt til að nota þetta á öll lög á spilunarlista eða albúm
|
||||||
|
|
||||||
|
- **Albúmslitaþema**: Beitir kraftmikið þema og sjónrænum áhrifum sem byggjast á litavali albúmsins
|
||||||
|
|
||||||
|
- **Umhverfishamur**: Beitir lýsingaráhrifum með því að varpa mildum litum úr myndbandinu í bakgrunn skjásins
|
||||||
|
|
||||||
|
- **Hljóðþjöppur**: Notaðu þjöppun á hljóð (lækkar hljóðstyrk háværustu hluta merkis og hækkar hljóðstyrk í mýkstu hlutunum)
|
||||||
|
|
||||||
|
- **Þoka Leiðsagnarstika**: Gerir leiðsögustikuna gagnsæja og óskýrt
|
||||||
|
|
||||||
|
- **Farið Framhjá Aldurstakmörkunum**: Framhjá aldursstaðfestingu YouTube
|
||||||
|
|
||||||
|
- **Yfirskriftarval**: Virkja skjátexta
|
||||||
|
|
||||||
|
- **Fyrirferðarlítillhliðarstika**: Stilltu hliðarstikuna alltaf í þétta stillingu
|
||||||
|
|
||||||
|
- **Krossfæra**: Krossfæra á milli lög
|
||||||
|
|
||||||
|
- **Slökkva á Sjálfvirkri Spilun**: Gerir lag að byrja í "hlé" ham
|
||||||
|
|
||||||
|
- **[Discord](https://discord.com/) Rík Nærveru**: Sýndu vinum þínum hvað þú hlustar á
|
||||||
|
með [Rík Nærveru](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||||
|
|
||||||
|
- **Niðurhalari**: Niðurhalum
|
||||||
|
MP3 [beint úr viðmótinu](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||||
|
|
||||||
|
- **Veldibundiðrúmmál**: Gerir hljóðstyrkssleðann [veldisvísis](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/)
|
||||||
|
svo það er auðveldara að velja lægra hljóðstyrk.
|
||||||
|
|
||||||
|
- **Valmynd í Forriti**: [Gefur börum flott, dökkt útlit](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
||||||
|
|
||||||
|
> (sjá [þessa færslu](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) ef þú átt í vandræðum
|
||||||
|
með að fá aðgang að valmyndinni eftir að hafa virkjað þessa viðbót og fela valmyndarvalkostinn)
|
||||||
|
|
||||||
|
- **Scrobbler**: Bætir við scrobbling stuðningi fyrir [Last.fm](https://www.last.fm/) og [ListenBrainz](https://listenbrainz.org/)
|
||||||
|
|
||||||
|
- **Lumia Stream**: Bætir við [Lumia Stream](https://lumiastream.com/) stuðningi
|
||||||
|
|
||||||
|
- **Söngtexti Snilld**: Bætir stuðningi við texta fyrir flest lög
|
||||||
|
|
||||||
|
- **Tónlist Saman**: Deila spilunarlista með öðrum. Þegar gestgjafinn spilar lag munu allir aðrir heyra sama lagið
|
||||||
|
|
||||||
|
- **Leiðsögn**: Næsta/Til baka leiðsagnarörvar beint samþættar í viðmótinu, eins og í uppáhalds vafranum þínum
|
||||||
|
|
||||||
|
- **Engin Google Innskráning**: Fjarlægðu Google innskráningarhnappa og tengla úr viðmótinu
|
||||||
|
|
||||||
|
- **Tilkynningar**: Birta tilkynningu þegar lag byrjar að spila
|
||||||
|
([gagnvirkartilkynningar](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) eru fáanlegar á Windows)
|
||||||
|
|
||||||
|
- **Mynd-í-Mynd**: Gerir kleift að skipta forritinu yfir í mynd-í-mynd stillingu
|
||||||
|
|
||||||
|
- **Spilunarhraði**: Hlustaðu hratt, hlustaðu hægt!
|
||||||
|
[Bætir við sleða sem stjórnar lagahraðanum](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
||||||
|
|
||||||
|
- **Nákvæmshljóðstyrkur**: Stjórnaðu hljóðstyrknum nákvæmlega með músarhjóli/hraðtökkum, með sérsniðnum HUD og sérsniðnum hljóðstyrksþrepum
|
||||||
|
|
||||||
|
- **Flýtileiðir (og MPRIS)**: Leyfir að stilla alþjóðlegarflýtilyklar fyrir spilun (spila/gera hlé/næsta/fyrri) +
|
||||||
|
óvirkja [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
|
||||||
|
með því að hnekkja miðlunarlyklum + virkja Ctrl/CMD + F til að leita + virkja linux mpris stuðning fyrir
|
||||||
|
miðlunarlyklar + [sérsniðnir flýtilyklar](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
|
||||||
|
fyrir [háþróaða notendur](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||||
|
- **Slepptu Lögum sem Mislíkuðust**: Sleppir mislíkaði lög
|
||||||
|
|
||||||
|
- **Slepptu Þögnum**: Slepptu sjálfkrafa þagnarköflum í lögum
|
||||||
|
|
||||||
|
- [**Styrktarblokk**](https://github.com/ajayyy/SponsorBlock): 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
|
||||||
|
|
||||||
|
- **Miðlunarstýringarverkefnastikunnar**: Stjórnaðu spilun frá [Windows verkefnastikunni þinni](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||||
|
|
||||||
|
- **Snertistiku**: Sérsniðið Snertistikuútlit fyrir macOS
|
||||||
|
|
||||||
|
- **Tuna OBS**: Samþætting við [OBS](https://obsproject.com/)
|
||||||
|
viðbótina [Tuna](https://obsproject.com/forum/resources/tuna.843/)
|
||||||
|
|
||||||
|
- **Myndbandgæðisbreyting**: Leyfir að breyta myndbandgæðum með
|
||||||
|
[hnappi](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) á
|
||||||
|
myndbandsyfirlaginu
|
||||||
|
|
||||||
|
- **Myndbandsrofi**: Bætir við [hnappi](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) til
|
||||||
|
að skipta á milli myndbands/lagshams. Getur einnig valfrjálst fjarlægt allan myndbandsflipann
|
||||||
|
|
||||||
|
- **Sjónrænir**: Mismunandi tónlist sjónrænir
|
||||||
|
|
||||||
|
## Þýðing
|
||||||
|
|
||||||
|
Þú getur aðstoðað við þýðingar á [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>
|
||||||
|
|
||||||
|
## Sækja
|
||||||
|
|
||||||
|
Þú getur skoðað [nýjustu útgáfuna](https://github.com/th-ch/youtube-music/releases/latest) til að finna fljótt
|
||||||
|
nýjustu útgáfuna.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
Settu upp [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) pakkann frá AUR. Fyrir AUR uppsetningarleiðbeiningar skaltu skoða
|
||||||
|
þessa [wiki síðu](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
|
||||||
|
Þú getur sett upp appið með því að nota Homebrew (sjá [cask skilgreiningu](https://github.com/th-ch/homebrew-youtube-music))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install th-ch/youtube-music/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
Ef þú setur upp forritið handvirkt og færð villu "er skemmd og ekki er hægt að opna það," þegar þú ræsir forritið skaltu keyra eftirfarandi í flugstöðinni:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xattr -cr /Applications/YouTube\ Music.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Þú getur notað [Scoop pakkastjórnun](https://scoop.sh) til að setja upp `youtube-music` pakkann frá
|
||||||
|
[`extras` fötunni](https://github.com/ScoopInstaller/Extras).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install extras/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
Að öðrum kosti geturðu notað [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), Windows 11s
|
||||||
|
opinber CLI pakkastjóri til að setja upp `th-ch.YouTubeMusic` pakkann.
|
||||||
|
|
||||||
|
*Athugið: Microsoft Defender SmartScreen gæti lokað uppsetningunni þar sem hún er frá „óþekktum útgefanda“. Þetta er einnig
|
||||||
|
satt fyrir handvirka uppsetningu þegar reynt er að keyra executable(.exe) eftir handvirkt niðurhal hér á github (sama
|
||||||
|
skrá).*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
winget install th-ch.YouTubeMusic
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Hvernig á að setja upp án nettengingar? (í Windows)
|
||||||
|
|
||||||
|
- Sæktu `*.nsis.7z` skrána fyrir _arkitektúr tækisins þíns_ á [útgáfusíðu](https://github.com/th-ch/youtube-music/releases/latest).
|
||||||
|
- `x64` fyrir 64-bita Windows
|
||||||
|
- `ia32` fyrir 32-bita Windows
|
||||||
|
- `arm64` fyrir ARM64 Windows
|
||||||
|
- Sæktu uppsetningarforrit á útgáfusíðu. (`*-Setup.exe`)
|
||||||
|
- Settu þær í **sömu möppuna**.
|
||||||
|
- Keyrðu uppsetningarforritið.
|
||||||
|
|
||||||
|
## Þemu
|
||||||
|
|
||||||
|
Þú getur hlaðið CSS skrám til að breyta útliti forritsins (Valkostir > Sjónræn klip > Þemu).
|
||||||
|
|
||||||
|
Sum fyrirframskilgreind þemu eru fáanleg á https://github.com/kerichdev/themes-for-ytmdesktop-player.
|
||||||
|
|
||||||
|
## Þróun
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/th-ch/youtube-music
|
||||||
|
cd youtube-music
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Búðu til þín eigin tengiforrit
|
||||||
|
|
||||||
|
Með því að nota tengiforrit geturðu:
|
||||||
|
|
||||||
|
- vinna með appið - `BrowserWindow` frá electron er sent til tengiforritsstjórans
|
||||||
|
- breyttu framhliðinni með því að vinna með HTML/CSS
|
||||||
|
|
||||||
|
### Er að búa til tengiforrit
|
||||||
|
|
||||||
|
Búðu til möppu í `src/plugins/YOUR-PLUGIN-NAME`:
|
||||||
|
|
||||||
|
- `index.ts`: aðal skránni af tengiforritið
|
||||||
|
```typescript
|
||||||
|
import style from './style.css?inline'; // flytja inn stíl sem inline
|
||||||
|
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Plugin Label',
|
||||||
|
restartNeeded: true, // ef gildi er satt, ytmusic sjá endurræsa gluggann
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // sérsniðnastillingar þinn
|
||||||
|
stylesheets: [style], // sérsniðnastílinn þinn
|
||||||
|
menu: async ({ getConfig, setConfig }) => {
|
||||||
|
// Allar *stillingaraðferðir eru umvafnar Lofor<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();
|
||||||
|
|
||||||
|
// þú getur tengst við renderer tengiforritið
|
||||||
|
ipc.handle('some-event', () => {
|
||||||
|
return 'hello';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// það kviknaði þegar stillingum var breytt
|
||||||
|
onConfigChange(newConfig) { /* ... */ },
|
||||||
|
// it fired when plugin disabled
|
||||||
|
stop(context) { /* ... */ },
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
async start(context) {
|
||||||
|
console.log(await context.ipc.invoke('some-event'));
|
||||||
|
},
|
||||||
|
// Aðeins krókur sem er í boði fyrir renderer
|
||||||
|
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
|
||||||
|
// stilltu stillingar viðbótarinnar auðveldlega
|
||||||
|
context.setConfig({ myConfig: api.getVolume() });
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) { /* ... */ },
|
||||||
|
stop(_context) { /* ... */ },
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
async start({ getConfig }) {
|
||||||
|
const config = await getConfig();
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) {},
|
||||||
|
stop(_context) {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Algeng notkunartilvik
|
||||||
|
|
||||||
|
- er að sprauta sérsniðnum CSS: búðu til `style.css` skrá í sömu möppu þá:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// index.ts
|
||||||
|
import style from './style.css?inline'; // flytja inn stíl sem inline
|
||||||
|
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Plugin Label',
|
||||||
|
restartNeeded: true, // ef gildi er satt, ytmusic sjá endurræsa gluggann
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // sérsniðnastillingar þinn
|
||||||
|
stylesheets: [style], // sérsniðnastílinn þinn
|
||||||
|
renderer() {} // skilgreina renderer krók
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ef þú vilt breyta HTML:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: 'Plugin Label',
|
||||||
|
restartNeeded: true, // ef gildi er satt, ytmusic sjá endurræsa gluggann
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
}, // sérsniðnastillingar þinn
|
||||||
|
renderer() {
|
||||||
|
// Fjarlægðu innskráningarhnappinn
|
||||||
|
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
|
||||||
|
} // skilgreina renderer krók
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- samskipti á milli að framan og aftan: hægt að gera með því að nota ipcMain eininguna frá electron. Sjá `index.ts` skrá og
|
||||||
|
dæmi í 'styrktarblokk' tengiforritinu.
|
||||||
|
|
||||||
|
## Byggja
|
||||||
|
|
||||||
|
1. Klóna geymsluna
|
||||||
|
2. Fylgdu [þessa handbók](https://pnpm.io/installation) til að setja upp 'pnpm'
|
||||||
|
3. Keyrðu `pnpm install --frozen-lockfile` til að setja upp ósjálfstæði
|
||||||
|
4. Keyrðu `pnpm build:OS`
|
||||||
|
|
||||||
|
- `pnpm dist:win` - Windows
|
||||||
|
- `pnpm dist:linux` - Linux
|
||||||
|
- `pnpm dist:mac` - MacOS
|
||||||
|
|
||||||
|
Byggir appið fyrir macOS, Linux og Windows,
|
||||||
|
með því að nota [electron-builder](https://github.com/electron-userland/electron-builder).
|
||||||
|
|
||||||
|
## Framleiðsluforskoðun
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prófanir
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Notar [Playwright](https://playwright.dev/) til að prófa forritið.
|
||||||
|
|
||||||
|
## Leyfi
|
||||||
|
|
||||||
|
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||||
|
|
||||||
|
## Algengustu Spurningar
|
||||||
|
|
||||||
|
### Hvers vegna forritavalmynd birtist ekki?
|
||||||
|
|
||||||
|
Ef valmöguleikinn „Fela valmynd“ er á - þú getur sýnt valmyndina með <kbd>alt</kbd> lyklinum (eða <kbd>\`</kbd> [bakka]
|
||||||
|
ef þú notar viðbótina fyrir valmynd í forriti)
|
||||||
@ -1,7 +1,7 @@
|
|||||||
# 유튜브 뮤직 (YouTube Music)
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|
# 유튜브 뮤직 (YouTube Music)
|
||||||
|
|
||||||
[](https://github.com/th-ch/youtube-music/releases/)
|
[](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/LICENSE)
|
||||||
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
||||||
@ -25,62 +25,26 @@
|
|||||||
- 원래의 인터페이스를 유지하는 것을 목표로 하는 네이티브 디자인 및 느낌
|
- 원래의 인터페이스를 유지하는 것을 목표로 하는 네이티브 디자인 및 느낌
|
||||||
- 맞춤 플러그인을 위한 프레임워크: 스타일, 콘텐츠, 기능 등 필요에 따라 유튜브 뮤직을 변경하고, 클릭 한 번으로 플러그인을 활성화/비활성화할 수 있습니다.
|
- 맞춤 플러그인을 위한 프레임워크: 스타일, 콘텐츠, 기능 등 필요에 따라 유튜브 뮤직을 변경하고, 클릭 한 번으로 플러그인을 활성화/비활성화할 수 있습니다.
|
||||||
|
|
||||||
## 번역
|
## Content
|
||||||
|
|
||||||
[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="번역 상태" />
|
- [다운로드](#다운로드)
|
||||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="번역 상태 2" />
|
- [Arch Linux](#arch-linux)
|
||||||
</a>
|
- [MacOS](#macos)
|
||||||
|
- [Windows](#windows)
|
||||||
## 다운로드
|
- [(Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?](#windows에서-네트워크에-연결하지-않고-설치하는-방법은-무엇인가요)
|
||||||
|
- [테마](#테마)
|
||||||
[최신 릴리즈](https://github.com/th-ch/youtube-music/releases/latest)를 확인하여 최신 버전을 빠르게 찾을 수 있습니다.
|
- [개발](#개발)
|
||||||
|
- [나만의 플러그인 만들기](#나만의-플러그인-만들기)
|
||||||
### Arch Linux
|
- [플러그인 만들기](#플러그인-만들기)
|
||||||
|
- [일반적인 사용 예](#일반적인-사용-예)
|
||||||
AUR에서 `youtube-music-bin` 패키지를 설치합니다. AUR 설치 지침은 [이 위키 페이지](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages)를 참조하세요.
|
- [빌드](#빌드)
|
||||||
|
- [프로덕션 빌드 미리보기](#프로덕션-빌드-미리보기)
|
||||||
### MacOS
|
- [테스트](#테스트)
|
||||||
|
- [라이선스](#라이선스)
|
||||||
Homebrew를 사용하여 앱을 설치할 수 있습니다:
|
- [자주 묻는 질문](#자주-묻는-질문)
|
||||||
```bash
|
|
||||||
brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master/youtube-music.rb
|
|
||||||
```
|
|
||||||
|
|
||||||
(앱을 수동으로 설치하고) 앱을 실행할 때 `손상되었기 때문에 열 수 없습니다.`라는 오류가 발생하면 터미널에서 다음을 실행하세요:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
xattr -cr /Applications/YouTube\ Music.app
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
[Scoop 패키지 매니저](https://scoop.sh)를 사용하여 [`extras` 버킷](https://github.com/ScoopInstaller/Extras)에서 `youtube-music` 패키지를 설치할 수 있습니다.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scoop bucket add extras
|
|
||||||
scoop install extras/youtube-music
|
|
||||||
```
|
|
||||||
|
|
||||||
또는 Windows 11의 공식 CLI 패키지 관리자인 [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)을 사용하여 `th-ch.YouTubeMusic` 패키지를 설치할 수 있습니다.
|
|
||||||
|
|
||||||
*참고: "알 수 없는 게시자"의 파일이기 때문에 Microsoft Defender의 SmartScreen에서 설치를 차단할 수 있습니다. 이는 GitHub에서 동일 파일을 수동으로 다운로드한 후 실행 파일(.exe)을 실행하려고 할 때도 마찬가지로 발생합니다.*
|
|
||||||
|
|
||||||
```bash
|
|
||||||
winget install th-ch.YouTubeMusic
|
|
||||||
```
|
|
||||||
|
|
||||||
#### (Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?
|
|
||||||
|
|
||||||
- [릴리즈 페이지](https://github.com/th-ch/youtube-music/releases/latest)에서 _본인 기기 아키텍처_에 맞는 `*.nsis.7z` 파일을 다운로드하세요.
|
|
||||||
- `x64`는 64비트 Windows 용입니다.
|
|
||||||
- `ia32`는 32비트 Windows 용입니다.
|
|
||||||
- `arm64`는 ARM64 Windows 용입니다.
|
|
||||||
- 릴리즈 페이지에서 설치기를 다운로드하세요. (`*-Setup.exe`)
|
|
||||||
- 두 파일을 **동일한 위치**에 놓아주세요.
|
|
||||||
- 설치기를 실행하세요.
|
|
||||||
|
|
||||||
## 기능:
|
## 기능:
|
||||||
|
|
||||||
@ -156,6 +120,63 @@ winget install th-ch.YouTubeMusic
|
|||||||
|
|
||||||
- **비주얼라이저**: 플레이어에 시각화 도구 추가
|
- **비주얼라이저**: 플레이어에 시각화 도구 추가
|
||||||
|
|
||||||
|
## 번역
|
||||||
|
|
||||||
|
[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="번역 상태" />
|
||||||
|
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="번역 상태 2" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## 다운로드
|
||||||
|
|
||||||
|
[최신 릴리즈](https://github.com/th-ch/youtube-music/releases/latest)를 확인하여 최신 버전을 빠르게 찾을 수 있습니다.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
AUR에서 [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) 패키지를 설치합니다. AUR 설치 지침은 [이 위키 페이지](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages)를 참조하세요.
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
|
||||||
|
Homebrew를 사용하여 앱을 설치할 수 있습니다:
|
||||||
|
```bash
|
||||||
|
brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master/youtube-music.rb
|
||||||
|
```
|
||||||
|
|
||||||
|
(앱을 수동으로 설치하고) 앱을 실행할 때 `손상되었기 때문에 열 수 없습니다.`라는 오류가 발생하면 터미널에서 다음을 실행하세요:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xattr -cr /Applications/YouTube\ Music.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
[Scoop 패키지 매니저](https://scoop.sh)를 사용하여 [`extras` 버킷](https://github.com/ScoopInstaller/Extras)에서 `youtube-music` 패키지를 설치할 수 있습니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install extras/youtube-music
|
||||||
|
```
|
||||||
|
|
||||||
|
또는 Windows 11의 공식 CLI 패키지 관리자인 [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)을 사용하여 `th-ch.YouTubeMusic` 패키지를 설치할 수 있습니다.
|
||||||
|
|
||||||
|
*참고: "알 수 없는 게시자"의 파일이기 때문에 Microsoft Defender의 SmartScreen에서 설치를 차단할 수 있습니다. 이는 GitHub에서 동일 파일을 수동으로 다운로드한 후 실행 파일(.exe)을 실행하려고 할 때도 마찬가지로 발생합니다.*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
winget install th-ch.YouTubeMusic
|
||||||
|
```
|
||||||
|
|
||||||
|
#### (Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?
|
||||||
|
|
||||||
|
- [릴리즈 페이지](https://github.com/th-ch/youtube-music/releases/latest)에서 _본인 기기 아키텍처_에 맞는 `*.nsis.7z` 파일을 다운로드하세요.
|
||||||
|
- `x64`는 64비트 Windows 용입니다.
|
||||||
|
- `ia32`는 32비트 Windows 용입니다.
|
||||||
|
- `arm64`는 ARM64 Windows 용입니다.
|
||||||
|
- 릴리즈 페이지에서 설치기를 다운로드하세요. (`*-Setup.exe`)
|
||||||
|
- 두 파일을 **동일한 위치**에 놓아주세요.
|
||||||
|
- 설치기를 실행하세요.
|
||||||
|
|
||||||
## 테마
|
## 테마
|
||||||
|
|
||||||
CSS 파일을 로드하여 애플리케이션의 모양을 변경할 수 있습니다(설정 > 시각적 변경 > 테마).
|
CSS 파일을 로드하여 애플리케이션의 모양을 변경할 수 있습니다(설정 > 시각적 변경 > 테마).
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { resolve, dirname } from 'node:path';
|
import { resolve, dirname, join } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import { defineConfig, defineViteConfig } from 'electron-vite';
|
import { defineConfig, defineViteConfig } from 'electron-vite';
|
||||||
@ -52,7 +52,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
if (mode === 'development') {
|
if (mode === 'development') {
|
||||||
commonConfig.plugins?.push(
|
commonConfig.plugins?.push(
|
||||||
Inspect({ build: true, outputDir: '.vite-inspect/backend' }),
|
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/backend') }),
|
||||||
);
|
);
|
||||||
return commonConfig;
|
return commonConfig;
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
if (mode === 'development') {
|
if (mode === 'development') {
|
||||||
commonConfig.plugins?.push(
|
commonConfig.plugins?.push(
|
||||||
Inspect({ build: true, outputDir: '.vite-inspect/preload' }),
|
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/preload') }),
|
||||||
);
|
);
|
||||||
return commonConfig;
|
return commonConfig;
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
if (mode === 'development') {
|
if (mode === 'development') {
|
||||||
commonConfig.plugins?.push(
|
commonConfig.plugins?.push(
|
||||||
Inspect({ build: true, outputDir: '.vite-inspect/renderer' }),
|
Inspect({ build: true, outputDir: join(__dirname, '.vite-inspect/renderer') }),
|
||||||
);
|
);
|
||||||
return commonConfig;
|
return commonConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
94
package.json
94
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-music",
|
"name": "youtube-music",
|
||||||
"productName": "YouTube Music",
|
"productName": "YouTube Music",
|
||||||
"version": "3.2.2",
|
"version": "3.3.12",
|
||||||
"description": "YouTube Music Desktop App - including custom plugins",
|
"description": "YouTube Music Desktop App - including custom plugins",
|
||||||
"main": "./dist/main/index.js",
|
"main": "./dist/main/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -107,6 +107,8 @@
|
|||||||
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
|
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
|
||||||
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
|
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
|
||||||
"dist:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p never",
|
"dist:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p never",
|
||||||
|
"dist:linux:deb-arm64": "pnpm clean && pnpm build && pnpm electron-builder --linux deb:arm64 -p never",
|
||||||
|
"dist:linux:rpm-arm64": "pnpm clean && pnpm build && pnpm electron-builder --linux rpm:arm64 -p never",
|
||||||
"dist:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:x64 -p never",
|
"dist:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:x64 -p never",
|
||||||
"dist:mac:arm64": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:arm64 -p never",
|
"dist:mac:arm64": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:arm64 -p never",
|
||||||
"dist:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p never",
|
"dist:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p never",
|
||||||
@ -119,114 +121,116 @@
|
|||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0",
|
||||||
|
"pnpm": ">=8"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"usocket": "1.0.1",
|
"usocket": "1.0.1",
|
||||||
"node-gyp": "10.0.1",
|
"node-gyp": "10.1.0",
|
||||||
"xml2js": "0.6.2",
|
"xml2js": "0.6.2",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"@electron/universal": "2.0.1",
|
"@electron/universal": "2.0.1",
|
||||||
"@babel/runtime": "7.23.8"
|
"@babel/runtime": "7.24.7",
|
||||||
|
"eslint": "9.4.0",
|
||||||
|
"@typescript-eslint/parser": "8.0.0-alpha.28"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"vudio@2.1.1": "patches/vudio@2.1.1.patch",
|
"vudio@2.1.1": "patches/vudio@2.1.1.patch",
|
||||||
"@xhayper/discord-rpc@1.1.2": "patches/@xhayper__discord-rpc@1.1.2.patch",
|
"@xhayper/discord-rpc@1.1.2": "patches/@xhayper__discord-rpc@1.1.2.patch",
|
||||||
"@astronautlabs/mdns@1.0.10": "patches/@astronautlabs__mdns@1.0.10.patch"
|
"app-builder-lib@24.13.3": "patches/app-builder-lib@24.13.3.patch",
|
||||||
|
"eslint-plugin-import@2.29.1": "patches/eslint-plugin-import@2.29.1.patch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cliqz/adblocker-electron": "1.26.15",
|
"@cliqz/adblocker-electron": "1.27.1",
|
||||||
"@cliqz/adblocker-electron-preload": "1.26.15",
|
"@cliqz/adblocker-electron-preload": "1.27.1",
|
||||||
"@electron-toolkit/tsconfig": "1.0.1",
|
"@electron-toolkit/tsconfig": "1.0.1",
|
||||||
"@electron/remote": "2.1.2",
|
"@electron/remote": "2.1.2",
|
||||||
"@ffmpeg.wasm/core-mt": "0.12.0",
|
"@ffmpeg.wasm/core-mt": "0.12.0",
|
||||||
"@ffmpeg.wasm/main": "0.12.0",
|
"@ffmpeg.wasm/main": "0.12.0",
|
||||||
"@floating-ui/dom": "1.6.3",
|
"@floating-ui/dom": "1.6.5",
|
||||||
"@foobar404/wave": "2.0.5",
|
"@foobar404/wave": "2.0.5",
|
||||||
"@jellybrick/electron-better-web-request": "1.0.4",
|
"@jellybrick/electron-better-web-request": "1.0.4",
|
||||||
"@jellybrick/electron-chromecast": "1.2.1",
|
|
||||||
"@jellybrick/mpris-service": "2.1.4",
|
"@jellybrick/mpris-service": "2.1.4",
|
||||||
"@xhayper/discord-rpc": "1.1.2",
|
"@xhayper/discord-rpc": "1.1.2",
|
||||||
"async-mutex": "0.4.1",
|
"async-mutex": "0.5.0",
|
||||||
"butterchurn": "3.0.0-beta.4",
|
"butterchurn": "3.0.0-beta.4",
|
||||||
"butterchurn-presets": "3.0.0-beta.4",
|
"butterchurn-presets": "3.0.0-beta.4",
|
||||||
"color": "4.2.3",
|
"color": "4.2.3",
|
||||||
"conf": "10.2.0",
|
"conf": "12.0.0",
|
||||||
"custom-electron-prompt": "1.5.7",
|
"custom-electron-prompt": "1.5.7",
|
||||||
"dbus-next": "0.10.2",
|
"dbus-next": "0.10.2",
|
||||||
"deepmerge-ts": "5.1.0",
|
"deepmerge-ts": "7.0.3",
|
||||||
"electron-debug": "3.2.0",
|
"electron-debug": "4.0.0",
|
||||||
"electron-is": "3.0.0",
|
"electron-is": "3.0.0",
|
||||||
"electron-localshortcut": "3.2.1",
|
"electron-localshortcut": "3.2.1",
|
||||||
"electron-store": "8.1.0",
|
"electron-store": "9.0.0",
|
||||||
"electron-unhandled": "4.0.1",
|
"electron-unhandled": "4.0.1",
|
||||||
"electron-updater": "6.1.7",
|
"electron-updater": "6.2.1",
|
||||||
"fast-average-color": "9.4.0",
|
"fast-average-color": "9.4.0",
|
||||||
"fast-equals": "5.0.1",
|
"fast-equals": "5.0.1",
|
||||||
"filenamify": "6.0.0",
|
"filenamify": "6.0.0",
|
||||||
"howler": "2.2.4",
|
"howler": "2.2.4",
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"i18next": "23.8.2",
|
"i18next": "23.11.5",
|
||||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||||
"keyboardevents-areequal": "0.2.2",
|
"keyboardevents-areequal": "0.2.2",
|
||||||
"node-html-parser": "6.1.12",
|
"node-html-parser": "6.1.13",
|
||||||
"node-id3": "0.2.6",
|
"node-id3": "0.2.6",
|
||||||
"peerjs": "1.5.2",
|
"peerjs": "1.5.4",
|
||||||
"semver": "7.6.0",
|
"semver": "7.6.2",
|
||||||
"serve": "14.2.1",
|
"serve": "14.2.3",
|
||||||
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
|
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
|
||||||
"solid-floating-ui": "0.3.1",
|
"solid-floating-ui": "0.3.1",
|
||||||
"solid-js": "1.8.14",
|
"solid-js": "1.8.17",
|
||||||
"solid-styled-components": "0.28.5",
|
"solid-styled-components": "0.28.5",
|
||||||
"solid-transition-group": "0.2.3",
|
"solid-transition-group": "0.2.3",
|
||||||
"ts-morph": "21.0.1",
|
"ts-morph": "22.0.0",
|
||||||
"vudio": "2.1.1",
|
"vudio": "2.1.1",
|
||||||
"x11": "2.3.0",
|
"x11": "2.3.0",
|
||||||
"youtubei.js": "9.0.2"
|
"youtubei.js": "9.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.41.2",
|
"@playwright/test": "1.44.1",
|
||||||
"@total-typescript/ts-reset": "0.5.1",
|
"@total-typescript/ts-reset": "0.5.1",
|
||||||
"@types/color": "3.0.6",
|
"@types/color": "3.0.6",
|
||||||
"@types/electron-localshortcut": "3.1.3",
|
"@types/electron-localshortcut": "3.1.3",
|
||||||
"@types/howler": "2.2.11",
|
"@types/howler": "2.2.11",
|
||||||
"@types/html-to-text": "9.0.4",
|
"@types/html-to-text": "9.0.4",
|
||||||
"@types/semver": "7.5.7",
|
"@types/semver": "7.5.8",
|
||||||
"@typescript-eslint/eslint-plugin": "7.0.1",
|
"@typescript-eslint/eslint-plugin": "8.0.0-alpha.28",
|
||||||
"bufferutil": "4.0.8",
|
"bufferutil": "4.0.8",
|
||||||
"builtin-modules": "3.3.0",
|
"builtin-modules": "4.0.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"del-cli": "5.1.0",
|
"del-cli": "5.1.0",
|
||||||
"discord-api-types": "0.37.69",
|
"discord-api-types": "0.37.87",
|
||||||
"electron": "28.2.2",
|
"electron": "30.1.0",
|
||||||
"electron-builder": "24.9.1",
|
"electron-builder": "24.13.3",
|
||||||
"electron-devtools-installer": "3.2.0",
|
"electron-devtools-installer": "3.2.0",
|
||||||
"electron-vite": "2.0.0",
|
"electron-vite": "2.2.0",
|
||||||
"esbuild": "0.20.0",
|
"esbuild": "0.21.4",
|
||||||
"eslint": "8.56.0",
|
"eslint": "9.4.0",
|
||||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||||
"eslint-import-resolver-typescript": "3.6.1",
|
"eslint-import-resolver-typescript": "3.6.1",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-prettier": "5.1.3",
|
"eslint-plugin-prettier": "5.1.3",
|
||||||
"glob": "10.3.10",
|
"glob": "10.4.1",
|
||||||
"node-gyp": "10.0.1",
|
"node-gyp": "10.1.0",
|
||||||
"playwright": "1.41.2",
|
"playwright": "1.44.1",
|
||||||
"rollup": "4.10.0",
|
"rollup": "4.18.0",
|
||||||
"typescript": "5.3.3",
|
"typescript": "5.4.5",
|
||||||
"utf-8-validate": "6.0.3",
|
"utf-8-validate": "6.0.4",
|
||||||
"vite": "5.1.1",
|
"vite": "5.2.13",
|
||||||
"vite-plugin-inspect": "0.8.3",
|
"vite-plugin-inspect": "0.8.4",
|
||||||
"vite-plugin-resolve": "2.5.1",
|
"vite-plugin-resolve": "2.5.1",
|
||||||
"vite-plugin-solid": "2.9.1",
|
"vite-plugin-solid": "2.10.2",
|
||||||
"ws": "8.16.0"
|
"ws": "8.17.0"
|
||||||
},
|
},
|
||||||
"auto-changelog": {
|
"auto-changelog": {
|
||||||
"hideCredit": true,
|
"hideCredit": true,
|
||||||
"package": true,
|
"package": true,
|
||||||
"unreleased": true,
|
"unreleased": true,
|
||||||
"output": "changelog.md"
|
"output": "changelog.md"
|
||||||
},
|
}
|
||||||
"packageManager": "pnpm@8.15.1"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
diff --git a/dist/Responder.js b/dist/Responder.js
|
|
||||||
index 7bb0e4e51f131cf257efc44190cf892ca0f1414e..f71688b8d7dc85bb6e23c9ec2aeec5cb98adc70a 100644
|
|
||||||
--- a/dist/Responder.js
|
|
||||||
+++ b/dist/Responder.js
|
|
||||||
@@ -32,6 +32,7 @@ const StateMachine_1 = require("./StateMachine");
|
|
||||||
const Probe_1 = require("./Probe");
|
|
||||||
const Response_1 = require("./Response");
|
|
||||||
const constants_1 = require("./constants");
|
|
||||||
+const { setTimeout, clearTimeout } = require('node:timers');
|
|
||||||
const ONE_SECOND = 1000;
|
|
||||||
/**
|
|
||||||
* Make ids, just to keep track of which responder is which in debug messages
|
|
||||||
@@ -43,7 +44,7 @@ const uniqueId = () => `id#${++counter}`;
|
|
||||||
* a responder has more than 15 conflicts in a small window then the responder
|
|
||||||
* should be throttled to prevent it from spamming everyone. Conflict count
|
|
||||||
* gets cleared after 15s w/o any conflicts
|
|
||||||
- */
|
|
||||||
+*/
|
|
||||||
class ConflictCounter {
|
|
||||||
constructor() {
|
|
||||||
this._count = 0;
|
|
||||||
diff --git a/dist/sleep.js b/dist/sleep.js
|
|
||||||
index 8e11b3900747a68814697943ec16af3280bca8b3..7896d16b43d3eb8fff175c30ea5903d6237cc634 100644
|
|
||||||
--- a/dist/sleep.js
|
|
||||||
+++ b/dist/sleep.js
|
|
||||||
@@ -5,6 +5,7 @@
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.resetSleep = exports.sleep = void 0;
|
|
||||||
const node_events_1 = require("node:events");
|
|
||||||
+const { setInterval, clearInterval } = require('node:timers');
|
|
||||||
exports.sleep = new node_events_1.EventEmitter();
|
|
||||||
exports.sleep.setMaxListeners(100);
|
|
||||||
let interval;
|
|
||||||
21
patches/app-builder-lib@24.13.3.patch
Normal file
21
patches/app-builder-lib@24.13.3.patch
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
diff --git a/out/targets/snap.js b/out/targets/snap.js
|
||||||
|
index f72c36355d27cd2d69fc5fdf2d8bb2451db0287f..baae112fe25ebb49ab8e25aaa48efd6bc43b598f 100644
|
||||||
|
--- a/out/targets/snap.js
|
||||||
|
+++ b/out/targets/snap.js
|
||||||
|
@@ -212,14 +212,14 @@ class SnapTarget extends core_1.Target {
|
||||||
|
args.push("--template-url", `electron4:${snapArch}`);
|
||||||
|
}
|
||||||
|
await (0, builder_util_1.executeAppBuilder)(args);
|
||||||
|
- const publishConfig = findSnapPublishConfig(this.packager.config);
|
||||||
|
+
|
||||||
|
await packager.info.callArtifactBuildCompleted({
|
||||||
|
file: artifactPath,
|
||||||
|
safeArtifactName: packager.computeSafeArtifactName(artifactName, "snap", arch, false),
|
||||||
|
target: this,
|
||||||
|
arch,
|
||||||
|
packager,
|
||||||
|
- publishConfig: publishConfig == null ? { provider: "snapStore" } : publishConfig,
|
||||||
|
+ publishConfig: options.publish == null ? { provider: "snapStore" } : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isElectronVersionGreaterOrEqualThan(version) {
|
||||||
161
patches/eslint-plugin-import@2.29.1.patch
Normal file
161
patches/eslint-plugin-import@2.29.1.patch
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
diff --git a/lib/importDeclaration.js b/lib/importDeclaration.js
|
||||||
|
index afb4de779034cfea080825a5f4320661c48bee32..f10b0a11a39577fbd42569e6b0e768255c1ef276 100644
|
||||||
|
--- a/lib/importDeclaration.js
|
||||||
|
+++ b/lib/importDeclaration.js
|
||||||
|
@@ -1,5 +1,5 @@
|
||||||
|
-"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports["default"] = importDeclaration;function importDeclaration(context) {
|
||||||
|
- var ancestors = context.getAncestors();
|
||||||
|
+"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports["default"] = importDeclaration;function importDeclaration(context, node) {
|
||||||
|
+ var ancestors = context.getSourceCode().getAncestors(node);
|
||||||
|
return ancestors[ancestors.length - 1];
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9pbXBvcnREZWNsYXJhdGlvbi5qcyJdLCJuYW1lcyI6WyJpbXBvcnREZWNsYXJhdGlvbiIsImNvbnRleHQiLCJhbmNlc3RvcnMiLCJnZXRBbmNlc3RvcnMiLCJsZW5ndGgiXSwibWFwcGluZ3MiOiJnR0FBd0JBLGlCLENBQVQsU0FBU0EsaUJBQVQsQ0FBMkJDLE9BQTNCLEVBQW9DO0FBQ2pELE1BQU1DLFlBQVlELFFBQVFFLFlBQVIsRUFBbEI7QUFDQSxTQUFPRCxVQUFVQSxVQUFVRSxNQUFWLEdBQW1CLENBQTdCLENBQVA7QUFDRCIsImZpbGUiOiJpbXBvcnREZWNsYXJhdGlvbi5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGltcG9ydERlY2xhcmF0aW9uKGNvbnRleHQpIHtcbiAgY29uc3QgYW5jZXN0b3JzID0gY29udGV4dC5nZXRBbmNlc3RvcnMoKTtcbiAgcmV0dXJuIGFuY2VzdG9yc1thbmNlc3RvcnMubGVuZ3RoIC0gMV07XG59XG4iXX0=
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/lib/rules/first.js b/lib/rules/first.js
|
||||||
|
index a77168660cf32c8c3e96f3ff4b8240a36d7de3a6..c0e00d75f9989916057fef3999eeee8d21820292 100644
|
||||||
|
--- a/lib/rules/first.js
|
||||||
|
+++ b/lib/rules/first.js
|
||||||
|
@@ -66,7 +66,7 @@ module.exports = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nonImportCount > 0) {var _iteratorNormalCompletion = true;var _didIteratorError = false;var _iteratorError = undefined;try {
|
||||||
|
- for (var _iterator = context.getDeclaredVariables(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var variable = _step.value;
|
||||||
|
+ for (var _iterator = sourceCode.getDeclaredVariables(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var variable = _step.value;
|
||||||
|
if (!shouldSort) {break;}
|
||||||
|
var references = variable.references;
|
||||||
|
if (references.length) {var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
|
||||||
|
diff --git a/lib/rules/namespace.js b/lib/rules/namespace.js
|
||||||
|
index 574d89a60d15c7e0e712956ea6a3ad2d0eac7f08..82e7cb3cff4246592d762cce86323f2b72de92e4 100644
|
||||||
|
--- a/lib/rules/namespace.js
|
||||||
|
+++ b/lib/rules/namespace.js
|
||||||
|
@@ -86,7 +86,7 @@ module.exports = {
|
||||||
|
|
||||||
|
// same as above, but does not add names to local map
|
||||||
|
ExportNamespaceSpecifier: function () {function ExportNamespaceSpecifier(namespace) {
|
||||||
|
- var declaration = (0, _importDeclaration2['default'])(context);
|
||||||
|
+ var declaration = (0, _importDeclaration2['default'])(context, namespace);
|
||||||
|
|
||||||
|
var imports = _ExportMap2['default'].get(declaration.source.value, context);
|
||||||
|
if (imports == null) {return null;}
|
||||||
|
diff --git a/lib/rules/newline-after-import.js b/lib/rules/newline-after-import.js
|
||||||
|
index 6cc15686464a17803a0b976c35b99627cdbfabee..520eec6d9a375527ab72c459960fe4416c046c17 100644
|
||||||
|
--- a/lib/rules/newline-after-import.js
|
||||||
|
+++ b/lib/rules/newline-after-import.js
|
||||||
|
@@ -194,7 +194,7 @@ module.exports = {
|
||||||
|
}return CallExpression;}(),
|
||||||
|
'Program:exit': function () {function ProgramExit() {
|
||||||
|
log('exit processing for', context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
|
||||||
|
- var scopeBody = getScopeBody(context.getScope());
|
||||||
|
+ var scopeBody = getScopeBody(context.getSourceCode().getScope(node));
|
||||||
|
log('got scope:', scopeBody);
|
||||||
|
|
||||||
|
requireCalls.forEach(function (node, index) {
|
||||||
|
diff --git a/lib/rules/no-amd.js b/lib/rules/no-amd.js
|
||||||
|
index 7ac108bf812ca4f78bfa6fe5ae8b9cf38e2ff497..346c3105dc70f72c4d76fcc6b96b946d1d4ec6d5 100644
|
||||||
|
--- a/lib/rules/no-amd.js
|
||||||
|
+++ b/lib/rules/no-amd.js
|
||||||
|
@@ -23,7 +23,7 @@ module.exports = {
|
||||||
|
create: function () {function create(context) {
|
||||||
|
return {
|
||||||
|
CallExpression: function () {function CallExpression(node) {
|
||||||
|
- if (context.getScope().type !== 'module') {return;}
|
||||||
|
+ if (context.getSourceCode().getScope(node).type !== 'module') {return;}
|
||||||
|
|
||||||
|
if (node.callee.type !== 'Identifier') {return;}
|
||||||
|
if (node.callee.name !== 'require' && node.callee.name !== 'define') {return;}
|
||||||
|
diff --git a/lib/rules/no-commonjs.js b/lib/rules/no-commonjs.js
|
||||||
|
index befeff0026d61d3ac1e6bbcea29f5c471dc1d353..e91c5ed34e968d5867e884ea800e166cda345aef 100644
|
||||||
|
--- a/lib/rules/no-commonjs.js
|
||||||
|
+++ b/lib/rules/no-commonjs.js
|
||||||
|
@@ -107,7 +107,7 @@ module.exports = {
|
||||||
|
|
||||||
|
// exports.
|
||||||
|
if (node.object.name === 'exports') {
|
||||||
|
- var isInScope = context.getScope().
|
||||||
|
+ var isInScope = context.getSourceCode().getScope(node).
|
||||||
|
variables.
|
||||||
|
some(function (variable) {return variable.name === 'exports';});
|
||||||
|
if (!isInScope) {
|
||||||
|
@@ -117,7 +117,7 @@ module.exports = {
|
||||||
|
|
||||||
|
}return MemberExpression;}(),
|
||||||
|
CallExpression: function () {function CallExpression(call) {
|
||||||
|
- if (!validateScope(context.getScope())) {return;}
|
||||||
|
+ if (!validateScope(context.getSourceCode().getScope(call))) {return;}
|
||||||
|
|
||||||
|
if (call.callee.type !== 'Identifier') {return;}
|
||||||
|
if (call.callee.name !== 'require') {return;}
|
||||||
|
diff --git a/lib/rules/no-mutable-exports.js b/lib/rules/no-mutable-exports.js
|
||||||
|
index 40bd1b4cfa95d41732bb13bba0ed1969a91cc7ff..8a25abfbfadb299204b36a6cbf283259bcc2e790 100644
|
||||||
|
--- a/lib/rules/no-mutable-exports.js
|
||||||
|
+++ b/lib/rules/no-mutable-exports.js
|
||||||
|
@@ -32,7 +32,7 @@ module.exports = {
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleExportDefault(node) {
|
||||||
|
- var scope = context.getScope();
|
||||||
|
+ var scope = context.getSourceCode().getScope(node);
|
||||||
|
|
||||||
|
if (node.declaration.name) {
|
||||||
|
checkDeclarationsInScope(scope, node.declaration.name);
|
||||||
|
@@ -40,7 +40,7 @@ module.exports = {
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleExportNamed(node) {
|
||||||
|
- var scope = context.getScope();
|
||||||
|
+ var scope = context.getSourceCode().getScope(node);
|
||||||
|
|
||||||
|
if (node.declaration) {
|
||||||
|
checkDeclaration(node.declaration);
|
||||||
|
diff --git a/lib/rules/no-named-as-default-member.js b/lib/rules/no-named-as-default-member.js
|
||||||
|
index 0c15051e027ad7d1d45f1b51c20be1c000b0af01..5b3d6ba415511b7f9f83a52e1acfebe5a1045a7b 100644
|
||||||
|
--- a/lib/rules/no-named-as-default-member.js
|
||||||
|
+++ b/lib/rules/no-named-as-default-member.js
|
||||||
|
@@ -35,7 +35,7 @@ module.exports = {
|
||||||
|
|
||||||
|
return {
|
||||||
|
ImportDefaultSpecifier: function () {function ImportDefaultSpecifier(node) {
|
||||||
|
- var declaration = (0, _importDeclaration2['default'])(context);
|
||||||
|
+ var declaration = (0, _importDeclaration2['default'])(context, node);
|
||||||
|
var exportMap = _ExportMap2['default'].get(declaration.source.value, context);
|
||||||
|
if (exportMap == null) {return;}
|
||||||
|
|
||||||
|
diff --git a/lib/rules/no-named-as-default.js b/lib/rules/no-named-as-default.js
|
||||||
|
index 63378a33a1c7da004c57a524cec1a1cddf23e210..c81b1f93b11628676158b79f1c4015911943cc7d 100644
|
||||||
|
--- a/lib/rules/no-named-as-default.js
|
||||||
|
+++ b/lib/rules/no-named-as-default.js
|
||||||
|
@@ -18,7 +18,7 @@ module.exports = {
|
||||||
|
// #566: default is a valid specifier
|
||||||
|
if (defaultSpecifier[nameKey].name === 'default') {return;}
|
||||||
|
|
||||||
|
- var declaration = (0, _importDeclaration2['default'])(context);
|
||||||
|
+ var declaration = (0, _importDeclaration2['default'])(context, defaultSpecifier);
|
||||||
|
|
||||||
|
var imports = _ExportMap2['default'].get(declaration.source.value, context);
|
||||||
|
if (imports == null) {return;}
|
||||||
|
diff --git a/lib/rules/no-namespace.js b/lib/rules/no-namespace.js
|
||||||
|
index 2b0c783adea788101b779b17f977bbcb582cfd3f..a7f7b202ac7c4a342febef2a993586c4cc84fc7a 100644
|
||||||
|
--- a/lib/rules/no-namespace.js
|
||||||
|
+++ b/lib/rules/no-namespace.js
|
||||||
|
@@ -43,7 +43,7 @@ var _docsUrl = require('../docsUrl');var _docsUrl2 = _interopRequireDefault(_doc
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
- var scopeVariables = context.getScope().variables;
|
||||||
|
+ var scopeVariables = context.getSourceCode().getScope(node).variables;
|
||||||
|
var namespaceVariable = scopeVariables.find(function (variable) {return variable.defs[0].node === node;});
|
||||||
|
var namespaceReferences = namespaceVariable.references;
|
||||||
|
var namespaceIdentifiers = namespaceReferences.map(function (reference) {return reference.identifier;});
|
||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index 5c0af48543483a21791fa23a4a583071d3551772..5deeac3d0accc3878ef0fc93dfb52a8ca7c46e84 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -72,7 +72,7 @@
|
||||||
|
"chai": "^4.3.10",
|
||||||
|
"cross-env": "^4.0.0",
|
||||||
|
"escope": "^3.6.0",
|
||||||
|
- "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8",
|
||||||
|
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9",
|
||||||
|
"eslint-doc-generator": "^1.6.1",
|
||||||
|
"eslint-import-resolver-node": "file:./resolvers/node",
|
||||||
|
"eslint-import-resolver-typescript": "^1.0.2 || ^1.1.1",
|
||||||
9834
pnpm-lock.yaml
generated
9834
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -16,25 +16,44 @@ const migrations = {
|
|||||||
secret?: string;
|
secret?: string;
|
||||||
};
|
};
|
||||||
if (lastfmConfig) {
|
if (lastfmConfig) {
|
||||||
const scrobblerConfig = store.get(
|
let scrobblerConfig = store.get(
|
||||||
'plugins.scrobbler',
|
'plugins.scrobbler',
|
||||||
) as {
|
) as {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
scrobblers: {
|
scrobblers?: {
|
||||||
lastfm: {
|
lastfm?: {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
token?: string;
|
token?: string;
|
||||||
session_key?: string;
|
sessionKey?: string;
|
||||||
api_root?: string;
|
apiRoot?: string;
|
||||||
api_key?: string;
|
apiKey?: string;
|
||||||
secret?: string;
|
secret?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
} | undefined;
|
||||||
|
|
||||||
scrobblerConfig.enabled = lastfmConfig.enabled;
|
if (!scrobblerConfig) {
|
||||||
scrobblerConfig.scrobblers.lastfm = lastfmConfig;
|
scrobblerConfig = {
|
||||||
|
enabled: lastfmConfig.enabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scrobblerConfig.scrobblers) {
|
||||||
|
scrobblerConfig.scrobblers = {
|
||||||
|
lastfm: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
scrobblerConfig.scrobblers.lastfm = {
|
||||||
|
enabled: lastfmConfig.enabled,
|
||||||
|
token: lastfmConfig.token,
|
||||||
|
sessionKey: lastfmConfig.session_key,
|
||||||
|
apiRoot: lastfmConfig.api_root,
|
||||||
|
apiKey: lastfmConfig.api_key,
|
||||||
|
secret: lastfmConfig.secret,
|
||||||
|
};
|
||||||
store.set('plugins.scrobbler', scrobblerConfig);
|
store.set('plugins.scrobbler', scrobblerConfig);
|
||||||
|
store.delete('plugins.lastfm');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'>=3.0.0'(store: Conf<Record<string, unknown>>) {
|
'>=3.0.0'(store: Conf<Record<string, unknown>>) {
|
||||||
|
|||||||
14
src/electron-chromecast.d.ts
vendored
14
src/electron-chromecast.d.ts
vendored
@ -1,14 +0,0 @@
|
|||||||
declare module '@jellybrick/electron-chromecast' {
|
|
||||||
export const chrome: typeof window.chrome;
|
|
||||||
export const requestHandler: (receiverList: Array<object>) => Promise<unknown>;
|
|
||||||
export const castSetting: {
|
|
||||||
devMode: boolean;
|
|
||||||
};
|
|
||||||
export const castConsole: {
|
|
||||||
log: (message: unknown[]) => void;
|
|
||||||
info: (message: unknown[]) => void;
|
|
||||||
warn: (message: unknown[]) => void;
|
|
||||||
error: (message: unknown[]) => void;
|
|
||||||
};
|
|
||||||
export const injectChromeCompatToObject: (obj: object) => void;
|
|
||||||
}
|
|
||||||
1
src/i18n/resources/ar.json
Normal file
1
src/i18n/resources/ar.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -3,7 +3,7 @@
|
|||||||
"console": {
|
"console": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"execute-failed": "Неуспешно изпълнение на плъгин {{pluginName}}::{{contextName}}",
|
"execute-failed": "Неуспешно изпълнение на плъгин {{pluginName}}::{{contextName}}",
|
||||||
"executed-at-ms": "Плъгин {{pluginName}}::{{contextName}} изпълнет в {{ms}}ms",
|
"executed-at-ms": "Плъгинът {{pluginName}}::{{contextName}} беше изпълнен на {{ms}}ms",
|
||||||
"initialize-failed": "Неуспешна инициализация на плъгин \"{{pluginName}}\"",
|
"initialize-failed": "Неуспешна инициализация на плъгин \"{{pluginName}}\"",
|
||||||
"load-all": "Зареждане на всички плъгини",
|
"load-all": "Зареждане на всички плъгини",
|
||||||
"load-failed": "Неуспешно зареждане на плъгин \"{{pluginName}}\"",
|
"load-failed": "Неуспешно зареждане на плъгин \"{{pluginName}}\"",
|
||||||
@ -41,6 +41,138 @@
|
|||||||
"window": {
|
"window": {
|
||||||
"tried-to-render-offscreen": "Прозореца се опита да се изрисува извън екрана, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
"tried-to-render-offscreen": "Прозореца се опита да се изрисува извън екрана, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Менюто е скрито. Използвайте \"Alt\", за да го покажете, или \"Escape\", ако използвате менюто в приложението",
|
||||||
|
"message": "\"Скриване на менюто\" е активирано",
|
||||||
|
"title": "\"Скриване на менюто\" активирано"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "По-късно",
|
||||||
|
"restart-now": "Рестартиране сега"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" плъгинът изисква рестартиране, за да влезе в сила",
|
||||||
|
"message": "\"{{pluginName}}\" трябва да рестартира",
|
||||||
|
"title": "Изисква се рестартиране"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Прекратяване",
|
||||||
|
"relaunch": "Повторно стартиране",
|
||||||
|
"wait": "Изчакване"
|
||||||
|
},
|
||||||
|
"detail": "Съжаляваме за неудобството! Моля, изберете какво да направите:",
|
||||||
|
"message": "Приложението не реагира",
|
||||||
|
"title": "Прозорецът не реагира"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Деактивиране на актуализациите",
|
||||||
|
"download": "Изтегляне",
|
||||||
|
"ok": "Добре"
|
||||||
|
},
|
||||||
|
"detail": "Налична е нова версия, която можете да изтеглите от {{downloadLink}}",
|
||||||
|
"message": "Налична е нова версия",
|
||||||
|
"title": "Налична е актуализация"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "За нас",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Навигация",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Копиране на текущия URL адрес",
|
||||||
|
"go-back": "Назад",
|
||||||
|
"go-forward": "Напред",
|
||||||
|
"quit": "Изход",
|
||||||
|
"restart": "Рестартиране на приложението"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Опции",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Разширени опции",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Нулиране на кеша на приложението при стартиране на приложението",
|
||||||
|
"disable-hardware-acceleration": "Деактивиране на хардуерното ускорение",
|
||||||
|
"edit-config-json": "Редактиране на config.json",
|
||||||
|
"override-user-agent": "Замяна на User-Agent",
|
||||||
|
"restart-on-config-changes": "Рестартиране при промени в конфигурацията",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Задаване на прокси",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Въведете адрес на прокси: (оставете празно, за да деактивирате)",
|
||||||
|
"placeholder": "Пример: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Задаване на прокси"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Активиране на DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Винаги отгоре",
|
||||||
|
"auto-update": "Автоматично актуализиране",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Менюто ще бъде скрито при следващото стартиране, използвайте [Alt], за да го покажете, или задния бутон [`], ако използвате менюто в приложението",
|
||||||
|
"title": "\"Скриване на менюто\" активирано"
|
||||||
|
},
|
||||||
|
"label": "Скриване на менюто"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Езикът ще бъде променен след рестартиране",
|
||||||
|
"title": "Езикът беше променен"
|
||||||
|
},
|
||||||
|
"label": "Език",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Искате да помогнете с езиковия превод? Кликнете тук"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Възобновяване на последната песен при стартиране на приложението",
|
||||||
|
"single-instance-lock": "Заключване до една инстанция",
|
||||||
|
"start-at-login": "Стартиране при вход",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Начална страница",
|
||||||
|
"unset": "Неустановена"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Панел",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Деактивирано",
|
||||||
|
"enabled-and-hide-app": "Активиране и скриване на приложението",
|
||||||
|
"enabled-and-show-app": "Активиране и показване на приложението",
|
||||||
|
"play-pause-on-click": "Възпроизвеждане/Спиране при кликване"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Визуални настройки",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "По подразбиране",
|
||||||
|
"force-show": "Принудително показване",
|
||||||
|
"hide": "Скриване",
|
||||||
|
"label": "Показване на \"Харесвам\" бутони"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Премахване на \"Ъпгрейд\" бутона",
|
||||||
|
"theme": {
|
||||||
|
"label": "Тема",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Импортиране на потребителски CSS файл",
|
||||||
|
"no-theme": "Без тема"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Активирани",
|
||||||
|
"label": "Плъгини",
|
||||||
|
"new": "НОВО"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@
|
|||||||
"hide-menu-enabled": {
|
"hide-menu-enabled": {
|
||||||
"detail": "Das Menü ist versteckt, nutze 'Alt', um es zu aufzurufen (oder 'Escape' beim Verwenden des In-App-Menüs)",
|
"detail": "Das Menü ist versteckt, nutze 'Alt', um es zu aufzurufen (oder 'Escape' beim Verwenden des In-App-Menüs)",
|
||||||
"message": "Menü verstecken ist aktiviert",
|
"message": "Menü verstecken ist aktiviert",
|
||||||
"title": "Menü Verstecken Aktiviert"
|
"title": "Menü verstecken aktiviert"
|
||||||
},
|
},
|
||||||
"need-to-restart": {
|
"need-to-restart": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@ -55,7 +55,7 @@
|
|||||||
},
|
},
|
||||||
"detail": "\"{{pluginName}}\"-Erweiterung erfordert einen Neustart, um in Kraft zu treten",
|
"detail": "\"{{pluginName}}\"-Erweiterung erfordert einen Neustart, um in Kraft zu treten",
|
||||||
"message": "\"{{pluginName}}\" muss neugestartet werden",
|
"message": "\"{{pluginName}}\" muss neugestartet werden",
|
||||||
"title": "Neustart Erforderlich"
|
"title": "Neustart erforderlich"
|
||||||
},
|
},
|
||||||
"unresponsive": {
|
"unresponsive": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@ -75,7 +75,7 @@
|
|||||||
},
|
},
|
||||||
"detail": "Eine neue Version ist verfügbar und kann unter {{downloadLink}} heruntergeladen werden",
|
"detail": "Eine neue Version ist verfügbar und kann unter {{downloadLink}} heruntergeladen werden",
|
||||||
"message": "Eine neue Version ist verfügbar",
|
"message": "Eine neue Version ist verfügbar",
|
||||||
"title": "Aktualisierung Verfügbar"
|
"title": "Aktualisierung verfügbar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
@ -87,7 +87,7 @@
|
|||||||
"go-back": "Zurück gehen",
|
"go-back": "Zurück gehen",
|
||||||
"go-forward": "Vorwärts gehen",
|
"go-forward": "Vorwärts gehen",
|
||||||
"quit": "Beenden",
|
"quit": "Beenden",
|
||||||
"restart": "Anwendung Neustarten"
|
"restart": "Anwendung neustarten"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
@ -124,7 +124,7 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"message": "Sprache wird nach Neustart geändert",
|
"message": "Sprache wird nach Neustart geändert",
|
||||||
"title": "Sprache Geändert"
|
"title": "Sprache geändert"
|
||||||
},
|
},
|
||||||
"label": "Sprache",
|
"label": "Sprache",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Upgrade-Schaltfläche entfernen",
|
"remove-upgrade-button": "Upgrade-Schaltfläche entfernen",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"remove": "Entfernen"
|
||||||
|
},
|
||||||
|
"remove-theme": "Sind Sie sich sicher, dass Sie das benutzerdefinierte Aussehen ändern wollen?",
|
||||||
|
"remove-theme-message": "Dies wird das benutzerdefinierte Aussehen löschen"
|
||||||
|
},
|
||||||
"label": "Thema",
|
"label": "Thema",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Importiere eigene CSS-Datei",
|
"import-css-file": "Importiere eigene CSS-Datei",
|
||||||
@ -212,6 +220,14 @@
|
|||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Wendet ein dynamisches Farbthema und visuelle Effekte auf Basis der Farbpalette des Albumcovers an",
|
"description": "Wendet ein dynamisches Farbthema und visuelle Effekte auf Basis der Farbpalette des Albumcovers an",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Farbmischungsverhältnis",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "Thema aus Albumfarbe"
|
"name": "Thema aus Albumfarbe"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
@ -230,7 +246,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"opacity": {
|
"opacity": {
|
||||||
"label": "Durchsichtigkeit",
|
"label": "Transparenz",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"percent": "{{opacity}}%"
|
"percent": "{{opacity}}%"
|
||||||
}
|
}
|
||||||
@ -275,7 +291,7 @@
|
|||||||
"description": "Untertitelwähler für YouTube Music-Audio-Lieder",
|
"description": "Untertitelwähler für YouTube Music-Audio-Lieder",
|
||||||
"menu": {
|
"menu": {
|
||||||
"autoload": "Wähle automatisch den zuletzt verwendeten Untertitel",
|
"autoload": "Wähle automatisch den zuletzt verwendeten Untertitel",
|
||||||
"disable-captions": "Standartmäßig keine Untertitel"
|
"disable-captions": "Standardmäßig keine Untertitel"
|
||||||
},
|
},
|
||||||
"name": "Untertitelwähler",
|
"name": "Untertitelwähler",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
@ -571,29 +587,39 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Scrobbling-Unterstützung aktivieren (z.B. für last.fm, Listenbrainz)",
|
"description": "Scrobbling-Unterstützung aktivieren (z.B. für last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Die Authentifizierung von Last.fm ist fehlgeschlagen.\nBlende das Pop-up bis zum nächsten Neustart aus.",
|
||||||
|
"title": "Authentifizierung fehlgeschlagen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Last.fm API Einstellungen"
|
"api-settings": "Last.fm API Einstellungen"
|
||||||
},
|
},
|
||||||
"listenbrainz": {
|
"listenbrainz": {
|
||||||
"token": "ListenBrainz-Benutzer-Token eintragen"
|
"token": "ListenBrainz-Benutzer-Token eintragen"
|
||||||
}
|
},
|
||||||
|
"scrobble-other-media": "Andere Medien scrobbeln"
|
||||||
},
|
},
|
||||||
"name": "Scrobbler",
|
"name": "Scrobbler",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-key": "Last.fm API-Schlüssel",
|
"api-key": "Last.fm API-Schlüssel",
|
||||||
"api-secret": "Last.fm API secret"
|
"api-secret": "Last.fm API-Kennwort"
|
||||||
},
|
},
|
||||||
"listenbrainz": {
|
"listenbrainz": {
|
||||||
"token": {
|
"token": {
|
||||||
"label": "ListenBrainz-Benutzer-Token eintragen"
|
"label": "ListenBrainz-Benutzer-Token eintragen:",
|
||||||
|
"title": "ListenBrainz-Token"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"description": "Ermöglicht das Festlegen globaler Hotkeys für die Wiedergabe (Abspielen/Pause/Nächster/Vorheriger) + Deaktivieren des Medien-OSD durch Überschreiben der Medientasten + Aktivieren von Strg/CMD + F zum Suchen + Aktivieren der Linux mpris-Unterstützung für Medientasten + Angepasste Tastenkürzel für fortgeschrittene Benutzer.",
|
"description": "Ermöglicht das Festlegen globaler Hotkeys für die Wiedergabe (Abspielen/Pause/Nächster/Vorheriger) + Deaktivieren des Medien-OSD durch Überschreiben der Medientasten + Aktivieren von Strg/CMD + F zum Suchen + Aktivieren der Linux mpris-Unterstützung für Medientasten + Angepasste Tastenkürzel für fortgeschrittene Benutzer",
|
||||||
"menu": {
|
"menu": {
|
||||||
"override-media-keys": "Medientasten überschreiben",
|
"override-media-keys": "Medientasten überschreiben",
|
||||||
"set-keybinds": "Globale Liedsteuerung setzen"
|
"set-keybinds": "Globale Liedsteuerung setzen"
|
||||||
|
|||||||
@ -2,7 +2,14 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"console": {
|
"console": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"execute-failed": "Αποτυχία εκτέλεσης προσθέτου {{pluginName}}::{{contextName}}"
|
"execute-failed": "Αποτυχία εκτέλεσης προσθέτου {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Το πρόσθετο {{pluginName}}::{{contextName}} εκτελέστηκε σε {{ms}}ms",
|
||||||
|
"initialize-failed": "Απέτυχε η αρχικοποίηση του πρόσθετου \"{{pluginName}}\"",
|
||||||
|
"load-all": "Φόρτωση όλων των πρόσθετων",
|
||||||
|
"load-failed": "Απέτυχε η φόρτωση του πρόσθετου \"{{pluginName}}\"",
|
||||||
|
"loaded": "Το πρόσθετο \"{{pluginName}}\" φορτώθηκε",
|
||||||
|
"unload-failed": "Απέτυχε η απεγκατάσταση του πρόσθετου \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Το πρόσθετο \"{{pluginName}}\" απεγκαταστάθηκε"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -12,44 +19,132 @@
|
|||||||
"name": "Greek"
|
"name": "Greek"
|
||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Η φόρτωση ολοκληρώθηκε. Τα DevTools άνοιξαν"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "Το i18n φορτώθηκε"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Λήφθηκε εντολή μέσω πρωτοκόλλου: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Το αρχείο CSS \"{{cssFile}}\" δεν υπάρχει, αγνοείται"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Σφάλμα ανταπόκρισης!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Εκκαθάριση της cache της εφαρμογής"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Το παράθυρο προσπάθησε να απεικονίσει εκτός οθόνης, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"hide-menu-enabled": {
|
"hide-menu-enabled": {
|
||||||
"message": "Απόκρυψη μενού είναι ενεργοποιημένο"
|
"detail": "Το μενού είναι κρυμμένο, χρησιμοποιήστε το 'Alt' για να το εμφανίσετε (ή το 'Escape' αν χρησιμοποιείτε το μενού εφαρμογής)",
|
||||||
|
"message": "Απόκρυψη μενού είναι ενεργοποιημένο",
|
||||||
|
"title": "Ενεργοποιήθηκε η Απόκρυψη του Μενού"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Αργότερα",
|
||||||
|
"restart-now": "Επανεκκίνηση Τώρα"
|
||||||
|
},
|
||||||
|
"detail": "Το πρόσθετο \"{{pluginName}}\" απαιτεί επανεκκίνηση για να ισχύσει",
|
||||||
|
"message": "Το \"{{pluginName}}\" χρειάζεται επανεκκίνηση",
|
||||||
|
"title": "Απαιτείται Επανεκκίνηση"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Έξοδος",
|
||||||
|
"relaunch": "Επανεκκίνηση",
|
||||||
|
"wait": "Περίμενε"
|
||||||
|
},
|
||||||
|
"detail": "Λυπούμαστε για την ταλαιπωρία! Παρακαλούμε επιλέξτε τι να κάνετε:",
|
||||||
|
"message": "Η εφαρμογή δεν ανταποκρίνεται",
|
||||||
|
"title": "Το παράθυρο δεν ανταποκρίνεται"
|
||||||
},
|
},
|
||||||
"update-available": {
|
"update-available": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"download": "Download",
|
"disable": "Απενεργοποίηση Ενημερώσεων",
|
||||||
"ok": "OK"
|
"download": "Λήψη",
|
||||||
}
|
"ok": "Εντάξει"
|
||||||
|
},
|
||||||
|
"detail": "Μια νέα έκδοση είναι διαθέσιμη και μπορεί να ληφθεί από τον σύνδεσμο {{downloadLink}}",
|
||||||
|
"message": "Μια νέα έκδοση είναι διαθέσιμη",
|
||||||
|
"title": "Υπάρχει Διαθέσιμη Ενημέρωση"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
|
"about": "Σχετικά",
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"label": "Navigation"
|
"label": "Πλοήγηση",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Αντιγραφή τρέχουσας διεύθυνσης URL",
|
||||||
|
"go-back": "Πήγαινε πίσω",
|
||||||
|
"go-forward": "Πήγαινε μπροστά",
|
||||||
|
"quit": "Έξοδος",
|
||||||
|
"restart": "Επανεκκίνηση Εφαρμογής"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"label": "Options",
|
"label": "Επιλογές",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"advanced-options": {
|
"advanced-options": {
|
||||||
|
"label": "Προηγμένες επιλογές",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Επαναφορά της cache της εφαρμογής κατά την εκκίνηση της εφαρμογής",
|
||||||
|
"disable-hardware-acceleration": "Απενεργοποίηση επιτάχυνσης υλικού",
|
||||||
|
"edit-config-json": "Επεξεργασία του config.json",
|
||||||
|
"override-user-agent": "Αντικατάσταση του User-Agent",
|
||||||
|
"restart-on-config-changes": "Επανεκκίνηση κατά τις αλλαγές στο config",
|
||||||
"set-proxy": {
|
"set-proxy": {
|
||||||
"label": "Set proxy",
|
"label": "Ορισμός διακομιστή μεσολάβησης (proxy)",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "Set proxy"
|
"label": "Εισαγωγή διεύθυνσης διακομιστή μεσολάβησης (proxy): (αφήστε κενό για απενεργοποίηση)",
|
||||||
|
"placeholder": "Παράδειγμα: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Ορισμός διακομιστή μεσολάβησης (proxy)"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"toggle-dev-tools": "Εναλλαγή DevTools"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"auto-update": "Auto Update",
|
"always-on-top": "Πάντα επάνω",
|
||||||
"language": {
|
"auto-update": "Αυτόματη Ενημέρωση",
|
||||||
"label": "Γλώσσα"
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Το μενού θα κρυφτεί στην επόμενη εκκίνηση, χρησιμοποιήστε [Alt] για να το εμφανίσετε (ή το πλήκτρο backtick [`] αν χρησιμοποιείτε το μενού εφαρμογής)",
|
||||||
|
"title": "Η Δυνατότητα Απόκρυψης του Μενού ενεργοποιήθηκε"
|
||||||
|
},
|
||||||
|
"label": "Απόκρυψη Μενού"
|
||||||
},
|
},
|
||||||
"start-at-login": "Start at login",
|
"language": {
|
||||||
"tray": {
|
"dialog": {
|
||||||
"label": "Tray",
|
"message": "Η γλώσσα θα αλλάξει μετά την επανεκκίνηση",
|
||||||
|
"title": "Η γλώσσα άλλαξε"
|
||||||
|
},
|
||||||
|
"label": "Γλώσσα",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"to-help-translate": "Θέλετε να βοηθήσετε στη μετάφραση; Κάντε κλικ εδώ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Συνέχιση του τελευταίου τραγουδιού όταν η εφαρμογή ξεκινά",
|
||||||
|
"single-instance-lock": "Κλείδωμα Μοναδικής Εκδοχής",
|
||||||
|
"start-at-login": "Έναρξη κατά την σύνδεση",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Σελίδα Έναρξης",
|
||||||
|
"unset": "Κατάργηση Ορισμού"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Δίσκος",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Απενεργοποιημένο",
|
||||||
"enabled-and-hide-app": "Ενεργοποιημένο και απόκρυψη της εφαρμογής",
|
"enabled-and-hide-app": "Ενεργοποιημένο και απόκρυψη της εφαρμογής",
|
||||||
"play-pause-on-click": "Play/Pause στο πάτημα"
|
"enabled-and-show-app": "Ενεργοποιημένο και εμφάνιση της εφαρμογής",
|
||||||
|
"play-pause-on-click": "Αναπαραγωγή/Παύση με κλικ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"visual-tweaks": {
|
"visual-tweaks": {
|
||||||
|
|||||||
@ -162,6 +162,14 @@
|
|||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Import custom CSS file",
|
"import-css-file": "Import custom CSS file",
|
||||||
"no-theme": "No theme"
|
"no-theme": "No theme"
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"remove-theme": "Are you sure you want to remove the custom theme?",
|
||||||
|
"remove-theme-message": "This will remove the custom theme",
|
||||||
|
"button": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"remove": "Remove"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -579,6 +587,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Add scrobbling support (etc. last.fm, Listenbrainz)",
|
"description": "Add scrobbling support (etc. last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"title": "Authentication Failed",
|
||||||
|
"message": "Failed to authenticate with Last.fm\nHide the popup until the next restart."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"scrobble-other-media": "Scrobble other media",
|
"scrobble-other-media": "Scrobble other media",
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"code": "es",
|
"code": "es",
|
||||||
"local-name": "Inglés",
|
"local-name": "Español",
|
||||||
"name": "Spanish"
|
"name": "Spanish"
|
||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Remover el botón de Actualización",
|
"remove-upgrade-button": "Remover el botón de Actualización",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"remove": "Quitar"
|
||||||
|
},
|
||||||
|
"remove-theme": "¿Estás seguro de que quieres eliminar el tema personalizado?",
|
||||||
|
"remove-theme-message": "Esto eliminará el tema personalizado"
|
||||||
|
},
|
||||||
"label": "Tema",
|
"label": "Tema",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Importar archivo CSS personalizado",
|
"import-css-file": "Importar archivo CSS personalizado",
|
||||||
@ -214,7 +222,7 @@
|
|||||||
"description": "Aplica un tema dinámico y efectos visuales basados en la paleta de colores del álbum",
|
"description": "Aplica un tema dinámico y efectos visuales basados en la paleta de colores del álbum",
|
||||||
"menu": {
|
"menu": {
|
||||||
"color-mix-ratio": {
|
"color-mix-ratio": {
|
||||||
"label": "Proporción de la mezcla de color",
|
"label": "Proporción de la mezcla de colores",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"percent": "{{ratio}}%"
|
"percent": "{{ratio}}%"
|
||||||
}
|
}
|
||||||
@ -434,7 +442,7 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"romanized-lyrics": "Letras Romanizadas"
|
"romanized-lyrics": "Letras Romanizadas"
|
||||||
},
|
},
|
||||||
"name": "Lyrics Genius",
|
"name": "Letras Genius",
|
||||||
"renderer": {
|
"renderer": {
|
||||||
"fetched-lyrics": "Letras recuperadas de Genius"
|
"fetched-lyrics": "Letras recuperadas de Genius"
|
||||||
}
|
}
|
||||||
@ -579,6 +587,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Añadir soporte para scrobbling (last.fm, Listenbrainz, etc.)",
|
"description": "Añadir soporte para scrobbling (last.fm, Listenbrainz, etc.)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Error al autenticar con Last.fm\nOcultar la ventana emergente hasta el próximo reinicio.",
|
||||||
|
"title": "Error de autenticación"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Ajustes de la API de Last.fm"
|
"api-settings": "Ajustes de la API de Last.fm"
|
||||||
|
|||||||
301
src/i18n/resources/fil.json
Normal file
301
src/i18n/resources/fil.json
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Nabigong patakbuin ang plugin {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Ang plugin na {{pluginName}}::{{contextName}} ay pinatakbo sa loob ng {{ms}}ms",
|
||||||
|
"initialize-failed": "Nabigo ang pagsimula ng plugin na \"{{pluginName}}\"",
|
||||||
|
"load-all": "Nilo-load lahat ng mga plugin",
|
||||||
|
"load-failed": "Nabigong i-load ang plugin na \"{{pluginName}}\"",
|
||||||
|
"loaded": "Na-load ang \"{{pluginName}}\" na plugin",
|
||||||
|
"unload-failed": "Nabigong i-unload ang plugin na \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Na-unload ang \"{{pluginName}}\" na plugin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "fil",
|
||||||
|
"local-name": "Tagalog",
|
||||||
|
"name": "Filipino"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Natapos ang pag-load. Nabuksan ang DevTools"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "na-load ang i18n"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Natanggap ang command sa pamamagitan ng protocol: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Ang CSS file na \"{{cssFile}}\" ay hindi umiiral, hindi papansin"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Hindi tumutugon na Error!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Naglilinis ng app cache"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Nasubukan ng window na mag-render sa labas ng screen, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Nakatago ang menu, gamitin ang 'Alt' para makita ito (o 'Escape' kung gagamitin ang In-App na Menu)",
|
||||||
|
"message": "Ang Pagtatago ng Menu ay napagana na",
|
||||||
|
"title": "Napagana ang Pagtatago ng Menu"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Mamaya",
|
||||||
|
"restart-now": "Mag-restart na"
|
||||||
|
},
|
||||||
|
"detail": "Ang plugin na \"{{pluginName}}\" ay kinakailangan ng restart para gumana ito",
|
||||||
|
"message": "Kinakailangan ng \"{{pluginName}}\" na mag-restart",
|
||||||
|
"title": "Kinakailangan ng Restart"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Umalis",
|
||||||
|
"relaunch": "Muling patakbuhin",
|
||||||
|
"wait": "Maghintay"
|
||||||
|
},
|
||||||
|
"detail": "Ikinalulungkot namin ang abala! piliin kung ano ang gagawin:",
|
||||||
|
"message": "Ang Application ay Hindi Tumutugon",
|
||||||
|
"title": "Di tumutugon ang Window"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Di-paganahin ang mga Update",
|
||||||
|
"download": "I-download",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "Ang isang bagong bersyon ay available at maaaring i-download sa {{downloadLink}}",
|
||||||
|
"message": "Mayroong bagong version ay available",
|
||||||
|
"title": "Available ang Update"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Patungkol",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Nabigasyon",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Kopyahin ang kasalukuyang URL",
|
||||||
|
"go-back": "Bumalik",
|
||||||
|
"go-forward": "Pasulong",
|
||||||
|
"quit": "Lumabas",
|
||||||
|
"restart": "I-restart ang App"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Mga Opsyon",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Mga advance na opsyon",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "I-reset ang app cache kapag nagsisimula ang app",
|
||||||
|
"disable-hardware-acceleration": "Di paganahin ang pagpapabilis ng hardware",
|
||||||
|
"edit-config-json": "I-edit ang config.json",
|
||||||
|
"override-user-agent": "I-override ang User-Agent",
|
||||||
|
"restart-on-config-changes": "I-restart kada may pagbabago sa config",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "I-set ang proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Ilagay ang Proxy Address: (iwanang walang laman para di-paganahin)",
|
||||||
|
"placeholder": "Halimbawa: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "I-set ang proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "I-toggle ang DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Laging nasa ibabaw",
|
||||||
|
"auto-update": "Awto Update",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Ang menu ay itatago sa susunod na pag-launch, gamitin ang [Alt] upang ipakita ito (o backtick [`] kung gumagamit ng in-app-menu)",
|
||||||
|
"title": "Pinagana ang Pagtatago ng Menu"
|
||||||
|
},
|
||||||
|
"label": "Pagtatago ng Menu"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Ang wika ay mababago pagkatapos mag-restart",
|
||||||
|
"title": "Napalitan ang Wika"
|
||||||
|
},
|
||||||
|
"label": "Wika",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Gusto mong tumulong sa pagsasalin? Mag-click dito"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Ipagpatuloy ang huling kanta kapag nagsisimula ang app",
|
||||||
|
"single-instance-lock": "I-lock sa isang Instance",
|
||||||
|
"start-at-login": "Magsimula sa pag-login",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Simulang page",
|
||||||
|
"unset": "I-unset"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Tray",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Di-napagana",
|
||||||
|
"enabled-and-hide-app": "Napagana at natago ang app",
|
||||||
|
"enabled-and-show-app": "Napagana at napakita ang app",
|
||||||
|
"play-pause-on-click": "Mag play/pause kada click"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Mga Biswal na Tweak",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Default",
|
||||||
|
"force-show": "Pilitang ipakita",
|
||||||
|
"hide": "Itago",
|
||||||
|
"label": "Mga Like na button"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Tanggalin ang upgrade na button",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Mag-import ng custom na CSS file",
|
||||||
|
"no-theme": "Walang tema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Napagana",
|
||||||
|
"label": "Mga Plugin",
|
||||||
|
"new": "BAGO"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "View",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Pilitang I-reload",
|
||||||
|
"reload": "I-reload",
|
||||||
|
"reset-zoom": "Aktuwal na Size",
|
||||||
|
"toggle-fullscreen": "I-toggle ang Full Screen",
|
||||||
|
"zoom-in": "Mag-zoom in",
|
||||||
|
"zoom-out": "Mag-zoom out"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Susunod",
|
||||||
|
"play-pause": "Mag-play/Mag-pause",
|
||||||
|
"previous": "Nakaraan",
|
||||||
|
"quit": "Lumabas",
|
||||||
|
"restart": "I-restart ang App",
|
||||||
|
"show": "Ipakita ang window",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "I-block lahat ng ad at tracking",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Blocker"
|
||||||
|
},
|
||||||
|
"name": "Ad Blocker"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Idadagdag ang Undislike, Dislike, Like, at Unlike na button para ilapat ito sa lahat ng kanta sa isang playlist o album",
|
||||||
|
"name": "Mga aksyon sa Album"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Naglalapat ng dynamic na tema at visual effect batay sa color palette ng album",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Ratio ng paghahalo ng kulay",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Tema ng Kulay ng Album"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Naglalapat ng lighting effect sa pamamagitan ng pag-cast ng mga magiliw na kulay mula sa video, sa background ng iyong screen",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Dami ng blur",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} na pixel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buffer",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Kalabuan (Opacity)",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Kalidad",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} na pixel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Laki",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Smoothness transition",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Habang {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Gumamit ng fullscreen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Ambient Mode"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Ilapat ang compression sa audio (pinababa ang volume ng pinakamalakas na bahagi ng signal at pinapataas ang volume ng pinakamalambot na bahagi)",
|
||||||
|
"name": "Compressor ng Audio"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Gawing transparent at malabo ang bar ng nabigasyon",
|
||||||
|
"name": "Palabuin ang Bar ng Nabigasyon"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "I-bypass ang pag-verify ng edad ng YouTube",
|
||||||
|
"name": "I-bypass ang Restriksyon sa Edad"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Tagapili ng caption para sa mga audio track ng YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Awtomatikong piliin ang huling ginamit na caption",
|
||||||
|
"disable-captions": "Walang mga caption bilang default"
|
||||||
|
},
|
||||||
|
"name": "Tagapili ng Caption",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Kasalukuyang wika ng caption:{{language}}",
|
||||||
|
"none": "Wala",
|
||||||
|
"title": "Pumili ng wika ng caption"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Bumukas ng pagpilian ng caption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -59,7 +59,7 @@
|
|||||||
},
|
},
|
||||||
"unresponsive": {
|
"unresponsive": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"quit": "Quitté",
|
"quit": "Quitter",
|
||||||
"relaunch": "Relancer",
|
"relaunch": "Relancer",
|
||||||
"wait": "Attendre"
|
"wait": "Attendre"
|
||||||
},
|
},
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"label": "Définir un proxy",
|
"label": "Définir un proxy",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"label": "Entrez l'adresse proxy : (laissez vide pour désactiver)",
|
"label": "Entrez l'adresse proxy : (laissez vide pour désactiver)",
|
||||||
"placeholder": "Exemple: socks5://127.0.0.1:9999",
|
"placeholder": "Exemple: SOCKS5://127.0.0.1:9999",
|
||||||
"title": "Définir un proxy"
|
"title": "Définir un proxy"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Supprimer le bouton de mise à niveau",
|
"remove-upgrade-button": "Supprimer le bouton de mise à niveau",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"remove": "Supprimer"
|
||||||
|
},
|
||||||
|
"remove-theme": "Êtes-vous sûr de supprimer le thème personnalisé ?",
|
||||||
|
"remove-theme-message": "Cela va supprimer le thème personnalisé"
|
||||||
|
},
|
||||||
"label": "Thème",
|
"label": "Thème",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Importer fichier CSS personnalisé",
|
"import-css-file": "Importer fichier CSS personnalisé",
|
||||||
@ -194,7 +202,7 @@
|
|||||||
"show": "Afficher la fenêtre",
|
"show": "Afficher la fenêtre",
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"default": "YouTube Music",
|
"default": "YouTube Music",
|
||||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
"with-song-info": "YouTube Music : {{artist}} - {{title}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -212,10 +220,18 @@
|
|||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Applique un thème dynamique et des effets visuels basés sur la palette des couleurs de l'album",
|
"description": "Applique un thème dynamique et des effets visuels basés sur la palette des couleurs de l'album",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Ratio de mélange des couleurs",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "Thème de couleur d'album"
|
"name": "Thème de couleur d'album"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
"description": "Applique un effet d'éclairage en jetant des couleurs douces de la vidéo, dans le fond de votre écran.",
|
"description": "Applique un effet d'éclairage en jetant des couleurs douces de la vidéo, dans le fond de votre écran",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
"label": "Quantité de flou",
|
"label": "Quantité de flou",
|
||||||
@ -302,8 +318,8 @@
|
|||||||
"prompt": {
|
"prompt": {
|
||||||
"options": {
|
"options": {
|
||||||
"multi-input": {
|
"multi-input": {
|
||||||
"fade-in-duration": "Durée du fondu (millisecondes)",
|
"fade-in-duration": "Durée du début du fondu (ms)",
|
||||||
"fade-out-duration": "Durée du fondu (millisecondes)",
|
"fade-out-duration": "Durée de sortie du fondu (ms)",
|
||||||
"fade-scaling": {
|
"fade-scaling": {
|
||||||
"label": "Mise à l'échelle du fondu",
|
"label": "Mise à l'échelle du fondu",
|
||||||
"linear": "Linéaire",
|
"linear": "Linéaire",
|
||||||
@ -372,7 +388,7 @@
|
|||||||
"converting": "Conversion…",
|
"converting": "Conversion…",
|
||||||
"done": "Terminé : {{filePath}}",
|
"done": "Terminé : {{filePath}}",
|
||||||
"download-info": "Téléchargement {{artist}} - {{title}} [{{videoId}}",
|
"download-info": "Téléchargement {{artist}} - {{title}} [{{videoId}}",
|
||||||
"download-progress": "Télécharger: {{percent}}%",
|
"download-progress": "Téléchargé : {{percent}}%",
|
||||||
"downloading": "Télécharge…",
|
"downloading": "Télécharge…",
|
||||||
"downloading-counter": "Télécharge {{current}}/{{total}}…",
|
"downloading-counter": "Télécharge {{current}}/{{total}}…",
|
||||||
"downloading-playlist": "Téléchargement de la playlist \"{{playlistTitle}}\" – {{playlistSize}} chansons ({{playlistId}})",
|
"downloading-playlist": "Téléchargement de la playlist \"{{playlistTitle}}\" – {{playlistSize}} chansons ({{playlistId}})",
|
||||||
@ -431,6 +447,52 @@
|
|||||||
"fetched-lyrics": "Paroles récupérées pour Genius"
|
"fetched-lyrics": "Paroles récupérées pour Genius"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Partage une playlist avec d'autres personnes. Quand l'hôte joue un son, tout les participants entendront le même son",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Entrer l'identifiant de l'hôte"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Enregistrer",
|
||||||
|
"track-source": "Source de la piste audio",
|
||||||
|
"unknown-user": "Utilisateur inconnu"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Copier l'identifiant de l'hôte",
|
||||||
|
"close": "Fermer Music Together",
|
||||||
|
"connected-users": "Utilisateurs connectés",
|
||||||
|
"disconnect": "Déconnecter Music Together",
|
||||||
|
"empty-user": "Aucun utilisateur connecté",
|
||||||
|
"host": "Hôte du Music Together",
|
||||||
|
"join": "Rejoindre le Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "Autorisez les invités à contrôler la musique et le player",
|
||||||
|
"host-only": "Seulement l'hôte peut contrôler les playlists et le lecteur",
|
||||||
|
"playlist": "Autoriser les invités à contrôler les playlists"
|
||||||
|
},
|
||||||
|
"set-permission": "Changer les permissions de contrôle",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Déconnecté",
|
||||||
|
"guest": "Connecté en tant qu'invité",
|
||||||
|
"host": "Connecté en tant qu'hôte"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [BETA]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Echec d'ajout de musique",
|
||||||
|
"closed": "Music Together fermé",
|
||||||
|
"disconnected": "Music Together déconnecté",
|
||||||
|
"host-failed": "Echec de l'hébergement du Music Together",
|
||||||
|
"id-copied": "Identifiant de l'hôte copié dans le presse papier",
|
||||||
|
"id-copy-failed": "Echec de la copie de l'identifiant de l'hôte dans le presse papier",
|
||||||
|
"join-failed": "Echec en rejoignant le Music Together",
|
||||||
|
"joined": "Rejoint le Music Together",
|
||||||
|
"permission-changed": "Permission du Music Together changé à \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Echec du retrait de la piste",
|
||||||
|
"user-connected": "{{name}} à rejoint le Music Together",
|
||||||
|
"user-disconnected": "{{name}} à quitté le Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"description": "Flèches de navigation Suivant/Retour directement intégrées dans l'interface, comme dans votre navigateur préféré",
|
"description": "Flèches de navigation Suivant/Retour directement intégrées dans l'interface, comme dans votre navigateur préféré",
|
||||||
"name": "Navigation"
|
"name": "Navigation"
|
||||||
@ -467,7 +529,7 @@
|
|||||||
"keybind-options": {
|
"keybind-options": {
|
||||||
"hotkey": "Raccourci clavier"
|
"hotkey": "Raccourci clavier"
|
||||||
},
|
},
|
||||||
"label": "Choisissez un raccourci clavier pour activer l'image dans l'image",
|
"label": "Choisissez un raccourci clavier pour activer le mode Image dans l'image",
|
||||||
"title": "Touche de raccourci Image dans l'image"
|
"title": "Touche de raccourci Image dans l'image"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -523,8 +585,41 @@
|
|||||||
"description": "Permet de changer la qualité vidéo avec un bouton sur la vidéo",
|
"description": "Permet de changer la qualité vidéo avec un bouton sur la vidéo",
|
||||||
"name": "Changeur de qualité vidéo"
|
"name": "Changeur de qualité vidéo"
|
||||||
},
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Ajouter le support de scrobbling (ex. last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Erreur lors de l'authetification avec Last.fm\nCachez la popup jusqu'au prochain redémarrage.",
|
||||||
|
"title": "Authentification échouée"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Paramètres API de Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Entrer le token utilisateur de ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Scrobbler d'autres médias"
|
||||||
|
},
|
||||||
|
"name": "Scrobble",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Clé API de Last.fm",
|
||||||
|
"api-secret": "API secret de Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Entrez votre token utilisateur ListenBrainz :",
|
||||||
|
"title": "Token ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"description": "Permet de définir des raccourcis clavier globaux pour la lecture (lecture/pause/suivant/précédent) + désactiver l'OSD multimédia en remplaçant les touches multimédias + activer Ctrl/CMD + F pour rechercher + activer la prise en charge Linux MPRIS pour les touches multimédias + raccourcis clavier personnalisés pour les utilisateurs avancés",
|
"description": "Permet de définir des raccourcis clavier globaux pour la lecture (lecture/pause/suivant/précédent) + désactiver l'OSD multimédia en remplaçant les touches multimédias + activer Ctrl/CMD + F pour rechercher + activer la prise en charge Linux MPRIS pour les touches multimédias + raccourcis clavier personnalisés pour les utilisateurs avancés.",
|
||||||
"menu": {
|
"menu": {
|
||||||
"override-media-keys": "Remplacer les touches multimédias",
|
"override-media-keys": "Remplacer les touches multimédias",
|
||||||
"set-keybinds": "Définir les contrôles globaux des morceaux"
|
"set-keybinds": "Définir les contrôles globaux des morceaux"
|
||||||
|
|||||||
7
src/i18n/resources/he.json
Normal file
7
src/i18n/resources/he.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"language": {
|
||||||
|
"code": "he",
|
||||||
|
"local-name": "עברית",
|
||||||
|
"name": "Hebrew"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/i18n/resources/hr.json
Normal file
15
src/i18n/resources/hr.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Neuspjelo izvršenje plugina {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin{{pluginName}}::{{contextName}}{{je izvršen za {{ms}}ms"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "hr",
|
||||||
|
"local-name": "Hrvatski",
|
||||||
|
"name": "Croatian"
|
||||||
|
}
|
||||||
|
}
|
||||||
655
src/i18n/resources/hu.json
Normal file
655
src/i18n/resources/hu.json
Normal file
@ -0,0 +1,655 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Nem sikerült futtatni a plugint {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} a {{ms}}ms időpontban lefutott",
|
||||||
|
"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",
|
||||||
|
"unloaded": "A \"{{pluginName}}\" bővítmény kikapcsolva"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "hu",
|
||||||
|
"local-name": "Magyar",
|
||||||
|
"name": "Hungarian"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Betöltés befejezve. DevTools megnyitva"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n betöltve"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Fogadott parancs a protokollon keresztül: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS fájl \"{{cssFile}}\" nem létezik, figyelmen kívül hagyva"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Nem reagál hiba!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Alkalmazás gyorsítótárának törlése"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Az ablak a képernyőn kívül próbált betölteni, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "A menü el van rejtve, a megjelenítéshez használd az 'Alt' billentyűt (vagy az 'Escape' billentyűt, ha az alkalmazáson belüli menüt használod)",
|
||||||
|
"message": "A menü elrejtése engedélyezve",
|
||||||
|
"title": "Menü elrejtése engedélyezve"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Később",
|
||||||
|
"restart-now": "Újraindítás most"
|
||||||
|
},
|
||||||
|
"detail": "A \"{{pluginName}}\" plugin újraindítást igényel a bekapcsoláshoz",
|
||||||
|
"message": "\"{{pluginName}}\" nevű plugin-t újra kell indítani",
|
||||||
|
"title": "Újraindítás szükséges"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Kilépés",
|
||||||
|
"relaunch": "Újraindítás",
|
||||||
|
"wait": "Várj"
|
||||||
|
},
|
||||||
|
"detail": "Elnézést a kellemetlenségért! Válaszdd ki mi történjen:",
|
||||||
|
"message": "Az alkalmazás nem válaszol",
|
||||||
|
"title": "Az ablak nem válaszol"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Frissítések kikapcsolása",
|
||||||
|
"download": "Letöltés",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "Az új verzió elérhető, és letölthető az alábbi linken {{downloadLink}}",
|
||||||
|
"message": "Új verzió áll rendelkezésre",
|
||||||
|
"title": "Frissítés elérhető"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Névjegy",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigáció",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Jelenlegi URL másolása",
|
||||||
|
"go-back": "Vissza",
|
||||||
|
"go-forward": "Előre",
|
||||||
|
"quit": "Kilépés",
|
||||||
|
"restart": "Alkalmazás újraindítása"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Beállítások",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Speciális beállítások",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Az alkalmazás gyorsítótárának törlése indításkor",
|
||||||
|
"disable-hardware-acceleration": "Hardveres gyorsítás kikapcsolása",
|
||||||
|
"edit-config-json": "config.json szerkesztése",
|
||||||
|
"restart-on-config-changes": "Újraindítás a konfigurációs változtatás után",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Proxy beállítása",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Proxy cím megadása: (Hagyja üresen a kikapcsoláshoz)",
|
||||||
|
"placeholder": "Példa: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Proxy beállítása"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Fejlesztőeszközök BE/KI"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Mindig látható",
|
||||||
|
"auto-update": "Automatikus frissítés",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "A menü a következő indításnál rejtve lesz, használja az [Alt] billentyűt a megjelenítéséhez (vagy a backtick [`] billentyűt, ha az alkalmazás belső menüjét használja)",
|
||||||
|
"title": "Menü elrejtés engedélyezve"
|
||||||
|
},
|
||||||
|
"label": "Menü elrejtése"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Az Újraindítást követően változik meg a nyelv",
|
||||||
|
"title": "Megváltozott a nyelv"
|
||||||
|
},
|
||||||
|
"label": "Nyelv",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Szeretne a fordításban segíteni? Kattintson ide"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Indításkor az utolsó zene folytatása",
|
||||||
|
"single-instance-lock": "Csak egy példány engedélyezése",
|
||||||
|
"start-at-login": "Futtatás rendszerindításkor",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Indítási hely"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Tálca",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Letiltva",
|
||||||
|
"enabled-and-hide-app": "Aktív és az alkalmazás elrejtve",
|
||||||
|
"enabled-and-show-app": "Aktív és az alkalmazás megjelenítve",
|
||||||
|
"play-pause-on-click": "Lejátszás/Szünet az ikonra kattintással"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Alapértelmezett",
|
||||||
|
"force-show": "Megjelenítés kényszerítése",
|
||||||
|
"hide": "Elrejtése",
|
||||||
|
"label": "Kedvelés gombok"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Előfizetés gombjának eltávolítása",
|
||||||
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Mégse",
|
||||||
|
"remove": "Eltávolít"
|
||||||
|
},
|
||||||
|
"remove-theme": "Biztos, hogy el akarja távolítani az egyéni témát?",
|
||||||
|
"remove-theme-message": "Ez el fogja távolítani az egyéni témát"
|
||||||
|
},
|
||||||
|
"label": "Téma",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Egyéni CSS fájl importálása",
|
||||||
|
"no-theme": "Nincs téma"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Bekapcsolva",
|
||||||
|
"label": "Bővítmények",
|
||||||
|
"new": "ÚJ"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Nézet",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Kényszerített újratöltés",
|
||||||
|
"reload": "Újratöltés",
|
||||||
|
"reset-zoom": "Valós méret",
|
||||||
|
"toggle-fullscreen": "Teljes képernyő be/ki",
|
||||||
|
"zoom-in": "Nagyítás",
|
||||||
|
"zoom-out": "Kicsinyítés"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Következő",
|
||||||
|
"play-pause": "Lejátszás/Szünet",
|
||||||
|
"previous": "Előző",
|
||||||
|
"quit": "Kilépés",
|
||||||
|
"restart": "YT Music újraindítása",
|
||||||
|
"show": "Ablak megjelenítése",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Blokkoló"
|
||||||
|
},
|
||||||
|
"name": "Reklámblokkoló"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"name": "Album műveletek"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Dinamikus téma és vizuális effektek alkalmazása az album színpalettája alapján",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Szín keverés aránya",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Album színtéma"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Fényhatás alkalmazása a videóból származó lágy színek vetítésével a képernyő hátterére",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Elmosódás mértéke",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} képpontok"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Átlátszóság",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Minőség",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} képpont"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Méret",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Teljes képernyő használata"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Hang tömörítés alkalmazása (csökkenti a jel legzajosabb részeinek hangerősségét, és emeli a legcsendesebb részek hangerősségét)",
|
||||||
|
"name": "Hangtömörítő"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Átlátszóvá és elmosódottá teszi a navigációs sávot"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "A YouTube korellenőrzését kihagyja, ezáltal nem kel meg erősíteni a zene meghallgatása elött. (Automatikusan megerősítve lesz.)",
|
||||||
|
"name": "Korellenőrzés kihagyása"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Felirat választó a YouTube Music zenékhez",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Automatikusan kiválasztja az utoljára használt feliratot",
|
||||||
|
"disable-captions": "Alapértelmezetten nincsenek feliratok"
|
||||||
|
},
|
||||||
|
"name": "Feliratválasztó",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Jelenlegi feliratnyelv: {{language}}",
|
||||||
|
"none": "Nincs",
|
||||||
|
"title": "Felirat nyelvének kiválasztása"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Feliratválasztó megnyitása"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Mindig becsukva tartja a bal oldali savót, ahol a Kezdőlap. Felfedezés, Könyvtár és egyebek láthatók. (Amit bármikor ki lehet nyitni)",
|
||||||
|
"name": "Kompakt oldalsáv"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Áttünést biztosít a dalok között, ami folytonossá teszi a zenehallgatást anélkül, hogy érezhető lenne a váltás",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Haladó"
|
||||||
|
},
|
||||||
|
"name": "Áttünés [Béta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Áttünés időtartama (ms)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "A Zenék nem fognak maguktól elindulni, a bővítmény használata során kézileg kel indítani a zenéket",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Csak induláskor alkalmazza"
|
||||||
|
},
|
||||||
|
"name": "Automatikus lejátszás letiltása"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Kapcsolódás kísérlete aktív kapcsolattal",
|
||||||
|
"connected": "Kapcsolódva a Discord-hoz",
|
||||||
|
"disconnected": "Kapcsolat bontva a Discord-al"
|
||||||
|
},
|
||||||
|
"description": "Mutassa meg barátainak, hogy mit hallgat a Rich Presence segítségével. (Ehez a Discord-on is engedélyezve kel lennie a Tevékenységállapot megosztásának [DC Beállítások -> Tevékenyég-adatvédelem -> Megoszthatod az észlelt tevékenységeidet másokkal])",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Automatikus újracsatlakozás",
|
||||||
|
"clear-activity": "Tevékenység törlése",
|
||||||
|
"clear-activity-after-timeout": "Tevékenység törlése időkorlát után",
|
||||||
|
"connected": "Kapcsolódva",
|
||||||
|
"disconnected": "Nincs Kapcsolódva",
|
||||||
|
"hide-duration-left": "Hátralévő idő elrejtése",
|
||||||
|
"hide-github-button": "GitHub link gombjának elrejtése",
|
||||||
|
"play-on-youtube-music": "Lejátszás a YouTube Music-on",
|
||||||
|
"set-inactivity-timeout": "Inaktivitási időkorlát beállítása"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Írja be az inaktivitási időkorlátot másodpercben:",
|
||||||
|
"title": "Inaktivitási időkorlát beállítása"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"message": "Hoppá! Elnézést, a letöltés sikertelen volt…",
|
||||||
|
"title": "A letöltés során hiba történt!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Rendben"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} dal)",
|
||||||
|
"message": "A(z) {{playlistTitle}} lejátszási lista letöltése",
|
||||||
|
"title": "A letöltés elindult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Konvetálás: {{percent}}%",
|
||||||
|
"converting": "Konvertálás…",
|
||||||
|
"done": "Kész: {{filePath}}",
|
||||||
|
"download-info": "Letöltés: {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Letöltés: {{percent}}%",
|
||||||
|
"downloading": "Letöltés folyamatban…",
|
||||||
|
"downloading-counter": "Letöltés: {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Letöltés a lejátszási listáról \"{{playlistTitle}}\" - {{playlistSize}} dal ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Hiba a \"{{author}} - {{title}}\" letöltésekor: {{error}}",
|
||||||
|
"folder-already-exists": "A {{playlistFolder}} nevű mappa már létezik",
|
||||||
|
"getting-playlist-info": "Lejátszási lista információinak lekérése…",
|
||||||
|
"loading": "Betöltés…",
|
||||||
|
"playlist-has-only-one-song": "A lejátszási listában csak egy elem van, letöltés közvetlenül",
|
||||||
|
"playlist-id-not-found": "Nem található lejátszási lista azonosítója",
|
||||||
|
"playlist-is-empty": "Lejátszási lista üres",
|
||||||
|
"playlist-is-mix-or-private": "Hiba a lejátszási lista információinak lekérésekor: győződjön meg róla, hogy nem privát vagy \"Saját egyveleg\" lejátszási lista\n\n{{error}}",
|
||||||
|
"preparing-file": "Fájl előkészítése…",
|
||||||
|
"saving": "Mentés…",
|
||||||
|
"trying-to-get-playlist-id": "Playlist ID lekérése: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Videó nem található",
|
||||||
|
"writing-id3": "ID3 címkék írása…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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-playlist": "Lejátszási lista letöltése",
|
||||||
|
"skip-existing": "Meglévő fájlok kihagyása"
|
||||||
|
},
|
||||||
|
"name": "Letöltő",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "A haladást nem lehet frissíteni"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Letöltés"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Az hangerő csúszka exponenciálissá tételével könnyebbé válik az alacsony hangerő kiválasztása.",
|
||||||
|
"name": "Exponenciális hangerő"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Menüsávok stílusos, sötét vagy album-színű megjelenítése",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "DOM ablakvezérlők elrejtése"
|
||||||
|
},
|
||||||
|
"name": "Alkalmazáson belüli menü"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Lumia Stream támogatás hozzáadása",
|
||||||
|
"name": "Lumia Stream [Béta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Dalszöveg támogatást ad a legtöbb dalhoz",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Latin betűs dalszövegek"
|
||||||
|
},
|
||||||
|
"name": "Lyrics Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Dalszövegek lekérése a Genius-ról"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Lehetővé teszi a lejátszási listák megosztását másokkal. Amikor a házigazda lejátszik egy dalt, mindenki más is ugyanazt a dalt fogja hallani",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Adja meg a házigazda azonosítóját"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Mentés",
|
||||||
|
"track-source": "Zeneszám forrása",
|
||||||
|
"unknown-user": "Ismeretlen felhasználó"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Házigazda azonosítójának másolása",
|
||||||
|
"close": "Zene együtt bezárása",
|
||||||
|
"connected-users": "Csatlakozott felhasználók",
|
||||||
|
"disconnect": "Zene együtt kapcsolatának megszakítása",
|
||||||
|
"empty-user": "Nincs csatlakozva felhasználó",
|
||||||
|
"host": "Music Together Házigazda",
|
||||||
|
"join": "Csatlakozás a Zene együtt-höz",
|
||||||
|
"permission": {
|
||||||
|
"all": "Engedélyezi a vendégeknek a lejátszási lista és a lejátszó vezérlését",
|
||||||
|
"host-only": "Csak a házigazda tudja vezérelni a lejátszási listát és a lejátszót",
|
||||||
|
"playlist": "Engedélyezi a vendégeknek a lejátszási lista vezérlését"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Kapcsolat bontva",
|
||||||
|
"guest": "Csatlakozva vendégként",
|
||||||
|
"host": "Csatlakozva házigazdaként"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Zene együtt [Béta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Sikertelen volt a dal hozzáadása",
|
||||||
|
"closed": "Zene együtt bezárva",
|
||||||
|
"disconnected": "Kapcsolat megszakadt a Music Together-el",
|
||||||
|
"host-failed": "Sikertelen volt a Zene együtt indítása",
|
||||||
|
"id-copied": "Házigazda azonosító a vágólapra másolva",
|
||||||
|
"id-copy-failed": "Nem sikerült a Házigazda azonosítóját a vágólapra másolni",
|
||||||
|
"join-failed": "Nem sikerült csatlakozni a Music Together-hez",
|
||||||
|
"joined": "Csatlakozott a Music Together-hez",
|
||||||
|
"permission-changed": "Music Together engedély megváltoztatva \"{{permission}}\" -re",
|
||||||
|
"remove-song-failed": "A dal eltávolítása sikertelen",
|
||||||
|
"user-connected": "{{name}} csatlakozott a Music Together-hez",
|
||||||
|
"user-disconnected": "{{name}} elhagyta a Music Together-t"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"name": "Navigáció"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "A Bejelentkezés gomb eltávolítása az interfészről (Jobb fentről eltünik a bejelentkezés gomb.)",
|
||||||
|
"name": "Nincs Google bejelentkezés"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Értesítés megjelenítése, amikor egy dal elindul (interaktív értesítések elérhetők Windows-on)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Interaktív Értesítések",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Interaktív beállítások",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Gombok szövegének elrejtése"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Értesítési prioritás"
|
||||||
|
},
|
||||||
|
"name": "Értesítések"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Lehetővé teszi az alkalmazás kép a képben módra váltását",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Mindig látható",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Gyorsbillentyű",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Gyorsbillentyű"
|
||||||
|
},
|
||||||
|
"label": "Válassz egy gyorsbillentyűt a kép a képben mód váltásához",
|
||||||
|
"title": "Kép a képben gyorsbillentyű"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Ablakpozíciójának mentése",
|
||||||
|
"save-window-size": "Ablakméretének mentése",
|
||||||
|
"use-native-pip": "A böngésző natív PiP(Kép a képben) használata"
|
||||||
|
},
|
||||||
|
"name": "Kép a képben",
|
||||||
|
"templates": {
|
||||||
|
"button": "Kép a képben"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Hallgass gyorsan, hallgass lassan! Hozzáad egy csúszkát, amely szabályozza a dal sebességét",
|
||||||
|
"name": "Lejátszás sebessége",
|
||||||
|
"templates": {
|
||||||
|
"button": "Sebesség"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"name": "Precíz hangerő",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Hangerő csökkentése",
|
||||||
|
"increase": "Hangerő növelése"
|
||||||
|
},
|
||||||
|
"label": "Válaszd ki a globális hangerő gyorsbillentyűket:",
|
||||||
|
"title": "Globális hangerő gyorsbillentyűk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Jelenlegi minőség: {{quality}}",
|
||||||
|
"message": "Válaszd ki a videó minőségét:",
|
||||||
|
"title": "Válaszd ki a videó minőségét"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Lehetővé teszi a videó minőségének megváltoztatását egy gombbal a videó fedvényen"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Last.fm hitelesítése nem sikerült\nA felugró ablak elrejtése a következő újraindításig.",
|
||||||
|
"title": "Hitelesítés sikertelen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API beállítások"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Add meg a ListenBrainz felhasználói tokenedet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API kulcs",
|
||||||
|
"api-secret": "Last.fm titkos API kulcs"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Add meg a ListenBrainz felhasználói tokenedet:",
|
||||||
|
"title": "ListenBrainz kulcs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Lehetővé teszi globális gyorsbillentyűk beállítását a lejátszáshoz (lejátszás/szünet/következő/előző), valamint a média OSD kikapcsolását a médiagombok felülírásával. Bekapcsolja a Ctrl/CMD + F billentyűkombinációt a kereséshez, a Linux MPRIS támogatását a médiagombokhoz, és egyedi gyorsbillentyűket a haladó felhasználók számára",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Médiagombok felülírása",
|
||||||
|
"set-keybinds": "Globális zenevezérlők beállítása"
|
||||||
|
},
|
||||||
|
"name": "Gyorsbillentyűk (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Következő",
|
||||||
|
"play-pause": "Lejátszás / Szünet",
|
||||||
|
"previous": "Előző"
|
||||||
|
},
|
||||||
|
"title": "Globális gyorsbillentyűk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Kihagyja a nem kedvelt dalokat",
|
||||||
|
"name": "Nem kedvelt dal kihagyása"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Automatikusan kihagyja a csendes részeket a dalokban",
|
||||||
|
"name": "Csend kihagyása"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Lejátszás vezérlése a Windows tálcáról"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integráció az OBS Tuna pluginjával",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Hozzáad egy gombot a Videó/Dal mód közötti váltáshoz. Opcionálisan teljesen eltávolíthatja a videó fület is",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Igazítás",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Balra",
|
||||||
|
"middle": "Középre",
|
||||||
|
"right": "Jobbra"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Videó fül kényszeritett eltávolítása",
|
||||||
|
"mode": {
|
||||||
|
"label": "Mód",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Egyedi kapcsoló",
|
||||||
|
"disabled": "Letiltva",
|
||||||
|
"native": "Natív kapcsoló"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Videó váltó",
|
||||||
|
"templates": {
|
||||||
|
"button": "Zeneszám"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Vizualizáció típus"
|
||||||
|
},
|
||||||
|
"name": "Vizualizáció"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -98,7 +98,7 @@
|
|||||||
"submenu": {
|
"submenu": {
|
||||||
"auto-reset-app-cache": "Mengatur ulang cache aplikasi saat aplikasi dimulai",
|
"auto-reset-app-cache": "Mengatur ulang cache aplikasi saat aplikasi dimulai",
|
||||||
"disable-hardware-acceleration": "Menonaktifkan akselerasi perangkat keras",
|
"disable-hardware-acceleration": "Menonaktifkan akselerasi perangkat keras",
|
||||||
"edit-config-json": "Edit config.json",
|
"edit-config-json": "Ubah config.json",
|
||||||
"override-user-agent": "Mengesampingkan User-Agent",
|
"override-user-agent": "Mengesampingkan User-Agent",
|
||||||
"restart-on-config-changes": "Mulai ulang pada perubahan konfigurasi",
|
"restart-on-config-changes": "Mulai ulang pada perubahan konfigurasi",
|
||||||
"set-proxy": {
|
"set-proxy": {
|
||||||
@ -194,7 +194,7 @@
|
|||||||
"show": "Tampilkan jendela",
|
"show": "Tampilkan jendela",
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"default": "YouTube Musik",
|
"default": "YouTube Musik",
|
||||||
"with-song-info": "YouTube Music: {{artist}} - {{Judul}}"
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -207,7 +207,7 @@
|
|||||||
"name": "Pemblokir Iklan"
|
"name": "Pemblokir Iklan"
|
||||||
},
|
},
|
||||||
"album-actions": {
|
"album-actions": {
|
||||||
"description": "Menambahkan tombol Tidak Suka, Tidak Suka, Suka, dan Tidak Suka untuk menerapkannya ke semua lagu dalam daftar putar atau album",
|
"description": "Tambah tombol Suka, Batal Suka, Tidak Suka dan Batal Tidak Suka untuk diterapkan ke semua lagu dalam daftar putar atau album",
|
||||||
"name": "Tindakan Album"
|
"name": "Tindakan Album"
|
||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
@ -398,7 +398,293 @@
|
|||||||
"video-id-not-found": "Video tidak ditemukan",
|
"video-id-not-found": "Video tidak ditemukan",
|
||||||
"writing-id3": "Menulis tanda ID3…"
|
"writing-id3": "Menulis tanda ID3…"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"description": "Unduh MP3 / sumber suara secara langsung via antarmuka",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Pilih folder unduhan",
|
||||||
|
"download-playlist": "Unduh daftar putar",
|
||||||
|
"presets": "Prasetel",
|
||||||
|
"skip-existing": "Lewati berkas yang sudah ada"
|
||||||
|
},
|
||||||
|
"name": "Pengunduh",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Tidak dapat memperbarui proses"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Unduh"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Buat penggeser volume menjadi eksponen sehingga memudahkan memilih volume yang lebih rendah.",
|
||||||
|
"name": "Volume Eksponen"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Buat bilah-menu terlihat indah, gelap atau serupa dengan album",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Sembunyikan DOM pengendali jendela"
|
||||||
|
},
|
||||||
|
"name": "Menu di Aplikasi"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Tambah dukungan Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Tambah dukungan lirik untuk kebanyakan lagu",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Romanisasi Lirik"
|
||||||
|
},
|
||||||
|
"name": "Lirik Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Lirik yang diambil untuk Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Bagikan daftar putar dengan yang lain. Saat host memainkan lagu, semua orang akan mendengarkan lagu yang sama",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Masukkan ID Host"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Simpan",
|
||||||
|
"track-source": "Sumber Trek",
|
||||||
|
"unknown-user": "Pengguna Tidak Diketahui"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Salin ID Host",
|
||||||
|
"close": "Tutup Musik Bersama",
|
||||||
|
"connected-users": "Pengguna Terhubung",
|
||||||
|
"disconnect": "Putuskan Musik Bersama",
|
||||||
|
"empty-user": "Tidak ada pengguna terhubung",
|
||||||
|
"host": "Host Musik Bersama",
|
||||||
|
"join": "Gabung Musik Bersama",
|
||||||
|
"permission": {
|
||||||
|
"all": "Izinkan tamu untuk mengendalikan daftar putar dan pemutar",
|
||||||
|
"host-only": "Hanya host yang dapat mengendalikan daftar putar dan pemutar",
|
||||||
|
"playlist": "Izinkan tamu untuk mengendalikan daftar putar"
|
||||||
|
},
|
||||||
|
"set-permission": "Ubah Pengendali Izin",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Terputus",
|
||||||
|
"guest": "Terhubung sebagai Tamu",
|
||||||
|
"host": "Terhubung sebagai Host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Musik Bersama [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Gagal untuk menambahkan lagu",
|
||||||
|
"closed": "Musik Bersama ditutup",
|
||||||
|
"disconnected": "Musik Bersama terputus",
|
||||||
|
"host-failed": "Gagal untuk memulai Musik Bersama",
|
||||||
|
"id-copied": "ID Host tersalin ke papan klip",
|
||||||
|
"id-copy-failed": "Gagal menyalin ID Host ke papan klip",
|
||||||
|
"join-failed": "Gagal untuk bergabung ke Musik Bersama",
|
||||||
|
"joined": "Bergabung ke Musik Bersama",
|
||||||
|
"permission-changed": "Perizinan Musik Bersama diubah ke \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Gagal menghapus lagu",
|
||||||
|
"user-connected": "{{name}} bergabung ke Musik Bersama",
|
||||||
|
"user-disconnected": "{{name}} meninggalkan Musik Bersama"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "panah navigasi Selanjutnya/Sebelumnya terintegrasi pada antarmuka, layaknya peramban kesukaan Anda",
|
||||||
|
"name": "Navigasi"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Hapus tombol dan tautan masuk Google dari antarmuka",
|
||||||
|
"name": "Tanpa Google Login"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Tampilkan pemberitahuan saat lagu dimainkan (pemberitahuan interaktif tersedia di Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Pemberitahuan Interaktif",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Pengaturan Interaktif",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Sembunyikan teks tombol",
|
||||||
|
"refresh-on-play-pause": "Segarkan saat Putar/Jeda",
|
||||||
|
"tray-controls": "Buka/Tutup saat baki ditekan"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Prioritas Pemberitahuan",
|
||||||
|
"toast-style": "Gaya Toast",
|
||||||
|
"unpause-notification": "Tampilkan pemberitahuan saat tidak dijeda"
|
||||||
|
},
|
||||||
|
"name": "Pemberitahuan"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Izinkan untuk memindahkan aplikasi ke mode gambar-dalam-gambar",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Selalu di atas",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Pintasan",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Pintasan"
|
||||||
|
},
|
||||||
|
"label": "Pilih pintasan untuk beralih ke gambar-dalam-gambar",
|
||||||
|
"title": "Pintasan gambar-dalam-gambar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Simpan posisi jendela",
|
||||||
|
"save-window-size": "Simpan ukuran jendela",
|
||||||
|
"use-native-pip": "Gunakan PiP bawaan peramban"
|
||||||
|
},
|
||||||
|
"name": "Gambar-dalam-gambar",
|
||||||
|
"templates": {
|
||||||
|
"button": "Gambar-dalam-gambar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Dengarkan cepat, dengarkan perlahan! Tambahkan penggeser untuk mengendalikan kecepatan lagu",
|
||||||
|
"name": "Kecepatan Pemutar",
|
||||||
|
"templates": {
|
||||||
|
"button": "Kecepatan"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Kendalikan volume secara presisi menggunakan roda tetikus/pintasan, dengan HUD kustom dan langkah volume yang dapat diatur",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Kendali Tombol Panah Lokal",
|
||||||
|
"custom-volume-steps": "Atur Langkah Volume Kustom",
|
||||||
|
"global-shortcuts": "Pintasan Global"
|
||||||
|
},
|
||||||
|
"name": "Volume Presisi",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Kurangi Volume",
|
||||||
|
"increase": "Tingkatkan Volume"
|
||||||
|
},
|
||||||
|
"label": "Pilih Pintasan Volume Global:",
|
||||||
|
"title": "Pintasan Volume Global"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Pilih Langkah Peningkatan/Pengurangan Volume",
|
||||||
|
"title": "Langkah Volume"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Kualitas Terkini: {{quality}}",
|
||||||
|
"message": "Pilih Kualitas Video:",
|
||||||
|
"title": "Pilih Kualitas Video"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Izinkan untuk mengubah kualitas video dengan tombol pada hamparan video",
|
||||||
|
"name": "Pengubah Kualitas Video"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Tambahkan dukungan scrobbling (mis. last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Gagal mengotentikasi Last.fm\nSembunyikan munculan hingga muat ulang selanjutnya.",
|
||||||
|
"title": "Otentikasi Gagal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Pengaturan API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Masukkan token pengguna ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Scrobble media lain"
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Kunci API Last.fm",
|
||||||
|
"api-secret": "Secret API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Masukkan token pengguna ListenBrainz Anda:",
|
||||||
|
"title": "Token ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Izinkan pengaturan pintasan global untuk pemutar (main/jeda/selanjutnya/sebelumnya) dan mematikan OSD media dengan mengesampingkan tombol media, mengaktifkan Ctrl/CMD + F untuk pencarian, mengaktifkan dukungan MPRIS Linux untuk tombol media, dan tombol pintasan kustom untuk pengguna lanjutan",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Timpa Tombol Media",
|
||||||
|
"set-keybinds": "Atur Pengendali Lagu Global"
|
||||||
|
},
|
||||||
|
"name": "Pintasan (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Selanjutnya",
|
||||||
|
"play-pause": "Main / Jeda",
|
||||||
|
"previous": "Sebelumnya"
|
||||||
|
},
|
||||||
|
"label": "Pilih Pintasan Global untuk Pengendali Lagu:",
|
||||||
|
"title": "Pintasan Global"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Lewati lagu yang tidak disukai",
|
||||||
|
"name": "Lewati Lagu yang Tidak Disukai"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Otomatis lewati bagian hening dari lagu",
|
||||||
|
"name": "Lewati Keheningan"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Otomatis Melewati bagian yang bukan musik seperti intro/outro atau bagian dari video musik di mana lagu tidak dimainkan",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Kendalikan pemutaran dari bilah alat Windows",
|
||||||
|
"name": "Pengendali Media di Bilah Alat"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Tambahkan widget TouchBar untuk pengguna macOS",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integrasi dengan plugin Tuna OBS",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Tambahkan tombol untuk beralih antara mode Lagu/Video. secara opsional juga dapat menghapus keseluruhan tab video",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Perataan",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Kiri",
|
||||||
|
"middle": "Tengah",
|
||||||
|
"right": "Kanan"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Paksa hapus tab video",
|
||||||
|
"mode": {
|
||||||
|
"label": "Mode",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Peralih kustom",
|
||||||
|
"disabled": "Mati",
|
||||||
|
"native": "Peralih bawaan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Peralih Video",
|
||||||
|
"templates": {
|
||||||
|
"button": "Lagu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Tambahkan visualisator ke pemutar",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Tipe Visualisator"
|
||||||
|
},
|
||||||
|
"name": "Visualisator"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
690
src/i18n/resources/is.json
Normal file
690
src/i18n/resources/is.json
Normal file
@ -0,0 +1,690 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Tókst ekki að framkvæma tengiforrit {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Tengiforrit {{pluginName}}::{{contextName}} var framkvæmd í {{ms}}ms",
|
||||||
|
"initialize-failed": "Tókst ekki að frumstilla tengiforrit \"{{pluginName}}\"",
|
||||||
|
"load-all": "Er að hlaða öllum tengiforritum",
|
||||||
|
"load-failed": "Tókst ekki að hlaða tengiforritinu \"{{pluginName}}\"",
|
||||||
|
"loaded": "Tengiforrit \"{{pluginName}}\" hlaðið",
|
||||||
|
"unload-failed": "Tókst ekki að afhlaða tengiforritinu \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Tengiforrit „{{pluginName}}“ óhlaðin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "is",
|
||||||
|
"local-name": "Íslenska",
|
||||||
|
"name": "Icelandic"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Lokið við hleðslu. DevTools opnuð"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n hlaðið"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Fengið skipun yfir prótókoll: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS skrá \"{{cssFile}}\" er ekki til, er að hunsa"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Viðbragðslaust Villa!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Er að hreinsa forritabúfera"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Gluggi reyndi að birta utan skjás, gluggastærð={{windowSize}}, skjástærð={{displaySize}}, stöðu={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Valmyndin er falin, notaðu 'Breytingarlykil' til að sýna hana (eða 'Útfararlykil' ef þú notar valmynd í forriti)",
|
||||||
|
"message": "Fela Valmynd er virkjuð",
|
||||||
|
"title": "Fela Valmynd Virkjuð"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Seinna",
|
||||||
|
"restart-now": "Endurræsa Núna"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" tengiforrit þarfnast endurræsingar til að taka gildi",
|
||||||
|
"message": "\"{{pluginName}}\" þarf að endurræsa",
|
||||||
|
"title": "Endurræsa Krafist"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Hætta",
|
||||||
|
"relaunch": "Endurræsa",
|
||||||
|
"wait": "Bíddu"
|
||||||
|
},
|
||||||
|
"detail": "Við biðjumst velvirðingar á óþægindunum! vinsamlegast veldu hvað á að gera:",
|
||||||
|
"message": "Umsóknin svarar ekki",
|
||||||
|
"title": "Gluggi er svarar ekki"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Gera Uppfærslur Óvirkar",
|
||||||
|
"download": "Sækja",
|
||||||
|
"ok": "Í lagi"
|
||||||
|
},
|
||||||
|
"detail": "Ný útgáfa er fáanleg og hægt er að hlaða henni niður á {{downloadLink}}",
|
||||||
|
"message": "Ný útgáfa er fáanleg",
|
||||||
|
"title": "Uppfærsla Fáanleg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Um",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Leiðsögn",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Afritaðu núverandi vefslóð",
|
||||||
|
"go-back": "Farðu til baka",
|
||||||
|
"go-forward": "Farðu áfram",
|
||||||
|
"quit": "Útganga",
|
||||||
|
"restart": "Endurræstu Forritið"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Valkostir",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Ítarlegravalkostir",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Endurstilltu skyndiminni forritsins þegar forritið ræsir",
|
||||||
|
"disable-hardware-acceleration": "Slökktu á vélbúnaðarhröðun",
|
||||||
|
"edit-config-json": "Breyta config.json",
|
||||||
|
"override-user-agent": "Hneka Notandaumboðsmanni",
|
||||||
|
"restart-on-config-changes": "Endurræstu við stillingarbreytingar",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Stilla umboð",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Sláðu inn umboðsfang: (skilið eftir autt til að slökkva á)",
|
||||||
|
"placeholder": "Dæmi: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Stilla umboð"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Breyta DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Alltaf á toppnum",
|
||||||
|
"auto-update": "Sjálfvirk Uppfærsla",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Valmyndin verður falin við næstu ræsingu, notaðu [Alt] til að sýna hana (eða bakaðu við [`] ef þú notar valmynd í forriti)",
|
||||||
|
"title": "Fela Valmynd Virkjuð"
|
||||||
|
},
|
||||||
|
"label": "Fela Valmynd"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Tungumáli verður breytt eftir endurræsingu",
|
||||||
|
"title": "Tungumáli Breytt"
|
||||||
|
},
|
||||||
|
"label": "Tungumál",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Viltu hjálpa til við að þýða? Smellið hér"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Haltu áfram síðasta lagi þegar forritið byrjar",
|
||||||
|
"single-instance-lock": "Eittdæmilás",
|
||||||
|
"start-at-login": "Byrjaðu á innskráningu",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Upphafssíða",
|
||||||
|
"unset": "Ósetja"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Bakki",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Fötluð",
|
||||||
|
"enabled-and-hide-app": "Bakki virkt, og fela forritsgluggi",
|
||||||
|
"enabled-and-show-app": "Virkjað og sýna forrit",
|
||||||
|
"play-pause-on-click": "Spila/hlé við smell"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Sjónrænaraðlögun",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Sjálfgefinn",
|
||||||
|
"force-show": "Þvingaðu sýna",
|
||||||
|
"hide": "Fela",
|
||||||
|
"label": "Líkartakkar"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Fjarlægja uppgræðartakkan",
|
||||||
|
"theme": {
|
||||||
|
"label": "Þema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Flytja inn sérsniðna CSS skrá",
|
||||||
|
"no-theme": "Engin þema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Virkt",
|
||||||
|
"label": "Tengiforrit",
|
||||||
|
"new": "NÝR"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Útsýni",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Þvingaðu Endurhleðslu",
|
||||||
|
"reload": "Endurhlaða",
|
||||||
|
"reset-zoom": "Raunveruleg Stærð",
|
||||||
|
"toggle-fullscreen": "Breyta Fullskjá",
|
||||||
|
"zoom-in": "Aðdráttur",
|
||||||
|
"zoom-out": "Aðdráttur út"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Næst",
|
||||||
|
"play-pause": "Spila/Hlé",
|
||||||
|
"previous": "Fyrri",
|
||||||
|
"quit": "Útganga",
|
||||||
|
"restart": "Endurræstu Forritið",
|
||||||
|
"show": "Sýna glugga",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Tónlist",
|
||||||
|
"with-song-info": "YouTube Tónlist: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Lokaðu fyrir allar auglýsingar og rakningar úr kassanum",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Blokkari"
|
||||||
|
},
|
||||||
|
"name": "Auglýsingablokkari"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Bætir Ódíslika, Mislíkt, Líkt, og Ólíkt til að nota þetta á öll lög á spilunarlista eða albúm",
|
||||||
|
"name": "Albúmsaðgerðir"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Beitir kraftmikið þema og sjónrænum áhrifum sem byggjast á litavali albúmsins",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Litablöndunarhlutfall",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Albúmlitaþema"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Beitir lýsingaráhrifum með því að varpa mildum litum úr myndbandinu í bakgrunn skjásins",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Þokuupphæð",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pixlum"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Stuðpúði",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Ógegnsæi",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Gæði",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} pixlum"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Sæði",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Slétt umskipti",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Meðan á {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Er að nota fullskjár"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Umhverfishamur"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Notaðu þjöppun á hljóð (lækkar hljóðstyrk háværustu hluta merkis og hækkar hljóðstyrk í mýkstu hlutunum)",
|
||||||
|
"name": "Hljóðþjöppu"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Gerir leiðsögustikuna gagnsæja og óskýrt",
|
||||||
|
"name": "Þoka Leiðsagnarstika"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Framhjá aldursstaðfestingu YouTube",
|
||||||
|
"name": "Farið Framhjá Aldurstakmörkunum"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Skjátextavali fyrir YouTube Tónlist hljóðrásir",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Veldu sjálfkrafa síðast notaða myndatexta",
|
||||||
|
"disable-captions": "Engir skjátextar sjálfgefið"
|
||||||
|
},
|
||||||
|
"name": "Yfirskriftarval",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Núverandi tungumál skjátexta: {{language}}",
|
||||||
|
"none": "Enginn",
|
||||||
|
"title": "Veldu tungumál fyrir skjátexta"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Opnaðu skjátextavali"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Stilltu hliðarstikuna alltaf í þétta stillingu",
|
||||||
|
"name": "Fyrirferðarlítillhliðarstika"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Krossfæra á milli lög",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Háþróaður"
|
||||||
|
},
|
||||||
|
"name": "Krossfæra [Prófunarútgáfa]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Dvína í lengd (ms)",
|
||||||
|
"fade-out-duration": "Dvína út lengd (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Fölunarskala",
|
||||||
|
"linear": "Línulegt",
|
||||||
|
"logarithmic": "Logaritmískt"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Krossfæra N sekúndum fyrir enda"
|
||||||
|
},
|
||||||
|
"title": "Krossfæravalkosti"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Gerir lag að byrja í \"hlé\" ham",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Á aðeins við ræsingu"
|
||||||
|
},
|
||||||
|
"name": "Slökkva á sjálfvirkri spilun"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Reyndi að tengja við virka tengingu",
|
||||||
|
"connected": "Tengdur við Discord",
|
||||||
|
"disconnected": "Aftengdur frá Discord"
|
||||||
|
},
|
||||||
|
"description": "Sýndu vinum þínum hvað þú hlustar á með Rík Nærvera",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Sjálfvirk endurtengja",
|
||||||
|
"clear-activity": "Hreinsa virkni",
|
||||||
|
"clear-activity-after-timeout": "Hreinsa virkni eftir tímamörk",
|
||||||
|
"connected": "Tengt",
|
||||||
|
"disconnected": "Aftengt",
|
||||||
|
"hide-duration-left": "Fela tímalengd til vinstri",
|
||||||
|
"hide-github-button": "Fela GitHub tengilhnapp",
|
||||||
|
"play-on-youtube-music": "Spilaðu á YouTube Tónlist",
|
||||||
|
"set-inactivity-timeout": "Stilltu tímamörk fyrir óvirkni"
|
||||||
|
},
|
||||||
|
"name": "Discord Rík Nærvera",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Sláðu inn óvirknitíma eftir sekúndur:",
|
||||||
|
"title": "Stilltu tímamörk fyrir óvirkni"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Í lagi"
|
||||||
|
},
|
||||||
|
"message": "Úff! Afsakið, niðurhal mistókst…",
|
||||||
|
"title": "Villa við niðurhal!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Í lagi"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} lög)",
|
||||||
|
"message": "Að sækja lagalista {{playlistTitle}}",
|
||||||
|
"title": "Niðurhal byrjað"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Umbreyting: {{percent}}%",
|
||||||
|
"converting": "Er að umbreytir…",
|
||||||
|
"done": "Búið: {{filePath}}",
|
||||||
|
"download-info": "Er að niðurhal {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Niðurhal: {{percent}}%",
|
||||||
|
"downloading": "Er að niðurhal…",
|
||||||
|
"downloading-counter": "Er að niðurhal {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Er að niðurhal spilunarlisti \"{{playlistTitle}}\" - {{playlistSize}} lög ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Villa við niðurhal \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "Mappan {{playlistFolder}} er þegar til",
|
||||||
|
"getting-playlist-info": "Sækir upplýsingar um spilunarlista…",
|
||||||
|
"loading": "Er að hlaða.…",
|
||||||
|
"playlist-has-only-one-song": "Spilunarlista hefur aðeins eitt atriði, það er verið að hlaða því niður beint",
|
||||||
|
"playlist-id-not-found": "Ekkert auðkenni spilunarlista fannst",
|
||||||
|
"playlist-is-empty": "Spilunarlistinn er tómur",
|
||||||
|
"playlist-is-mix-or-private": "Villa við að fá upplýsingar um spilunarlista: Gakktu úr skugga um að þetta sé ekki einkaspilunarlisti eða \"Mixað fyrir þig\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Er að undirbúa skrá…",
|
||||||
|
"saving": "Er að vista…",
|
||||||
|
"trying-to-get-playlist-id": "Er að reyna að fá auðkenni spilunarlista: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Myndband fannst ekki",
|
||||||
|
"writing-id3": "Að skrifa ID3 tög…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Niðurhalar MP3 / upprunahljóði beint úr viðmótinu",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Veldu niðurhalsmöppu",
|
||||||
|
"download-playlist": "Sækja spilunarlista",
|
||||||
|
"presets": "Forstillingar",
|
||||||
|
"skip-existing": "Slepptu núverandi skrám"
|
||||||
|
},
|
||||||
|
"name": "Niðurhalari",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Ekki er hægt að uppfæra framvindu"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Sækja"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Gerir hljóðstyrkssleðann veldisvísis svo það er auðveldara að velja lægra hljóðstyrk.",
|
||||||
|
"name": "Veldibundiðrúmmál"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Gefur valmyndastikum glæsilegt, dökkt eða albúmslitsjáðu",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Fela DOM gluggastýringar"
|
||||||
|
},
|
||||||
|
"name": "Valmynd í forriti"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Bætir við Lumia Stream stuðningi",
|
||||||
|
"name": "Lumia Stream [Prófunarútgáfa]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Bætir stuðningi við texta fyrir flest lög",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Rómaníseraðir Söngtexti"
|
||||||
|
},
|
||||||
|
"name": "Söngtexti Snilld",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Sótt söngtexti fyrir Snilld"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Deila spilunarlista með öðrum. Þegar gestgjafinn spilar lag munu allir aðrir heyra sama lagið",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Sláðu inn auðkenni gestgjafa"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Vista",
|
||||||
|
"track-source": "Lagsuppspretta",
|
||||||
|
"unknown-user": "Óþekktur notandi"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Afritaðu hýsingarauðkenni",
|
||||||
|
"close": "Lokaðu Tónlist Saman",
|
||||||
|
"connected-users": "Tengdir Notendur",
|
||||||
|
"disconnect": "Aftengdu Tónlist Saman",
|
||||||
|
"empty-user": "Engir tengdir notendur",
|
||||||
|
"host": "Tónlist Saman Gestgjafi",
|
||||||
|
"join": "Vertu með Tónlist Saman",
|
||||||
|
"permission": {
|
||||||
|
"all": "Leyfðu gestum að stjórna spilunarlista og spilara",
|
||||||
|
"host-only": "Aðeins gestgjafi getur stjórnað spilunarlista og spilara",
|
||||||
|
"playlist": "Leyfðu gestum að stjórna spilunarlista"
|
||||||
|
},
|
||||||
|
"set-permission": "Breyta Stjórnunarheimild",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Aftengt",
|
||||||
|
"guest": "Tengdur sem Gestur",
|
||||||
|
"host": "Tengdur sem Gestgjafi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Tónlist Saman [Prófunarútgáfa]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Mistókst að bæta við lagi",
|
||||||
|
"closed": "Tónlist Saman lokað",
|
||||||
|
"disconnected": "Tónlist Saman aftengt",
|
||||||
|
"host-failed": "Mistókst að hýsa Tónlist Saman",
|
||||||
|
"id-copied": "Gestgjafaauðkenni afritað á klippiborð",
|
||||||
|
"id-copy-failed": "Mistókst að afrita Hýsingarauðkenni á klippiborð",
|
||||||
|
"join-failed": "Ekki tókst að taka þátt í Tónlist Saman",
|
||||||
|
"joined": "Tengd Tónlist Saman",
|
||||||
|
"permission-changed": "Tónlist Saman leyfi breytt í \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Tókst ekki að fjarlægja lag",
|
||||||
|
"user-connected": "{{name}} tengd Tónlist Saman",
|
||||||
|
"user-disconnected": "{{name}} fór frá Tónlist Saman"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Næsta/Til baka leiðsagnarörvar beint samþættar í viðmótinu, eins og í uppáhalds vafranum þínum",
|
||||||
|
"name": "Leiðsögn"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Fjarlægðu Google innskráningarhnappa og tengla úr viðmótinu",
|
||||||
|
"name": "Engin Google innskráning"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Birta tilkynningu þegar lag byrjar að spila (gagnvirkartilkynningar eru fáanlegar á Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Gagnvirkartilkynningar",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Gagnvirkarstillingar",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Fela hnappatexta",
|
||||||
|
"refresh-on-play-pause": "Endurnýjaðu í Spilun/Hlé",
|
||||||
|
"tray-controls": "Opna/loka á bakka smellur"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Tilkynningaforgangur",
|
||||||
|
"toast-style": "Ristað brauð stíl",
|
||||||
|
"unpause-notification": "Sýna tilkynningu þegar ekki er gert hlé"
|
||||||
|
},
|
||||||
|
"name": "Tilkynningar"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Gerir kleift að skipta forritinu yfir í mynd-í-mynd stillingu",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Alltaf á toppnum",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Flýtilykil",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Flýtilykil"
|
||||||
|
},
|
||||||
|
"label": "Veldu flýtilykil til að skipta mynd-í-mynd",
|
||||||
|
"title": "Mynd-í-mynd Flýtilykil"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Vista gluggastöðu",
|
||||||
|
"save-window-size": "Vista gluggastærð",
|
||||||
|
"use-native-pip": "Notaðu innbyggða PiP í vafra"
|
||||||
|
},
|
||||||
|
"name": "Mynd-í-mynd",
|
||||||
|
"templates": {
|
||||||
|
"button": "Mynd-í-mynd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Hlustaðu hratt, hlustaðu hægt! Bætir við sleða sem stjórnar lagahraðanum",
|
||||||
|
"name": "Spilunarhraði",
|
||||||
|
"templates": {
|
||||||
|
"button": "Hraði"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Stjórnaðu hljóðstyrknum nákvæmlega með músarhjóli/hraðtökkum, með sérsniðnum HUD og sérsniðnum hljóðstyrksþrepum",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Staðbundnar Örvatakkar Stjórna",
|
||||||
|
"custom-volume-steps": "Stilltu Sérsniðin Hljóðstyrksskref",
|
||||||
|
"global-shortcuts": "Alþjóðlegarflýtilyklar"
|
||||||
|
},
|
||||||
|
"name": "Nákvæmshljóðstyrkur",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Minnka Hljóðstyrk",
|
||||||
|
"increase": "Auka Hljóðstyrk"
|
||||||
|
},
|
||||||
|
"label": "Veldu Alþjóðleghljóðstyrklyklabindingar:",
|
||||||
|
"title": "Alþjóðleghljóðstyrklyklabindingar"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Veldu Hljóðstyrksauka/Minnka Skref",
|
||||||
|
"title": "Hljóðstyrksskref"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Núverandi Gæði: {{quality}}",
|
||||||
|
"message": "Veldu Myndbandsgæði:",
|
||||||
|
"title": "Veldu Myndbandsgæði"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Leyfir að breyta myndbandgæðum með hnappi á myndbandsyfirlaginu",
|
||||||
|
"name": "Myndbandgæðisbreyting"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Bæta við scrobbling stuðningi (osv. last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Mistókst að auðkenna með Last.fm\nFela sprettigluggann þar til næstu endurræsingu.",
|
||||||
|
"title": "Auðkenning Mistókst"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API Stillingar"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Sláðu inn ListenBrainz notandalykilinn"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Scrobble aðra fjölmiðla"
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API lykill",
|
||||||
|
"api-secret": "Last.fm API leyndarmál"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Sláðu inn ListenBrainz notandatáknið þitt:",
|
||||||
|
"title": "ListenBrainz tákn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Leyfir að stilla alþjóðlegaflýtilykla fyrir spilun (spila/gera hlé/næsta/fyrri) og slökkva á OSD miðla með því að hnekkja miðlunartökkum, kveikja á Ctrl/CMD + F til að leita, kveikja á Linux MPRIS stuðningi fyrir miðlunarlykla og sérsniðna flýtilykla fyrir lengra komna notendur",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Hneka Fjölmiðlalykla",
|
||||||
|
"set-keybinds": "Stilltu Alþjóðlegslagastýringar"
|
||||||
|
},
|
||||||
|
"name": "Flýtileiðir (og MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Næst",
|
||||||
|
"play-pause": "Spila / Hlé",
|
||||||
|
"previous": "Fyrri"
|
||||||
|
},
|
||||||
|
"label": "Veldu Alþjóðlegslyklabind fyrir Lagastýringu:",
|
||||||
|
"title": "Alþjóðlegslyklabindingar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Sleppir mislíkaði lög",
|
||||||
|
"name": "Slepptu Mislíkaði Lög"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Slepptu sjálfkrafa þagnarköflum í lögum",
|
||||||
|
"name": "Slepptu Þögnum"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Stjórnaðu spilun frá Windows verkefnastikunni þinni",
|
||||||
|
"name": "Miðlunarstýringarverkefnastikunnar"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Bætir við Snertistiku græju fyrir macOS notendur",
|
||||||
|
"name": "Snertistiku"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Samþætting við OBS viðbót Tuna",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Bætir við hnappi til að skipta á milli myndbands/lagshams. Getur einnig valfrjálst fjarlægt allan myndbandsflipann",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Jöfnun",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Vinstri",
|
||||||
|
"middle": "Miðja",
|
||||||
|
"right": "Rétt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Þvingaðu fjarlægja myndbandsflipann",
|
||||||
|
"mode": {
|
||||||
|
"label": "Hamur",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Sérsniðinn rofi",
|
||||||
|
"disabled": "Fötluð",
|
||||||
|
"native": "Innfæddsrofi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Myndbandsrofi",
|
||||||
|
"templates": {
|
||||||
|
"button": "Lag"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Bætir sýndarstýringar við spilarann",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Sýndarstýringartegund"
|
||||||
|
},
|
||||||
|
"name": "Sýndarstýringar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -212,6 +212,14 @@
|
|||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Applica un tema dinamico e degli effetti visivi basandosi sul colore dell'album",
|
"description": "Applica un tema dinamico e degli effetti visivi basandosi sul colore dell'album",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Percentiuale colore",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "Tema abbinato a colore album"
|
"name": "Tema abbinato a colore album"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
@ -571,13 +579,22 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Aggiunge il supporto per lo scrobbling (Last.fm, Listenbrainz ecc.)",
|
"description": "Aggiunge il supporto per lo scrobbling (Last.fm, Listenbrainz ecc.)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Impossibile autenticarsi con Last.fm\nNascondi il popup fino al prossimo riavvio.",
|
||||||
|
"title": "Autenticazione fallita"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Impostazione Last.fm API"
|
"api-settings": "Impostazione Last.fm API"
|
||||||
},
|
},
|
||||||
"listenbrainz": {
|
"listenbrainz": {
|
||||||
"token": "Inserire il token utente per ListenBrainz"
|
"token": "Inserire il token utente per ListenBrainz"
|
||||||
}
|
},
|
||||||
|
"scrobble-other-media": "Scrobble altri media"
|
||||||
},
|
},
|
||||||
"name": "Scrobbler",
|
"name": "Scrobbler",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
|
|||||||
@ -579,6 +579,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "スクロブリング対応を追加します(例:last.fm、Listenbrainzなど)",
|
"description": "スクロブリング対応を追加します(例:last.fm、Listenbrainzなど)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Last.fm の認証に失敗しました\n次の再起動までポップアップは非表示になります。",
|
||||||
|
"title": "認証に失敗"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Last.fm API 設定"
|
"api-settings": "Last.fm API 設定"
|
||||||
|
|||||||
@ -162,6 +162,14 @@
|
|||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "사용자 정의 CSS 파일 가져오기",
|
"import-css-file": "사용자 정의 CSS 파일 가져오기",
|
||||||
"no-theme": "테마 없음"
|
"no-theme": "테마 없음"
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"remove-theme": "사용자 정의 테마를 제거하시겠습니까?",
|
||||||
|
"remove-theme-message": "사용자 정의 테마가 제거됩니다. 계속하시겠습니까?",
|
||||||
|
"button": {
|
||||||
|
"cancel": "취소",
|
||||||
|
"remove": "제거"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -579,6 +587,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "스크로블링 지원을 추가합니다 (예: last.fm, Listenbrainz)",
|
"description": "스크로블링 지원을 추가합니다 (예: last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Last.fm 인증에 실패했습니다\n다음에 다시 시작할 때까지 팝업을 숨깁니다.",
|
||||||
|
"title": "인증 실패"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Last.fm API 설정"
|
"api-settings": "Last.fm API 설정"
|
||||||
|
|||||||
@ -170,7 +170,8 @@
|
|||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"enabled": "Įjungta",
|
"enabled": "Įjungta",
|
||||||
"label": "Įskiepiai"
|
"label": "Įskiepiai",
|
||||||
|
"new": "NAUJIENA"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"label": "Vaizdas",
|
"label": "Vaizdas",
|
||||||
|
|||||||
7
src/i18n/resources/ms.json
Normal file
7
src/i18n/resources/ms.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"language": {
|
||||||
|
"code": "ms",
|
||||||
|
"local-name": "Bahasa Malaysia",
|
||||||
|
"name": "Malay"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,15 +36,15 @@
|
|||||||
"details": "Svarer ikke\n{{error}}"
|
"details": "Svarer ikke\n{{error}}"
|
||||||
},
|
},
|
||||||
"when-ready": {
|
"when-ready": {
|
||||||
"clearing-cache-after-20s": "Tømmer programhurtiglager"
|
"clearing-cache-after-20s": "Tømmer programhurtigbuffer"
|
||||||
},
|
},
|
||||||
"window": {
|
"window": {
|
||||||
"tried-to-render-offscreen": "Prøvde å tegne vindu utenfor skjermen. Størrelse={{windowSize}}, skjermstørrelse={{displaySize}}, posisjon={{position}}"
|
"tried-to-render-offscreen": "Prøvde å tegne vindu utenfor skjermen, størrelse={{windowSize}}, skjermstørrelse={{displaySize}}, posisjon={{position}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"hide-menu-enabled": {
|
"hide-menu-enabled": {
|
||||||
"detail": "Menyen er skjult. Bruk «Alt» for å vise den, (ller «Esc» for å bruke menyen i programmet).",
|
"detail": "Menyen er skjult, bruk 'Alt' for å vise den (eller 'Escape' for å bruke menyen i programmet)",
|
||||||
"message": "Meny skjult",
|
"message": "Meny skjult",
|
||||||
"title": "Meny vist"
|
"title": "Meny vist"
|
||||||
},
|
},
|
||||||
@ -85,7 +85,7 @@
|
|||||||
"submenu": {
|
"submenu": {
|
||||||
"copy-current-url": "Kopier nåværende nettadresse",
|
"copy-current-url": "Kopier nåværende nettadresse",
|
||||||
"go-back": "Tilbake",
|
"go-back": "Tilbake",
|
||||||
"go-forward": "Forover",
|
"go-forward": "Framover",
|
||||||
"quit": "Avslutt",
|
"quit": "Avslutt",
|
||||||
"restart": "Programomstart"
|
"restart": "Programomstart"
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@
|
|||||||
"advanced-options": {
|
"advanced-options": {
|
||||||
"label": "Avanserte alternativer",
|
"label": "Avanserte alternativer",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"auto-reset-app-cache": "Tilbakestill programhurtiglager når programmet startes",
|
"auto-reset-app-cache": "Tilbakestill programhurtigbuffer når programmet startes",
|
||||||
"disable-hardware-acceleration": "Skru av maskinvareakselerasjon",
|
"disable-hardware-acceleration": "Skru av maskinvareakselerasjon",
|
||||||
"edit-config-json": "Rediger config.json",
|
"edit-config-json": "Rediger config.json",
|
||||||
"override-user-agent": "Overstyr brukeragent",
|
"override-user-agent": "Overstyr brukeragent",
|
||||||
|
|||||||
698
src/i18n/resources/ne.json
Normal file
698
src/i18n/resources/ne.json
Normal file
@ -0,0 +1,698 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "प्लगइन {{pluginName}}::{{contextName}} को कार्यान्वयन गर्न असफल भयो",
|
||||||
|
"executed-at-ms": "प्लगइन {{pluginName}}::{{contextName}} {{ms}} मिलिसेकेण्डमा कार्यान्वित भयो",
|
||||||
|
"initialize-failed": "प्लगइन \"{{pluginName}}\" आरम्भ गर्न मिलेन",
|
||||||
|
"load-all": "सबै प्लगइनहरू लोड हुँदैछ",
|
||||||
|
"load-failed": "प्लगइन \"{{pluginName}}\" लोड गर्न मिलेन",
|
||||||
|
"loaded": "प्लगइन \"{{pluginName}}\" लोड भयो",
|
||||||
|
"unload-failed": "प्लगइन \"{{pluginName}}\" अनलोड गर्न मिलेन",
|
||||||
|
"unloaded": "प्लगइन \"{{pluginName}}\" अनलोड भयो"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "ne",
|
||||||
|
"local-name": "नेपाली",
|
||||||
|
"name": "Nepali"
|
||||||
|
},
|
||||||
|
"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": "Windowले स्क्रीन बाहिर रेन्डर गर्न कोशिस गर्यो, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "मेनु लुकिएको छ, यसलाई देखाउन 'Alt' प्रयोग गर्नुहोस् (वा 'Escape' यदि इन-एप मेनु प्रयोग गर्नुहोस्)",
|
||||||
|
"message": "हाइड मेनु सक्षम गरिएको छ",
|
||||||
|
"title": "हाइड मेनु इनेबल गरियो"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "पछि",
|
||||||
|
"restart-now": "अहिले पुन: सुरु गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"detail": "{{pluginName}}\" प्लगइनले प्रभाव समेत गर्नका लागि पुन: सुरु गर्नुपर्दछ",
|
||||||
|
"message": "{{pluginName}}\" पुनः सुरु गर्नुपर्छ",
|
||||||
|
"title": "पुनः सुरु गर्नुपर्छ"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "बन्द गर्नुहोस्",
|
||||||
|
"relaunch": "पुन: सुरु गर्नुहोस्",
|
||||||
|
"wait": "प्रतीक्षा गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"detail": "हामी असुविधाका लागि क्षमा गर्दछौं! कृपया के गर्नुहोस् छन् छान्नुहोस्:",
|
||||||
|
"message": "अनुप्रयोग असार्थक छ",
|
||||||
|
"title": "विन्डो संपर्क नाघेको छ"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "अपडेटहरू निष्क्रिय गर्नुहोस्",
|
||||||
|
"download": "डाउनलोड गर्नुहोस्",
|
||||||
|
"ok": "ठिक छ"
|
||||||
|
},
|
||||||
|
"detail": "नयाँ संस्करण उपलब्ध छ र यसलाई {{downloadLink}} बाट डाउनलोड गर्न सकिन्छ",
|
||||||
|
"message": "नयाँ संस्करण उपलब्ध छ",
|
||||||
|
"title": "अपडेट उपलब्ध छ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "बारेमा",
|
||||||
|
"navigation": {
|
||||||
|
"label": "नेभिगेसन",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "हालको URL प्रतिलिपि गर्नुहोस्",
|
||||||
|
"go-back": "पछाडि जानुहोस्",
|
||||||
|
"go-forward": "अघि जानुहोस्",
|
||||||
|
"quit": "बाहिर निस्कनुहोस्",
|
||||||
|
"restart": "अनुप्रयोग पुनः सुरु गर्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "विकल्पहरू",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "उन्नत विकल्पहरू",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "एप सुरु हुँदा एप क्यास रिसेट गर्नुहोस्",
|
||||||
|
"disable-hardware-acceleration": "हार्डवेयर तेजीगरी निष्क्रिय गर्नुहोस्",
|
||||||
|
"edit-config-json": "config.json सम्पादन गर्नुहोस्",
|
||||||
|
"override-user-agent": "प्रयोगकर्ता-एजेन्ट अधिलेखन गर्नुहोस्",
|
||||||
|
"restart-on-config-changes": "कन्फिगरेसन परिवर्तनमा पुनः सुरु गर्नुहोस्",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "प्रोक्सी सेट गर्नुहोस्",
|
||||||
|
"prompt": {
|
||||||
|
"label": "प्रोक्सी ठेगाना प्रविष्टि: (निष्क्रिय गर्नका लागि खाली छोड्नुहोस्)",
|
||||||
|
"placeholder": "उदाहरण: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "प्रोक्सी सेट गर्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "डेभटुल्स परिस्थिति परिवर्तन गर्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "सधैं माथिल्लोमा",
|
||||||
|
"auto-update": "स्वत: अपडेट",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "मेनु अर्को लन्चमा लुकिनेछ, यसलाई देखाउनका लागि [Alt] प्रयोग गर्नुहोस् (वा इन-एप-मेनु प्रयोग गर्दा backtick [`])प्रयोग गर्नुहोस्",
|
||||||
|
"title": "हाइड मेनु इनेबल गरियो"
|
||||||
|
},
|
||||||
|
"label": "हाइड मेनु"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "भाषा पुनः सुरु गर्नपछि परिवर्तन गरिनेछ",
|
||||||
|
"title": "भाषा परिवर्तित गरियो"
|
||||||
|
},
|
||||||
|
"label": "भाषा",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "अनुवाद गर्न मद्दत गर्न चाहनुहुन्छ? यहाँ क्लिक गर्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "एप सुरु हुँदा अन्तिम गीत पुनः सुरु गर्नुहोस्",
|
||||||
|
"single-instance-lock": "एकल उदाहरण तालिका",
|
||||||
|
"start-at-login": "लगइनमा सुरु गर्नुहोस्",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "सुरु गर्नुहोस्",
|
||||||
|
"unset": "अनसेट"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "ट्रे(tray)",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "हाल बन्द",
|
||||||
|
"enabled-and-hide-app": "ट्रे इनेबल गरिएको छ र एप बन्द गरिएको छ",
|
||||||
|
"enabled-and-show-app": "एप देखाउनुहोस्",
|
||||||
|
"play-pause-on-click": "क्लिकमा खेल्नुहोस्/रोक्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "भिजुअल ट्वीक्स",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "पूर्वनिर्धारित",
|
||||||
|
"force-show": "देखाउनुहोस",
|
||||||
|
"hide": "लुकाउनुहोस",
|
||||||
|
"label": "लाइक बटनहरू"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "अपग्रेड बटन हटाउनुहोस्",
|
||||||
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "रद्द गर्नुहोस्",
|
||||||
|
"remove": "हटाउनुहोस्"
|
||||||
|
},
|
||||||
|
"remove-theme": "के तपाईँ निश्चित हुनुहुन्छ कि तपाईँ कस्टम थिम हटाउन चाहनुहुन्छ?",
|
||||||
|
"remove-theme-message": "यसले कस्टम थिम हटाउनेछ"
|
||||||
|
},
|
||||||
|
"label": "थिम",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "कस्टम CSS फाइल आयात गर्नुहोस्",
|
||||||
|
"no-theme": "कुनै थिम छैन"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "सक्षम गरियो",
|
||||||
|
"label": "प्लगइनहरू",
|
||||||
|
"new": "NEW"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "हेर्नुहोस्",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "फोर्स रिलोड",
|
||||||
|
"reload": "पुनः लोड गर्नुहोस्",
|
||||||
|
"reset-zoom": "वास्तविक आकार",
|
||||||
|
"toggle-fullscreen": "पूर्ण स्क्रिन टगल गर्नुहोस्",
|
||||||
|
"zoom-in": "जुम इन गर्नुहोस्",
|
||||||
|
"zoom-out": "जुम आउट गर्नुहोस्"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "अर्को",
|
||||||
|
"play-pause": "खेल्नुहोस्/रोक्नुहोस्",
|
||||||
|
"previous": "अघिल्ला",
|
||||||
|
"quit": "बाहिर निस्कनुहोस्",
|
||||||
|
"restart": "एप पुनः सुरु गर्नुहोस्",
|
||||||
|
"show": "विन्डो देखाउनुहोस्",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "युट्युब मिउजिक",
|
||||||
|
"with-song-info": "युट्युब मिउजिक:{{artist}}-{{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "सबै विज्ञापन र ट्र्याकइंगहरू ब्लक गर्नुहोस्",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "अवरोधक"
|
||||||
|
},
|
||||||
|
"name": "विज्ञापन अवरोधक"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "प्लेलिस्ट वा एल्बममा सबै गीतहरूमा यो लागू गर्न नचाहेको, मन नपरोस्, मनपर्यो, र विपरीत बटनहरू थप्दछ",
|
||||||
|
"name": "एल्बम कार्यहरू"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "एल्बम रङ प्यालेटमा आधारित गतिशील विषयवस्तु र दृश्य प्रभावहरू लागू गर्दछ",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "रङ मिश्रण अनुपात",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "एल्बम रङ विषयवस्तु"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "तपाईँको स्क्रिनको पृष्ठभूमिमा भिडियोबाट कोमल रङहरू कास्ट गरेर प्रकाश प्रभाव लागू गर्दछ",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "ब्लर रकम",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} पिक्सेलमा"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "बफर",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "अपारदर्शिता",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "गुणस्तर",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} पिक्सेलमा"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "आकार",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "सुगमता संक्रमण",
|
||||||
|
"submenu": {
|
||||||
|
"during": "{{interpolationTime}} सेकेन्ड को समयमा"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "फुलस्क्रिन प्रयोग गर्दै"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "परिवेश मोड"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "अडियोमा कम्प्रेसन लागू गर्नुहोस् (सङ्केतको सबैभन्दा चर्को भागहरूको भोल्युम कम गर्दछ र नरम भागहरूको भोल्युम बढाउँछ)",
|
||||||
|
"name": "अडियो कम्प्रेसर"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "नेभिगेसन बारलाई पारदर्शी र धुवाँलो बनाउँछ",
|
||||||
|
"name": "ब्लर नेभिगेसन बार"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "युट्युबको उमेर प्रमाणिकरणलाई बाइपास गर्नुहोस्",
|
||||||
|
"name": "उमेरका प्रतिबन्धहरू बाइपास गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "युट्युब सङ्गीत अडियो ट्र्याकहरूका लागि क्याप्सन चयनकर्ता",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "स्वचालित रूपमा अन्तिम प्रयोग गरिएको क्याप्सन चयन गर्नुहोस्",
|
||||||
|
"disable-captions": "पूर्वनिर्धारित रूपमा कुनै क्याप्सनहरू छैनन्"
|
||||||
|
},
|
||||||
|
"name": "शीर्षक चयनकर्ता",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "हालको शीर्षक भाषाः {{language}}",
|
||||||
|
"none": "केही छैन",
|
||||||
|
"title": "क्याप्सन भाषा चयन गर्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "क्याप्सन चयनकर्ता खोल्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "सँधै साइडबारलाई कम्प्याक्ट मोडमा सेट गर्नुहोस्",
|
||||||
|
"name": "कम्प्याक्ट साइडबार"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "गीतहरू बिच क्रसफेड",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "उन्नत"
|
||||||
|
},
|
||||||
|
"name": "क्रसफेड [बीटा]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "फेड इन समय (ms)",
|
||||||
|
"fade-out-duration": "फेड आउट समय (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "फेड स्केलिङ",
|
||||||
|
"linear": "रैखिक",
|
||||||
|
"logarithmic": "लघुगणक"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "अन्त्य हुनुभन्दा अघि क्रसफेड सेकेन्ड"
|
||||||
|
},
|
||||||
|
"title": "क्रसफेड विकल्पहरू"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "\"रोकिएको\" स्मोथिथिमा गीत सुरु गराउँछ",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "स्टार्टअपमा मात्र लागु हुन्छ"
|
||||||
|
},
|
||||||
|
"name": "स्वतः खेल निष्क्रिय गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "सक्रिय जडानसँग जडान गर्ने प्रयास गरियो",
|
||||||
|
"connected": "डिस्कर्डमा जोडियो",
|
||||||
|
"disconnected": "डिस्कोर्डबाट डिसकनेक्ट गरियो"
|
||||||
|
},
|
||||||
|
"description": "Rich Presence प्रयोग गरेर साथीहरुलाइ के सुन्दैछु देखाउने",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "आफै रिकनेक्ट होस्",
|
||||||
|
"clear-activity": "एकटिभिटी क्लियर गर",
|
||||||
|
"clear-activity-after-timeout": "समय पछि एकटिभिटी क्लियर गर",
|
||||||
|
"connected": "कनेक्टेड",
|
||||||
|
"disconnected": "डिसकन्एक्टेड",
|
||||||
|
"hide-duration-left": "बाकी समय लुकाऊ",
|
||||||
|
"hide-github-button": "GitHub लिंक लुकाऊ",
|
||||||
|
"play-on-youtube-music": "YouTube music मा बजाउ",
|
||||||
|
"set-inactivity-timeout": "इनएक्टिभिटी टाइमआउट राख"
|
||||||
|
},
|
||||||
|
"name": "डिसकार्ड रिच प्रीसेंस",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "इनएक्टिभिटी टाइमआउट सेकन्डमा लेख्नुहोस्:",
|
||||||
|
"title": "इनएक्टिभिटी टाइमआउट राख्नुहोस्"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "ओके"
|
||||||
|
},
|
||||||
|
"message": "अह:! माफ गर्नुहोस्, डाउनलोड सफलहुन सकेन…",
|
||||||
|
"title": "डाउनलोडमा त्रुटि भयो!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "ओके"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}}गाना)",
|
||||||
|
"message": "प्लेलिस्ट {{playlistTitle}} डाउनलोड हुदै छ",
|
||||||
|
"title": "डाउनलोड सुरुभयो"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "रूपान्तरणः {{percent}}%",
|
||||||
|
"converting": "रूपान्तरण हुदैछ…",
|
||||||
|
"done": "गरियो: {{filePath}}",
|
||||||
|
"download-info": "{{artist}}को - {{title}}{{videoId}}डाउनलोड हुदैछ",
|
||||||
|
"download-progress": "डाउनलोड: {{percent}}%",
|
||||||
|
"downloading": "डाउनलोड हुदैछ…",
|
||||||
|
"downloading-counter": "{{current}}/{{total}} डाउनलोड गरिदै…",
|
||||||
|
"downloading-playlist": "प्लेलिस्ट \"{{playlistTitle}}\" -{{playlistSize}} गाना {{playlistId}} डाउनलोड गरिदैछ",
|
||||||
|
"error-while-downloading": "\"{{author}}\" - {{title}}\":{{error}} डाउनलोडमा समस्या आयो",
|
||||||
|
"folder-already-exists": "{{playlistFolder}} नाम गरेको फोल्डर अघाडी देखि छ",
|
||||||
|
"getting-playlist-info": "प्लेलिस्टको डाटा हासिल गरिदै छ…",
|
||||||
|
"loading": "लोडिंग…",
|
||||||
|
"playlist-has-only-one-song": "प्लेलिस्टमा एउता मात्रै गानाछ, तेस्लाई डाइरेक्ट डाउनलोड गर्नुहोस",
|
||||||
|
"playlist-id-not-found": "प्लेलिस्ट ID भेटियेन",
|
||||||
|
"playlist-is-empty": "प्लेलिस्ट खाली छ",
|
||||||
|
"playlist-is-mix-or-private": "प्लेलिस्ट जानकारी प्राप्त गर्न त्रुटिः निश्चित गर्नुहोस् कि यो निजी वा \"तपाईँका लागि मिश्रित\" प्लेलिस्ट होइन\n\n{{error}}",
|
||||||
|
"preparing-file": "फाइल तयार गरिदै छ…",
|
||||||
|
"saving": "सेव गरिदै…",
|
||||||
|
"trying-to-get-playlist-id": "प्लेलिस्ट आईडी: {{playlistId}} प्राप्त गर्ने प्रयास",
|
||||||
|
"video-id-not-found": "भिडियो फेला परेन",
|
||||||
|
"writing-id3": "ID3 ट्यागहरू लेखीदै…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "इन्टरफेसबाट सिधै MP3/स्रोत अडियो डाउनलोड गर",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "डाउनलोड फोल्डर चयन गर्नुहोस्",
|
||||||
|
"download-playlist": "डाउनलोड प्लेलिस्ट",
|
||||||
|
"presets": "प्रिसेटहरू",
|
||||||
|
"skip-existing": "विद्यमान फाइलहरू स्किप गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"name": "डाउनलोडर",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "प्रगति अद्यावधिक गर्न सकिँदैन"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "डाउनलोड"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "भोल्युम स्लाइडरलाई घातीय बनाउँछ त्यसैले कम भोल्युमहरू चयन गर्न सजिलो हुन्छ।",
|
||||||
|
"name": "एक्सपोनेन्सियल भोल्युम"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "मेनु-बारहरूलाई फेन्सी, गाढा वा एल्बम-रङ्गको रूप दिन्छ",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "DOM विन्डो नियन्त्रणहरू लुकाउनुहोस्"
|
||||||
|
},
|
||||||
|
"name": "इन-एप मेनु"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "लुमिया स्ट्रिम सपोर्ट थप्दछ",
|
||||||
|
"name": "लुमिया स्ट्रिम [बिटा]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "धेरैजसो गीतहरूका लागि लिरिक्स थप्दछ",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "रोमनाइज्ड लिरिक्स"
|
||||||
|
},
|
||||||
|
"name": "लिरिक्स जिनियस",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "लिरिक्स जिनियसबाट लिरिक्स प्राप्त गरियो"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "अरूसँग प्लेलिस्ट साझा गर्नुहोस्। जब होस्टले गीत बजाउँछ, अरू सबैले एउटै गीत सुन्नेछन्",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "होस्ट आईडी प्रविष्ट गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "सेभ गर्नुहोस्",
|
||||||
|
"track-source": "गानाको स्रोत",
|
||||||
|
"unknown-user": "अज्ञात प्रयोगकर्ता"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "होस्ट आईडी कपी गर्नुहोस्",
|
||||||
|
"close": "मिउजीक टुगेदर बन्द गर्नुहोस्",
|
||||||
|
"connected-users": "जोडिएका प्रयोगकर्ताहरू",
|
||||||
|
"disconnect": "मिउजीक टुगेदर डिस्कनेक्ट गर्नुहोस्",
|
||||||
|
"empty-user": "कोही प्रयोगकर्ताहरू जोडिएका छैनन्",
|
||||||
|
"host": "मिउजीक टुगेदर होस्ट",
|
||||||
|
"join": "मिउजीक टुगेदर जोइन गनुहोस",
|
||||||
|
"permission": {
|
||||||
|
"all": "पाहुनाहरूलाई प्लेलिस्ट र प्लेयर नियन्त्रण गर्न अनुमति दिनुहोस्",
|
||||||
|
"host-only": "केवल होस्टले प्लेलिस्ट र प्लेयर नियन्त्रण गर्न सक्छ",
|
||||||
|
"playlist": "पाहुनाहरूलाई प्लेलिस्ट नियन्त्रण गर्न अनुमति दिनुहोस्"
|
||||||
|
},
|
||||||
|
"set-permission": "नियन्त्रण अनुमति परिवर्तन गर्नुहोस्",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "विच्छेदित",
|
||||||
|
"guest": "पाहुनाका रूपमा जोडियो",
|
||||||
|
"host": "होस्टका रूपमा जोडियो"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "मिउजीक टुगेदर [बिटा]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "गीत थप्न असफल",
|
||||||
|
"closed": "मिउजीक टुगेदर बन्द गरियो",
|
||||||
|
"disconnected": "मिउजीक टुगेदर डिसकनेक्टेड",
|
||||||
|
"host-failed": "मिउजीक टुगेदर होस्ट गर्न असफल",
|
||||||
|
"id-copied": "होस्ट आईडी क्लिपबोर्डमा कपी गरियो",
|
||||||
|
"id-copy-failed": "होस्ट आईडी क्लिपबोर्डमा कपी गर्न असफल",
|
||||||
|
"join-failed": "मिउजीक टुगेदर जोइन हुन असफल",
|
||||||
|
"joined": "मिउजीक टुगेदरमा जोडियो",
|
||||||
|
"permission-changed": "मिउजीक टुगेदर अनुमति \"{{permission}}\" मा परिवर्तन गरियो",
|
||||||
|
"remove-song-failed": "गाना हटाउनमा समस्या आयो",
|
||||||
|
"user-connected": "मिउजीक टुगेदरमा {{name}} जोडीनुभायो",
|
||||||
|
"user-disconnected": "{{name}}ले मिउजीक टुगेदर छोडनु भयो"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "अर्को/पछाडि नेभिगेसन तपाईँको मनपर्ने ब्राउजरमा जस्तै सिधा इन्टरफेसमा एकीकृत तीरहरू",
|
||||||
|
"name": "नेभिगेसन"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "इन्टरफेसबाट गुगल लगइन बटन र लिङ्कहरू हटाउनुहोस्",
|
||||||
|
"name": "गुगल लगइन छैन"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "गीत बज्न थाल्दा सूचना देखाउनुहोस् (अन्तरक्रियात्मक सूचनाहरू Windowsमा उपलब्ध छन्)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "अन्तरक्रियात्मक सूचनाहरू",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "अन्तरक्रियात्मक सेटिङहरू",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "टेक्सट बटन लुकाउनुहोस्",
|
||||||
|
"refresh-on-play-pause": "प्ले/पोजमा ताजा गर्नुहोस्",
|
||||||
|
"tray-controls": "ट्रेमा क्लिक गर्दा खोल्नुहोस्/बन्द गर्नुहोस्"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "अधिसूचना प्राथमिकता",
|
||||||
|
"toast-style": "टोस्ट शैली",
|
||||||
|
"unpause-notification": "अनपउसमा सूचना देखाउनुहोस्"
|
||||||
|
},
|
||||||
|
"name": "सूचनाहरू"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "एपलाई पिक्चर-इन-पिक्चर मोडमा परिवर्तन गर्न अनुमति दिन्छ",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "सधैँ शीर्षमा",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "हटकी",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "हटकी"
|
||||||
|
},
|
||||||
|
"label": "पिक्चर-इन-पिक्चर टगल गर्न हटकी छान्नुहोस्",
|
||||||
|
"title": "पिक्चर-इन-पिक्चर हटकी"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "windowको स्थान सेव गर्नुहोस्",
|
||||||
|
"save-window-size": "windowको आकार सेव गर्नुहोस्",
|
||||||
|
"use-native-pip": "ब्राउजरको नेटिभ PiP प्रयोग गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"name": "पिक्चर-इन-पिक्चर",
|
||||||
|
"templates": {
|
||||||
|
"button": "पिक्चर-इन-पिक्चर"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "छिटो सुन्नुहोस्, ढिलो सुन्नुहोस्! एसले गीतको गति नियन्त्रण गर्ने स्लाइडर थप्छ",
|
||||||
|
"name": "प्लेब्याक स्पीड",
|
||||||
|
"templates": {
|
||||||
|
"button": "स्पीड"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "कस्टम HUD र अनुकूलन योग्य भोल्युम चरणहरूको साथ माउसव्हील/हटकीहरू प्रयोग गरेर भोल्युम नियन्त्रण गर्नुहोस्",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "लोकल एरो-की नियन्त्रणहरू",
|
||||||
|
"custom-volume-steps": "कस्टम भोल्युम चरणहरू सेट गर्नुहोस्",
|
||||||
|
"global-shortcuts": "ग्लोबल हटकीहरू"
|
||||||
|
},
|
||||||
|
"name": "सटीक भोल्युम",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "भोल्युम घटाउनुहोस्",
|
||||||
|
"increase": "भोल्युम बढाउनुहोस्"
|
||||||
|
},
|
||||||
|
"label": "ग्लोबल भोल्युम किबाइन्डहरू चयन गर्नुहोस्:",
|
||||||
|
"title": "ग्लोबल भोल्युम किबाइन्डहरू"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "भोल्युम वृद्धि/घटाउने चरणहरू चयन गर्नुहोस्",
|
||||||
|
"title": "भोल्युमका चरणहरू"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "हालको गुणस्तरः {{quality}}",
|
||||||
|
"message": "भिडियो गुणस्तर चयन गर्नुहोस्:",
|
||||||
|
"title": "भिडियोको गुणस्तर चयन गर्नुहोस्"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "भिडियो ओभरलेमा बटनको साथ भिडियो गुणस्तर परिवर्तन गर्न अनुमति दिन्छ",
|
||||||
|
"name": "भिडियो गुणस्तर परिवर्तनकर्ता"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "स्क्रोब्लिङ समर्थन थप्नुहोस् (etc. last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Last.fm सँग प्रमाणिकरण गर्न असफल\nअर्को पुनः सुरु नभएसम्म पपअप लुकाउनुहोस्।",
|
||||||
|
"title": "प्रमाणीकरण असफल"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API सेटिङहरू"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "ListenBrainz प्रयोगकर्ता टोकन प्रविष्ट गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "अन्य मिडियालाई स्क्रबल गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"name": "स्क्रबबलर",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API कुञ्जी",
|
||||||
|
"api-secret": "Last.fm एपीआई गुप्त"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "आफ्नो ListenBrainz प्रयोगकर्ता टोकन प्रविष्ट गर्नुहोस्:",
|
||||||
|
"title": "ListenBrainz टोकन"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "प्लेब्याक (प्ले/विराम/अर्को/अघिल्लो) का लागि ग्लोबल हटकीहरू सेट गर्न र मिडिया कुञ्जीहरू ओभरराइड गरेर मिडिया ओएसडी बन्द गर्न अनुमति दिन्छ, खोजी गर्न Ctrl/CMD + F सक्रिय गर्दछ, मिडिया कुञ्जीहरूका लागि लिनक्स MPRIS र उन्नत प्रयोगकर्ताहरूका लागि कस्टम हटकीहरू समर्थन सक्रिय गर्दछ",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "मिडिया कुञ्जीहरूलाई ओभरराइड गर्नुहोस्",
|
||||||
|
"set-keybinds": "विश्वव्यापी गीत नियन्त्रणहरू सेट गर्नुहोस्"
|
||||||
|
},
|
||||||
|
"name": "सर्टकटहरू (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "अर्को",
|
||||||
|
"play-pause": "खेल्नुहोस् / रोक्नुहोस्",
|
||||||
|
"previous": "अघिल्ला"
|
||||||
|
},
|
||||||
|
"label": "गीत नियन्त्रणका लागि ग्लोबल किबाइन्डहरू छान्नुहोस्:",
|
||||||
|
"title": "ग्लोबल किबाइन्डहरू"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "नापसन्द गरिएका गीतहरू छोड्नुहोस्",
|
||||||
|
"name": "मन नपरेका गीतहरू छोड्नुहोस्"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "गीतहरूमा मौन खण्डहरू स्वचालित रूपमा स्किप गर्नुहोस्",
|
||||||
|
"name": "मौन छोड्नुहोस्"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "स्वचालित रूपमा गैर-सङ्गीत भागहरू जस्तै इन्ट्रो/आउट्रो वा सङ्गीत भिडियोका भागहरू छोड्नुहोस्",
|
||||||
|
"name": "स्पन्सरब्लक"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "तपाईँको विन्डोज टास्कबारबाट प्लेब्याक नियन्त्रण गर्नुहोस्",
|
||||||
|
"name": "टास्कबार मेडिया कन्ट्रोल"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "MacOS प्रयोगकर्ताहरूका लागि टचबार विजेट थप्दछ",
|
||||||
|
"name": "टचबार"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "OBS को टुना प्लगइनसँग एकीकरण",
|
||||||
|
"name": "टुना OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "भिडियो/गीत मोड बीच स्विच गर्न बटन थप्दछ। वैकल्पिक रूपमा सम्पूर्ण भिडियो ट्याब हटाउन पनि सक्छ",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "संरेखण",
|
||||||
|
"submenu": {
|
||||||
|
"left": "बायाँ",
|
||||||
|
"middle": "बिचमा",
|
||||||
|
"right": "दायाँ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "बलपूर्वक भिडियो ट्याब हटाउनुहोस्",
|
||||||
|
"mode": {
|
||||||
|
"label": "मोड",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "कस्टम टगल",
|
||||||
|
"disabled": "अक्षम",
|
||||||
|
"native": "नेटिभ टगल"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "भिडियो टगल",
|
||||||
|
"templates": {
|
||||||
|
"button": "गीत"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "प्लेयरमा भिजुअलाइजर थप्छ",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "भिजुअलाइजरको प्रकार"
|
||||||
|
},
|
||||||
|
"name": "भिजुअलाइजर"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
531
src/i18n/resources/nl.json
Normal file
531
src/i18n/resources/nl.json
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Mislukt om plugin {{pluginName}}::{{contextName}} uit te voeren",
|
||||||
|
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} uitgevoerd in {{ms}}ms",
|
||||||
|
"initialize-failed": "Initialisatie van plugin \"{{pluginName}}\" mislukt",
|
||||||
|
"load-all": "Alle plugins laden",
|
||||||
|
"load-failed": "Mislukt om plugin \"{{pluginName}}\" te laden",
|
||||||
|
"loaded": "Plugin \"{{pluginName}}\" geladen",
|
||||||
|
"unload-failed": "Mislukt om plugin \"{{pluginName}}\" te lossen",
|
||||||
|
"unloaded": "Plugin \"{{pluginName}}\" gelost"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "nl",
|
||||||
|
"local-name": "Nederlands",
|
||||||
|
"name": "Nederlands"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Laden voltooid. DevTools geopend"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n geladen"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Commando ontvangen via protocol: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS-bestand \"{{cssFile}}\" bestaat niet, wordt genegeerd"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Onverantwoordelijkheidsfout!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "App-cache wissen"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Venster probeerde buiten het scherm te renderen, venstergrootte={{windowSize}}, weergavegrootte={{displaySize}}, positie={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Menu is verborgen, gebruik 'Alt' om het weer te geven (of 'Escape' als u de In-App Menu gebruikt)",
|
||||||
|
"message": "Menu verbergen is ingeschakeld",
|
||||||
|
"title": "Menu Verbergen Ingeschakeld"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Later",
|
||||||
|
"restart-now": "Nu Opnieuw Opstarten"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" plugin vereist een herstart om van kracht te worden",
|
||||||
|
"message": "\"{{pluginName}}\" moet opnieuw worden opgestart",
|
||||||
|
"title": "Herstart Vereist"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Stoppen",
|
||||||
|
"relaunch": "Opnieuw starten",
|
||||||
|
"wait": "Wachten"
|
||||||
|
},
|
||||||
|
"detail": "Het programma reageert niet! Kies wat u wilt doen:",
|
||||||
|
"message": "De applicatie reageert niet",
|
||||||
|
"title": "Venster Niet Reagerend"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Updates uitschakelen",
|
||||||
|
"download": "Downloaden",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "Er is een nieuwe versie beschikbaar en kan worden gedownload op {{downloadLink}}",
|
||||||
|
"message": "Een nieuwe versie is beschikbaar",
|
||||||
|
"title": "Update Beschikbaar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Over",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigatie",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Huidige URL kopiëren",
|
||||||
|
"go-back": "Terug",
|
||||||
|
"go-forward": "Vooruit",
|
||||||
|
"quit": "Afsluiten",
|
||||||
|
"restart": "Applicatie Opnieuw Opstarten"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Opties",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Geavanceerde opties",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "App-cache resetten bij het starten van de app",
|
||||||
|
"disable-hardware-acceleration": "Hardwareversnelling uitschakelen",
|
||||||
|
"edit-config-json": "Config.json bewerken",
|
||||||
|
"override-user-agent": "Gebruikersagent overschrijven",
|
||||||
|
"restart-on-config-changes": "Herstarten bij configuratiewijzigingen",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Proxy instellen",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Proxy-adres invoeren: (leeg laten om uit te schakelen)",
|
||||||
|
"placeholder": "Voorbeeld: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Proxy instellen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "DevTools schakelen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Altijd bovenop",
|
||||||
|
"auto-update": "Automatisch bijwerken",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Menu wordt verborgen bij de volgende start, gebruik [Alt] om het weer te geven (of backtick [`] als u de in-app-menu gebruikt)",
|
||||||
|
"title": "Menu Verbergen Ingeschakeld"
|
||||||
|
},
|
||||||
|
"label": "Menu Verbergen"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Taal wordt gewijzigd na herstart",
|
||||||
|
"title": "Taal Gewijzigd"
|
||||||
|
},
|
||||||
|
"label": "Taal",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Wil je helpen vertalen? Klik hier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Hervat laatste liedje bij het opstarten van de app",
|
||||||
|
"single-instance-lock": "Eenmalige instantievergrendeling",
|
||||||
|
"start-at-login": "Starten bij het inloggen",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Startpagina",
|
||||||
|
"unset": "Niet ingesteld"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Systeemvak",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Uitgeschakeld",
|
||||||
|
"enabled-and-hide-app": "Ingeschakeld en applicatie verbergen",
|
||||||
|
"enabled-and-show-app": "Ingeschakeld en applicatie weergeven",
|
||||||
|
"play-pause-on-click": "Afspelen/Pauzeren bij klikken"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Visuele Aanpassingen",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Standaard",
|
||||||
|
"force-show": "Forceren weergeven",
|
||||||
|
"hide": "Verbergen",
|
||||||
|
"label": "Vind ik leuk-knoppen"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Upgrade-knop verwijderen",
|
||||||
|
"theme": {
|
||||||
|
"label": "Thema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Aangepast CSS-bestand importeren",
|
||||||
|
"no-theme": "Geen thema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Ingeschakeld",
|
||||||
|
"label": "Plugins",
|
||||||
|
"new": "NIEUW"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Weergave",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Forceer Herladen",
|
||||||
|
"reload": "Herladen",
|
||||||
|
"reset-zoom": "Ware Grootte",
|
||||||
|
"toggle-fullscreen": "Volledig Scherm Wisselen",
|
||||||
|
"zoom-in": "Inzoomen",
|
||||||
|
"zoom-out": "Uitzoomen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Volgende",
|
||||||
|
"play-pause": "Afspelen/Pauzeren",
|
||||||
|
"previous": "Vorige",
|
||||||
|
"quit": "Afsluiten",
|
||||||
|
"restart": "Applicatie Opnieuw Opstarten",
|
||||||
|
"show": "Venster Weergeven",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Blokkeer alle advertenties en tracking vanuit de doos",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Blokkeerder"
|
||||||
|
},
|
||||||
|
"name": "Advertentieblokkeerder"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Voegt knoppen toe voor Ondisliken, Disliken, Liken en Ontliken om dit toe te passen op alle nummers in een afspeellijst of album",
|
||||||
|
"name": "Albumacties"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Past een dynamisch thema en visuele effecten toe op basis van het kleurenpalet van het album",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Kleurmixverhouding",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Albumkleurthema"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Past een verlichtingseffect toe door zachte kleuren van de video op het achtergrondscherm te werpen",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Vervagingshoeveelheid",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pixels"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buffer",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Dekking",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Kwaliteit",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} pixels"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Formaat",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Soepelheid overgang",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Tijdens {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Volledig scherm gebruiken"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Omgevingsmodus"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Past compressie toe op audio (verlaagt het volume van de luidste delen van het signaal en verhoogt het volume van de zachtste delen)",
|
||||||
|
"name": "Audiocompressor"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Maakt de navigatiebalk transparant en wazig",
|
||||||
|
"name": "Vervagen Navigatiebalk"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Omzeil de leeftijdsverificatie van YouTube",
|
||||||
|
"name": "Leeftijdsbeperkingen Omzeilen"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Ondertitelkeuze voor YouTube Music-audiotracks",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Automatisch de laatst gebruikte ondertitel selecteren",
|
||||||
|
"disable-captions": "Standaard geen ondertitels"
|
||||||
|
},
|
||||||
|
"name": "Ondertitelkeuze",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Huidige ondertitel taal: {{language}}",
|
||||||
|
"none": "Geen",
|
||||||
|
"title": "Selecteer ondertitel taal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Open ondertitelkeuze"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Stel de zijbalk altijd in op compacte modus",
|
||||||
|
"name": "Compacte Zijbalk"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Vervagen tussen nummers",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Geavanceerd"
|
||||||
|
},
|
||||||
|
"name": "Crossfade [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Fade-in-duur (ms)",
|
||||||
|
"fade-out-duration": "Fade-out-duur (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Vervagingschaal",
|
||||||
|
"linear": "Lineair",
|
||||||
|
"logarithmic": "Logaritmisch"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Vervagen N seconden voor het einde"
|
||||||
|
},
|
||||||
|
"title": "Crossfade-opties"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Zorgt ervoor dat nummers starten in 'gepauzeerde' modus",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Alleen toepassen bij opstarten"
|
||||||
|
},
|
||||||
|
"name": "Automatisch afspelen uitschakelen"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Geprobeerd verbinding te maken met een actieve verbinding",
|
||||||
|
"connected": "Verbonden met Discord",
|
||||||
|
"disconnected": "Verbinding met Discord verbroken"
|
||||||
|
},
|
||||||
|
"description": "Laat je vrienden zien waar je naar luistert met Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Automatisch opnieuw verbinden",
|
||||||
|
"clear-activity": "Activiteit wissen",
|
||||||
|
"clear-activity-after-timeout": "Activiteit na time-out wissen",
|
||||||
|
"connected": "Verbonden",
|
||||||
|
"disconnected": "Verbinding verbroken",
|
||||||
|
"hide-duration-left": "Verberg resterende tijd",
|
||||||
|
"hide-github-button": "GitHub-knop verbergen",
|
||||||
|
"play-on-youtube-music": "Afspelen op YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Inactiviteitstime-out instellen"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Voer inactiviteitstime-out in seconden in:",
|
||||||
|
"title": "Inactiviteitstime-out instellen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Oke"
|
||||||
|
},
|
||||||
|
"message": "Excuses, de download is mislukt…",
|
||||||
|
"title": "Er is een fout opgetreden tijdens het downloaden!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Oké"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} nummers)",
|
||||||
|
"message": "Afspeellijst \"{{playlistTitle}}\" aan het downloaden",
|
||||||
|
"title": "Download is gestart"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Converteren: {{percent}}%",
|
||||||
|
"converting": "Converteren…",
|
||||||
|
"done": "Gereed: {{filePath}}",
|
||||||
|
"download-info": "{{artist}} - {{title}} ([{{videoId}}) aan het downloaden",
|
||||||
|
"download-progress": "Downloaden: {{percent}}%",
|
||||||
|
"downloading": "Aan het downloaden…",
|
||||||
|
"downloading-counter": "{{current}}/{{total}} aan het downloaden…",
|
||||||
|
"downloading-playlist": "Afspeellijst \"{{playlistTitle}}\" {{playlistId}} aan het downloaden ({{playlistSize}} liederen)",
|
||||||
|
"error-while-downloading": "Er is een fout opgetreden tijdens het downloaden van \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "De map \"{{playlistFolder}}\" bestaat al",
|
||||||
|
"getting-playlist-info": "Afspeellijst informatie ophalen…",
|
||||||
|
"loading": "Laden…",
|
||||||
|
"playlist-has-only-one-song": "Afspeellijst heeft maar 1 item, item direct aan het downloaden",
|
||||||
|
"playlist-id-not-found": "Geen playlist ID gevonden",
|
||||||
|
"playlist-is-empty": "Afspeellijst is leeg",
|
||||||
|
"playlist-is-mix-or-private": "Er is een fout opgetreden tijdens het ophalen van de afspeellijst informatie: zorg ervoor dat het geen verborgen of \"Mixed for you\" afspeellijst is\n\n{{error}}",
|
||||||
|
"preparing-file": "Bestand voorbereiden…",
|
||||||
|
"saving": "Opslaan…",
|
||||||
|
"trying-to-get-playlist-id": "Proberen om het afspeellijst ID op te halen: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Video niet gevonden",
|
||||||
|
"writing-id3": "ID3 tags aan het schrijven…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Download MP3 / bron-audio rechtstreeks vanuit de interface",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Kies de downloadmap",
|
||||||
|
"download-playlist": "Afspeellijst downloaden",
|
||||||
|
"presets": "Voorinstellingen",
|
||||||
|
"skip-existing": "Bestaande bestanden overslaan"
|
||||||
|
},
|
||||||
|
"name": "Downloader",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Kan de voortgang niet bijwerken"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Download"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Maakt de volumeschuif exponentieel zodat het gemakkelijker is om lagere volumes te selecteren.",
|
||||||
|
"name": "Exponentieel Volume"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Geeft menubalken een chique, donkere of albumkleurige uitstraling",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Verberg DOM-vensterbedieningselementen"
|
||||||
|
},
|
||||||
|
"name": "In-App menu"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Voegt ondersteuning voor Lumia Stream toe",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Voegt tekstondersteuning toe voor de meeste nummers",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Geromaniseerde Teksten"
|
||||||
|
},
|
||||||
|
"name": "Lyrics Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Teksten opgehaald voor Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Deel een afspeellijst met anderen. Wanneer de host een nummer afspeelt, hoort iedereen hetzelfde nummer",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Voer Host-ID in"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Opslaan",
|
||||||
|
"track-source": "Bron van nummers",
|
||||||
|
"unknown-user": "Onbekende gebruiker"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Host-ID kopiëren",
|
||||||
|
"close": "Sluit Music Samen",
|
||||||
|
"connected-users": "Verbonden gebruikers",
|
||||||
|
"disconnect": "Music Samen verbreken",
|
||||||
|
"empty-user": "Geen verbonden gebruikers",
|
||||||
|
"host": "Music Samen Host",
|
||||||
|
"join": "Voeg Music Samen toe",
|
||||||
|
"permission": {
|
||||||
|
"all": "Gasten toestaan de afspeellijst en speler te bedienen",
|
||||||
|
"host-only": "Alleen de host kan de afspeellijst en speler bedienen",
|
||||||
|
"playlist": "Gasten toestaan de afspeellijst te bedienen"
|
||||||
|
},
|
||||||
|
"set-permission": "Bedieningsmachtiging wijzigen",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Verbroken",
|
||||||
|
"guest": "Verbonden als Gast",
|
||||||
|
"host": "Verbonden als Host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Samen [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Toevoegen van nummer mislukt",
|
||||||
|
"closed": "Music Samen gesloten",
|
||||||
|
"disconnected": "Music Samen verbroken",
|
||||||
|
"host-failed": "Hosten van Music Samen mislukt",
|
||||||
|
"id-copied": "Host-ID gekopieerd naar klembord",
|
||||||
|
"id-copy-failed": "Kopiëren van Host-ID naar klembord mislukt",
|
||||||
|
"join-failed": "Aansluiten bij Music Samen mislukt",
|
||||||
|
"joined": "Music Samen toegetreden",
|
||||||
|
"permission-changed": "Music Samen machtiging gewijzigd naar \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Verwijderen van nummer mislukt",
|
||||||
|
"user-connected": "{{name}} heeft Music Samen toegetreden",
|
||||||
|
"user-disconnected": "{{name}} heeft Music Samen verlaten"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Volgende/Vorige navigatiepijlen rechtstreeks geïntegreerd in de interface, zoals in je favoriete browser",
|
||||||
|
"name": "Navigatie"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Verwijder Google aanmeldknoppen en -links uit de interface",
|
||||||
|
"name": "Geen Google Aanmelding"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Toont een melding wanneer een nummer begint te spelen (interactieve meldingen zijn beschikbaar op Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Interactieve Meldingen",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Interactieve instellingen",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Verberg tekst op de knop",
|
||||||
|
"refresh-on-play-pause": "Herlaad bij het afspelen/pauzeren"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unpause-notification": "Laat een notificatie zijn bij het depauzeren"
|
||||||
|
},
|
||||||
|
"name": "Meldingen"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Altijd bovenaan",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Sneltoets",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Sneltoets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Sla schermpositie op",
|
||||||
|
"save-window-size": "Sla schermgrootte op"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Voeg een visuele equalizer toe",
|
||||||
|
"name": "Visualisator"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Usuń przycisk subskrypcji premium",
|
"remove-upgrade-button": "Usuń przycisk subskrypcji premium",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Anuluj",
|
||||||
|
"remove": "Usuń"
|
||||||
|
},
|
||||||
|
"remove-theme": "Czy na pewno chcesz usunąć niestandardowy motyw?",
|
||||||
|
"remove-theme-message": "Spowoduje to usunięcie niestandarowego motywu"
|
||||||
|
},
|
||||||
"label": "Motyw",
|
"label": "Motyw",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Importuj własny plik CSS",
|
"import-css-file": "Importuj własny plik CSS",
|
||||||
@ -212,6 +220,14 @@
|
|||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Stosuje dynamiczny motyw i efekty wizualne w oparciu o paletę kolorów albumu",
|
"description": "Stosuje dynamiczny motyw i efekty wizualne w oparciu o paletę kolorów albumu",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Intensywność koloru",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "Motyw kolorów albumu"
|
"name": "Motyw kolorów albumu"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
@ -432,7 +448,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"music-together": {
|
"music-together": {
|
||||||
"description": "Pozwala na udostępnianie listy odtwarzania w taki sposób, że osoby po wejściu w link słuchają tego samego utworu co host (jeżeli słucha jej z owej listy odtwarzania)",
|
"description": "Pozwala na udostępnianie listy odtwarzania z możliwością słuchania tego samego utworu co host",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"enter-host": "Wpisz ID hosta"
|
"enter-host": "Wpisz ID hosta"
|
||||||
},
|
},
|
||||||
@ -447,7 +463,7 @@
|
|||||||
"connected-users": "Połączeni użytkownicy",
|
"connected-users": "Połączeni użytkownicy",
|
||||||
"disconnect": "Rozłącz z hosta",
|
"disconnect": "Rozłącz z hosta",
|
||||||
"empty-user": "Brak połączonych użytkowników",
|
"empty-user": "Brak połączonych użytkowników",
|
||||||
"host": "Host słuchania razem",
|
"host": "Udostępnij tą listę odtwarzania",
|
||||||
"join": "Połącz z hostem",
|
"join": "Połącz z hostem",
|
||||||
"permission": {
|
"permission": {
|
||||||
"all": "Połączeni użytkownicy mają kontrolę nad listą odtwarzania oraz playerem",
|
"all": "Połączeni użytkownicy mają kontrolę nad listą odtwarzania oraz playerem",
|
||||||
@ -569,6 +585,39 @@
|
|||||||
"description": "Umożliwia zmianę jakości wideo za pomocą przycisku na nakładce wideo",
|
"description": "Umożliwia zmianę jakości wideo za pomocą przycisku na nakładce wideo",
|
||||||
"name": "Zmieniacz jakości wideo"
|
"name": "Zmieniacz jakości wideo"
|
||||||
},
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Umożliwia scrobbling utworów do m.in. last.fm lub Listenbrainz",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Podczas autoryzowania z last.fm wystąpił błąd.\nSchowaj pop-up aż do następnego uruchomienia.",
|
||||||
|
"title": "Podczas autoryzowania wystąpił błąd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Ustawienia API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Podaj token użytkownika ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Scrobbluj pozostałe multimedia"
|
||||||
|
},
|
||||||
|
"name": "Scrobblowanie",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "klucz API Last.fm",
|
||||||
|
"api-secret": "Sekretny klucz Last.fm API (\"secret key\")"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Podaj swój token użytkownika ListenBrainz:",
|
||||||
|
"title": "Token ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"description": "Umożliwia ustawienie globalnych skrótów klawiszowych do odtwarzania (odtwarzanie/pauza/następny/poprzedni) + wyłączanie OSD multimediów poprzez zastąpienie klawiszy multimediów, włączając kombinację klawiszy Ctrl/CMD + F w celu wyszukiwania, obsługę Linux MPRIS dla klawiszy multimediów oraz niestandardowe skróty klawiszowe dla zaawansowanych użytkowników",
|
"description": "Umożliwia ustawienie globalnych skrótów klawiszowych do odtwarzania (odtwarzanie/pauza/następny/poprzedni) + wyłączanie OSD multimediów poprzez zastąpienie klawiszy multimediów, włączając kombinację klawiszy Ctrl/CMD + F w celu wyszukiwania, obsługę Linux MPRIS dla klawiszy multimediów oraz niestandardowe skróty klawiszowe dla zaawansowanych użytkowników",
|
||||||
"menu": {
|
"menu": {
|
||||||
|
|||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Remover botão upgrade",
|
"remove-upgrade-button": "Remover botão upgrade",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"remove": "Remover"
|
||||||
|
},
|
||||||
|
"remove-theme": "Você tem certeza que quer remover o tema customizado?",
|
||||||
|
"remove-theme-message": "Isso removerá o tema customizado"
|
||||||
|
},
|
||||||
"label": "Tema",
|
"label": "Tema",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Importar arquivo CSS personalizado",
|
"import-css-file": "Importar arquivo CSS personalizado",
|
||||||
@ -207,11 +215,19 @@
|
|||||||
"name": "Bloqueador de anúncios"
|
"name": "Bloqueador de anúncios"
|
||||||
},
|
},
|
||||||
"album-actions": {
|
"album-actions": {
|
||||||
"description": "Adiciona os botões Gostei e Não Gostei para ser aplicado a todas as músicas em uma lista de reprodução ou álbum.",
|
"description": "Adiciona os botões Anular Rejeição, Não Gostei, Gostei e Anular o Gosto para ser aplicado a todas as músicas de uma lista de reprodução ou álbum",
|
||||||
"name": "Ações no álbum"
|
"name": "Ações no álbum"
|
||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum",
|
"description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Rácio de mistura das cores",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "Proporção"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "Tema de cores do álbum"
|
"name": "Tema de cores do álbum"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
@ -569,8 +585,41 @@
|
|||||||
"description": "Permite alterar a qualidade do vídeo com um botão na sobreposição de vídeo",
|
"description": "Permite alterar a qualidade do vídeo com um botão na sobreposição de vídeo",
|
||||||
"name": "Trocador de qualidade do vídeo"
|
"name": "Trocador de qualidade do vídeo"
|
||||||
},
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Adicionar suporte para scrobbling (Last.fm, ListenBrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Falha ao autenticar com a Last.fm\nOculte o pop-up até a próxima reinicialização.",
|
||||||
|
"title": "Falha na autenticação"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Configurações de API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Insira o token de utilizador ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Scrobble outros mídia"
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Chave de API Last.fm",
|
||||||
|
"api-secret": "Segredo da API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Insira seu token de usuário do ListenBrainz:",
|
||||||
|
"title": "Token ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"description": "Permite definir teclas de atalho globais para reprodução (reproduzir/pausar/próximo/anterior) e desligar o OSD de mídia substituindo as teclas de mídia, ativando Ctrl/CMD + F para pesquisar, ativando o suporte Linux MPRIS para teclas de mídia e teclas de atalho personalizadas para usuários avançados.",
|
"description": "Permite definir teclas de atalho globais para reprodução (reproduzir/pausar/próximo/anterior) e desligar o OSD de mídia substituindo as teclas de mídia, ativando Ctrl/CMD + F para pesquisar, ativando o suporte Linux MPRIS para teclas de mídia e teclas de atalho personalizadas para usuários avançados",
|
||||||
"menu": {
|
"menu": {
|
||||||
"override-media-keys": "Substituir teclas de mídia",
|
"override-media-keys": "Substituir teclas de mídia",
|
||||||
"set-keybinds": "Definir controles globais de música"
|
"set-keybinds": "Definir controles globais de música"
|
||||||
|
|||||||
690
src/i18n/resources/ro.json
Normal file
690
src/i18n/resources/ro.json
Normal file
@ -0,0 +1,690 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Nu s-a reusit executarea plugin-ului {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin-ul {{pluginName}}::{{contextName}} s-a executat in {{ms}} ms",
|
||||||
|
"initialize-failed": "Initializarea plugin-ului \"{{pluginName}}\" a esuat",
|
||||||
|
"load-all": "Se incarca toate plugin-urile",
|
||||||
|
"load-failed": "Esec la incarcarea plugin-ului \"{{pluginName}}\"",
|
||||||
|
"loaded": "Plugin-ul \"{{pluginName}}\" s-a incarcat",
|
||||||
|
"unload-failed": "Esec la oprirea plugin-ului \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Plugin-ul \"{{pluginName}}\" s-a terminat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "ro",
|
||||||
|
"local-name": "Română",
|
||||||
|
"name": "Romanian"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "S-a terminat incarcarea. Panoul de developer e deschis"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n incarcat"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Comanda primita prin protocol: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Fisierul CSS \"{{cssFile}}\" nu exista, se ignora"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Eroare, procesul nu raspunde\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Se sterge cache-ul aplicatiei"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Fereastra a incercat sa fie randata in afara ecranului, marimeaFerestrei={{windowSize}}, marimeaEcranului={{displaySize}}, pozitia={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Meniul este ascuns, folositi tasta 'Alt' pentru a-l face sa apara (sau tasta 'Esc' daca folositi meniul din aplicatie)",
|
||||||
|
"message": "Ascunderea meniului este activata",
|
||||||
|
"title": "Ascunderea meniului activata"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Mai tarziu",
|
||||||
|
"restart-now": "Reporneste acum"
|
||||||
|
},
|
||||||
|
"detail": "Plugin-ul \"{{pluginName}}\" necesita o repornire pentru a intra in efect",
|
||||||
|
"message": "Pugin-ul \"{{pluginName}}\" trebuie repornit",
|
||||||
|
"title": "Repornire necesara"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Iesi",
|
||||||
|
"relaunch": "Reporneste",
|
||||||
|
"wait": "Asteapta"
|
||||||
|
},
|
||||||
|
"detail": "Ne cerem scuze pentru incovenient! va rugam alegeti ce doriti sa faceti:",
|
||||||
|
"message": "Applicatia nu raspunde",
|
||||||
|
"title": "Fereastra nu raspunde"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Dezactiveaza actualizarile",
|
||||||
|
"download": "Descarca",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "O noua versiune este disponibila si poate fi descarcata pe {{downloadLink}}",
|
||||||
|
"message": "O noua versiune este disponibila",
|
||||||
|
"title": "Actualizare disponibila"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Despre",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigatie",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Copiaza URL-ul actual",
|
||||||
|
"go-back": "Mergi inapoi",
|
||||||
|
"go-forward": "Mergi inainte",
|
||||||
|
"quit": "Iesi",
|
||||||
|
"restart": "Reporneste aplicatia"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Setari",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Setari avansate",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Reseteaza cache-ul aplicatiei la pornire",
|
||||||
|
"disable-hardware-acceleration": "Dezactiveaza acceleratia hardware",
|
||||||
|
"edit-config-json": "Editeaza config,json",
|
||||||
|
"override-user-agent": "Suprascrie User-Agent-ul",
|
||||||
|
"restart-on-config-changes": "Reporneste la modificarea configuratiei",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Seteaza proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Introduceti adresa proxy: (lasati liber pentru a dezactiva)",
|
||||||
|
"placeholder": "Exemplu: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Seteaza proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Deschide uneltele de dezvoltator"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Mereu deasupra",
|
||||||
|
"auto-update": "Actualizare automata",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Meniul va fi ascuns la urmatoarea pornire, folositi [Alt] pentru a-l face sa apara (sau ghilimea intoarsa [`] daca folositi meniul applicatiei)",
|
||||||
|
"title": "Ascunderea meniului pornita"
|
||||||
|
},
|
||||||
|
"label": "Ascunde meniul"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Limba va fi schimbata dupa repornire",
|
||||||
|
"title": "Limba actualizata"
|
||||||
|
},
|
||||||
|
"label": "Limba",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Vrei sa ajuti la traducere? Apasa aici"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Continua ultimul cantec ascultat cand porneste aplicatia",
|
||||||
|
"single-instance-lock": "Oprirea deschiderii mai multor instante",
|
||||||
|
"start-at-login": "Incepe la autentificare",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Pagina de pornire",
|
||||||
|
"unset": "Deselectat"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Tray",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Dezactivat",
|
||||||
|
"enabled-and-hide-app": "Activeaza si ascunde fereastra aplicatiei",
|
||||||
|
"enabled-and-show-app": "Activeaza si arata fereastra aplicatiei",
|
||||||
|
"play-pause-on-click": "Start/Pauza la click"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Optimizari vizuale",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Default",
|
||||||
|
"force-show": "Forteaza randarea",
|
||||||
|
"hide": "Ascunde",
|
||||||
|
"label": "Butoane de like"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Elimina butonul de upgrade",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Importa fisiere CSS proprii",
|
||||||
|
"no-theme": "Fara tema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Activat",
|
||||||
|
"label": "Plugins",
|
||||||
|
"new": "NOU"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Aspect",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Reimprospatare fortata",
|
||||||
|
"reload": "Reimprospateaza",
|
||||||
|
"reset-zoom": "Marimea actuala",
|
||||||
|
"toggle-fullscreen": "Porneste Full Screen",
|
||||||
|
"zoom-in": "Mareste",
|
||||||
|
"zoom-out": "Micsoreaza"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Urmatorul",
|
||||||
|
"play-pause": "Reda/Pauza",
|
||||||
|
"previous": "Anteriorul",
|
||||||
|
"quit": "Iesi",
|
||||||
|
"restart": "Reporneste aplicatia",
|
||||||
|
"show": "Arata fereastra",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Blocheaza toate reclamele si trackers",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Blocator"
|
||||||
|
},
|
||||||
|
"name": "Blocator de reclame"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Adauga butoane pentru Undislike, Like si Unlike pentru toate piesele dintr-un playlist sau album",
|
||||||
|
"name": "Actiuni pentru album"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Aplica o tema dinamica si efecte vizuale bazate pe paleta de culori a albumului",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Raportul amestecului de culori",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Tema de culori a albumului"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Aplica un efect de iluminare, aplicand culori preluate din video pe fundalul ecranului",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Cantitatea de blur",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pixeli"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buffer",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Opacitate",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Calitate",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} pixeli"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Marime",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Fluenta tranzitiei",
|
||||||
|
"submenu": {
|
||||||
|
"during": "In timpul {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Foloseste fullscreen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Mod ambiental"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Aplica compresie pe audio (scade volumul partilor cele mai sonore si creste volumul partilor mai putin sonore)",
|
||||||
|
"name": "Compresor audio"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Fa bara de navigare semi-transparenta",
|
||||||
|
"name": "Bara de naviagtie semi-transparenta"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Treci peste verificarea de varsta a YouTube-ului",
|
||||||
|
"name": "Ignora restrictiile de varsta"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Selector de subtitrari pentru piesele audio de pe YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Selecteaza automat ultima subtitrare folosita",
|
||||||
|
"disable-captions": "Fara subtitrari by default"
|
||||||
|
},
|
||||||
|
"name": "Selector de subtitrari",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Limba curenta a subtitrarilor: {{language}}",
|
||||||
|
"none": "Niciuna",
|
||||||
|
"title": "Alege limba subtitrarilor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Deschide selectorul de subtitrari"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Pastreaza bara laterala mereu in modul compact",
|
||||||
|
"name": "Bara laterala compacta"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Tranzitioneaza intre cantece",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Avansat"
|
||||||
|
},
|
||||||
|
"name": "Tranzitie [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Durata tranzitie de inceput (ms)",
|
||||||
|
"fade-out-duration": "Durata tranzitie de sfarsit (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Scala tranzitiei",
|
||||||
|
"linear": "Linear",
|
||||||
|
"logarithmic": "Logaritmic"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Tranzitie N secunde inainte de final"
|
||||||
|
},
|
||||||
|
"title": "Optiuni de tranzitie"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Fa cantecul sa inceapa in modul \"pauza\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Se aplica doar la pornirea aplicatiei"
|
||||||
|
},
|
||||||
|
"name": "Dezactiveaza redarea automata"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "S-a incercat conectarea cu o conexiune activa",
|
||||||
|
"connected": "Conectat la Discord",
|
||||||
|
"disconnected": "Deconectat de la Discord"
|
||||||
|
},
|
||||||
|
"description": "Arata-le prietenilor ce asculti cu Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Reconectare automata",
|
||||||
|
"clear-activity": "Sterge activitatea",
|
||||||
|
"clear-activity-after-timeout": "Sterge activitatea dupa timeout",
|
||||||
|
"connected": "Conectat",
|
||||||
|
"disconnected": "Deconectat",
|
||||||
|
"hide-duration-left": "Ascunde timpul ramas",
|
||||||
|
"hide-github-button": "Ascunde butonul cu link-ul GitHub",
|
||||||
|
"play-on-youtube-music": "Reda pe YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Seteaza intervalul de inactivitate"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Introduceti perioada de inactivitate dorita in secunde:",
|
||||||
|
"title": "Seteaza timpul de inactivitate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"message": "Argh! Scuze, descarcarea a esuat…",
|
||||||
|
"title": "Eroare la descarcare!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} cantece)",
|
||||||
|
"message": "Se descarca Playlist-ul {{playlistTitle}}",
|
||||||
|
"title": "Descarcarea a inceput"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Conversie: {{percent}}%",
|
||||||
|
"converting": "Se converteste…",
|
||||||
|
"done": "Descarcat: {{filePath}}",
|
||||||
|
"download-info": "Se descarca {{artist}} -{{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Se descarca: {{percent}}%",
|
||||||
|
"downloading": "Se descarca…",
|
||||||
|
"downloading-counter": "Se descarca {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Se descarca playlist-ul \"{{playlistTitle}}\" - {{playlistSize}} piese ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Eroare la descarcarea piesei \"{{author}} - {{title}}\":{{error}}",
|
||||||
|
"folder-already-exists": "Folderul {{playlistFolder}} exista deja",
|
||||||
|
"getting-playlist-info": "Se aduna informatiile despre playlist…",
|
||||||
|
"loading": "Se incarca…",
|
||||||
|
"playlist-has-only-one-song": "Playlist-ul are doar un element, acesta va fi descarcat direct",
|
||||||
|
"playlist-id-not-found": "Niciun ID al playlist-ului nu a fost gasit",
|
||||||
|
"playlist-is-empty": "Playlist-ul este gol",
|
||||||
|
"playlist-is-mix-or-private": "Eroare la colectarea informatiilor despre playlist: asigurati-va ca nu este privat sau un playlist \"Mixed for you\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Se pregateste fisierul…",
|
||||||
|
"saving": "Se salveaza…",
|
||||||
|
"trying-to-get-playlist-id": "Se incearca obtinerea ID-ului playlist-ului: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Video-ul nu a fost gasit",
|
||||||
|
"writing-id3": "Se scriu tag-urile ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Descarca MP3 / sursa audio direct din interfata",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Alege folderul de descarcari",
|
||||||
|
"download-playlist": "Descarca playlist-ul",
|
||||||
|
"presets": "Setari implicite",
|
||||||
|
"skip-existing": "Treci peste fisierele existente"
|
||||||
|
},
|
||||||
|
"name": "Downloader",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Nu se poate actualiza progresul"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Descarca"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Fa slider-ul de volum exponential pentru a fi mai usor de selectat volumuri reduse.",
|
||||||
|
"name": "Volum exponential"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Ofera barelor de meniu un aspect extravagant, intunecat sau de culoarea albumului",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Ascunde controalele ferestrei DOM"
|
||||||
|
},
|
||||||
|
"name": "Meniul aplicatiei"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Adauga asistenta pentru Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Adauga versuri pentru majoritatea cantecelor",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Versuri romantizate"
|
||||||
|
},
|
||||||
|
"name": "Lyrics Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Versuri preluate de pe Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Impartaseste playlist-ul cu altii. Cand gazda va pune o piesa, toti ceilalti vor auzi acelasi cantec",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Introdu ID-ul host-ului"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Salveaza",
|
||||||
|
"track-source": "Sursa piesei",
|
||||||
|
"unknown-user": "Utilizator necunoscut"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Copiaza ID-ul host-ului",
|
||||||
|
"close": "Inchide Music Together",
|
||||||
|
"connected-users": "Utilizatori conectati",
|
||||||
|
"disconnect": "Deconecteaza Music Together",
|
||||||
|
"empty-user": "Niciun utilizator conectat",
|
||||||
|
"host": "Gazda Music Together",
|
||||||
|
"join": "Alatura-te Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "Permite invitatilor sa controleze playlist-ul si player-ul",
|
||||||
|
"host-only": "Doar gazda poate controla playlist-ul si player-ul",
|
||||||
|
"playlist": "Permite invitatilor controlul asupra playlist-ului"
|
||||||
|
},
|
||||||
|
"set-permission": "Schimba controlul permisiunilor",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Deconectat",
|
||||||
|
"guest": "Conectat ca invitat",
|
||||||
|
"host": "Conectat ca gazda"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Adaugarea piesei a esuat",
|
||||||
|
"closed": "Music Together inchis",
|
||||||
|
"disconnected": "Music Together deconectat",
|
||||||
|
"host-failed": "Nu s-a reusit gazduirea Music Together",
|
||||||
|
"id-copied": "ID-ul host-ului a fost copiat in clipboard",
|
||||||
|
"id-copy-failed": "Eroare la copierea ID-ului host-ului in clipboard",
|
||||||
|
"join-failed": "Nu s-a reusit alaturarea la Music Together",
|
||||||
|
"joined": "V-ati alaturat Music Together",
|
||||||
|
"permission-changed": "Permisiunile Music Together s-au schimbat la \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Eroare la indepartarea cantecului",
|
||||||
|
"user-connected": "{{name}} s-a alaturat la Music Together",
|
||||||
|
"user-disconnected": "{{name}} a parasit Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Sagetile pentru Urmatorul/Anteriorul integrate direct in interfata, ca in browser-ul tau preferat",
|
||||||
|
"name": "Navigatie"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Elimina butonul de autentificare Google si link-urile din interfata",
|
||||||
|
"name": "Nicio autentificare Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Afiseaza o notificare cand incepe sa cante o piesa (notificarile interactive sunt disponibile pe Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Notificari interactive",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Setari interactive",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Ascunde textul butoanelor",
|
||||||
|
"refresh-on-play-pause": "Reimprospateaza la Reda/Pauza",
|
||||||
|
"tray-controls": "Deschide/Inchide la apasarea icnoitei pentru meniul Tray"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Prioritatea notificarilor",
|
||||||
|
"toast-style": "Stilul notificarilor",
|
||||||
|
"unpause-notification": "Arata notificarile la pauza"
|
||||||
|
},
|
||||||
|
"name": "Notificari"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Permite sa schimbi aplicatie la modul picture-in-picture",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Mereu deasupra",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Scurtaturi pe tastatura",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Scurtaturi din taste"
|
||||||
|
},
|
||||||
|
"label": "Scurtaturi din taste pentru picture-in-picture",
|
||||||
|
"title": "Scurtatura Picture-in-picture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Salveaza pozitia ferestrei",
|
||||||
|
"save-window-size": "Salveaza marimea ferestrei",
|
||||||
|
"use-native-pip": "Foloseste PiP-ul nativ pentru broswer"
|
||||||
|
},
|
||||||
|
"name": "Picture-in-picture",
|
||||||
|
"templates": {
|
||||||
|
"button": "Picture-in-picture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Asculta rapid, asculta lent! Adauga un slider pentru viteza de redare a cantecului",
|
||||||
|
"name": "Viteza de redare",
|
||||||
|
"templates": {
|
||||||
|
"button": "Viteza"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Controleaza volumul precis folosind rotita mouse-ului/scurtaturi din tastatura, cu un HUD personalizat si incremente de volum personalizate",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Control cu tastele sageti locale",
|
||||||
|
"custom-volume-steps": "Seteaza incrementele de volum",
|
||||||
|
"global-shortcuts": "Scurtaturi de tastatura globale"
|
||||||
|
},
|
||||||
|
"name": "Volum precis",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Redu volumul audio",
|
||||||
|
"increase": "Creste volumul audio"
|
||||||
|
},
|
||||||
|
"label": "Alege combinatiile de taste globale pentru volumul audio:",
|
||||||
|
"title": "Combinatii globale de taste pentru volum"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Alege pasii de increment pentru volum audio",
|
||||||
|
"title": "Incremente de volum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Calitate actuala: {{quality}}",
|
||||||
|
"message": "Alegeti calitatea video:",
|
||||||
|
"title": "Alegeti calitatea video"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Permite schimbarea calitatii video cu un buton prezent peste video",
|
||||||
|
"name": "Modificator de calitate video"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Adauga asistenta pentru scrobbling (etc. last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Autentificarea cu Last.fm a esuat\nAscunde acest pop-up pana la urmatoarea repornire.",
|
||||||
|
"title": "Autentificare Esuata"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Setari pentru API-ul Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Introdu token-ul de utilizator ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Scrobble alte surse media"
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Cheia API Last.fm",
|
||||||
|
"api-secret": "Secret API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Introdu token-ul tau de utilizator ListenBrainz:",
|
||||||
|
"title": "Token-ul ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Permite setari globale pentru scurtaturi pe tastatura pentru playback (reda/pauza/urmatorul/anteriorul), pentru oprirea media OSD prin suprascriera tastelor media, pentru folosirea combinatiei Ctrl/CMD + F pentru a cauta, pentru asistenta Linux MPRIS pentru taste media si pentru scurtaturi perosnalizate pentru utilizatori avansati",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Suprascrie tastele media",
|
||||||
|
"set-keybinds": "Seteaza scurtaturile globale pentru cantece"
|
||||||
|
},
|
||||||
|
"name": "Scurtaturi (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Urmatorul",
|
||||||
|
"play-pause": "Reda / Pauza",
|
||||||
|
"previous": "Anteriorul"
|
||||||
|
},
|
||||||
|
"label": "Alege combinatia de taste globala pentru controlul cantecelor:",
|
||||||
|
"title": "Scurtaturi pe tastatura globale"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Sari peste cantecele disliked",
|
||||||
|
"name": "Treci peste cantecele disliked"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Treci automat peste sectiunile de liniste din cantece",
|
||||||
|
"name": "Treci peste liniste"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Treci automat peste partile non-muzicale precum intro/outro sau parti din video-ul catecului, cand nu se aude cantecul",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Controleaza redarea din Bara de Activitati Windows",
|
||||||
|
"name": "Control media in Bara de Activitate"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Adauga un widget TouchBar pentru utilizatorii macOS",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integrare cu plugin-ul OBS Tuna",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Adauga un buton ce schimba intre modurile Video/Cantec. se poate optional elimia complet optiunea video",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Aliniere",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Stanga",
|
||||||
|
"middle": "Mijloc",
|
||||||
|
"right": "Dreapta"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Forteaza eliminarea tab-ului video",
|
||||||
|
"mode": {
|
||||||
|
"label": "Mod",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Comutatoare personalizate",
|
||||||
|
"disabled": "Dezactivat",
|
||||||
|
"native": "Comutatoare native"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Comutator video",
|
||||||
|
"templates": {
|
||||||
|
"button": "Cantec"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Adauga un visualizer la player",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Tip de visualizer"
|
||||||
|
},
|
||||||
|
"name": "Visualizer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Убрать кнопку Youtube Premium",
|
"remove-upgrade-button": "Убрать кнопку Youtube Premium",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"remove": "Убрать"
|
||||||
|
},
|
||||||
|
"remove-theme": "Вы уверены, что хотите убрать пользовательскую тему?",
|
||||||
|
"remove-theme-message": "Это уберёт пользовательскую тему"
|
||||||
|
},
|
||||||
"label": "Тема",
|
"label": "Тема",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Импортировать кастомный CSS файл",
|
"import-css-file": "Импортировать кастомный CSS файл",
|
||||||
@ -491,7 +499,7 @@
|
|||||||
},
|
},
|
||||||
"no-google-login": {
|
"no-google-login": {
|
||||||
"description": "Убрать из интерфейса кнопки и ссылки для входа через Google",
|
"description": "Убрать из интерфейса кнопки и ссылки для входа через Google",
|
||||||
"name": "Нет входа в систему Google"
|
"name": "Без входа в систему Google"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"description": "Показывать уведомления о начале воспроизведения песни (интерактивные уведомления доступны в Windows)",
|
"description": "Показывать уведомления о начале воспроизведения песни (интерактивные уведомления доступны в Windows)",
|
||||||
@ -578,7 +586,15 @@
|
|||||||
"name": "Изменение качества видео"
|
"name": "Изменение качества видео"
|
||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Добавьте поддержку скробблинга (например, last.fm, Listenbrainz)",
|
"description": "Добавляет поддержку скробблинга (last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Не удалось войти с помощью Last.fm\nСкрыть сообщение до следующего запуска",
|
||||||
|
"title": "Ошибка аунтефикации"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Настройки API Last.fm"
|
"api-settings": "Настройки API Last.fm"
|
||||||
|
|||||||
21
src/i18n/resources/si.json
Normal file
21
src/i18n/resources/si.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "ප්ලගිනය ක්රියාත්මක කිරීමට අසමත් විය {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "ප්ලගිනය {{pluginName}}::{{contextName}} {{ms}}ms හිදී ක්රියාත්මක කරන ලදී",
|
||||||
|
"initialize-failed": "\"{{pluginName}}\" ප්ලගිනය ආරම්භ කිරීමට අසමත් විය",
|
||||||
|
"load-all": "සියලුම ප්ලගින පූරණය කරමින්",
|
||||||
|
"load-failed": "\"{{pluginName}}\" ප්ලගිනය පූරණය කිරීමට අසමත් විය",
|
||||||
|
"loaded": "ප්ලගිනය \"{{pluginName}}\" පූරණය කරන ලදී"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"dialog": {
|
||||||
|
"need-to-restart": {
|
||||||
|
"title": "නැවත ආරම්භ කිරීම අවශ්යයි"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
107
src/i18n/resources/sv.json
Normal file
107
src/i18n/resources/sv.json
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"language": {
|
||||||
|
"code": "sv",
|
||||||
|
"local-name": "Svenska",
|
||||||
|
"name": "Swedish"
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"navigation": {
|
||||||
|
"name": "Navigering"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"name": "Inget Google Login"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"name": "Notiser"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"menu": {
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Snabbkommando",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Snabbkommando"
|
||||||
|
},
|
||||||
|
"title": "Bild-I-Bild genväg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Bild-I-Bild",
|
||||||
|
"templates": {
|
||||||
|
"button": "Bild-i-bild"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"name": "Uppspelningshastighet",
|
||||||
|
"templates": {
|
||||||
|
"button": "Hasighet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Minska Volym",
|
||||||
|
"increase": "Öka Volym"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"title": "Volymsteg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Nuvarande kvalité: {{quality}}",
|
||||||
|
"message": "Välj Video Kvalité:",
|
||||||
|
"title": "Välj Video Kvalité"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API nyckel"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"title": "ListenBrainz token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Nästa",
|
||||||
|
"play-pause": "Spela / Pausa",
|
||||||
|
"previous": "Föregående"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"submenu": {
|
||||||
|
"left": "Vänster",
|
||||||
|
"middle": "Mitten",
|
||||||
|
"right": "Höger"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Inaktiverad"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Låt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,12 +4,12 @@
|
|||||||
"plugins": {
|
"plugins": {
|
||||||
"execute-failed": "ปลั๊กอิน {{pluginName}}::{{contextName}} ไม่สามารถทำงานได้",
|
"execute-failed": "ปลั๊กอิน {{pluginName}}::{{contextName}} ไม่สามารถทำงานได้",
|
||||||
"executed-at-ms": "ปลั๊กอิน {{pluginName}}::{{contextName}} ทำงานแล้วที่ {{ms}}ms",
|
"executed-at-ms": "ปลั๊กอิน {{pluginName}}::{{contextName}} ทำงานแล้วที่ {{ms}}ms",
|
||||||
"initialize-failed": "ไม่สามารถเริ่มต้นปลั๊กอิน \"{{pluginName}}\"",
|
"initialize-failed": "ไม่สามารถเริ่มปลั๊กอิน \"{{pluginName}}\"ได้",
|
||||||
"load-all": "กำลังโหลดปลั๊กอินทั้งหมด",
|
"load-all": "กำลังโหลดปลั๊กอินทั้งหมด",
|
||||||
"load-failed": "ไม่สามารถโหลดปลั๊กอิน \"{{pluginName}}\"",
|
"load-failed": "ไม่สามารถโหลดปลั๊กอิน \"{{pluginName}}\"ได้",
|
||||||
"loaded": "โหลดปลั๊กอิน \"{{pluginName}}\" แล้ว",
|
"loaded": "โหลดปลั๊กอิน \"{{pluginName}}\" เรียบร้อยแล้ว",
|
||||||
"unload-failed": "ล้มเหลวในการยกเลิกการโหลดปลั๊กอิน \"{{pluginName}}\"",
|
"unload-failed": "ไม่สามารถโหลดปลั๊กอิน \"{{pluginName}}\"ได้",
|
||||||
"unloaded": "ยกเลิกการโหลดปลั๊กอิน \"{{pluginName}}\" แล้ว"
|
"unloaded": "ยกเลิกโหลดปลั๊กอิน \"{{pluginName}}\" แล้ว"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -21,32 +21,51 @@
|
|||||||
"main": {
|
"main": {
|
||||||
"console": {
|
"console": {
|
||||||
"did-finish-load": {
|
"did-finish-load": {
|
||||||
"dev-tools": "การโหลดเสร็จสิ้น DevTools ได้ถูกเปิดแล้ว"
|
"dev-tools": "การโหลดเสร็จสิ้น. โหมดนักพัฒนาสามรถใช้งานได้แล้ว"
|
||||||
},
|
},
|
||||||
"i18n": {
|
"i18n": {
|
||||||
"loaded": "โหลด i18n แล้ว"
|
"loaded": "โหลด i18n แล้ว"
|
||||||
},
|
},
|
||||||
"second-instance": {
|
"second-instance": {
|
||||||
"receive-command": "คำสั่งที่ได้รับผ่านโปรโตคอล: \"{{command}}\""
|
"receive-command": "รับคำสั่งผ่านโปรโตคอล: \"{{command}}\""
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"css-file-not-found": "กำลังเพิกเฉยไฟล์ CSS \"{{cssFile}}\" เนื่องจากไม่มีอยู่"
|
"css-file-not-found": "ไม่พบไฟล์ CSS \"{{cssFile}}\" กำลังข้าม"
|
||||||
},
|
},
|
||||||
"unresponsive": {
|
"unresponsive": {
|
||||||
"details": "มีข้อผิดพลาดจากไม่การตอบสนอง!\n{{error}}"
|
"details": "พบข้อผิดพลาด!\n{{error}}"
|
||||||
},
|
},
|
||||||
"when-ready": {
|
"when-ready": {
|
||||||
"clearing-cache-after-20s": "กำลังล้างแคชของแอป"
|
"clearing-cache-after-20s": "กำลังล้างแคชแอป"
|
||||||
},
|
},
|
||||||
"window": {
|
"window": {
|
||||||
"tried-to-render-offscreen": "หน้าต่างพยายามแสดงผลเกินขนาดหน้าจอ windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
"tried-to-render-offscreen": "หน้าต่างพยายามแสดงผลเกินขนาดหน้าจอ windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "เมนูถูกซ่อนไว้ กด'Alt' เพื่อแสดงเมนู (หรือ 'Escape' หากอยู่ในเมนูแอป)",
|
||||||
|
"message": "การซ่อนเมนูถูกเปิดใช้งาน",
|
||||||
|
"title": "เปิดใช้งานการซ่อนเมนู"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "ภายหลัง",
|
||||||
|
"restart-now": "รีสตาร์ทตอนนี้"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" ปลั๊กอินต้องการการรีสตาร์ทเพื่อแสดงผล",
|
||||||
|
"message": "\"{{pluginName}}\" ต้องการรีสตาร์ท",
|
||||||
|
"title": "แนะนำให้รีสตาร์ท"
|
||||||
|
},
|
||||||
"unresponsive": {
|
"unresponsive": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"quit": "เลิก"
|
"quit": "ออก",
|
||||||
}
|
"relaunch": "เปิดใหม่",
|
||||||
|
"wait": "รอซักครู่"
|
||||||
|
},
|
||||||
|
"detail": "ขออภัยในความไม่สะดวก! โปรดเลือกสิ่งที่ต้องการจะทำ:",
|
||||||
|
"message": "แอปพลิเคชันไม่ตอบสนอง",
|
||||||
|
"title": "หน้าต่างไม่ตอบสนอง"
|
||||||
},
|
},
|
||||||
"update-available": {
|
"update-available": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@ -62,6 +81,7 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"about": "เกี่ยวกับ",
|
"about": "เกี่ยวกับ",
|
||||||
"navigation": {
|
"navigation": {
|
||||||
|
"label": "การนำทาง",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"copy-current-url": "คัดลอก URL ปัจจุบัน",
|
"copy-current-url": "คัดลอก URL ปัจจุบัน",
|
||||||
"go-back": "ก่อนหน้า",
|
"go-back": "ก่อนหน้า",
|
||||||
@ -79,22 +99,291 @@
|
|||||||
"auto-reset-app-cache": "รีเซตแอปแคชเมื่อเริ่มแอป",
|
"auto-reset-app-cache": "รีเซตแอปแคชเมื่อเริ่มแอป",
|
||||||
"disable-hardware-acceleration": "ปิดการใช้งานตัวเร่งประสิทธิภาพด้วยฮาร์ดแวร์",
|
"disable-hardware-acceleration": "ปิดการใช้งานตัวเร่งประสิทธิภาพด้วยฮาร์ดแวร์",
|
||||||
"edit-config-json": "แก้ไข config.json",
|
"edit-config-json": "แก้ไข config.json",
|
||||||
"override-user-agent": "แทนที่ User-Agent"
|
"override-user-agent": "แทนที่ User-Agent",
|
||||||
|
"restart-on-config-changes": "รีสตาร์ทเมื่อมีการเปลี่ยนแปลงคอนฟิก",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "ตั้งค่าพร็อกซี่",
|
||||||
|
"prompt": {
|
||||||
|
"label": "ใส่ที่อยู่พร็อกซี่: (ปล่อยให้ว่างเพื่อปิดใช้งาน)",
|
||||||
|
"placeholder": "ตัวอย่าง: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "ตั้งค่าพร็อกซี่"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "เปิด-ปิด DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "อยู่ด้านบนตลอดเวลา",
|
||||||
|
"auto-update": "อัปเดตอัตโนมัติ",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "เมนูจะถูกซ่อนในการเปิดครั้งถัดไป กด [Alt] เพื่อแสดงเมนู (หรือใช้ backtick [`] หากอยู่ในเมนูแอป)",
|
||||||
|
"title": "เปิดใช้งานการซ่อนเมนู"
|
||||||
|
},
|
||||||
|
"label": "ซ่อนเมนู"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "ภาษาจะเปลี่ยนหลังจากทำการรีสตาร์ท",
|
||||||
|
"title": "ภาษาถูกเปลี่ยนแล้ว"
|
||||||
|
},
|
||||||
|
"label": "ภาษา",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "ต้องการช่วยแปล? คลิกที่นี่"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "เล่นเพลงล่าสุดต่อ เมื่อแอปเริ่มต้น",
|
||||||
|
"single-instance-lock": "ล็อกการทำงานเพียงรายการเดียว",
|
||||||
|
"start-at-login": "เริ่มต้นที่การเข้าสู่ระบบ",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "หน้าเริ่มต้น",
|
||||||
|
"unset": "ยกเลิกการตั้งค่า"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "ถาดรายการ",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "ปิดการใช้งาน",
|
||||||
|
"enabled-and-hide-app": "เปิดใช้งานและซ่อนแอป",
|
||||||
|
"enabled-and-show-app": "เปิดใช้งานและแสดงแอป",
|
||||||
|
"play-pause-on-click": "เล่น/หยุดเล่น เมื่อคลิก"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "การปรับแต่งหน้าตาแอป",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "ค่าเริ่มต้น",
|
||||||
|
"force-show": "บังคับให้แสดง",
|
||||||
|
"hide": "ซ่อน",
|
||||||
|
"label": "ปุ่มถูกใจ"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "ลบปุ่มอัปเกรด",
|
||||||
|
"theme": {
|
||||||
|
"label": "ธีม",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "นำเข้าไฟล์ CSS ที่กำหนดเอง",
|
||||||
|
"no-theme": "ไม่มีธีม"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "เปิดใช้งาน",
|
||||||
|
"label": "ปลั๊กอิน",
|
||||||
|
"new": "ใหม่"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "มุมมอง",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "บังคับโหลดใหม่",
|
||||||
|
"reload": "โหลดใหม่",
|
||||||
|
"reset-zoom": "ขนาดจริง",
|
||||||
|
"toggle-fullscreen": "สลับเต็มหน้าจอ",
|
||||||
|
"zoom-in": "ซูมเข้า",
|
||||||
|
"zoom-out": "ซูมออก"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "ค่อไป",
|
||||||
|
"play-pause": "เล่น/พัก",
|
||||||
|
"previous": "ก่อนหน้า",
|
||||||
|
"quit": "ออก",
|
||||||
|
"restart": "รีสตาร์ทแอป",
|
||||||
|
"show": "แสดงหน้าต่าง",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "ยูทุปมิวสิค",
|
||||||
|
"with-song-info": "ยูทูปมิวสิค: {{artist}} - {{title}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "บล็อกโฆษณาและการติดตามทั้งหมดอย่างอัตโนมัติ",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "เครื่องมือบล็อก"
|
||||||
|
},
|
||||||
|
"name": "ตัวบล็อกโฆษณา"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "เพิ่ม ยกเลิกไม่ชอบ, ไม่ชอบ, 'ถูกใจ', และ 'ยกเลิกถูกใจ' เพื่อให้สามารถใช้งานกับเพลงทั้งหมดในรายการเพลงหรืออัลบั้ม",
|
||||||
|
"name": "การกระทำที่เกี่ยวกับอัลบั้ม"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "ใช้ธีมและเอฟเฟกต์ไดนามิก โดยขึ้นอยู่กับสีของอัลบั้ม",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "สัดส่วนการผสมสี",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}เปอร์เซ็น"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "ธีมสีของอัลบั้ม"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "เอฟเฟกต์แสงจะใช้สีอ่อนๆจากวีดิโอมาเป็พื้นหลังสกรีน",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "ระดับความเบลอ",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} พิกเซล"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "บัฟเฟอร์",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "ความโปร่งใส",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "คุณภาพ",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} พิกเซล"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "ขนาด",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "การเปลี่ยนแบบสมูท",
|
||||||
|
"submenu": {
|
||||||
|
"during": "ระหว่าง {{interpolationTime}} วินาที"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "ใช้โหมดเต็มหน้าจอ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "โหมดสภาพแวดล้อม"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "ใช้การบีบอัดเสียง (ลดระดับเสียงของส่วนที่ดังที่สุดของสัญญาณและเพิ่มระดับเสียงของส่วนที่เบาที่สุด)",
|
||||||
|
"name": "เครื่องมือบีบอัดเสียง"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "ทำให้แถบนำทางโปร่งแสงและเบลอ",
|
||||||
|
"name": "เบลอแถบนำทาง"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "ข้ามการตรวจสอบอายุของยูทูป",
|
||||||
|
"name": "ข้ามข้อจำกัดอายุ"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "ตัวเลือกคำบรรยายสำหรับเพลงในYoutube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "เลือกคำบรรยายที่ใช้ครั้งล่าสุดโดยอัตโนมัติ",
|
||||||
|
"disable-captions": "ไม่มีคำบรรยายเป็นค่าเริ่มต้น"
|
||||||
|
},
|
||||||
|
"name": "ตัวเลือกคำบรรยาย",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "ภาษาคำบรรยายปัจจุบัน: {{language}}",
|
||||||
|
"none": "ไม่มี",
|
||||||
|
"title": "เลือกภาษาคำบรรยาย"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "เปิดตัวเลือกคำบรรยาย"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "ตั้งค่าแถบข้างให้อยู่ในโหมดกระชับเสมอ",
|
||||||
|
"name": "แถบข้างแบบกระชับ"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "การเฟดเพลงระหว่างเพลง",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "ขั้นสูง"
|
||||||
|
},
|
||||||
|
"name": "การเฟดเพลง [เบต้า]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "ระยะเวลาการเฟดเข้าสู่ (มิลลิวินาที)",
|
||||||
|
"fade-out-duration": "ระยะเวลาการเฟดออก (มิลลิวินาที)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "การปรับขนาดของการเฟด",
|
||||||
|
"linear": "การเปลี่ยนแปลงเชิงเส้น",
|
||||||
|
"logarithmic": "การเปลี่ยนแปลงเชิงลอการิทึม"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "เฟดเพลง N วินาทีก่อนจบ"
|
||||||
|
},
|
||||||
|
"title": "ตัวเลือกเฟดเพลง"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "เริ่มเพลงในโหมดหยุด",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "ใช้เฉพาะเมื่อเริ่มต้น"
|
||||||
|
},
|
||||||
|
"name": "ปิดใช้งานการเล่นอัตโนมัติ"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "พยายามเชื่อมต่อกับการเชื่อมต่อที่ทำงานอยู่",
|
||||||
|
"connected": "เชื่อมต่อกับดิสคอร์ดแล้ว",
|
||||||
|
"disconnected": "ตัดการเชื่อมต่อออกจากดิสคอร์ด"
|
||||||
|
},
|
||||||
|
"description": "แสดงให้เพื่อนเห็นว่าคุณกำลังฟังอะไรด้วย Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "เชื่อมต่อใหม่โดยอัตโนมัติ",
|
||||||
|
"clear-activity": "ล้างกิจกรรม",
|
||||||
|
"clear-activity-after-timeout": "ล้างกิจกรรมหลังจากหมดเวลา",
|
||||||
|
"connected": "เชื่อมต่อแล้ว",
|
||||||
|
"disconnected": "ตัดการเชื่อมต่อ",
|
||||||
|
"hide-duration-left": "ซ่อนระยะเวลาที่เหลือ",
|
||||||
|
"hide-github-button": "ซ่อนปุ่มลิงก์ GitHub",
|
||||||
|
"play-on-youtube-music": "เล่นบนยูทูปมิวสุค",
|
||||||
|
"set-inactivity-timeout": "ตั้งระยะเวลาไม่มีกิจกรรม"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "ป้อนระยะเวลาไม่มีกิจกรรมเป็นวินาที:",
|
||||||
|
"title": "ตั้งระยะเวลาไม่มีกิจกรรม"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"downloader": {
|
"downloader": {
|
||||||
"backend": {
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "ตกลง"
|
||||||
|
},
|
||||||
|
"message": "อ๊ะ! ขออภัย ดาวน์โหลดล้มเหลว…",
|
||||||
|
"title": "มีข้อผิดพลาดในการดาวน์โหลด!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "ตกลง"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} เพลง)",
|
||||||
|
"message": "กำลังดาวน์โหลดเพลย์ลิสต์ {{playlistTitle}}",
|
||||||
|
"title": "เริ่มต้นการดาวน์โหลดแล้ว"
|
||||||
|
}
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
|
"conversion-progress": "การแปลง: {{percent}}%",
|
||||||
|
"converting": "กำลังแปลง…",
|
||||||
|
"done": "เสร็จสิ้น: {{filePath}}",
|
||||||
"download-info": "กำลังดาวน์โหลด {{artist}} - {{title}} [{{videoId}}",
|
"download-info": "กำลังดาวน์โหลด {{artist}} - {{title}} [{{videoId}}",
|
||||||
"download-progress": "ดาวน์โหลด: {{percent}}%",
|
"download-progress": "ดาวน์โหลด: {{percent}}%",
|
||||||
"downloading": "กำลังดาวน์โหลด…",
|
"downloading": "กำลังดาวน์โหลด…",
|
||||||
"downloading-counter": "กำลังดาวน์โหลด {{current}}/{{total}}…",
|
"downloading-counter": "กำลังดาวน์โหลด {{current}}/{{total}}…",
|
||||||
"downloading-playlist": "กำลังดาวน์โหลดเพลย์ลีสต์ \"{{playlistTitle}}\" - {{playlistSize}} เพลง ({{playlistId}})",
|
"downloading-playlist": "กำลังดาวน์โหลดเพลย์ลิสต์ \"{{playlistTitle}}\" - {{playlistSize}} เพลง ({{playlistId}})",
|
||||||
"error-while-downloading": "เกิดข้อผิดพลาดในการดาวน์โหลด \"{{author}} - {{title}}\": {{error}}",
|
"error-while-downloading": "เกิดข้อผิดพลาดในการดาวน์โหลด \"{{author}} - {{title}}\": {{error}}",
|
||||||
"folder-already-exists": "มีโฟลเดอร์ {{playlistFolder}} อยู่แล้ว",
|
"folder-already-exists": "มีโฟลเดอร์ {{playlistFolder}} อยู่แล้ว",
|
||||||
"getting-playlist-info": "กำลังรับข้อมูลเพลย์ลิสต์…",
|
"getting-playlist-info": "กำลังรับข้อมูลเพลย์ลิสต์…",
|
||||||
@ -114,6 +403,7 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"choose-download-folder": "เลือกโฟลเดอร์ดาวน์โหลด",
|
"choose-download-folder": "เลือกโฟลเดอร์ดาวน์โหลด",
|
||||||
"download-playlist": "ดาวน์โหลดเพลย์ลิสต์",
|
"download-playlist": "ดาวน์โหลดเพลย์ลิสต์",
|
||||||
|
"presets": "พรีเซ็ต",
|
||||||
"skip-existing": "ข้ามไฟล์ที่มีอยู่แล้ว"
|
"skip-existing": "ข้ามไฟล์ที่มีอยู่แล้ว"
|
||||||
},
|
},
|
||||||
"name": "ตัวดาวน์โหลด",
|
"name": "ตัวดาวน์โหลด",
|
||||||
@ -123,6 +413,67 @@
|
|||||||
"templates": {
|
"templates": {
|
||||||
"button": "ดาวน์โหลด"
|
"button": "ดาวน์โหลด"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "ทำให้ตัวเลือกความดังมีลักษณะเอ็กซ์โปเนนเชียล เพื่อให้ง่ายต่อการเลือกระดับความดังที่ต่ำลง",
|
||||||
|
"name": "ระดับเสียงแบบเอ็กซโปเนนเชียล"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "ให้เมนูบาร์ดูทันสมัย มืดหรือเป็นสีของอัลบั้มอย่างน่าสนใจ",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "ซ่อนตัวควบคุมหน้าต่าง DOM"
|
||||||
|
},
|
||||||
|
"name": "เมนูในแอป"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "เพิ่มการรองรับ ลูเมีย สตรีม",
|
||||||
|
"name": "ลูเมีย สตรีม [เบต้า]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "เพิ่มการสนับสนุนเนื้อเพลงสำหรับเพลงหลายๆ เพลง",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "เนื้อเพลงโรมันไรซ์"
|
||||||
|
},
|
||||||
|
"name": "เนื้อเพลงแบบอัจฉริยะ",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "ดึงข้อมูลเนื้อเพลงแบบอัจฉริยะแล้ว"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "แบ่งปันเพลย์ลิสต์กับผู้อื่น โดยเมื่อเจ้าภาพเล่นเพลง ทุกคนอื่นๆ จะได้ยินเพลงเดียวกัน",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "ป้อนรหัสโฮสต์"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "บันทึก",
|
||||||
|
"track-source": "แหล่งข้อมูลเพลง",
|
||||||
|
"unknown-user": "ผู้ใช้ที่ไม่รู้จัก"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "คัดลอกรหัสโฮสต์",
|
||||||
|
"close": "ปิด Music Together",
|
||||||
|
"connected-users": "ผู้ใช้ที่เชื่อมต่อ",
|
||||||
|
"disconnect": "ตัดการเชื่อมต่อ Music Together",
|
||||||
|
"empty-user": "ไม่มีผู้ใช้ที่เชื่อมต่อ",
|
||||||
|
"host": "โฮสต์ Music Together",
|
||||||
|
"join": "เข้าร่วม Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "อนุญาตให้แขกควบคุมเพลย์ลิสต์และเครื่องเล่น",
|
||||||
|
"host-only": "เฉพาะโฮสต์เท่านั้นที่สามารถควบคุมเพลย์ลิสต์และเครื่องเล่นได้",
|
||||||
|
"playlist": "อนุญาตให้แขกควบคุมเพลย์ลิสต์"
|
||||||
|
},
|
||||||
|
"set-permission": "เปลี่ยนการอนุญาตในการควบคุม",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "ตัดการเชื่อมต่อแล้ว",
|
||||||
|
"guest": "เชื่อมต่อเป็นแขก",
|
||||||
|
"host": "เชื่อมต่อเป็นโฮสต์"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [เบต้า]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "เพิ่มเพลงล้มเหลว",
|
||||||
|
"closed": "ปิด Music Together แล้ว"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Yükseltme düğmesini kaldır",
|
"remove-upgrade-button": "Yükseltme düğmesini kaldır",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "İptal",
|
||||||
|
"remove": "Kaldır"
|
||||||
|
},
|
||||||
|
"remove-theme": "Özel temayı kaldırmak istediğinizden emin misiniz?",
|
||||||
|
"remove-theme-message": "Bu işlem özel temayı kaldıracaktır"
|
||||||
|
},
|
||||||
"label": "Tema",
|
"label": "Tema",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Özel CSS dosyanı içeri aktar",
|
"import-css-file": "Özel CSS dosyanı içeri aktar",
|
||||||
@ -579,6 +587,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Listeleme desteği ekler (lastfm, listenbrainz ve benzeri)",
|
"description": "Listeleme desteği ekler (lastfm, listenbrainz ve benzeri)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Last.fm ile kimlik doğrulaması yapılamadı\nBir sonraki yeniden başlatmaya kadar açılır pencereyi gizle.",
|
||||||
|
"title": "Kimlik Doğrulama Başarısız"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Last.fm API Ayarları"
|
"api-settings": "Last.fm API Ayarları"
|
||||||
|
|||||||
@ -579,6 +579,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Додає підтримку скроблінгу (last.fm, Listenbrainz тощо)",
|
"description": "Додає підтримку скроблінгу (last.fm, Listenbrainz тощо)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Не вдалося автентифікуватися на Last.fm\nСховати до наступного запуску.",
|
||||||
|
"title": "Не вдалося автентифікуватися"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Налаштування API Last.fm"
|
"api-settings": "Налаштування API Last.fm"
|
||||||
|
|||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "Xóa nút nâng cấp",
|
"remove-upgrade-button": "Xóa nút nâng cấp",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "Hủy",
|
||||||
|
"remove": "Loại bỏ"
|
||||||
|
},
|
||||||
|
"remove-theme": "Bạn có chắc muốn loại bỏ chủ đề tùy chỉnh không?",
|
||||||
|
"remove-theme-message": "Tùy chọn này sẽ loại bỏ chủ đề tùy chỉnh"
|
||||||
|
},
|
||||||
"label": "Chủ đề",
|
"label": "Chủ đề",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Nhập tệp CSS tùy chỉnh",
|
"import-css-file": "Nhập tệp CSS tùy chỉnh",
|
||||||
@ -212,6 +220,14 @@
|
|||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Áp dụng chủ đề động và hiệu ứng hình ảnh dựa trên bảng màu của album",
|
"description": "Áp dụng chủ đề động và hiệu ứng hình ảnh dựa trên bảng màu của album",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Tỉ lệ trộn màu",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "Màu nền album"
|
"name": "Màu nền album"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
@ -498,7 +514,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"priority": "Ưu tiên thông báo",
|
"priority": "Ưu tiên thông báo",
|
||||||
"toast-style": "Kiểu toast",
|
"toast-style": "Kiểu thông báo bật lên",
|
||||||
"unpause-notification": "Hiển thị thông báo khi bỏ tạm dừng"
|
"unpause-notification": "Hiển thị thông báo khi bỏ tạm dừng"
|
||||||
},
|
},
|
||||||
"name": "Thông báo"
|
"name": "Thông báo"
|
||||||
@ -571,13 +587,22 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "Thêm hỗ trợ scrobbling (v.v. Last.fm, Listenbrainz)",
|
"description": "Thêm hỗ trợ scrobbling (v.v. Last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Không thể xác minh với \nẨn thông báo cho đến lần bật ứng dụng tiếp theo.",
|
||||||
|
"title": "Xác minh thất bại"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Cài đặt API Last.fm"
|
"api-settings": "Cài đặt API Last.fm"
|
||||||
},
|
},
|
||||||
"listenbrainz": {
|
"listenbrainz": {
|
||||||
"token": "Nhập mã người dùng ListenBrainz"
|
"token": "Nhập mã người dùng ListenBrainz"
|
||||||
}
|
},
|
||||||
|
"scrobble-other-media": "Scrobber nội dung khác"
|
||||||
},
|
},
|
||||||
"name": "Scrobbler",
|
"name": "Scrobbler",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
|
|||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "移除升级按钮",
|
"remove-upgrade-button": "移除升级按钮",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "取消",
|
||||||
|
"remove": "移除"
|
||||||
|
},
|
||||||
|
"remove-theme": "您确定要移除自定义主题?",
|
||||||
|
"remove-theme-message": "此操作将移除自定义主题"
|
||||||
|
},
|
||||||
"label": "主题",
|
"label": "主题",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "导入自定义 CSS 文件",
|
"import-css-file": "导入自定义 CSS 文件",
|
||||||
@ -579,6 +587,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "添加歌曲追踪支持(如 Last.fm 和 Listenbrainz)",
|
"description": "添加歌曲追踪支持(如 Last.fm 和 Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "与 Last.fm 认证时失败\n弹出窗口将在下次重启前隐藏。",
|
||||||
|
"title": "认证失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Last.fm API 设置"
|
"api-settings": "Last.fm API 设置"
|
||||||
|
|||||||
@ -69,9 +69,9 @@
|
|||||||
},
|
},
|
||||||
"update-available": {
|
"update-available": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"disable": "停用更新",
|
"disable": "停用新版本通知",
|
||||||
"download": "下載",
|
"download": "前往下載",
|
||||||
"ok": "確定"
|
"ok": "略過"
|
||||||
},
|
},
|
||||||
"detail": "新版本已經推出,你可以至 {{downloadLink}} 下載",
|
"detail": "新版本已經推出,你可以至 {{downloadLink}} 下載",
|
||||||
"message": "有新版本可用",
|
"message": "有新版本可用",
|
||||||
@ -83,7 +83,7 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"label": "導覽列",
|
"label": "導覽列",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"copy-current-url": "複製目前的網址",
|
"copy-current-url": "複製當前頁面的網址",
|
||||||
"go-back": "回到上一頁",
|
"go-back": "回到上一頁",
|
||||||
"go-forward": "回到下一頁",
|
"go-forward": "回到下一頁",
|
||||||
"quit": "退出",
|
"quit": "退出",
|
||||||
@ -112,7 +112,7 @@
|
|||||||
"toggle-dev-tools": "切換開發者人員工具"
|
"toggle-dev-tools": "切換開發者人員工具"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"always-on-top": "永遠顯示在最上層",
|
"always-on-top": "最上層顯示",
|
||||||
"auto-update": "自動更新",
|
"auto-update": "自動更新",
|
||||||
"hide-menu": {
|
"hide-menu": {
|
||||||
"dialog": {
|
"dialog": {
|
||||||
@ -128,22 +128,22 @@
|
|||||||
},
|
},
|
||||||
"label": "語言",
|
"label": "語言",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"to-help-translate": "想要協助翻譯?按一下這裡"
|
"to-help-translate": "想協助翻譯?按一下這裡"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resume-on-start": "應用啟動時繼續上次播放的歌曲",
|
"resume-on-start": "應用啟動時繼續上次播放的歌曲",
|
||||||
"single-instance-lock": "單視窗鎖定",
|
"single-instance-lock": "單實例模式",
|
||||||
"start-at-login": "開機時啟動",
|
"start-at-login": "開機時啟動",
|
||||||
"starting-page": {
|
"starting-page": {
|
||||||
"label": "啟動頁面",
|
"label": "啟動頁面",
|
||||||
"unset": "不指定"
|
"unset": "不指定"
|
||||||
},
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"label": "系統閘圖式",
|
"label": "系統匣",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"disabled": "已停用",
|
"disabled": "已停用",
|
||||||
"enabled-and-hide-app": "啟用並隱藏應用程式",
|
"enabled-and-hide-app": "啟用並最小化應用程式",
|
||||||
"enabled-and-show-app": "啟用並顯示應用程式",
|
"enabled-and-show-app": "啟用但持續顯示應用程式",
|
||||||
"play-pause-on-click": "點擊時播放/暫停"
|
"play-pause-on-click": "點擊時播放/暫停"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -158,6 +158,14 @@
|
|||||||
},
|
},
|
||||||
"remove-upgrade-button": "移除升級按鈕",
|
"remove-upgrade-button": "移除升級按鈕",
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"dialog": {
|
||||||
|
"button": {
|
||||||
|
"cancel": "取消",
|
||||||
|
"remove": "確定移除"
|
||||||
|
},
|
||||||
|
"remove-theme": "確定要移除自訂主題嗎?",
|
||||||
|
"remove-theme-message": "這將會移除自訂主題"
|
||||||
|
},
|
||||||
"label": "主題",
|
"label": "主題",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "匯入自訂 CSS 檔案",
|
"import-css-file": "匯入自訂 CSS 檔案",
|
||||||
@ -514,7 +522,7 @@
|
|||||||
"picture-in-picture": {
|
"picture-in-picture": {
|
||||||
"description": "允許應用程式切換至子母畫面模式",
|
"description": "允許應用程式切換至子母畫面模式",
|
||||||
"menu": {
|
"menu": {
|
||||||
"always-on-top": "永遠顯示在最上層",
|
"always-on-top": "最上層顯示",
|
||||||
"hotkey": {
|
"hotkey": {
|
||||||
"label": "快捷鍵",
|
"label": "快捷鍵",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
@ -579,6 +587,14 @@
|
|||||||
},
|
},
|
||||||
"scrobbler": {
|
"scrobbler": {
|
||||||
"description": "額外新增 scrobbling 支援 (例如:last.fm, Listenbrainz)",
|
"description": "額外新增 scrobbling 支援 (例如:last.fm, Listenbrainz)",
|
||||||
|
"dialog": {
|
||||||
|
"lastfm": {
|
||||||
|
"auth-failed": {
|
||||||
|
"message": "Last.fm認證失敗\n將隱藏彈窗直到重啟。",
|
||||||
|
"title": "認證失敗"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"api-settings": "Last.fm API 設定"
|
"api-settings": "Last.fm API 設定"
|
||||||
|
|||||||
16
src/index.ts
16
src/index.ts
@ -53,6 +53,8 @@ import {
|
|||||||
import { LoggerPrefix } from '@/utils';
|
import { LoggerPrefix } from '@/utils';
|
||||||
import { loadI18n, setLanguage, t } from '@/i18n';
|
import { loadI18n, setLanguage, t } from '@/i18n';
|
||||||
|
|
||||||
|
import ErrorHtmlAsset from '@assets/error.html?asset';
|
||||||
|
|
||||||
import type { PluginConfig } from '@/types/plugins';
|
import type { PluginConfig } from '@/types/plugins';
|
||||||
|
|
||||||
if (!is.macOS()) {
|
if (!is.macOS()) {
|
||||||
@ -80,11 +82,15 @@ if (!gotTheLock) {
|
|||||||
app.exit();
|
app.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ozone platform hint: Required for Wayland support
|
||||||
|
app.commandLine.appendSwitch('ozone-platform-hint', 'auto');
|
||||||
// SharedArrayBuffer: Required for downloader (@ffmpeg/core-mt)
|
// SharedArrayBuffer: Required for downloader (@ffmpeg/core-mt)
|
||||||
// OverlayScrollbar: Required for overlay scrollbars
|
// OverlayScrollbar: Required for overlay scrollbars
|
||||||
|
// UseOzonePlatform: Required for Wayland support
|
||||||
|
// WaylandWindowDecorations: Required for Wayland decorations
|
||||||
app.commandLine.appendSwitch(
|
app.commandLine.appendSwitch(
|
||||||
'enable-features',
|
'enable-features',
|
||||||
'OverlayScrollbar,SharedArrayBuffer',
|
'OverlayScrollbar,SharedArrayBuffer,UseOzonePlatform,WaylandWindowDecorations',
|
||||||
);
|
);
|
||||||
if (config.get('options.disableHardwareAcceleration')) {
|
if (config.get('options.disableHardwareAcceleration')) {
|
||||||
if (is.dev()) {
|
if (is.dev()) {
|
||||||
@ -505,7 +511,7 @@ app.once('browser-window-created', (_event, win) => {
|
|||||||
if (errorCode !== -3) {
|
if (errorCode !== -3) {
|
||||||
// -3 is a false positive
|
// -3 is a false positive
|
||||||
win.webContents.send('log', log);
|
win.webContents.send('log', log);
|
||||||
win.webContents.loadFile(path.join(__dirname, 'error.html'));
|
win.webContents.loadFile(ErrorHtmlAsset);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -586,7 +592,7 @@ app.whenReady().then(async () => {
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
// Check if shortcut is registered and valid
|
// Check if shortcut is registered and valid
|
||||||
const shortcutDetails = shell.readShortcutLink(shortcutPath); // Throw error if doesn't exist yet
|
const shortcutDetails = shell.readShortcutLink(shortcutPath); // Throw error if it doesn't exist yet
|
||||||
if (
|
if (
|
||||||
shortcutDetails.target !== appLocation ||
|
shortcutDetails.target !== appLocation ||
|
||||||
shortcutDetails.appUserModelId !== appID
|
shortcutDetails.appUserModelId !== appID
|
||||||
@ -671,7 +677,9 @@ app.whenReady().then(async () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleProtocol(command);
|
const splited = decodeURIComponent(command).split(' ');
|
||||||
|
|
||||||
|
handleProtocol(splited.shift()!, splited);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
148
src/menu.ts
148
src/menu.ts
@ -1,13 +1,5 @@
|
|||||||
import is from 'electron-is';
|
import is from 'electron-is';
|
||||||
import {
|
import { app, BrowserWindow, clipboard, dialog, Menu, MenuItem, shell, } from 'electron';
|
||||||
app,
|
|
||||||
BrowserWindow,
|
|
||||||
clipboard,
|
|
||||||
dialog,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
shell,
|
|
||||||
} from 'electron';
|
|
||||||
import prompt from 'custom-electron-prompt';
|
import prompt from 'custom-electron-prompt';
|
||||||
import { satisfies } from 'semver';
|
import { satisfies } from 'semver';
|
||||||
|
|
||||||
@ -235,16 +227,41 @@ export const mainMenuTemplate = async (
|
|||||||
'main.menu.options.submenu.visual-tweaks.submenu.theme.label',
|
'main.menu.options.submenu.visual-tweaks.submenu.theme.label',
|
||||||
),
|
),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
...((config.get('options.themes')?.length ?? 0) === 0
|
||||||
label: t(
|
? [
|
||||||
'main.menu.options.submenu.visual-tweaks.submenu.theme.submenu.no-theme',
|
{
|
||||||
),
|
label: t(
|
||||||
type: 'radio',
|
'main.menu.options.submenu.visual-tweaks.submenu.theme.submenu.no-theme',
|
||||||
checked: config.get('options.themes')?.length === 0, // Todo rename "themes"
|
),
|
||||||
click() {
|
}
|
||||||
config.set('options.themes', []);
|
]
|
||||||
},
|
: []),
|
||||||
},
|
...(config.get('options.themes')?.map((theme: string) => ({
|
||||||
|
type: 'normal' as const,
|
||||||
|
label: theme,
|
||||||
|
async click() {
|
||||||
|
const { response } = await dialog.showMessageBox(win, {
|
||||||
|
type: 'question',
|
||||||
|
defaultId: 1,
|
||||||
|
title: t(
|
||||||
|
'main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.remove-theme',
|
||||||
|
),
|
||||||
|
message: t(
|
||||||
|
'main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.remove-theme-message',
|
||||||
|
{ theme },
|
||||||
|
),
|
||||||
|
buttons: [
|
||||||
|
t('main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.button.cancel'),
|
||||||
|
t('main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.button.remove'),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response === 1) {
|
||||||
|
config.set('options.themes', config.get('options.themes')?.filter((t) => t !== theme) ?? []);
|
||||||
|
innerRefreshMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})) ?? []),
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: t(
|
label: t(
|
||||||
@ -258,6 +275,7 @@ export const mainMenuTemplate = async (
|
|||||||
});
|
});
|
||||||
if (filePaths) {
|
if (filePaths) {
|
||||||
config.set('options.themes', filePaths);
|
config.set('options.themes', filePaths);
|
||||||
|
innerRefreshMenu();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -288,40 +306,40 @@ export const mainMenuTemplate = async (
|
|||||||
},
|
},
|
||||||
...((is.windows() || is.linux()
|
...((is.windows() || is.linux()
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t('main.menu.options.submenu.hide-menu.label'),
|
label: t('main.menu.options.submenu.hide-menu.label'),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: config.get('options.hideMenu'),
|
checked: config.get('options.hideMenu'),
|
||||||
click(item) {
|
click(item) {
|
||||||
config.setMenuOption('options.hideMenu', item.checked);
|
config.setMenuOption('options.hideMenu', item.checked);
|
||||||
if (item.checked && !config.get('options.hideMenuWarned')) {
|
if (item.checked && !config.get('options.hideMenuWarned')) {
|
||||||
dialog.showMessageBox(win, {
|
dialog.showMessageBox(win, {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: t(
|
title: t(
|
||||||
'main.menu.options.submenu.hide-menu.dialog.title',
|
'main.menu.options.submenu.hide-menu.dialog.title',
|
||||||
),
|
),
|
||||||
message: t(
|
message: t(
|
||||||
'main.menu.options.submenu.hide-menu.dialog.message',
|
'main.menu.options.submenu.hide-menu.dialog.message',
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
|
]
|
||||||
: []) satisfies Electron.MenuItemConstructorOptions[]),
|
: []) satisfies Electron.MenuItemConstructorOptions[]),
|
||||||
...((is.windows() || is.macOS()
|
...((is.windows() || is.macOS()
|
||||||
? // Only works on Win/Mac
|
? // Only works on Win/Mac
|
||||||
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
|
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
label: t('main.menu.options.submenu.start-at-login'),
|
label: t('main.menu.options.submenu.start-at-login'),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: config.get('options.startAtLogin'),
|
checked: config.get('options.startAtLogin'),
|
||||||
click(item) {
|
click(item) {
|
||||||
config.setMenuOption('options.startAtLogin', item.checked);
|
config.setMenuOption('options.startAtLogin', item.checked);
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
|
]
|
||||||
: []) satisfies Electron.MenuItemConstructorOptions[]),
|
: []) satisfies Electron.MenuItemConstructorOptions[]),
|
||||||
{
|
{
|
||||||
label: t('main.menu.options.submenu.tray.label'),
|
label: t('main.menu.options.submenu.tray.label'),
|
||||||
@ -475,25 +493,25 @@ export const mainMenuTemplate = async (
|
|||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
is.macOS()
|
is.macOS()
|
||||||
? {
|
? {
|
||||||
label: t(
|
label: t(
|
||||||
'main.menu.options.submenu.advanced-options.submenu.toggle-dev-tools',
|
'main.menu.options.submenu.advanced-options.submenu.toggle-dev-tools',
|
||||||
),
|
),
|
||||||
// Cannot use "toggleDevTools" role in macOS
|
// Cannot use "toggleDevTools" role in macOS
|
||||||
click() {
|
click() {
|
||||||
const { webContents } = win;
|
const { webContents } = win;
|
||||||
if (webContents.isDevToolsOpened()) {
|
if (webContents.isDevToolsOpened()) {
|
||||||
webContents.closeDevTools();
|
webContents.closeDevTools();
|
||||||
} else {
|
} else {
|
||||||
webContents.openDevTools();
|
webContents.openDevTools();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
label: t(
|
|
||||||
'main.menu.options.submenu.advanced-options.submenu.toggle-dev-tools',
|
|
||||||
),
|
|
||||||
role: 'toggleDevTools',
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
label: t(
|
||||||
|
'main.menu.options.submenu.advanced-options.submenu.toggle-dev-tools',
|
||||||
|
),
|
||||||
|
role: 'toggleDevTools',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t(
|
label: t(
|
||||||
'main.menu.options.submenu.advanced-options.submenu.edit-config-json',
|
'main.menu.options.submenu.advanced-options.submenu.edit-config-json',
|
||||||
|
|||||||
@ -8,13 +8,17 @@ import { app, net } from 'electron';
|
|||||||
const SOURCES = [
|
const SOURCES = [
|
||||||
'https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt',
|
'https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt',
|
||||||
// UBlock Origin
|
// UBlock Origin
|
||||||
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt',
|
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters.txt',
|
||||||
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2020.txt',
|
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/quick-fixes.txt',
|
||||||
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2021.txt',
|
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/unbreak.txt',
|
||||||
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2022.txt',
|
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2020.txt',
|
||||||
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2023.txt',
|
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2021.txt',
|
||||||
|
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2022.txt',
|
||||||
|
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2023.txt',
|
||||||
// Fanboy Annoyances
|
// Fanboy Annoyances
|
||||||
'https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt',
|
'https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt',
|
||||||
|
// AdGuard
|
||||||
|
'https://filters.adtidy.org/extension/ublock/filters/122_optimized.txt',
|
||||||
];
|
];
|
||||||
|
|
||||||
let blocker: ElectronBlocker | undefined;
|
let blocker: ElectronBlocker | undefined;
|
||||||
|
|||||||
@ -22,10 +22,17 @@ export const inject = (contextBridge) => {
|
|||||||
const pruner = function (o) {
|
const pruner = function (o) {
|
||||||
delete o.playerAds;
|
delete o.playerAds;
|
||||||
delete o.adPlacements;
|
delete o.adPlacements;
|
||||||
|
delete o.adSlots;
|
||||||
//
|
//
|
||||||
if (o.playerResponse) {
|
if (o.playerResponse) {
|
||||||
delete o.playerResponse.playerAds;
|
delete o.playerResponse.playerAds;
|
||||||
delete o.playerResponse.adPlacements;
|
delete o.playerResponse.adPlacements;
|
||||||
|
delete o.playerResponse.adSlots;
|
||||||
|
}
|
||||||
|
if (o.ytInitialPlayerResponse) {
|
||||||
|
delete o.ytInitialPlayerResponse.playerAds;
|
||||||
|
delete o.ytInitialPlayerResponse.adPlacements;
|
||||||
|
delete o.ytInitialPlayerResponse.adSlots;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -45,9 +52,26 @@ export const inject = (contextBridge) => {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
(function () {
|
const chains = [
|
||||||
let cValue = 'undefined';
|
{
|
||||||
const chain = 'playerResponse.adPlacements';
|
chain: 'playerResponse.adPlacements',
|
||||||
|
cValue: 'undefined',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chain: 'ytInitialPlayerResponse.playerAds',
|
||||||
|
cValue: 'undefined',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chain: 'ytInitialPlayerResponse.adPlacements',
|
||||||
|
cValue: 'undefined',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chain: 'ytInitialPlayerResponse.adSlots',
|
||||||
|
cValue: 'undefined',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
chains.forEach(function ({ chain, cValue }) {
|
||||||
const thisScript = document.currentScript;
|
const thisScript = document.currentScript;
|
||||||
//
|
//
|
||||||
switch (cValue) {
|
switch (cValue) {
|
||||||
@ -241,203 +265,5 @@ export const inject = (contextBridge) => {
|
|||||||
|
|
||||||
//
|
//
|
||||||
trapChain(window, chain);
|
trapChain(window, chain);
|
||||||
})();
|
});
|
||||||
|
|
||||||
(function () {
|
|
||||||
let cValue = 'undefined';
|
|
||||||
const thisScript = document.currentScript;
|
|
||||||
const chain = 'ytInitialPlayerResponse.adPlacements';
|
|
||||||
//
|
|
||||||
switch (cValue) {
|
|
||||||
case 'null': {
|
|
||||||
cValue = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "''": {
|
|
||||||
cValue = '';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'true': {
|
|
||||||
cValue = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'false': {
|
|
||||||
cValue = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'undefined': {
|
|
||||||
cValue = undefined;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'noopFunc': {
|
|
||||||
cValue = function () {};
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'trueFunc': {
|
|
||||||
cValue = function () {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'falseFunc': {
|
|
||||||
cValue = function () {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
if (/^\d+$/.test(cValue)) {
|
|
||||||
cValue = Number.parseFloat(cValue);
|
|
||||||
//
|
|
||||||
if (isNaN(cValue)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(cValue) > 0x7f_ff) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
let aborted = false;
|
|
||||||
const mustAbort = function (v) {
|
|
||||||
if (aborted) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
aborted =
|
|
||||||
v !== undefined &&
|
|
||||||
v !== null &&
|
|
||||||
cValue !== undefined &&
|
|
||||||
cValue !== null &&
|
|
||||||
typeof v !== typeof cValue;
|
|
||||||
return aborted;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Support multiple trappers for the same property:
|
|
||||||
https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
|
||||||
*/
|
|
||||||
|
|
||||||
const trapProp = function (owner, prop, configurable, handler) {
|
|
||||||
if (handler.init(owner[prop]) === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
|
||||||
let previousGetter;
|
|
||||||
let previousSetter;
|
|
||||||
if (odesc instanceof Object) {
|
|
||||||
if (odesc.configurable === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (odesc.get instanceof Function) {
|
|
||||||
previousGetter = odesc.get;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (odesc.set instanceof Function) {
|
|
||||||
previousSetter = odesc.set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
Object.defineProperty(owner, prop, {
|
|
||||||
configurable,
|
|
||||||
get() {
|
|
||||||
if (previousGetter !== undefined) {
|
|
||||||
previousGetter();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
return handler.getter();
|
|
||||||
},
|
|
||||||
set(a) {
|
|
||||||
if (previousSetter !== undefined) {
|
|
||||||
previousSetter(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
handler.setter(a);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const trapChain = function (owner, chain) {
|
|
||||||
const pos = chain.indexOf('.');
|
|
||||||
if (pos === -1) {
|
|
||||||
trapProp(owner, chain, false, {
|
|
||||||
v: undefined,
|
|
||||||
getter() {
|
|
||||||
return document.currentScript === thisScript ? this.v : cValue;
|
|
||||||
},
|
|
||||||
setter(a) {
|
|
||||||
if (mustAbort(a) === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cValue = a;
|
|
||||||
},
|
|
||||||
init(v) {
|
|
||||||
if (mustAbort(v)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
this.v = v;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
//
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
const prop = chain.slice(0, pos);
|
|
||||||
const v = owner[prop];
|
|
||||||
//
|
|
||||||
chain = chain.slice(pos + 1);
|
|
||||||
if (v instanceof Object || (typeof v === 'object' && v !== null)) {
|
|
||||||
trapChain(v, chain);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
trapProp(owner, prop, true, {
|
|
||||||
v: undefined,
|
|
||||||
getter() {
|
|
||||||
return this.v;
|
|
||||||
},
|
|
||||||
setter(a) {
|
|
||||||
this.v = a;
|
|
||||||
if (a instanceof Object) {
|
|
||||||
trapChain(a, chain);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
init(v) {
|
|
||||||
this.v = v;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
trapChain(window, chain);
|
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,19 +24,13 @@ yt-page-navigation-progress {
|
|||||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#img,
|
|
||||||
#player,
|
|
||||||
.song-media-controls.style-scope.ytmusic-player {
|
|
||||||
border-radius: 2% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#items {
|
#items {
|
||||||
border-radius: 10px !important;
|
border-radius: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fix blur navigation bar */
|
/* fix blur navigation bar */
|
||||||
|
|
||||||
ytmusic-app-layout > [slot='player-page'] {
|
ytmusic-app-layout > [slot="player-page"]:not([is-mweb-modernization-enabled]):not(:has(ytmusic-player[player-ui-state=FULLSCREEN])) {
|
||||||
padding-top: 90px;
|
padding-top: 90px;
|
||||||
margin-top: calc(-90px + var(--menu-bar-height, 0px)) !important;
|
margin-top: calc(-90px + var(--menu-bar-height, 0px)) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,10 @@
|
|||||||
import style from './style.css?inline';
|
import style from './style.css?inline';
|
||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
import { menu } from './menu';
|
||||||
|
import { AmbientModePluginConfig } from './types';
|
||||||
|
|
||||||
export type AmbientModePluginConfig = {
|
|
||||||
enabled: boolean;
|
|
||||||
quality: number;
|
|
||||||
buffer: number;
|
|
||||||
interpolationTime: number;
|
|
||||||
blur: number;
|
|
||||||
size: number;
|
|
||||||
opacity: number;
|
|
||||||
fullscreen: boolean;
|
|
||||||
};
|
|
||||||
const defaultConfig: AmbientModePluginConfig = {
|
const defaultConfig: AmbientModePluginConfig = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
quality: 50,
|
quality: 50,
|
||||||
@ -30,205 +22,78 @@ export default createPlugin({
|
|||||||
restartNeeded: false,
|
restartNeeded: false,
|
||||||
config: defaultConfig,
|
config: defaultConfig,
|
||||||
stylesheets: [style],
|
stylesheets: [style],
|
||||||
menu: async ({ getConfig, setConfig }) => {
|
menu: menu,
|
||||||
const interpolationTimeList = [0, 500, 1000, 1500, 2000, 3000, 4000, 5000];
|
|
||||||
const qualityList = [10, 25, 50, 100, 200, 500, 1000];
|
|
||||||
const sizeList = [100, 110, 125, 150, 175, 200, 300];
|
|
||||||
const bufferList = [1, 5, 10, 20, 30];
|
|
||||||
const blurAmountList = [0, 5, 10, 25, 50, 100, 150, 200, 500];
|
|
||||||
const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
|
|
||||||
|
|
||||||
const config = await getConfig();
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: t('plugins.ambient-mode.menu.smoothness-transition.label'),
|
|
||||||
submenu: interpolationTimeList.map((interpolationTime) => ({
|
|
||||||
label: t(
|
|
||||||
'plugins.ambient-mode.menu.smoothness-transition.submenu.during',
|
|
||||||
{
|
|
||||||
interpolationTime: interpolationTime / 1000,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
type: 'radio',
|
|
||||||
checked: config.interpolationTime === interpolationTime,
|
|
||||||
click() {
|
|
||||||
setConfig({ interpolationTime });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('plugins.ambient-mode.menu.quality.label'),
|
|
||||||
submenu: qualityList.map((quality) => ({
|
|
||||||
label: t('plugins.ambient-mode.menu.quality.submenu.pixels', {
|
|
||||||
quality,
|
|
||||||
}),
|
|
||||||
type: 'radio',
|
|
||||||
checked: config.quality === quality,
|
|
||||||
click() {
|
|
||||||
setConfig({ quality });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('plugins.ambient-mode.menu.size.label'),
|
|
||||||
submenu: sizeList.map((size) => ({
|
|
||||||
label: t('plugins.ambient-mode.menu.size.submenu.percent', { size }),
|
|
||||||
type: 'radio',
|
|
||||||
checked: config.size === size,
|
|
||||||
click() {
|
|
||||||
setConfig({ size });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('plugins.ambient-mode.menu.buffer.label'),
|
|
||||||
submenu: bufferList.map((buffer) => ({
|
|
||||||
label: t('plugins.ambient-mode.menu.buffer.submenu.buffer', {
|
|
||||||
buffer,
|
|
||||||
}),
|
|
||||||
type: 'radio',
|
|
||||||
checked: config.buffer === buffer,
|
|
||||||
click() {
|
|
||||||
setConfig({ buffer });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('plugins.ambient-mode.menu.opacity.label'),
|
|
||||||
submenu: opacityList.map((opacity) => ({
|
|
||||||
label: t('plugins.ambient-mode.menu.opacity.submenu.percent', {
|
|
||||||
opacity: opacity * 100,
|
|
||||||
}),
|
|
||||||
type: 'radio',
|
|
||||||
checked: config.opacity === opacity,
|
|
||||||
click() {
|
|
||||||
setConfig({ opacity });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('plugins.ambient-mode.menu.blur-amount.label'),
|
|
||||||
submenu: blurAmountList.map((blur) => ({
|
|
||||||
label: t('plugins.ambient-mode.menu.blur-amount.submenu.pixels', {
|
|
||||||
blurAmount: blur,
|
|
||||||
}),
|
|
||||||
type: 'radio',
|
|
||||||
checked: config.blur === blur,
|
|
||||||
click() {
|
|
||||||
setConfig({ blur });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('plugins.ambient-mode.menu.use-fullscreen.label'),
|
|
||||||
type: 'checkbox',
|
|
||||||
checked: config.fullscreen,
|
|
||||||
click(item) {
|
|
||||||
setConfig({ fullscreen: item.checked });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
renderer: {
|
renderer: {
|
||||||
interpolationTime: defaultConfig.interpolationTime,
|
interpolationTime: defaultConfig.interpolationTime,
|
||||||
buffer: defaultConfig.buffer,
|
buffer: defaultConfig.buffer,
|
||||||
qualityRatio: defaultConfig.quality,
|
qualityRatio: defaultConfig.quality,
|
||||||
sizeRatio: defaultConfig.size / 100,
|
size: defaultConfig.size,
|
||||||
blur: defaultConfig.blur,
|
blur: defaultConfig.blur,
|
||||||
opacity: defaultConfig.opacity,
|
opacity: defaultConfig.opacity,
|
||||||
isFullscreen: defaultConfig.fullscreen,
|
isFullscreen: defaultConfig.fullscreen,
|
||||||
|
|
||||||
unregister: null as (() => void) | null,
|
unregister: null as (() => void) | null,
|
||||||
update: null as (() => void) | null,
|
update: null as (() => void) | null,
|
||||||
observer: null as MutationObserver | null,
|
interval: null as NodeJS.Timeout | null,
|
||||||
|
lastMediaType: null as "video" | "image" | null,
|
||||||
|
lastVideoSource: null as string | null,
|
||||||
|
lastImageSource: null as string | null,
|
||||||
|
|
||||||
|
async start({ getConfig }) {
|
||||||
|
const config = await getConfig();
|
||||||
|
this.interpolationTime = config.interpolationTime;
|
||||||
|
this.buffer = config.buffer;
|
||||||
|
this.qualityRatio = config.quality;
|
||||||
|
this.size = config.size;
|
||||||
|
this.blur = config.blur;
|
||||||
|
this.opacity = config.opacity;
|
||||||
|
this.isFullscreen = config.fullscreen;
|
||||||
|
|
||||||
|
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 videoWrapper = document.querySelector('#song-video > .player-wrapper');
|
||||||
|
|
||||||
start() {
|
|
||||||
const injectBlurImage = () => {
|
const injectBlurImage = () => {
|
||||||
const songImage = document.querySelector<HTMLImageElement>(
|
if (!songImage || !image) return null;
|
||||||
'#song-image',
|
|
||||||
);
|
|
||||||
const image = document.querySelector<HTMLImageElement>(
|
|
||||||
'#song-image yt-img-shadow > img',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!songImage) return null;
|
this.lastImageSource = image.src;
|
||||||
if (!image) return null;
|
|
||||||
|
|
||||||
const blurImage = document.createElement('img');
|
const blurImage = document.createElement('img');
|
||||||
blurImage.classList.add('html5-blur-image');
|
blurImage.classList.add('html5-blur-image');
|
||||||
blurImage.src = image.src;
|
blurImage.src = image.src;
|
||||||
|
|
||||||
const applyImageAttribute = () => {
|
this.update = () => {
|
||||||
const rect = image.getBoundingClientRect();
|
|
||||||
|
|
||||||
const newWidth = Math.floor(image.width || rect.width);
|
|
||||||
const newHeight = Math.floor(image.height || rect.height);
|
|
||||||
|
|
||||||
if (newWidth === 0 || newHeight === 0) return;
|
|
||||||
|
|
||||||
if (this.isFullscreen) blurImage.classList.add('fullscreen');
|
if (this.isFullscreen) blurImage.classList.add('fullscreen');
|
||||||
else blurImage.classList.remove('fullscreen');
|
else blurImage.classList.remove('fullscreen');
|
||||||
|
|
||||||
const leftOffset = (newWidth * (this.sizeRatio - 1)) / 2;
|
blurImage.style.setProperty('--width', `${this.size}%`);
|
||||||
const topOffset = (newHeight * (this.sizeRatio - 1)) / 2;
|
blurImage.style.setProperty('--height', `${this.size}%`);
|
||||||
blurImage.style.setProperty('--left', `${-1 * leftOffset}px`);
|
|
||||||
blurImage.style.setProperty('--top', `${-1 * topOffset}px`);
|
|
||||||
blurImage.style.setProperty('--width', `${newWidth * this.sizeRatio}px`);
|
|
||||||
blurImage.style.setProperty('--height', `${newHeight * this.sizeRatio}px`);
|
|
||||||
blurImage.style.setProperty('--blur', `${this.blur}px`);
|
blurImage.style.setProperty('--blur', `${this.blur}px`);
|
||||||
blurImage.style.setProperty('--opacity', `${this.opacity}`);
|
blurImage.style.setProperty('--opacity', `${this.opacity}`);
|
||||||
};
|
};
|
||||||
|
this.update();
|
||||||
this.update = applyImageAttribute;
|
|
||||||
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.type === 'attributes') {
|
|
||||||
applyImageAttribute();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
applyImageAttribute();
|
|
||||||
});
|
|
||||||
|
|
||||||
applyImageAttribute();
|
|
||||||
observer.observe(songImage, { attributes: true });
|
|
||||||
resizeObserver.observe(songImage);
|
|
||||||
window.addEventListener('resize', applyImageAttribute);
|
|
||||||
|
|
||||||
/* injecting */
|
/* injecting */
|
||||||
songImage.prepend(blurImage);
|
songImage.prepend(blurImage);
|
||||||
|
|
||||||
/* cleanup */
|
/* cleanup */
|
||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
|
||||||
resizeObserver.disconnect();
|
|
||||||
window.removeEventListener('resize', applyImageAttribute);
|
|
||||||
|
|
||||||
if (blurImage.isConnected) blurImage.remove();
|
if (blurImage.isConnected) blurImage.remove();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const injectBlurVideo = (): (() => void) | null => {
|
const injectBlurVideo = () => {
|
||||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
if (!songVideo || !video || !videoWrapper) return null;
|
||||||
const video = document.querySelector<HTMLVideoElement>(
|
|
||||||
'#song-video .html5-video-container > video',
|
|
||||||
);
|
|
||||||
const wrapper = document.querySelector('#song-video > .player-wrapper');
|
|
||||||
|
|
||||||
if (!songVideo) return null;
|
this.lastVideoSource = video.src;
|
||||||
if (!video) return null;
|
|
||||||
if (!wrapper) return null;
|
|
||||||
|
|
||||||
const blurCanvas = document.createElement('canvas');
|
const blurCanvas = document.createElement('canvas');
|
||||||
blurCanvas.classList.add('html5-blur-canvas');
|
blurCanvas.classList.add('html5-blur-canvas');
|
||||||
|
|
||||||
const context = blurCanvas.getContext('2d', {
|
const context = blurCanvas.getContext('2d', { willReadFrequently: true });
|
||||||
willReadFrequently: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
/* effect */
|
/* effect */
|
||||||
let lastEffectWorkId: number | null = null;
|
let lastEffectWorkId: number | null = null;
|
||||||
@ -242,17 +107,13 @@ export default createPlugin({
|
|||||||
if (!context) return;
|
if (!context) return;
|
||||||
|
|
||||||
const width = this.qualityRatio;
|
const width = this.qualityRatio;
|
||||||
let height = Math.max(
|
let height = Math.max(Math.floor((blurCanvas.height / blurCanvas.width) * width), 1,);
|
||||||
Math.floor((blurCanvas.height / blurCanvas.width) * width),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
if (!Number.isFinite(height)) height = width;
|
if (!Number.isFinite(height)) height = width;
|
||||||
if (!height) return;
|
if (!height) return;
|
||||||
|
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
if (lastImageData) {
|
if (lastImageData) {
|
||||||
const frameOffset =
|
const frameOffset = (1 / this.buffer) * (1000 / this.interpolationTime);
|
||||||
(1 / this.buffer) * (1000 / this.interpolationTime);
|
|
||||||
context.globalAlpha = 1 - (frameOffset * 2); // because of alpha value must be < 1
|
context.globalAlpha = 1 - (frameOffset * 2); // because of alpha value must be < 1
|
||||||
context.putImageData(lastImageData, 0, 0);
|
context.putImageData(lastImageData, 0, 0);
|
||||||
context.globalAlpha = frameOffset;
|
context.globalAlpha = frameOffset;
|
||||||
@ -265,7 +126,7 @@ export default createPlugin({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyVideoAttributes = () => {
|
this.update = () => {
|
||||||
const rect = video.getBoundingClientRect();
|
const rect = video.getBoundingClientRect();
|
||||||
|
|
||||||
const newWidth = Math.floor(video.width || rect.width);
|
const newWidth = Math.floor(video.width || rect.width);
|
||||||
@ -274,45 +135,21 @@ export default createPlugin({
|
|||||||
if (newWidth === 0 || newHeight === 0) return;
|
if (newWidth === 0 || newHeight === 0) return;
|
||||||
|
|
||||||
blurCanvas.width = this.qualityRatio;
|
blurCanvas.width = this.qualityRatio;
|
||||||
blurCanvas.height = Math.floor(
|
blurCanvas.height = Math.floor((newHeight / newWidth) * this.qualityRatio);
|
||||||
(newHeight / newWidth) * this.qualityRatio,
|
|
||||||
);
|
|
||||||
blurCanvas.style.width = `${newWidth * this.sizeRatio}px`;
|
|
||||||
blurCanvas.style.height = `${newHeight * this.sizeRatio}px`;
|
|
||||||
|
|
||||||
if (this.isFullscreen) blurCanvas.classList.add('fullscreen');
|
if (this.isFullscreen) blurCanvas.classList.add('fullscreen');
|
||||||
else blurCanvas.classList.remove('fullscreen');
|
else blurCanvas.classList.remove('fullscreen');
|
||||||
|
|
||||||
const leftOffset = (newWidth * (this.sizeRatio - 1)) / 2;
|
blurCanvas.style.setProperty('--width', `${this.size}%`);
|
||||||
const topOffset = (newHeight * (this.sizeRatio - 1)) / 2;
|
blurCanvas.style.setProperty('--height', `${this.size}%`);
|
||||||
blurCanvas.style.setProperty('--left', `${-1 * leftOffset}px`);
|
|
||||||
blurCanvas.style.setProperty('--top', `${-1 * topOffset}px`);
|
|
||||||
blurCanvas.style.setProperty('--blur', `${this.blur}px`);
|
blurCanvas.style.setProperty('--blur', `${this.blur}px`);
|
||||||
blurCanvas.style.setProperty('--opacity', `${this.opacity}`);
|
blurCanvas.style.setProperty('--opacity', `${this.opacity}`);
|
||||||
};
|
};
|
||||||
this.update = applyVideoAttributes;
|
this.update();
|
||||||
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.type === 'attributes') {
|
|
||||||
applyVideoAttributes();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
applyVideoAttributes();
|
|
||||||
});
|
|
||||||
|
|
||||||
/* hooking */
|
/* hooking */
|
||||||
let canvasInterval: NodeJS.Timeout | null = null;
|
let canvasInterval: NodeJS.Timeout | null = null;
|
||||||
canvasInterval = setInterval(
|
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / this.buffer)));
|
||||||
onSync,
|
|
||||||
Math.max(1, Math.ceil(1000 / this.buffer)),
|
|
||||||
);
|
|
||||||
applyVideoAttributes();
|
|
||||||
observer.observe(songVideo, { attributes: true });
|
|
||||||
resizeObserver.observe(songVideo);
|
|
||||||
window.addEventListener('resize', applyVideoAttributes);
|
|
||||||
|
|
||||||
const onPause = () => {
|
const onPause = () => {
|
||||||
if (canvasInterval) clearInterval(canvasInterval);
|
if (canvasInterval) clearInterval(canvasInterval);
|
||||||
@ -320,16 +157,13 @@ export default createPlugin({
|
|||||||
};
|
};
|
||||||
const onPlay = () => {
|
const onPlay = () => {
|
||||||
if (canvasInterval) clearInterval(canvasInterval);
|
if (canvasInterval) clearInterval(canvasInterval);
|
||||||
canvasInterval = setInterval(
|
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / this.buffer)));
|
||||||
onSync,
|
|
||||||
Math.max(1, Math.ceil(1000 / this.buffer)),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
songVideo.addEventListener('pause', onPause);
|
songVideo.addEventListener('pause', onPause);
|
||||||
songVideo.addEventListener('play', onPlay);
|
songVideo.addEventListener('play', onPlay);
|
||||||
|
|
||||||
/* injecting */
|
/* injecting */
|
||||||
wrapper.prepend(blurCanvas);
|
videoWrapper.prepend(blurCanvas);
|
||||||
|
|
||||||
/* cleanup */
|
/* cleanup */
|
||||||
return () => {
|
return () => {
|
||||||
@ -338,55 +172,63 @@ export default createPlugin({
|
|||||||
songVideo.removeEventListener('pause', onPause);
|
songVideo.removeEventListener('pause', onPause);
|
||||||
songVideo.removeEventListener('play', onPlay);
|
songVideo.removeEventListener('play', onPlay);
|
||||||
|
|
||||||
observer.disconnect();
|
|
||||||
resizeObserver.disconnect();
|
|
||||||
window.removeEventListener('resize', applyVideoAttributes);
|
|
||||||
|
|
||||||
if (blurCanvas.isConnected) blurCanvas.remove();
|
if (blurCanvas.isConnected) blurCanvas.remove();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const isVideoMode = () => {
|
const isVideoMode = () => {
|
||||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||||
if (!songVideo) return false;
|
if (!songVideo) {
|
||||||
|
this.lastMediaType = "image";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return getComputedStyle(songVideo).display !== 'none';
|
const isVideo = getComputedStyle(songVideo).display !== 'none';
|
||||||
|
this.lastMediaType = isVideo ? "video" : "image";
|
||||||
|
return isVideo;
|
||||||
};
|
};
|
||||||
|
|
||||||
const playerPage = document.querySelector<HTMLElement>('#player-page');
|
const playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||||
|
|
||||||
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
|
const injectBlurElement = (force?: boolean): boolean | void => {
|
||||||
if (isPageOpen) {
|
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
|
||||||
this.unregister?.();
|
if (isPageOpen) {
|
||||||
this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
|
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;
|
||||||
|
}
|
||||||
|
this.unregister?.();
|
||||||
|
this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||||
|
} else {
|
||||||
|
this.unregister?.();
|
||||||
|
this.unregister = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* needed for switching between different views (e.g. miniplayer) */
|
||||||
const observer = new MutationObserver((mutationsList) => {
|
const observer = new MutationObserver((mutationsList) => {
|
||||||
for (const mutation of mutationsList) {
|
for (const mutation of mutationsList) {
|
||||||
if (mutation.type === 'attributes') {
|
if (mutation.type === 'attributes') {
|
||||||
const isPageOpen =
|
injectBlurElement(true);
|
||||||
ytmusicAppLayout?.hasAttribute('player-page-open');
|
break;
|
||||||
if (isPageOpen) {
|
|
||||||
this.unregister?.();
|
|
||||||
this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
|
|
||||||
} else {
|
|
||||||
this.unregister?.();
|
|
||||||
this.unregister = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (playerPage) {
|
if (playerPage) {
|
||||||
observer.observe(playerPage, { attributes: true });
|
observer.observe(playerPage, { attributes: true });
|
||||||
|
|
||||||
|
/* fallback ticker for when the observer isn't triggered */
|
||||||
|
this.interval = setInterval(injectBlurElement, 1000);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onConfigChange(newConfig) {
|
onConfigChange(newConfig) {
|
||||||
this.interpolationTime = newConfig.interpolationTime;
|
this.interpolationTime = newConfig.interpolationTime;
|
||||||
this.buffer = newConfig.buffer;
|
this.buffer = newConfig.buffer;
|
||||||
this.qualityRatio = newConfig.quality;
|
this.qualityRatio = newConfig.quality;
|
||||||
this.sizeRatio = newConfig.size / 100;
|
this.size = newConfig.size;
|
||||||
this.blur = newConfig.blur;
|
this.blur = newConfig.blur;
|
||||||
this.opacity = newConfig.opacity;
|
this.opacity = newConfig.opacity;
|
||||||
this.isFullscreen = newConfig.fullscreen;
|
this.isFullscreen = newConfig.fullscreen;
|
||||||
@ -394,9 +236,9 @@ export default createPlugin({
|
|||||||
this.update?.();
|
this.update?.();
|
||||||
},
|
},
|
||||||
stop() {
|
stop() {
|
||||||
this.observer?.disconnect();
|
|
||||||
this.update = null;
|
this.update = null;
|
||||||
this.unregister?.();
|
this.unregister?.();
|
||||||
|
if (this.interval) clearInterval(this.interval);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
110
src/plugins/ambient-mode/menu.ts
Normal file
110
src/plugins/ambient-mode/menu.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { t } from "@/i18n";
|
||||||
|
import { MenuContext } from "@/types/contexts";
|
||||||
|
import { MenuItemConstructorOptions } from "electron";
|
||||||
|
import { AmbientModePluginConfig } from "./types";
|
||||||
|
|
||||||
|
export interface menuParameters {
|
||||||
|
getConfig: () => AmbientModePluginConfig | Promise<AmbientModePluginConfig>;
|
||||||
|
setConfig: (conf: Partial<Omit<AmbientModePluginConfig, "enabled">>) => void | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const menu: (ctx: MenuContext<AmbientModePluginConfig>) => MenuItemConstructorOptions[] | Promise<MenuItemConstructorOptions[]> = async ({ getConfig, setConfig }: menuParameters) => {
|
||||||
|
const interpolationTimeList = [0, 500, 1000, 1500, 2000, 3000, 4000, 5000];
|
||||||
|
const qualityList = [10, 25, 50, 100, 200, 500, 1000];
|
||||||
|
const sizeList = [100, 110, 125, 150, 175, 200, 300];
|
||||||
|
const bufferList = [1, 5, 10, 20, 30];
|
||||||
|
const blurAmountList = [0, 5, 10, 25, 50, 100, 150, 200, 500];
|
||||||
|
const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
|
||||||
|
|
||||||
|
const config = await getConfig();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('plugins.ambient-mode.menu.smoothness-transition.label'),
|
||||||
|
submenu: interpolationTimeList.map((interpolationTime) => ({
|
||||||
|
label: t(
|
||||||
|
'plugins.ambient-mode.menu.smoothness-transition.submenu.during',
|
||||||
|
{
|
||||||
|
interpolationTime: interpolationTime / 1000,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.interpolationTime === interpolationTime,
|
||||||
|
click() {
|
||||||
|
setConfig({ interpolationTime });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.ambient-mode.menu.quality.label'),
|
||||||
|
submenu: qualityList.map((quality) => ({
|
||||||
|
label: t('plugins.ambient-mode.menu.quality.submenu.pixels', {
|
||||||
|
quality,
|
||||||
|
}),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.quality === quality,
|
||||||
|
click() {
|
||||||
|
setConfig({ quality });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.ambient-mode.menu.size.label'),
|
||||||
|
submenu: sizeList.map((size) => ({
|
||||||
|
label: t('plugins.ambient-mode.menu.size.submenu.percent', { size }),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.size === size,
|
||||||
|
click() {
|
||||||
|
setConfig({ size });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.ambient-mode.menu.buffer.label'),
|
||||||
|
submenu: bufferList.map((buffer) => ({
|
||||||
|
label: t('plugins.ambient-mode.menu.buffer.submenu.buffer', {
|
||||||
|
buffer,
|
||||||
|
}),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.buffer === buffer,
|
||||||
|
click() {
|
||||||
|
setConfig({ buffer });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.ambient-mode.menu.opacity.label'),
|
||||||
|
submenu: opacityList.map((opacity) => ({
|
||||||
|
label: t('plugins.ambient-mode.menu.opacity.submenu.percent', {
|
||||||
|
opacity: opacity * 100,
|
||||||
|
}),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.opacity === opacity,
|
||||||
|
click() {
|
||||||
|
setConfig({ opacity });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.ambient-mode.menu.blur-amount.label'),
|
||||||
|
submenu: blurAmountList.map((blur) => ({
|
||||||
|
label: t('plugins.ambient-mode.menu.blur-amount.submenu.pixels', {
|
||||||
|
blurAmount: blur,
|
||||||
|
}),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.blur === blur,
|
||||||
|
click() {
|
||||||
|
setConfig({ blur });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.ambient-mode.menu.use-fullscreen.label'),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: config.fullscreen,
|
||||||
|
click(item: Electron.MenuItem) {
|
||||||
|
setConfig({ fullscreen: item.checked });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -1,40 +1,36 @@
|
|||||||
#song-video canvas.html5-blur-canvas {
|
#song-video canvas.html5-blur-canvas,
|
||||||
|
#song-image .html5-blur-image {
|
||||||
filter: blur(var(--blur, 100px));
|
filter: blur(var(--blur, 100px));
|
||||||
opacity: var(--opacity, 1);
|
opacity: var(--opacity, 1);
|
||||||
|
width: var(--width, 100%);
|
||||||
|
height: var(--height, 100%);
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#song-video canvas.html5-blur-canvas:not(.fullscreen) {
|
#song-video canvas.html5-blur-canvas:not(.fullscreen),
|
||||||
|
#song-image .html5-blur-image {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
left: var(--left, 0px);
|
top: 50%;
|
||||||
top: var(--top, 0px);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#song-video canvas.html5-blur-canvas.fullscreen {
|
#song-video canvas.html5-blur-canvas.fullscreen {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
width: 100% !important;
|
width: 100%;
|
||||||
height: 100% !important;
|
height: 100%;
|
||||||
left: 0 !important;
|
|
||||||
top: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#song-video .html5-video-container > video {
|
#song-video .html5-video-container {
|
||||||
top: 0 !important;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#song-image .html5-blur-image {
|
#player:not([video-mode]):not(.video-mode):not([player-ui-state='MINIPLAYER']):not([is-mweb-modernization-enabled]) {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
|
margin: 0 auto !important;
|
||||||
left: var(--left, 0px);
|
overflow: visible;
|
||||||
top: var(--top, 0px);
|
|
||||||
width: var(--width, 100%) !important;
|
|
||||||
height: var(--height, 100%) !important;
|
|
||||||
|
|
||||||
filter: blur(var(--blur, 100px));
|
|
||||||
opacity: var(--opacity, 1);
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/plugins/ambient-mode/types.ts
Normal file
10
src/plugins/ambient-mode/types.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export type AmbientModePluginConfig = {
|
||||||
|
enabled: boolean;
|
||||||
|
quality: number;
|
||||||
|
buffer: number;
|
||||||
|
interpolationTime: number;
|
||||||
|
blur: number;
|
||||||
|
size: number;
|
||||||
|
opacity: number;
|
||||||
|
fullscreen: boolean;
|
||||||
|
};
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
createWriteStream,
|
|
||||||
existsSync,
|
existsSync,
|
||||||
mkdirSync,
|
mkdirSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
@ -30,9 +29,8 @@ import {
|
|||||||
|
|
||||||
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
|
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
|
||||||
import { isEnabled } from '@/config/plugins';
|
import { isEnabled } from '@/config/plugins';
|
||||||
import { cleanupName, getImage, SongInfo } from '@/providers/song-info';
|
import { cleanupName, getImage, MediaType, type SongInfo } from '@/providers/song-info';
|
||||||
import { getNetFetchAsFetch } from '@/plugins/utils/main';
|
import { getNetFetchAsFetch } from '@/plugins/utils/main';
|
||||||
import { cache } from '@/providers/decorators';
|
|
||||||
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
@ -297,7 +295,7 @@ async function downloadSongUnsafe(
|
|||||||
mkdirSync(dir);
|
mkdirSync(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileBuffer = await iterableStreamToTargetFile(
|
let fileBuffer = await iterableStreamToProcessedUint8Array(
|
||||||
iterableStream,
|
iterableStream,
|
||||||
targetFileExtension,
|
targetFileExtension,
|
||||||
metadata,
|
metadata,
|
||||||
@ -307,19 +305,16 @@ async function downloadSongUnsafe(
|
|||||||
increasePlaylistProgress,
|
increasePlaylistProgress,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (fileBuffer && targetFileExtension === 'mp3') {
|
||||||
|
fileBuffer = await writeID3(
|
||||||
|
Buffer.from(fileBuffer),
|
||||||
|
metadata,
|
||||||
|
sendFeedback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (fileBuffer) {
|
if (fileBuffer) {
|
||||||
if (targetFileExtension !== 'mp3') {
|
writeFileSync(filePath, fileBuffer);
|
||||||
createWriteStream(filePath).write(fileBuffer);
|
|
||||||
} else {
|
|
||||||
const buffer = await writeID3(
|
|
||||||
Buffer.from(fileBuffer),
|
|
||||||
metadata,
|
|
||||||
sendFeedback,
|
|
||||||
);
|
|
||||||
if (buffer) {
|
|
||||||
writeFileSync(filePath, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFeedback(null, -1);
|
sendFeedback(null, -1);
|
||||||
@ -330,15 +325,12 @@ async function downloadSongUnsafe(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function iterableStreamToTargetFile(
|
async function downloadChunks(
|
||||||
stream: AsyncGenerator<Uint8Array, void>,
|
stream: AsyncGenerator<Uint8Array, void>,
|
||||||
extension: string,
|
|
||||||
metadata: CustomSongInfo,
|
|
||||||
presetFfmpegArgs: string[],
|
|
||||||
contentLength: number,
|
contentLength: number,
|
||||||
sendFeedback: (str: string, value?: number) => void,
|
sendFeedback: (str: string, value?: number) => void,
|
||||||
increasePlaylistProgress: (value: number) => void = () => {},
|
increasePlaylistProgress: (value: number) => void = () => {},
|
||||||
): Promise<Uint8Array | null> {
|
) {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
let downloaded = 0;
|
let downloaded = 0;
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
@ -356,65 +348,80 @@ async function iterableStreamToTargetFile(
|
|||||||
// This is a very rough estimate, trying to make the progress bar look nice
|
// This is a very rough estimate, trying to make the progress bar look nice
|
||||||
increasePlaylistProgress(ratio * 0.15);
|
increasePlaylistProgress(ratio * 0.15);
|
||||||
}
|
}
|
||||||
|
return chunks;
|
||||||
sendFeedback(t('plugins.downloader.backend.feedback.loading'), 2); // Indefinite progress bar after download
|
|
||||||
|
|
||||||
const buffer = Buffer.concat(chunks);
|
|
||||||
const safeVideoName = randomBytes(32).toString('hex');
|
|
||||||
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!ffmpeg.isLoaded()) {
|
|
||||||
await ffmpeg.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFeedback(t('plugins.downloader.backend.feedback.preparing-file'));
|
|
||||||
ffmpeg.FS('writeFile', safeVideoName, buffer);
|
|
||||||
|
|
||||||
sendFeedback(t('plugins.downloader.backend.feedback.converting'));
|
|
||||||
|
|
||||||
ffmpeg.setProgress(({ ratio }) => {
|
|
||||||
sendFeedback(
|
|
||||||
t('plugins.downloader.backend.feedback.conversion-progress', {
|
|
||||||
percent: Math.floor(ratio * 100),
|
|
||||||
}),
|
|
||||||
ratio,
|
|
||||||
);
|
|
||||||
increasePlaylistProgress(0.15 + (ratio * 0.85));
|
|
||||||
});
|
|
||||||
|
|
||||||
const safeVideoNameWithExtension = `${safeVideoName}.${extension}`;
|
|
||||||
try {
|
|
||||||
await ffmpeg.run(
|
|
||||||
'-i',
|
|
||||||
safeVideoName,
|
|
||||||
...presetFfmpegArgs,
|
|
||||||
...getFFmpegMetadataArgs(metadata),
|
|
||||||
safeVideoNameWithExtension,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
ffmpeg.FS('unlink', safeVideoName);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFeedback(t('plugins.downloader.backend.feedback.saving'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
return ffmpeg.FS('readFile', safeVideoNameWithExtension);
|
|
||||||
} finally {
|
|
||||||
ffmpeg.FS('unlink', safeVideoNameWithExtension);
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
sendError(error as Error, safeVideoName);
|
|
||||||
} finally {
|
|
||||||
releaseFFmpegMutex();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCoverBuffer = cache(async (url: string) => {
|
async function iterableStreamToProcessedUint8Array(
|
||||||
|
stream: AsyncGenerator<Uint8Array, void>,
|
||||||
|
extension: string,
|
||||||
|
metadata: CustomSongInfo,
|
||||||
|
presetFfmpegArgs: string[],
|
||||||
|
contentLength: number,
|
||||||
|
sendFeedback: (str: string, value?: number) => void,
|
||||||
|
increasePlaylistProgress: (value: number) => void = () => {},
|
||||||
|
): Promise<Uint8Array | null> {
|
||||||
|
sendFeedback(t('plugins.downloader.backend.feedback.loading'), 2); // Indefinite progress bar after download
|
||||||
|
|
||||||
|
const safeVideoName = randomBytes(32).toString('hex');
|
||||||
|
|
||||||
|
return await ffmpegMutex.runExclusive(async () => {
|
||||||
|
try {
|
||||||
|
if (!ffmpeg.isLoaded()) {
|
||||||
|
await ffmpeg.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFeedback(t('plugins.downloader.backend.feedback.preparing-file'));
|
||||||
|
ffmpeg.FS(
|
||||||
|
'writeFile',
|
||||||
|
safeVideoName,
|
||||||
|
Buffer.concat(
|
||||||
|
await downloadChunks(stream, contentLength, sendFeedback, increasePlaylistProgress),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
sendFeedback(t('plugins.downloader.backend.feedback.converting'));
|
||||||
|
|
||||||
|
ffmpeg.setProgress(({ ratio }) => {
|
||||||
|
sendFeedback(
|
||||||
|
t('plugins.downloader.backend.feedback.conversion-progress', {
|
||||||
|
percent: Math.floor(ratio * 100),
|
||||||
|
}),
|
||||||
|
ratio,
|
||||||
|
);
|
||||||
|
increasePlaylistProgress(0.15 + (ratio * 0.85));
|
||||||
|
});
|
||||||
|
|
||||||
|
const safeVideoNameWithExtension = `${safeVideoName}.${extension}`;
|
||||||
|
try {
|
||||||
|
await ffmpeg.run(
|
||||||
|
'-i',
|
||||||
|
safeVideoName,
|
||||||
|
...presetFfmpegArgs,
|
||||||
|
...getFFmpegMetadataArgs(metadata),
|
||||||
|
safeVideoNameWithExtension,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
ffmpeg.FS('unlink', safeVideoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFeedback(t('plugins.downloader.backend.feedback.saving'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ffmpeg.FS('readFile', safeVideoNameWithExtension);
|
||||||
|
} finally {
|
||||||
|
ffmpeg.FS('unlink', safeVideoNameWithExtension);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
sendError(error as Error, safeVideoName);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCoverBuffer = async (url: string) => {
|
||||||
const nativeImage = cropMaxWidth(await getImage(url));
|
const nativeImage = cropMaxWidth(await getImage(url));
|
||||||
return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null;
|
return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null;
|
||||||
});
|
};
|
||||||
|
|
||||||
async function writeID3(
|
async function writeID3(
|
||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
@ -686,6 +693,7 @@ const getMetadata = (info: TrackInfo): CustomSongInfo => ({
|
|||||||
?.url,
|
?.url,
|
||||||
views: info.basic_info.view_count!,
|
views: info.basic_info.view_count!,
|
||||||
songDuration: info.basic_info.duration!,
|
songDuration: info.basic_info.duration!,
|
||||||
|
mediaType: MediaType.Audio,
|
||||||
});
|
});
|
||||||
|
|
||||||
// This is used to bypass age restrictions
|
// This is used to bypass age restrictions
|
||||||
|
|||||||
@ -4,8 +4,24 @@ export interface InAppMenuConfig {
|
|||||||
}
|
}
|
||||||
export const defaultInAppMenuConfig: InAppMenuConfig = {
|
export const defaultInAppMenuConfig: InAppMenuConfig = {
|
||||||
enabled:
|
enabled:
|
||||||
(typeof window !== 'undefined' &&
|
(
|
||||||
!window.navigator?.userAgent?.includes('mac')) ||
|
(
|
||||||
(typeof global !== 'undefined' && global.process?.platform !== 'darwin'),
|
typeof window !== 'undefined' &&
|
||||||
|
!window.navigator?.userAgent?.toLowerCase().includes('mac')
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
typeof global !== 'undefined' &&
|
||||||
|
global.process?.platform !== 'darwin'
|
||||||
|
)
|
||||||
|
) && (
|
||||||
|
(
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
!window.navigator?.userAgent?.toLowerCase().includes('linux')
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
typeof global !== 'undefined' &&
|
||||||
|
global.process?.platform !== 'linux'
|
||||||
|
)
|
||||||
|
),
|
||||||
hideDOMWindowControls: false,
|
hideDOMWindowControls: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import is from 'electron-is';
|
|||||||
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
import type { InAppMenuConfig } from './index';
|
import type { InAppMenuConfig } from './constants';
|
||||||
import type { MenuContext } from '@/types/contexts';
|
import type { MenuContext } from '@/types/contexts';
|
||||||
import type { MenuTemplate } from '@/menu';
|
import type { MenuTemplate } from '@/menu';
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { JSX, splitProps } from 'solid-js';
|
import { JSX, splitProps } from 'solid-js';
|
||||||
import { css } from 'solid-styled-components';
|
import { css } from 'solid-styled-components';
|
||||||
|
|
||||||
import { cache } from '@/providers/decorators';
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
const menuStyle = cache(() => css`
|
const menuStyle = cache(() => css`
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { css } from 'solid-styled-components';
|
|||||||
import { Transition } from 'solid-transition-group';
|
import { Transition } from 'solid-transition-group';
|
||||||
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
|
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
|
||||||
import { useFloating } from 'solid-floating-ui';
|
import { useFloating } from 'solid-floating-ui';
|
||||||
|
|
||||||
import { cache } from '@/providers/decorators';
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
const panelStyle = cache(() => css`
|
const panelStyle = cache(() => css`
|
||||||
@ -131,6 +132,7 @@ export const Panel = (props: PanelProps) => {
|
|||||||
<Show when={local.open}>
|
<Show when={local.open}>
|
||||||
<ul
|
<ul
|
||||||
{...leftProps}
|
{...leftProps}
|
||||||
|
data-ytmd-sub-panel={true}
|
||||||
ref={setPanel}
|
ref={setPanel}
|
||||||
class={panelStyle()}
|
class={panelStyle()}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Menu, MenuItem } from 'electron';
|
import { Menu, MenuItem } from 'electron';
|
||||||
import { createEffect, createResource, createSignal, Index, Match, onMount, Show, Switch } from 'solid-js';
|
import { createEffect, createResource, createSignal, Index, Match, onCleanup, onMount, Show, Switch } from 'solid-js';
|
||||||
import { css } from 'solid-styled-components';
|
import { css } from 'solid-styled-components';
|
||||||
import { TransitionGroup } from 'solid-transition-group';
|
import { TransitionGroup } from 'solid-transition-group';
|
||||||
|
|
||||||
@ -9,9 +9,10 @@ import { PanelItem } from './PanelItem';
|
|||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
import { WindowController } from './WindowController';
|
import { WindowController } from './WindowController';
|
||||||
|
|
||||||
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
import type { RendererContext } from '@/types/contexts';
|
import type { RendererContext } from '@/types/contexts';
|
||||||
import type { InAppMenuConfig } from '../constants';
|
import type { InAppMenuConfig } from '../constants';
|
||||||
import { cache } from '@/providers/decorators';
|
|
||||||
|
|
||||||
const titleStyle = cache(() => css`
|
const titleStyle = cache(() => css`
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
@ -37,11 +38,16 @@ const titleStyle = cache(() => css`
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
transition: opacity 200ms ease 0s,
|
transition: opacity 200ms ease 0s,
|
||||||
|
transform 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s,
|
||||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
||||||
|
|
||||||
&[data-macos="true"] {
|
&[data-macos="true"] {
|
||||||
padding: 4px 4px 4px 74px;
|
padding: 4px 4px 4px 74px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ytmusic-app:has(ytmusic-player[player-ui-state=FULLSCREEN]) ~ &:not([data-show="true"]) {
|
||||||
|
transform: translateY(calc(-1 * var(--menu-bar-height, 32px)));
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const separatorStyle = cache(() => css`
|
const separatorStyle = cache(() => css`
|
||||||
@ -161,6 +167,7 @@ export const TitleBar = (props: TitleBarProps) => {
|
|||||||
const [ignoreTransition, setIgnoreTransition] = createSignal(false);
|
const [ignoreTransition, setIgnoreTransition] = createSignal(false);
|
||||||
const [openTarget, setOpenTarget] = createSignal<HTMLElement | null>(null);
|
const [openTarget, setOpenTarget] = createSignal<HTMLElement | null>(null);
|
||||||
const [menu, setMenu] = createSignal<Menu | null>(null);
|
const [menu, setMenu] = createSignal<Menu | null>(null);
|
||||||
|
const [mouseY, setMouseY] = createSignal(0);
|
||||||
|
|
||||||
const [data, { refetch }] = createResource(async () => await props.ipc.invoke('get-menu') as Promise<Menu | null>);
|
const [data, { refetch }] = createResource(async () => await props.ipc.invoke('get-menu') as Promise<Menu | null>);
|
||||||
const [isMaximized, { refetch: refetchMaximize }] = createResource(async () => await props.ipc.invoke('window-is-maximized') as Promise<boolean>);
|
const [isMaximized, { refetch: refetchMaximize }] = createResource(async () => await props.ipc.invoke('window-is-maximized') as Promise<boolean>);
|
||||||
@ -223,6 +230,10 @@ export const TitleBar = (props: TitleBarProps) => {
|
|||||||
setMenu(await refreshMenuItem(menuData, commandId));
|
setMenu(await refreshMenuItem(menuData, commandId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listener = (e: MouseEvent) => {
|
||||||
|
setMouseY(e.clientY);
|
||||||
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
props.ipc.on('close-all-in-app-menu-panel', async () => {
|
props.ipc.on('close-all-in-app-menu-panel', async () => {
|
||||||
setIgnoreTransition(true);
|
setIgnoreTransition(true);
|
||||||
@ -243,16 +254,49 @@ export const TitleBar = (props: TitleBarProps) => {
|
|||||||
|
|
||||||
props.ipc.on('window-maximize', refetchMaximize);
|
props.ipc.on('window-maximize', refetchMaximize);
|
||||||
props.ipc.on('window-unmaximize', refetchMaximize);
|
props.ipc.on('window-unmaximize', refetchMaximize);
|
||||||
|
|
||||||
|
// close menu when the outside of the panel or sub-panel is clicked
|
||||||
|
document.body.addEventListener('click', (e) => {
|
||||||
|
if (
|
||||||
|
e.target instanceof HTMLElement &&
|
||||||
|
!(
|
||||||
|
e.target.closest('nav[data-ytmd-main-panel]') ||
|
||||||
|
e.target.closest('ul[data-ytmd-sub-panel]')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setOpenTarget(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// tracking mouse position
|
||||||
|
window.addEventListener('mousemove', listener);
|
||||||
|
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||||
|
ytmusicAppLayout?.addEventListener("scroll",()=>{
|
||||||
|
const scrollValue = ytmusicAppLayout.scrollTop;
|
||||||
|
if (scrollValue > 20){
|
||||||
|
ytmusicAppLayout.classList.add("content-scrolled");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
ytmusicAppLayout.classList.remove("content-scrolled");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!menu() && data()) {
|
if (!menu() && data()) {
|
||||||
setMenu(data() ?? null);
|
setMenu(data() ?? null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
window.removeEventListener('mousemove', listener);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav class={titleStyle()} data-macos={props.isMacOS}>
|
<nav data-ytmd-main-panel={true} class={titleStyle()} data-macos={props.isMacOS} data-show={mouseY() < 32}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => setCollapsed(!collapsed())}
|
onClick={() => setCollapsed(!collapsed())}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -4,10 +4,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* youtube-music style */
|
/* youtube-music style */
|
||||||
|
|
||||||
ytmusic-app-layout {
|
ytmusic-app-layout {
|
||||||
|
overflow: scroll;
|
||||||
|
height: calc(100vh - var(--menu-bar-height, 36px));
|
||||||
margin-top: var(--menu-bar-height, 36px) !important;
|
margin-top: var(--menu-bar-height, 36px) !important;
|
||||||
}
|
}
|
||||||
|
ytmusic-app-layout#layout {
|
||||||
|
--ytmusic-nav-bar-offset: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-app-layout::-webkit-scrollbar{
|
||||||
|
width: var(--ytmusic-scrollbar-width);
|
||||||
|
}
|
||||||
|
ytmusic-app-layout::-webkit-scrollbar-thumb{
|
||||||
|
background-color: rgb(126, 126, 126);
|
||||||
|
}
|
||||||
|
|
||||||
ytmusic-app-layout > [slot='nav-bar'],
|
ytmusic-app-layout > [slot='nav-bar'],
|
||||||
#nav-bar-background.ytmusic-app-layout {
|
#nav-bar-background.ytmusic-app-layout {
|
||||||
@ -51,3 +62,13 @@ ytmusic-guide-renderer {
|
|||||||
100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height)
|
100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height)
|
||||||
) !important;
|
) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* fix mini player behavior */
|
||||||
|
ytmusic-app-layout ytmusic-player-page[is-mweb-modernization-enabled] .side-panel.ytmusic-player-page {
|
||||||
|
transform: translate(0, calc(var(--ytmusic-player-page-inner-height) - var(--ytmusic-player-page-tabs-header-height) - var(--ytmusic-player-page-player-bar-height) - var(--menu-bar-height, 32px) ));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ytm-bugs: see https://github.com/th-ch/youtube-music/issues/1737 */
|
||||||
|
html {
|
||||||
|
scrollbar-color: unset;
|
||||||
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export const fetchFromGenius = async (metadata: SongInfo) => {
|
|||||||
const songArtist = `${cleanupName(metadata.artist)}`;
|
const songArtist = `${cleanupName(metadata.artist)}`;
|
||||||
let lyrics: string | null;
|
let lyrics: string | null;
|
||||||
|
|
||||||
/* Uses Regex to test the title and artist first for said characters if romanization is enabled. Otherwise normal
|
/* Uses Regex to test the title and artist first for said characters if romanization is enabled. Otherwise, normal
|
||||||
Genius Lyrics behavior is observed.
|
Genius Lyrics behavior is observed.
|
||||||
*/
|
*/
|
||||||
let hasAsianChars = false;
|
let hasAsianChars = false;
|
||||||
|
|||||||
@ -74,7 +74,7 @@ export class Connection {
|
|||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect() {
|
disconnect() {
|
||||||
if (this._mode === 'disconnected') throw new Error('Already disconnected');
|
if (this._mode === 'disconnected') throw new Error('Already disconnected');
|
||||||
|
|
||||||
this._mode = 'disconnected';
|
this._mode = 'disconnected';
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import { t } from '@/i18n';
|
|||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import promptOptions from '@/providers/prompt-options';
|
import promptOptions from '@/providers/prompt-options';
|
||||||
|
|
||||||
import { AppAPI, getDefaultProfile, Permission, Profile, VideoData } from './types';
|
import { getDefaultProfile, type Permission, type Profile, type VideoData } from './types';
|
||||||
import { Queue } from './queue';
|
import { Queue } from './queue';
|
||||||
import { Connection, ConnectionEventUnion } from './connection';
|
import { Connection, type ConnectionEventUnion } from './connection';
|
||||||
import { createHostPopup } from './ui/host';
|
import { createHostPopup } from './ui/host';
|
||||||
import { createGuestPopup } from './ui/guest';
|
import { createGuestPopup } from './ui/guest';
|
||||||
import { createSettingPopup } from './ui/setting';
|
import { createSettingPopup } from './ui/setting';
|
||||||
@ -19,6 +19,7 @@ import style from './style.css?inline';
|
|||||||
import type { YoutubePlayer } from '@/types/youtube-player';
|
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||||
import type { RendererContext } from '@/types/contexts';
|
import type { RendererContext } from '@/types/contexts';
|
||||||
import type { VideoDataChanged } from '@/types/video-data-changed';
|
import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||||
|
import type { AppElement } from '@/types/queue';
|
||||||
|
|
||||||
type RawAccountData = {
|
type RawAccountData = {
|
||||||
accountName: {
|
accountName: {
|
||||||
@ -41,7 +42,7 @@ export default createPlugin<
|
|||||||
{
|
{
|
||||||
connection?: Connection;
|
connection?: Connection;
|
||||||
ipc?: RendererContext<never>['ipc'];
|
ipc?: RendererContext<never>['ipc'];
|
||||||
api: HTMLElement & AppAPI | null;
|
api: AppElement | null;
|
||||||
queue?: Queue;
|
queue?: Queue;
|
||||||
playerApi?: YoutubePlayer;
|
playerApi?: YoutubePlayer;
|
||||||
showPrompt: (title: string, label: string) => Promise<string>;
|
showPrompt: (title: string, label: string) => Promise<string>;
|
||||||
@ -557,7 +558,7 @@ export default createPlugin<
|
|||||||
start({ ipc }) {
|
start({ ipc }) {
|
||||||
this.ipc = ipc;
|
this.ipc = ipc;
|
||||||
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
|
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
|
||||||
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
|
this.api = document.querySelector<AppElement>('ytmusic-app');
|
||||||
|
|
||||||
/* setup */
|
/* setup */
|
||||||
document.querySelector('#right-content > ytmusic-settings-button')?.insertAdjacentHTML('beforebegin', settingHTML);
|
document.querySelector('#right-content > ytmusic-settings-button')?.insertAdjacentHTML('beforebegin', settingHTML);
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { getMusicQueueRenderer } from './song';
|
import { getMusicQueueRenderer } from './song';
|
||||||
import { mapQueueItem } from './utils';
|
import { mapQueueItem } from './utils';
|
||||||
|
|
||||||
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
import type { Profile, QueueAPI, VideoData } from '../types';
|
import type { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
||||||
|
import type { Profile, VideoData } from '../types';
|
||||||
import type { QueueItem } from '@/types/datahost-get-state';
|
import type { QueueItem } from '@/types/datahost-get-state';
|
||||||
|
import type { QueueElement } from '@/types/queue';
|
||||||
|
|
||||||
const getHeaderPayload = (() => {
|
const getHeaderPayload = (() => {
|
||||||
let payload: {
|
let payload: {
|
||||||
@ -103,26 +104,29 @@ const getHeaderPayload = (() => {
|
|||||||
export type QueueOptions = {
|
export type QueueOptions = {
|
||||||
videoList?: VideoData[];
|
videoList?: VideoData[];
|
||||||
owner?: Profile;
|
owner?: Profile;
|
||||||
queue?: HTMLElement & QueueAPI;
|
queue?: QueueElement;
|
||||||
getProfile: (id: string) => Profile | undefined;
|
getProfile: (id: string) => Profile | undefined;
|
||||||
}
|
}
|
||||||
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
||||||
|
|
||||||
export class Queue {
|
export class Queue {
|
||||||
private queue: (HTMLElement & QueueAPI);
|
private readonly queue: QueueElement;
|
||||||
|
|
||||||
private originalDispatch?: (obj: {
|
private originalDispatch?: (obj: {
|
||||||
type: string;
|
type: string;
|
||||||
payload?: { items?: QueueItem[] | undefined; };
|
payload?: { items?: QueueItem[] | undefined; };
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|
||||||
private internalDispatch = false;
|
private internalDispatch = false;
|
||||||
private ignoreFlag = false;
|
private ignoreFlag = false;
|
||||||
private listeners: QueueEventListener[] = [];
|
private listeners: QueueEventListener[] = [];
|
||||||
private owner: Profile | null = null;
|
|
||||||
private getProfile: (id: string) => Profile | undefined;
|
private owner: Profile | null;
|
||||||
|
private readonly getProfile: (id: string) => Profile | undefined;
|
||||||
|
|
||||||
constructor(options: QueueOptions) {
|
constructor(options: QueueOptions) {
|
||||||
this.getProfile = options.getProfile;
|
this.getProfile = options.getProfile;
|
||||||
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue')!;
|
this.queue = options.queue ?? (document.querySelector<QueueElement>('#queue')!);
|
||||||
this.owner = options.owner ?? null;
|
this.owner = options.owner ?? null;
|
||||||
this._videoList = options.videoList ?? [];
|
this._videoList = options.videoList ?? [];
|
||||||
}
|
}
|
||||||
@ -135,11 +139,11 @@ export class Queue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get selectedIndex() {
|
get selectedIndex() {
|
||||||
return mapQueueItem((it) => it?.selected, this.queue.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
return mapQueueItem((it) => it?.selected, this.queue.queue.store.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get rawItems() {
|
get rawItems() {
|
||||||
return this.queue?.store.getState().queue.items;
|
return this.queue?.queue.store.store.getState().queue.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
get flatItems() {
|
get flatItems() {
|
||||||
@ -169,8 +173,8 @@ export class Queue {
|
|||||||
this.queue?.dispatch({
|
this.queue?.dispatch({
|
||||||
type: 'ADD_ITEMS',
|
type: 'ADD_ITEMS',
|
||||||
payload: {
|
payload: {
|
||||||
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
|
nextQueueItemId: this.queue.queue.store.store.getState().queue.nextQueueItemId,
|
||||||
index: index ?? this.queue.store.getState().queue.items.length ?? 0,
|
index: index ?? this.queue.queue.store.store.getState().queue.items.length ?? 0,
|
||||||
items,
|
items,
|
||||||
shuffleEnabled: false,
|
shuffleEnabled: false,
|
||||||
shouldAssignIds: true
|
shouldAssignIds: true
|
||||||
@ -249,7 +253,7 @@ export class Queue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.originalDispatch) this.queue.store.dispatch = this.originalDispatch;
|
if (this.originalDispatch) this.queue.queue.store.store.dispatch = this.originalDispatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
injection() {
|
injection() {
|
||||||
@ -258,8 +262,8 @@ export class Queue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.originalDispatch = this.queue.store.dispatch;
|
this.originalDispatch = this.queue.queue.store.store.dispatch;
|
||||||
this.queue.store.dispatch = (event) => {
|
this.queue.queue.store.store.dispatch = (event) => {
|
||||||
if (!this.queue || !this.owner) {
|
if (!this.queue || !this.owner) {
|
||||||
console.error('Queue is not initialized!');
|
console.error('Queue is not initialized!');
|
||||||
return;
|
return;
|
||||||
@ -361,10 +365,13 @@ export class Queue {
|
|||||||
|
|
||||||
const fakeContext = {
|
const fakeContext = {
|
||||||
...this.queue,
|
...this.queue,
|
||||||
store: {
|
queue: {
|
||||||
...this.queue.store,
|
...this.queue.queue,
|
||||||
dispatch: this.originalDispatch
|
store: {
|
||||||
}
|
...this.queue.queue.store,
|
||||||
|
dispatch: this.originalDispatch,
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
this.originalDispatch?.call(fakeContext, event);
|
this.originalDispatch?.call(fakeContext, event);
|
||||||
};
|
};
|
||||||
@ -400,7 +407,7 @@ export class Queue {
|
|||||||
type: 'UPDATE_ITEMS',
|
type: 'UPDATE_ITEMS',
|
||||||
payload: {
|
payload: {
|
||||||
items: items,
|
items: items,
|
||||||
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
|
nextQueueItemId: this.queue.queue.store.store.getState().queue.nextQueueItemId,
|
||||||
shouldAssignIds: true,
|
shouldAssignIds: true,
|
||||||
currentIndex: -1
|
currentIndex: -1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,3 @@
|
|||||||
import { YoutubePlayer } from '@/types/youtube-player';
|
|
||||||
import { GetState, QueueItem } from '@/types/datahost-get-state';
|
|
||||||
|
|
||||||
type StoreState = GetState;
|
|
||||||
type Store = {
|
|
||||||
dispatch: (obj: {
|
|
||||||
type: string;
|
|
||||||
payload?: {
|
|
||||||
items?: QueueItem[];
|
|
||||||
};
|
|
||||||
}) => void;
|
|
||||||
|
|
||||||
getState: () => StoreState;
|
|
||||||
replaceReducer: (param1: unknown) => unknown;
|
|
||||||
subscribe: (callback: () => void) => unknown;
|
|
||||||
}
|
|
||||||
export type QueueAPI = {
|
|
||||||
dispatch(obj: {
|
|
||||||
type: string;
|
|
||||||
payload?: unknown;
|
|
||||||
}): void;
|
|
||||||
getItems(): unknown[];
|
|
||||||
store: Store;
|
|
||||||
continuation?: string;
|
|
||||||
autoPlaying?: boolean;
|
|
||||||
};
|
|
||||||
export type AppAPI = {
|
|
||||||
queue_: QueueAPI;
|
|
||||||
playerApi_: YoutubePlayer;
|
|
||||||
openToast: (message: string) => void;
|
|
||||||
|
|
||||||
// TODO: Add more
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Profile = {
|
export type Profile = {
|
||||||
id: string;
|
id: string;
|
||||||
handleId: string;
|
handleId: string;
|
||||||
|
|||||||
@ -307,9 +307,9 @@ export default (
|
|||||||
savedNotification?.close();
|
savedNotification?.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
changeProtocolHandler((cmd) => {
|
changeProtocolHandler((cmd, args) => {
|
||||||
if (Object.keys(songControls).includes(cmd)) {
|
if (Object.keys(songControls).includes(cmd)) {
|
||||||
songControls[cmd as keyof typeof songControls]();
|
songControls[cmd as keyof typeof songControls](args as never);
|
||||||
if (
|
if (
|
||||||
config().refreshOnPlayPause &&
|
config().refreshOnPlayPause &&
|
||||||
(cmd === 'pause' || (cmd === 'play' && !config().unpauseNotification))
|
(cmd === 'pause' || (cmd === 'play' && !config().unpauseNotification))
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { app, NativeImage } from 'electron';
|
|||||||
|
|
||||||
import youtubeMusicIcon from '@assets/youtube-music.png?asset&asarUnpack';
|
import youtubeMusicIcon from '@assets/youtube-music.png?asset&asarUnpack';
|
||||||
|
|
||||||
import { cache } from '@/providers/decorators';
|
|
||||||
import { SongInfo } from '@/providers/song-info';
|
import { SongInfo } from '@/providers/song-info';
|
||||||
|
|
||||||
import type { NotificationsPluginConfig } from './index';
|
import type { NotificationsPluginConfig } from './index';
|
||||||
@ -30,7 +29,7 @@ export const urgencyLevels = [
|
|||||||
{ name: 'High', value: 'critical' } as const,
|
{ name: 'High', value: 'critical' } as const,
|
||||||
];
|
];
|
||||||
|
|
||||||
const nativeImageToLogo = cache((nativeImage: NativeImage) => {
|
const nativeImageToLogo = (nativeImage: NativeImage) => {
|
||||||
const temporaryImage = nativeImage.resize({ height: 256 });
|
const temporaryImage = nativeImage.resize({ height: 256 });
|
||||||
const margin = Math.max(temporaryImage.getSize().width - 256, 0);
|
const margin = Math.max(temporaryImage.getSize().width - 256, 0);
|
||||||
|
|
||||||
@ -40,7 +39,7 @@ const nativeImageToLogo = cache((nativeImage: NativeImage) => {
|
|||||||
width: 256,
|
width: 256,
|
||||||
height: 256,
|
height: 256,
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
export const notificationImage = (
|
export const notificationImage = (
|
||||||
songInfo: SongInfo,
|
songInfo: SongInfo,
|
||||||
@ -66,7 +65,7 @@ export const notificationImage = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const saveImage = cache((img: NativeImage, savePath: string) => {
|
export const saveImage = (img: NativeImage, savePath: string) => {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(savePath, img.toPNG());
|
fs.writeFileSync(savePath, img.toPNG());
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@ -76,7 +75,7 @@ export const saveImage = cache((img: NativeImage, savePath: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return savePath;
|
return savePath;
|
||||||
});
|
};
|
||||||
|
|
||||||
export const snakeToCamel = (string_: string) =>
|
export const snakeToCamel = (string_: string) =>
|
||||||
string_.replaceAll(/([-_][a-z]|^[a-z])/g, (group) =>
|
string_.replaceAll(/([-_][a-z]|^[a-z])/g, (group) =>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export interface ScrobblerPluginConfig {
|
|||||||
*
|
*
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
scrobble_other_media: boolean,
|
scrobbleOtherMedia: boolean,
|
||||||
scrobblers: {
|
scrobblers: {
|
||||||
lastfm: {
|
lastfm: {
|
||||||
/**
|
/**
|
||||||
@ -27,19 +27,19 @@ export interface ScrobblerPluginConfig {
|
|||||||
/**
|
/**
|
||||||
* Session key used for scrobbling
|
* Session key used for scrobbling
|
||||||
*/
|
*/
|
||||||
session_key: string | undefined,
|
sessionKey: string | undefined,
|
||||||
/**
|
/**
|
||||||
* Root of the Last.fm API
|
* Root of the Last.fm API
|
||||||
*
|
*
|
||||||
* @default 'http://ws.audioscrobbler.com/2.0/'
|
* @default 'http://ws.audioscrobbler.com/2.0/'
|
||||||
*/
|
*/
|
||||||
api_root: string,
|
apiRoot: string,
|
||||||
/**
|
/**
|
||||||
* Last.fm api key registered by @semvis123
|
* Last.fm api key registered by @semvis123
|
||||||
*
|
*
|
||||||
* @default '04d76faaac8726e60988e14c105d421a'
|
* @default '04d76faaac8726e60988e14c105d421a'
|
||||||
*/
|
*/
|
||||||
api_key: string,
|
apiKey: string,
|
||||||
/**
|
/**
|
||||||
* Last.fm api secret registered by @semvis123
|
* Last.fm api secret registered by @semvis123
|
||||||
*
|
*
|
||||||
@ -63,27 +63,27 @@ export interface ScrobblerPluginConfig {
|
|||||||
*
|
*
|
||||||
* @default 'https://api.listenbrainz.org/1/'
|
* @default 'https://api.listenbrainz.org/1/'
|
||||||
*/
|
*/
|
||||||
api_root: string,
|
apiRoot: string,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultConfig: ScrobblerPluginConfig = {
|
export const defaultConfig: ScrobblerPluginConfig = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
scrobble_other_media: true,
|
scrobbleOtherMedia: true,
|
||||||
scrobblers: {
|
scrobblers: {
|
||||||
lastfm: {
|
lastfm: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
token: undefined,
|
token: undefined,
|
||||||
session_key: undefined,
|
sessionKey: undefined,
|
||||||
api_root: 'http://ws.audioscrobbler.com/2.0/',
|
apiRoot: 'https://ws.audioscrobbler.com/2.0/',
|
||||||
api_key: '04d76faaac8726e60988e14c105d421a',
|
apiKey: '04d76faaac8726e60988e14c105d421a',
|
||||||
secret: 'a5d2a36fdf64819290f6982481eaffa2',
|
secret: 'a5d2a36fdf64819290f6982481eaffa2',
|
||||||
},
|
},
|
||||||
listenbrainz: {
|
listenbrainz: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
token: undefined,
|
token: undefined,
|
||||||
api_root: 'https://api.listenbrainz.org/1/',
|
apiRoot: 'https://api.listenbrainz.org/1/',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
import { BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
import registerCallback, { MediaType, type SongInfo } from '@/providers/song-info';
|
||||||
import { createBackend } from '@/utils';
|
import { createBackend } from '@/utils';
|
||||||
|
|
||||||
import { ScrobblerPluginConfig } from './index';
|
|
||||||
import { LastFmScrobbler } from './services/lastfm';
|
import { LastFmScrobbler } from './services/lastfm';
|
||||||
import { ListenbrainzScrobbler } from './services/listenbrainz';
|
import { ListenbrainzScrobbler } from './services/listenbrainz';
|
||||||
import { ScrobblerBase } from './services/base';
|
|
||||||
|
import type { ScrobblerPluginConfig } from './index';
|
||||||
|
import type { ScrobblerBase } from './services/base';
|
||||||
|
|
||||||
export type SetConfType = (
|
export type SetConfType = (
|
||||||
conf: Partial<Omit<ScrobblerPluginConfig, 'enabled'>>,
|
conf: Partial<Omit<ScrobblerPluginConfig, 'enabled'>>,
|
||||||
@ -12,14 +15,17 @@ export type SetConfType = (
|
|||||||
|
|
||||||
export const backend = createBackend<{
|
export const backend = createBackend<{
|
||||||
config?: ScrobblerPluginConfig;
|
config?: ScrobblerPluginConfig;
|
||||||
|
window?: BrowserWindow;
|
||||||
enabledScrobblers: Map<string, ScrobblerBase>;
|
enabledScrobblers: Map<string, ScrobblerBase>;
|
||||||
toggleScrobblers(config: ScrobblerPluginConfig): void;
|
toggleScrobblers(config: ScrobblerPluginConfig, window: BrowserWindow): void;
|
||||||
|
createSessions(config: ScrobblerPluginConfig, setConfig: SetConfType): Promise<void>;
|
||||||
|
setConfig?: SetConfType;
|
||||||
}, ScrobblerPluginConfig>({
|
}, ScrobblerPluginConfig>({
|
||||||
enabledScrobblers: new Map(),
|
enabledScrobblers: new Map(),
|
||||||
|
|
||||||
toggleScrobblers(config: ScrobblerPluginConfig) {
|
toggleScrobblers(config: ScrobblerPluginConfig, window: BrowserWindow) {
|
||||||
if (config.scrobblers.lastfm && config.scrobblers.lastfm.enabled) {
|
if (config.scrobblers.lastfm && config.scrobblers.lastfm.enabled) {
|
||||||
this.enabledScrobblers.set('lastfm', new LastFmScrobbler());
|
this.enabledScrobblers.set('lastfm', new LastFmScrobbler(window));
|
||||||
} else {
|
} else {
|
||||||
this.enabledScrobblers.delete('lastfm');
|
this.enabledScrobblers.delete('lastfm');
|
||||||
}
|
}
|
||||||
@ -31,20 +37,27 @@ export const backend = createBackend<{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async start({
|
async createSessions(config: ScrobblerPluginConfig, setConfig: SetConfType) {
|
||||||
getConfig,
|
|
||||||
setConfig,
|
|
||||||
}) {
|
|
||||||
const config = this.config = await getConfig();
|
|
||||||
// This will store the timeout that will trigger addScrobble
|
|
||||||
let scrobbleTimer: NodeJS.Timeout | undefined;
|
|
||||||
|
|
||||||
this.toggleScrobblers(config);
|
|
||||||
for (const [, scrobbler] of this.enabledScrobblers) {
|
for (const [, scrobbler] of this.enabledScrobblers) {
|
||||||
if (!scrobbler.isSessionCreated(config)) {
|
if (!scrobbler.isSessionCreated(config)) {
|
||||||
await scrobbler.createSession(config, setConfig);
|
await scrobbler.createSession(config, setConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async start({
|
||||||
|
getConfig,
|
||||||
|
setConfig,
|
||||||
|
window,
|
||||||
|
}) {
|
||||||
|
const config = this.config = await getConfig();
|
||||||
|
// This will store the timeout that will trigger addScrobble
|
||||||
|
let scrobbleTimer: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
|
this.window = window;
|
||||||
|
this.toggleScrobblers(config, window);
|
||||||
|
await this.createSessions(config, setConfig);
|
||||||
|
this.setConfig = setConfig;
|
||||||
|
|
||||||
registerCallback((songInfo: SongInfo) => {
|
registerCallback((songInfo: SongInfo) => {
|
||||||
// Set remove the old scrobble timer
|
// Set remove the old scrobble timer
|
||||||
@ -52,7 +65,7 @@ export const backend = createBackend<{
|
|||||||
if (!songInfo.isPaused) {
|
if (!songInfo.isPaused) {
|
||||||
const configNonnull = this.config!;
|
const configNonnull = this.config!;
|
||||||
// Scrobblers normally have no trouble working with official music videos
|
// Scrobblers normally have no trouble working with official music videos
|
||||||
if (!configNonnull.scrobble_other_media && (songInfo.mediaType !== 'AUDIO' && songInfo.mediaType !== 'ORIGINAL_MUSIC_VIDEO')) {
|
if (!configNonnull.scrobbleOtherMedia && (songInfo.mediaType !== MediaType.Audio && songInfo.mediaType !== MediaType.OriginalMusicVideo)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,12 +84,25 @@ export const backend = createBackend<{
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onConfigChange(newConfig: ScrobblerPluginConfig) {
|
async onConfigChange(newConfig: ScrobblerPluginConfig) {
|
||||||
this.enabledScrobblers.clear();
|
this.enabledScrobblers.clear();
|
||||||
|
|
||||||
this.config = newConfig;
|
this.toggleScrobblers(newConfig, this.window!);
|
||||||
|
for (const [scrobblerName, scrobblerConfig] of Object.entries(newConfig.scrobblers)) {
|
||||||
|
if (scrobblerConfig.enabled) {
|
||||||
|
const scrobbler = this.enabledScrobblers.get(scrobblerName);
|
||||||
|
if (
|
||||||
|
this.config?.scrobblers?.[scrobblerName as keyof typeof newConfig.scrobblers]?.enabled !== scrobblerConfig.enabled &&
|
||||||
|
scrobbler &&
|
||||||
|
!scrobbler.isSessionCreated(newConfig) &&
|
||||||
|
this.setConfig
|
||||||
|
) {
|
||||||
|
await scrobbler.createSession(newConfig, this.setConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.toggleScrobblers(this.config);
|
this.config = newConfig;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ async function promptLastFmOptions(options: ScrobblerPluginConfig, setConfig: Se
|
|||||||
multiInputOptions: [
|
multiInputOptions: [
|
||||||
{
|
{
|
||||||
label: t('plugins.scrobbler.prompt.lastfm.api-key'),
|
label: t('plugins.scrobbler.prompt.lastfm.api-key'),
|
||||||
value: options.scrobblers.lastfm?.api_key,
|
value: options.scrobblers.lastfm?.apiKey,
|
||||||
inputAttrs: {
|
inputAttrs: {
|
||||||
type: 'text'
|
type: 'text'
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ async function promptLastFmOptions(options: ScrobblerPluginConfig, setConfig: Se
|
|||||||
|
|
||||||
if (output) {
|
if (output) {
|
||||||
if (output[0]) {
|
if (output[0]) {
|
||||||
options.scrobblers.lastfm.api_key = output[0];
|
options.scrobblers.lastfm.apiKey = output[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output[1]) {
|
if (output[1]) {
|
||||||
@ -82,9 +82,9 @@ export const onMenu = async ({
|
|||||||
{
|
{
|
||||||
label: t('plugins.scrobbler.menu.scrobble-other-media'),
|
label: t('plugins.scrobbler.menu.scrobble-other-media'),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: Boolean(config.scrobble_other_media),
|
checked: Boolean(config.scrobbleOtherMedia),
|
||||||
click(item) {
|
click(item) {
|
||||||
config.scrobble_other_media = item.checked;
|
config.scrobbleOtherMedia = item.checked;
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -96,7 +96,7 @@ export const onMenu = async ({
|
|||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: Boolean(config.scrobblers.lastfm?.enabled),
|
checked: Boolean(config.scrobblers.lastfm?.enabled),
|
||||||
click(item) {
|
click(item) {
|
||||||
backend.toggleScrobblers(config);
|
backend.toggleScrobblers(config, window);
|
||||||
config.scrobblers.lastfm.enabled = item.checked;
|
config.scrobblers.lastfm.enabled = item.checked;
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
},
|
},
|
||||||
@ -117,7 +117,7 @@ export const onMenu = async ({
|
|||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: Boolean(config.scrobblers.listenbrainz?.enabled),
|
checked: Boolean(config.scrobblers.listenbrainz?.enabled),
|
||||||
click(item) {
|
click(item) {
|
||||||
backend.toggleScrobblers(config);
|
backend.toggleScrobblers(config, window);
|
||||||
config.scrobblers.listenbrainz.enabled = item.checked;
|
config.scrobblers.listenbrainz.enabled = item.checked;
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { ScrobblerPluginConfig } from '../index';
|
import type { ScrobblerPluginConfig } from '../index';
|
||||||
import { SetConfType } from '../main';
|
import type { SetConfType } from '../main';
|
||||||
|
|
||||||
import type { SongInfo } from '@/providers/song-info';
|
import type { SongInfo } from '@/providers/song-info';
|
||||||
|
|
||||||
export abstract class ScrobblerBase {
|
export abstract class ScrobblerBase {
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
|
|
||||||
import { net, shell } from 'electron';
|
import { BrowserWindow, dialog, net } from 'electron';
|
||||||
|
|
||||||
import { ScrobblerBase } from './base';
|
import { ScrobblerBase } from './base';
|
||||||
|
|
||||||
import { ScrobblerPluginConfig } from '../index';
|
import { t } from '@/i18n';
|
||||||
import { SetConfType } from '../main';
|
|
||||||
|
|
||||||
|
import type { ScrobblerPluginConfig } from '../index';
|
||||||
|
import type { SetConfType } from '../main';
|
||||||
import type { SongInfo } from '@/providers/song-info';
|
import type { SongInfo } from '@/providers/song-info';
|
||||||
|
|
||||||
interface LastFmData {
|
interface LastFmData {
|
||||||
@ -28,21 +29,32 @@ interface LastFmSongData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LastFmScrobbler extends ScrobblerBase {
|
export class LastFmScrobbler extends ScrobblerBase {
|
||||||
isSessionCreated(config: ScrobblerPluginConfig): boolean {
|
mainWindow: BrowserWindow;
|
||||||
return !!config.scrobblers.lastfm.session_key;
|
|
||||||
|
constructor(mainWindow: BrowserWindow) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(config: ScrobblerPluginConfig, setConfig: SetConfType): Promise<ScrobblerPluginConfig> {
|
override isSessionCreated(config: ScrobblerPluginConfig): boolean {
|
||||||
|
return !!config.scrobblers.lastfm.sessionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async createSession(
|
||||||
|
config: ScrobblerPluginConfig,
|
||||||
|
setConfig: SetConfType,
|
||||||
|
): Promise<ScrobblerPluginConfig> {
|
||||||
// Get and store the session key
|
// Get and store the session key
|
||||||
const data = {
|
const data = {
|
||||||
api_key: config.scrobblers.lastfm.api_key,
|
api_key: config.scrobblers.lastfm.apiKey,
|
||||||
format: 'json',
|
format: 'json',
|
||||||
method: 'auth.getsession',
|
method: 'auth.getsession',
|
||||||
token: config.scrobblers.lastfm.token,
|
token: config.scrobblers.lastfm.token,
|
||||||
};
|
};
|
||||||
const apiSignature = createApiSig(data, config.scrobblers.lastfm.secret);
|
const apiSignature = createApiSig(data, config.scrobblers.lastfm.secret);
|
||||||
const response = await net.fetch(
|
const response = await net.fetch(
|
||||||
`${config.scrobblers.lastfm.api_root}${createQueryString(data, apiSignature)}`,
|
`${config.scrobblers.lastfm.apiRoot}${createQueryString(data, apiSignature)}`,
|
||||||
);
|
);
|
||||||
const json = (await response.json()) as {
|
const json = (await response.json()) as {
|
||||||
error?: string;
|
error?: string;
|
||||||
@ -52,18 +64,25 @@ export class LastFmScrobbler extends ScrobblerBase {
|
|||||||
};
|
};
|
||||||
if (json.error) {
|
if (json.error) {
|
||||||
config.scrobblers.lastfm.token = await createToken(config);
|
config.scrobblers.lastfm.token = await createToken(config);
|
||||||
await authenticate(config);
|
// If is successful, we need retry the request
|
||||||
setConfig(config);
|
authenticate(config, this.mainWindow).then((it) => {
|
||||||
|
if (it) {
|
||||||
|
this.createSession(config, setConfig);
|
||||||
|
} else {
|
||||||
|
// failed
|
||||||
|
setConfig(config);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (json.session) {
|
if (json.session) {
|
||||||
config.scrobblers.lastfm.session_key = json.session.key;
|
config.scrobblers.lastfm.sessionKey = json.session.key;
|
||||||
}
|
}
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
setNowPlaying(songInfo: SongInfo, config: ScrobblerPluginConfig, setConfig: SetConfType): void {
|
override setNowPlaying(songInfo: SongInfo, config: ScrobblerPluginConfig, setConfig: SetConfType): void {
|
||||||
if (!config.scrobblers.lastfm.session_key) {
|
if (!config.scrobblers.lastfm.sessionKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,8 +93,8 @@ export class LastFmScrobbler extends ScrobblerBase {
|
|||||||
this.postSongDataToAPI(songInfo, config, data, setConfig);
|
this.postSongDataToAPI(songInfo, config, data, setConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
addScrobble(songInfo: SongInfo, config: ScrobblerPluginConfig, setConfig: SetConfType): void {
|
override addScrobble(songInfo: SongInfo, config: ScrobblerPluginConfig, setConfig: SetConfType): void {
|
||||||
if (!config.scrobblers.lastfm.session_key) {
|
if (!config.scrobblers.lastfm.sessionKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,14 +106,14 @@ export class LastFmScrobbler extends ScrobblerBase {
|
|||||||
this.postSongDataToAPI(songInfo, config, data, setConfig);
|
this.postSongDataToAPI(songInfo, config, data, setConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
async postSongDataToAPI(
|
private async postSongDataToAPI(
|
||||||
songInfo: SongInfo,
|
songInfo: SongInfo,
|
||||||
config: ScrobblerPluginConfig,
|
config: ScrobblerPluginConfig,
|
||||||
data: LastFmData,
|
data: LastFmData,
|
||||||
setConfig: SetConfType,
|
setConfig: SetConfType,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// This sends a post request to the api, and adds the common data
|
// This sends a post request to the api, and adds the common data
|
||||||
if (!config.scrobblers.lastfm.session_key) {
|
if (!config.scrobblers.lastfm.sessionKey) {
|
||||||
await this.createSession(config, setConfig);
|
await this.createSession(config, setConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,8 +122,8 @@ export class LastFmScrobbler extends ScrobblerBase {
|
|||||||
duration: songInfo.songDuration,
|
duration: songInfo.songDuration,
|
||||||
artist: songInfo.artist,
|
artist: songInfo.artist,
|
||||||
...(songInfo.album ? { album: songInfo.album } : undefined), // Will be undefined if current song is a video
|
...(songInfo.album ? { album: songInfo.album } : undefined), // Will be undefined if current song is a video
|
||||||
api_key: config.scrobblers.lastfm.api_key,
|
api_key: config.scrobblers.lastfm.apiKey,
|
||||||
sk: config.scrobblers.lastfm.session_key,
|
sk: config.scrobblers.lastfm.sessionKey,
|
||||||
format: 'json',
|
format: 'json',
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
@ -126,10 +145,16 @@ export class LastFmScrobbler extends ScrobblerBase {
|
|||||||
}) => {
|
}) => {
|
||||||
if (error?.response?.data?.error === 9) {
|
if (error?.response?.data?.error === 9) {
|
||||||
// Session key is invalid, so remove it from the config and reauthenticate
|
// Session key is invalid, so remove it from the config and reauthenticate
|
||||||
config.scrobblers.lastfm.session_key = undefined;
|
config.scrobblers.lastfm.sessionKey = undefined;
|
||||||
config.scrobblers.lastfm.token = await createToken(config);
|
config.scrobblers.lastfm.token = await createToken(config);
|
||||||
await authenticate(config);
|
authenticate(config, this.mainWindow).then((it) => {
|
||||||
setConfig(config);
|
if (it) {
|
||||||
|
this.createSession(config, setConfig);
|
||||||
|
} else {
|
||||||
|
// failed
|
||||||
|
setConfig(config);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@ -168,17 +193,17 @@ const createQueryString = (
|
|||||||
|
|
||||||
const createApiSig = (parameters: LastFmSongData, secret: string) => {
|
const createApiSig = (parameters: LastFmSongData, secret: string) => {
|
||||||
// This function creates the api signature, see: https://www.last.fm/api/authspec
|
// This function creates the api signature, see: https://www.last.fm/api/authspec
|
||||||
const keys = Object.keys(parameters);
|
|
||||||
|
|
||||||
keys.sort();
|
|
||||||
let sig = '';
|
let sig = '';
|
||||||
for (const key of keys) {
|
|
||||||
if (key === 'format') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
sig += `${key}${parameters[key as keyof LastFmSongData]}`;
|
Object
|
||||||
}
|
.entries(parameters)
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
|
.forEach(([key, value]) => {
|
||||||
|
if (key === 'format') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sig += key + value;
|
||||||
|
});
|
||||||
|
|
||||||
sig += secret;
|
sig += secret;
|
||||||
sig = crypto.createHash('md5').update(sig, 'utf-8').digest('hex');
|
sig = crypto.createHash('md5').update(sig, 'utf-8').digest('hex');
|
||||||
@ -188,14 +213,18 @@ const createApiSig = (parameters: LastFmSongData, secret: string) => {
|
|||||||
const createToken = async ({
|
const createToken = async ({
|
||||||
scrobblers: {
|
scrobblers: {
|
||||||
lastfm: {
|
lastfm: {
|
||||||
api_key: apiKey,
|
apiKey,
|
||||||
api_root: apiRoot,
|
apiRoot,
|
||||||
secret,
|
secret,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}: ScrobblerPluginConfig) => {
|
}: ScrobblerPluginConfig) => {
|
||||||
// Creates and stores the auth token
|
// Creates and stores the auth token
|
||||||
const data = {
|
const data: {
|
||||||
|
method: string;
|
||||||
|
api_key: string;
|
||||||
|
format: string;
|
||||||
|
} = {
|
||||||
method: 'auth.gettoken',
|
method: 'auth.gettoken',
|
||||||
api_key: apiKey,
|
api_key: apiKey,
|
||||||
format: 'json',
|
format: 'json',
|
||||||
@ -208,9 +237,68 @@ const createToken = async ({
|
|||||||
return json?.token;
|
return json?.token;
|
||||||
};
|
};
|
||||||
|
|
||||||
const authenticate = async (config: ScrobblerPluginConfig) => {
|
let authWindowOpened = false;
|
||||||
// Asks the user for authentication
|
let latestAuthResult = false;
|
||||||
await shell.openExternal(
|
|
||||||
`https://www.last.fm/api/auth/?api_key=${config.scrobblers.lastfm.api_key}&token=${config.scrobblers.lastfm.token}`,
|
const authenticate = async (config: ScrobblerPluginConfig, mainWindow: BrowserWindow) => {
|
||||||
);
|
return new Promise<boolean>((resolve) => {
|
||||||
|
if (!authWindowOpened) {
|
||||||
|
authWindowOpened = true;
|
||||||
|
const url = `https://www.last.fm/api/auth/?api_key=${config.scrobblers.lastfm.apiKey}&token=${config.scrobblers.lastfm.token}`;
|
||||||
|
const browserWindow = new BrowserWindow({
|
||||||
|
width: 500,
|
||||||
|
height: 600,
|
||||||
|
show: false,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: false,
|
||||||
|
},
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
parent: mainWindow,
|
||||||
|
minimizable: false,
|
||||||
|
maximizable: false,
|
||||||
|
paintWhenInitiallyHidden: true,
|
||||||
|
modal: true,
|
||||||
|
center: true,
|
||||||
|
});
|
||||||
|
browserWindow.loadURL(url).then(() => {
|
||||||
|
browserWindow.show();
|
||||||
|
browserWindow.webContents.on('did-navigate', async (_, newUrl) => {
|
||||||
|
const url = new URL(newUrl);
|
||||||
|
if (url.hostname.endsWith('last.fm')) {
|
||||||
|
if (url.pathname === '/api/auth') {
|
||||||
|
const isApproveScreen = await browserWindow.webContents.executeJavaScript(
|
||||||
|
'!!document.getElementsByName(\'confirm\').length'
|
||||||
|
) as boolean;
|
||||||
|
// successful authentication
|
||||||
|
if (!isApproveScreen) {
|
||||||
|
resolve(true);
|
||||||
|
latestAuthResult = true;
|
||||||
|
browserWindow.close();
|
||||||
|
}
|
||||||
|
} else if (url.pathname === '/api/None') {
|
||||||
|
resolve(false);
|
||||||
|
latestAuthResult = false;
|
||||||
|
browserWindow.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
browserWindow.on('closed', () => {
|
||||||
|
if (!latestAuthResult) {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
title: t('plugins.scrobbler.dialog.lastfm.auth-failed.title'),
|
||||||
|
message: t('plugins.scrobbler.dialog.lastfm.auth-failed.message'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
authWindowOpened = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// wait for the previous window to close
|
||||||
|
while (authWindowOpened) {
|
||||||
|
// wait
|
||||||
|
}
|
||||||
|
resolve(latestAuthResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,10 +2,8 @@ import { net } from 'electron';
|
|||||||
|
|
||||||
import { ScrobblerBase } from './base';
|
import { ScrobblerBase } from './base';
|
||||||
|
|
||||||
import { SetConfType } from '../main';
|
import type { SetConfType } from '../main';
|
||||||
|
|
||||||
import type { SongInfo } from '@/providers/song-info';
|
import type { SongInfo } from '@/providers/song-info';
|
||||||
|
|
||||||
import type { ScrobblerPluginConfig } from '../index';
|
import type { ScrobblerPluginConfig } from '../index';
|
||||||
|
|
||||||
interface ListenbrainzRequestBody {
|
interface ListenbrainzRequestBody {
|
||||||
@ -27,16 +25,16 @@ interface ListenbrainzRequestBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ListenbrainzScrobbler extends ScrobblerBase {
|
export class ListenbrainzScrobbler extends ScrobblerBase {
|
||||||
isSessionCreated(): boolean {
|
override isSessionCreated(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
createSession(config: ScrobblerPluginConfig, _setConfig: SetConfType): Promise<ScrobblerPluginConfig> {
|
override createSession(config: ScrobblerPluginConfig, _setConfig: SetConfType): Promise<ScrobblerPluginConfig> {
|
||||||
return Promise.resolve(config);
|
return Promise.resolve(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
setNowPlaying(songInfo: SongInfo, config: ScrobblerPluginConfig, _setConfig: SetConfType): void {
|
override setNowPlaying(songInfo: SongInfo, config: ScrobblerPluginConfig, _setConfig: SetConfType): void {
|
||||||
if (!config.scrobblers.listenbrainz.api_root || !config.scrobblers.listenbrainz.token) {
|
if (!config.scrobblers.listenbrainz.apiRoot || !config.scrobblers.listenbrainz.token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +42,8 @@ export class ListenbrainzScrobbler extends ScrobblerBase {
|
|||||||
submitListen(body, config);
|
submitListen(body, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
addScrobble(songInfo: SongInfo, config: ScrobblerPluginConfig, _setConfig: SetConfType): void {
|
override addScrobble(songInfo: SongInfo, config: ScrobblerPluginConfig, _setConfig: SetConfType): void {
|
||||||
if (!config.scrobblers.listenbrainz.api_root || !config.scrobblers.listenbrainz.token) {
|
if (!config.scrobblers.listenbrainz.apiRoot || !config.scrobblers.listenbrainz.token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +78,7 @@ function createRequestBody(listenType: string, songInfo: SongInfo): Listenbrainz
|
|||||||
}
|
}
|
||||||
|
|
||||||
function submitListen(body: ListenbrainzRequestBody, config: ScrobblerPluginConfig) {
|
function submitListen(body: ListenbrainzRequestBody, config: ScrobblerPluginConfig) {
|
||||||
net.fetch(config.scrobblers.listenbrainz.api_root + 'submit-listens',
|
net.fetch(config.scrobblers.listenbrainz.apiRoot + 'submit-listens',
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { BrowserWindow, globalShortcut } from 'electron';
|
import { BrowserWindow, ipcMain, globalShortcut } from 'electron';
|
||||||
import is from 'electron-is';
|
import is from 'electron-is';
|
||||||
import { register as registerElectronLocalShortcut } from 'electron-localshortcut';
|
import { register as registerElectronLocalShortcut } from 'electron-localshortcut';
|
||||||
|
|
||||||
@ -48,7 +48,9 @@ export const onMainLoad = async ({
|
|||||||
_registerLocalShortcut(window, 'CommandOrControl+L', search);
|
_registerLocalShortcut(window, 'CommandOrControl+L', search);
|
||||||
|
|
||||||
if (is.linux()) {
|
if (is.linux()) {
|
||||||
registerMPRIS(window);
|
ipcMain.once('ytmd:video-src-changed', (_) => {
|
||||||
|
registerMPRIS(window);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { global, local } = config;
|
const { global, local } = config;
|
||||||
|
|||||||
112
src/plugins/shortcuts/mpris-service.d.ts
vendored
112
src/plugins/shortcuts/mpris-service.d.ts
vendored
@ -4,10 +4,10 @@ declare module '@jellybrick/mpris-service' {
|
|||||||
import { interface as dbusInterface } from 'dbus-next';
|
import { interface as dbusInterface } from 'dbus-next';
|
||||||
|
|
||||||
interface RootInterfaceOptions {
|
interface RootInterfaceOptions {
|
||||||
identity: string;
|
identity?: string;
|
||||||
supportedUriSchemes: string[];
|
supportedUriSchemes?: string[];
|
||||||
supportedMimeTypes: string[];
|
supportedMimeTypes?: string[];
|
||||||
desktopEntry: string;
|
desktopEntry?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Track {
|
export interface Track {
|
||||||
@ -35,6 +35,32 @@ declare module '@jellybrick/mpris-service' {
|
|||||||
'xesam:userRating'?: number;
|
'xesam:userRating'?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PlayBackStatus = 'Playing' | 'Paused' | 'Stopped';
|
||||||
|
|
||||||
|
export type LoopStatus = 'None' | 'Track' | 'Playlist';
|
||||||
|
|
||||||
|
export const PLAYBACK_STATUS_PLAYING: 'Playing';
|
||||||
|
export const PLAYBACK_STATUS_PAUSED: 'Paused';
|
||||||
|
export const PLAYBACK_STATUS_STOPPED: 'Stopped';
|
||||||
|
|
||||||
|
export const LOOP_STATUS_NONE: 'None';
|
||||||
|
export const LOOP_STATUS_TRACK: 'Track';
|
||||||
|
export const LOOP_STATUS_PLAYLIST: 'Playlist';
|
||||||
|
|
||||||
|
export type Interfaces = 'player' | 'trackList' | 'playlists';
|
||||||
|
|
||||||
|
export interface AdditionalPlayerOptions {
|
||||||
|
name: string;
|
||||||
|
supportedInterfaces: Interfaces[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlayerOptions = RootInterfaceOptions & AdditionalPlayerOptions;
|
||||||
|
|
||||||
|
export interface Position {
|
||||||
|
trackId: string;
|
||||||
|
position: number;
|
||||||
|
}
|
||||||
|
|
||||||
declare class Player extends EventEmitter {
|
declare class Player extends EventEmitter {
|
||||||
constructor(opts: {
|
constructor(opts: {
|
||||||
name: string;
|
name: string;
|
||||||
@ -43,20 +69,46 @@ declare module '@jellybrick/mpris-service' {
|
|||||||
supportedInterfaces?: string[];
|
supportedInterfaces?: string[];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//RootInterface
|
||||||
|
on(event: 'quit', listener: () => void): this;
|
||||||
|
on(event: 'raise', listener: () => void): this;
|
||||||
|
on(
|
||||||
|
event: 'fullscreen',
|
||||||
|
listener: (fullscreenEnabled: boolean) => void,
|
||||||
|
): this;
|
||||||
|
|
||||||
|
emit(type: string, ...args: unknown[]): unknown;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
identity: string;
|
identity: string;
|
||||||
fullscreen: boolean;
|
fullscreen?: boolean;
|
||||||
supportedUriSchemes: string[];
|
supportedUriSchemes: string[];
|
||||||
supportedMimeTypes: string[];
|
supportedMimeTypes: string[];
|
||||||
canQuit: boolean;
|
canQuit: boolean;
|
||||||
canRaise: boolean;
|
canRaise: boolean;
|
||||||
canSetFullscreen: boolean;
|
canSetFullscreen?: boolean;
|
||||||
|
desktopEntry?: string;
|
||||||
hasTrackList: boolean;
|
hasTrackList: boolean;
|
||||||
desktopEntry: string;
|
|
||||||
playbackStatus: string;
|
// PlayerInterface
|
||||||
loopStatus: string;
|
on(event: 'next', listener: () => void): this;
|
||||||
|
on(event: 'previous', listener: () => void): this;
|
||||||
|
on(event: 'pause', listener: () => void): this;
|
||||||
|
on(event: 'playpause', listener: () => void): this;
|
||||||
|
on(event: 'stop', listener: () => void): this;
|
||||||
|
on(event: 'play', listener: () => void): this;
|
||||||
|
on(event: 'seek', listener: (offset: number) => void): this;
|
||||||
|
on(event: 'open', listener: ({ uri: string }) => void): this;
|
||||||
|
on(event: 'loopStatus', listener: (status: LoopStatus) => void): this;
|
||||||
|
on(event: 'rate', listener: () => void): this;
|
||||||
|
on(event: 'shuffle', listener: (enableShuffle: boolean) => void): this;
|
||||||
|
on(event: 'volume', listener: (newVolume: number) => void): this;
|
||||||
|
on(event: 'position', listener: (position: Position) => void): this;
|
||||||
|
|
||||||
|
playbackStatus: PlayBackStatus;
|
||||||
|
loopStatus: LoopStatus;
|
||||||
shuffle: boolean;
|
shuffle: boolean;
|
||||||
metadata: object;
|
metadata: Track;
|
||||||
volume: number;
|
volume: number;
|
||||||
canControl: boolean;
|
canControl: boolean;
|
||||||
canPause: boolean;
|
canPause: boolean;
|
||||||
@ -67,9 +119,40 @@ declare module '@jellybrick/mpris-service' {
|
|||||||
rate: number;
|
rate: number;
|
||||||
minimumRate: number;
|
minimumRate: number;
|
||||||
maximumRate: number;
|
maximumRate: number;
|
||||||
playlists: unknown[];
|
|
||||||
|
abstract getPosition(): number;
|
||||||
|
|
||||||
|
seeked(position: number): void;
|
||||||
|
|
||||||
|
// TracklistInterface
|
||||||
|
on(event: 'addTrack', listener: () => void): this;
|
||||||
|
on(event: 'removeTrack', listener: () => void): this;
|
||||||
|
on(event: 'goTo', listener: () => void): this;
|
||||||
|
|
||||||
|
tracks: Track[];
|
||||||
|
canEditTracks: boolean;
|
||||||
|
|
||||||
|
on(event: '*', a: unknown[]): this;
|
||||||
|
|
||||||
|
addTrack(track: string): void;
|
||||||
|
|
||||||
|
removeTrack(trackId: string): void;
|
||||||
|
|
||||||
|
// PlaylistsInterface
|
||||||
|
on(event: 'activatePlaylist', listener: () => void): this;
|
||||||
|
|
||||||
|
playlists: Playlist[];
|
||||||
activePlaylist: string;
|
activePlaylist: string;
|
||||||
|
|
||||||
|
setPlaylists(playlists: Playlist[]): void;
|
||||||
|
|
||||||
|
setActivePlaylist(playlistId: string): void;
|
||||||
|
|
||||||
|
// Player methods
|
||||||
|
constructor(opts: PlayerOptions);
|
||||||
|
|
||||||
|
on(event: 'error', listener: (error: Error) => void): this;
|
||||||
|
|
||||||
init(opts: RootInterfaceOptions): void;
|
init(opts: RootInterfaceOptions): void;
|
||||||
|
|
||||||
objectPath(subpath?: string): string;
|
objectPath(subpath?: string): string;
|
||||||
@ -91,13 +174,6 @@ declare module '@jellybrick/mpris-service' {
|
|||||||
setPlaylists(playlists: Track[]): void;
|
setPlaylists(playlists: Track[]): void;
|
||||||
|
|
||||||
setActivePlaylist(playlistId: string): void;
|
setActivePlaylist(playlistId: string): void;
|
||||||
|
|
||||||
static PLAYBACK_STATUS_PLAYING: 'Playing';
|
|
||||||
static PLAYBACK_STATUS_PAUSED: 'Paused';
|
|
||||||
static PLAYBACK_STATUS_STOPPED: 'Stopped';
|
|
||||||
static LOOP_STATUS_NONE: 'None';
|
|
||||||
static LOOP_STATUS_TRACK: 'Track';
|
|
||||||
static LOOP_STATUS_PLAYLIST: 'Playlist';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MprisInterface extends dbusInterface.Interface {
|
interface MprisInterface extends dbusInterface.Interface {
|
||||||
|
|||||||
@ -1,37 +1,117 @@
|
|||||||
import { BrowserWindow, ipcMain } from 'electron';
|
import { BrowserWindow, ipcMain } from 'electron';
|
||||||
|
|
||||||
import mpris, { Track } from '@jellybrick/mpris-service';
|
import MprisPlayer, {
|
||||||
|
Track,
|
||||||
|
LoopStatus,
|
||||||
|
type PlayBackStatus,
|
||||||
|
type PlayerOptions,
|
||||||
|
PLAYBACK_STATUS_STOPPED,
|
||||||
|
PLAYBACK_STATUS_PAUSED,
|
||||||
|
PLAYBACK_STATUS_PLAYING,
|
||||||
|
LOOP_STATUS_NONE,
|
||||||
|
LOOP_STATUS_PLAYLIST,
|
||||||
|
LOOP_STATUS_TRACK,
|
||||||
|
type Position,
|
||||||
|
} from '@jellybrick/mpris-service';
|
||||||
|
|
||||||
import registerCallback from '@/providers/song-info';
|
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
||||||
import getSongControls from '@/providers/song-controls';
|
import getSongControls from '@/providers/song-controls';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import { LoggerPrefix } from '@/utils';
|
||||||
|
|
||||||
|
import type { RepeatMode } from '@/types/datahost-get-state';
|
||||||
|
import type { QueueResponse } from '@/types/youtube-music-desktop-internal';
|
||||||
|
|
||||||
|
class YTPlayer extends MprisPlayer {
|
||||||
|
/**
|
||||||
|
* @type {number} The current position in microseconds
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private currentPosition: number;
|
||||||
|
|
||||||
|
constructor(opts: PlayerOptions) {
|
||||||
|
super(opts);
|
||||||
|
|
||||||
|
this.currentPosition = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition(t: number) {
|
||||||
|
this.currentPosition = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
override getPosition(): number {
|
||||||
|
return this.currentPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoopStatus(status: LoopStatus) {
|
||||||
|
this.loopStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlaying(): boolean {
|
||||||
|
return this.playbackStatus === PLAYBACK_STATUS_PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPaused(): boolean {
|
||||||
|
return this.playbackStatus === PLAYBACK_STATUS_PAUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
isStopped(): boolean {
|
||||||
|
return this.playbackStatus === PLAYBACK_STATUS_STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlaybackStatus(status: PlayBackStatus) {
|
||||||
|
this.playbackStatus = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setupMPRIS() {
|
function setupMPRIS() {
|
||||||
const instance = new mpris({
|
const instance = new YTPlayer({
|
||||||
name: 'youtube-music',
|
name: 'YoutubeMusic',
|
||||||
identity: 'YouTube Music',
|
identity: 'YouTube Music',
|
||||||
supportedMimeTypes: ['audio/mpeg'],
|
supportedMimeTypes: ['audio/mpeg'],
|
||||||
supportedInterfaces: ['player'],
|
supportedInterfaces: ['player'],
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.canRaise = true;
|
instance.canRaise = true;
|
||||||
instance.supportedUriSchemes = ['https'];
|
instance.canQuit = false;
|
||||||
|
instance.canSetFullscreen = true;
|
||||||
|
instance.supportedUriSchemes = ['http', 'https'];
|
||||||
instance.desktopEntry = 'youtube-music';
|
instance.desktopEntry = 'youtube-music';
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerMPRIS(win: BrowserWindow) {
|
function registerMPRIS(win: BrowserWindow) {
|
||||||
const songControls = getSongControls(win);
|
const songControls = getSongControls(win);
|
||||||
const { playPause, next, previous, volumeMinus10, volumePlus10, shuffle } =
|
const {
|
||||||
songControls;
|
playPause,
|
||||||
|
next,
|
||||||
|
previous,
|
||||||
|
setVolume,
|
||||||
|
shuffle,
|
||||||
|
switchRepeat,
|
||||||
|
setFullscreen,
|
||||||
|
requestFullscreenInformation,
|
||||||
|
requestQueueInformation,
|
||||||
|
} = songControls;
|
||||||
try {
|
try {
|
||||||
// TODO: "Typing" for this arguments
|
let currentSongInfo: SongInfo | null = null;
|
||||||
const secToMicro = (n: unknown) => Math.round(Number(n) * 1e6);
|
const secToMicro = (n: number) => Math.round(Number(n) * 1e6);
|
||||||
const microToSec = (n: unknown) => Math.round(Number(n) / 1e6);
|
const microToSec = (n: number) => Math.round(Number(n) / 1e6);
|
||||||
|
|
||||||
const seekTo = (e: { position: unknown }) =>
|
const correctId = (videoId: string) => {
|
||||||
win.webContents.send('ytmd:seek-to', microToSec(e.position));
|
return videoId.replace(/-/g, '_MINUS_');
|
||||||
const seekBy = (o: unknown) =>
|
};
|
||||||
win.webContents.send('ytmd:seek-by', microToSec(o));
|
|
||||||
|
const seekTo = (event: Position) => {
|
||||||
|
if (
|
||||||
|
currentSongInfo?.videoId &&
|
||||||
|
event.trackId.endsWith(correctId(currentSongInfo.videoId))
|
||||||
|
) {
|
||||||
|
win.webContents.send('ytmd:seek-to', microToSec(event.position ?? 0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const seekBy = (offset: number) =>
|
||||||
|
win.webContents.send('ytmd:seek-by', microToSec(offset));
|
||||||
|
|
||||||
const player = setupMPRIS();
|
const player = setupMPRIS();
|
||||||
|
|
||||||
@ -40,73 +120,145 @@ function registerMPRIS(win: BrowserWindow) {
|
|||||||
win.webContents.send('ytmd:setup-time-changed-listener', 'mpris');
|
win.webContents.send('ytmd:setup-time-changed-listener', 'mpris');
|
||||||
win.webContents.send('ytmd:setup-repeat-changed-listener', 'mpris');
|
win.webContents.send('ytmd:setup-repeat-changed-listener', 'mpris');
|
||||||
win.webContents.send('ytmd:setup-volume-changed-listener', 'mpris');
|
win.webContents.send('ytmd:setup-volume-changed-listener', 'mpris');
|
||||||
|
win.webContents.send('ytmd:setup-fullscreen-changed-listener', 'mpris');
|
||||||
|
win.webContents.send('ytmd:setup-autoplay-changed-listener', 'mpris');
|
||||||
|
requestFullscreenInformation();
|
||||||
|
requestQueueInformation();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('ytmd:seeked', (_, t: number) => player.seeked(secToMicro(t)));
|
ipcMain.on('ytmd:seeked', (_, t: number) => player.seeked(secToMicro(t)));
|
||||||
|
|
||||||
let currentSeconds = 0;
|
ipcMain.on('ytmd:time-changed', (_, t: number) => {
|
||||||
ipcMain.on('ytmd:time-changed', (_, t: number) => (currentSeconds = t));
|
player.setPosition(secToMicro(t));
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.on('ytmd:repeat-changed', (_, mode: string) => {
|
ipcMain.on('ytmd:repeat-changed', (_, mode: RepeatMode) => {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'NONE': {
|
case 'NONE': {
|
||||||
player.loopStatus = mpris.LOOP_STATUS_NONE;
|
player.setLoopStatus(LOOP_STATUS_NONE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ONE': {
|
case 'ONE': {
|
||||||
player.loopStatus = mpris.LOOP_STATUS_TRACK;
|
player.setLoopStatus(LOOP_STATUS_TRACK);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ALL': {
|
case 'ALL': {
|
||||||
player.loopStatus = mpris.LOOP_STATUS_PLAYLIST;
|
player.setLoopStatus(LOOP_STATUS_PLAYLIST);
|
||||||
// No default
|
// No default
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
requestQueueInformation();
|
||||||
});
|
});
|
||||||
player.on('loopStatus', (status: string) => {
|
|
||||||
|
ipcMain.on('ytmd:fullscreen-changed', (_, changedTo: boolean) => {
|
||||||
|
if (player.fullscreen === undefined || !player.canSetFullscreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.fullscreen =
|
||||||
|
changedTo !== undefined ? changedTo : !player.fullscreen;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on(
|
||||||
|
'ytmd:set-fullscreen',
|
||||||
|
(_, isFullscreen: boolean | undefined) => {
|
||||||
|
if (!player.canSetFullscreen || isFullscreen === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.fullscreen = isFullscreen;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ipcMain.on(
|
||||||
|
'ytmd:fullscreen-changed-supported',
|
||||||
|
(_, isFullscreenSupported: boolean) => {
|
||||||
|
player.canSetFullscreen = isFullscreenSupported;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ipcMain.on('ytmd:autoplay-changed', (_) => {
|
||||||
|
requestQueueInformation();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('ytmd:get-queue-response', (_, queue: QueueResponse) => {
|
||||||
|
if (!queue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPosition = queue.items?.findIndex((it) =>
|
||||||
|
it?.playlistPanelVideoRenderer?.selected ||
|
||||||
|
it?.playlistPanelVideoWrapperRenderer?.primaryRenderer?.playlistPanelVideoRenderer?.selected
|
||||||
|
) ?? 0;
|
||||||
|
player.canGoPrevious = currentPosition !== 0;
|
||||||
|
|
||||||
|
let hasNext: boolean;
|
||||||
|
if (queue.autoPlaying) {
|
||||||
|
hasNext = true;
|
||||||
|
} else if (player.loopStatus === LOOP_STATUS_PLAYLIST) {
|
||||||
|
hasNext = true;
|
||||||
|
} else {
|
||||||
|
// Example: currentPosition = 0, queue.items.length = 29 -> hasNext = true
|
||||||
|
hasNext = !!(currentPosition - (queue?.items?.length ?? 0 - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
player.canGoNext = hasNext;
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on('loopStatus', (status: LoopStatus) => {
|
||||||
// SwitchRepeat cycles between states in that order
|
// SwitchRepeat cycles between states in that order
|
||||||
const switches = [
|
const switches = [
|
||||||
mpris.LOOP_STATUS_NONE,
|
LOOP_STATUS_NONE,
|
||||||
mpris.LOOP_STATUS_PLAYLIST,
|
LOOP_STATUS_PLAYLIST,
|
||||||
mpris.LOOP_STATUS_TRACK,
|
LOOP_STATUS_TRACK,
|
||||||
];
|
];
|
||||||
const currentIndex = switches.indexOf(player.loopStatus);
|
const currentIndex = switches.indexOf(player.loopStatus);
|
||||||
const targetIndex = switches.indexOf(status);
|
const targetIndex = switches.indexOf(status);
|
||||||
|
|
||||||
// Get a delta in the range [0,2]
|
// Get a delta in the range [0,2]
|
||||||
const delta = (targetIndex - currentIndex + 3) % 3;
|
const delta = (targetIndex - currentIndex + 3) % 3;
|
||||||
songControls.switchRepeat(delta);
|
switchRepeat(delta);
|
||||||
});
|
});
|
||||||
player.getPosition = () => secToMicro(currentSeconds);
|
|
||||||
|
|
||||||
player.on('raise', () => {
|
player.on('raise', () => {
|
||||||
|
if (!player.canRaise) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
win.setSkipTaskbar(false);
|
win.setSkipTaskbar(false);
|
||||||
win.show();
|
win.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
player.on('fullscreen', (fullscreenEnabled: boolean) => {
|
||||||
|
setFullscreen(fullscreenEnabled);
|
||||||
|
});
|
||||||
|
|
||||||
player.on('play', () => {
|
player.on('play', () => {
|
||||||
if (player.playbackStatus !== mpris.PLAYBACK_STATUS_PLAYING) {
|
if (!player.isPlaying()) {
|
||||||
player.playbackStatus = mpris.PLAYBACK_STATUS_PLAYING;
|
player.setPlaybackStatus(PLAYBACK_STATUS_PLAYING);
|
||||||
playPause();
|
playPause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
player.on('pause', () => {
|
player.on('pause', () => {
|
||||||
if (player.playbackStatus !== mpris.PLAYBACK_STATUS_PAUSED) {
|
if (!player.isPaused()) {
|
||||||
player.playbackStatus = mpris.PLAYBACK_STATUS_PAUSED;
|
player.setPlaybackStatus(PLAYBACK_STATUS_PAUSED);
|
||||||
playPause();
|
playPause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
player.on('playpause', () => {
|
player.on('playpause', () => {
|
||||||
player.playbackStatus =
|
player.setPlaybackStatus(
|
||||||
player.playbackStatus === mpris.PLAYBACK_STATUS_PLAYING
|
player.isPlaying() ? PLAYBACK_STATUS_PAUSED : PLAYBACK_STATUS_PLAYING,
|
||||||
? mpris.PLAYBACK_STATUS_PAUSED
|
);
|
||||||
: mpris.PLAYBACK_STATUS_PLAYING;
|
|
||||||
playPause();
|
playPause();
|
||||||
});
|
});
|
||||||
|
|
||||||
player.on('next', next);
|
player.on('next', () => {
|
||||||
player.on('previous', previous);
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on('previous', () => {
|
||||||
|
previous();
|
||||||
|
});
|
||||||
|
|
||||||
player.on('seek', seekBy);
|
player.on('seek', seekBy);
|
||||||
player.on('position', seekTo);
|
player.on('position', seekTo);
|
||||||
@ -114,10 +266,18 @@ function registerMPRIS(win: BrowserWindow) {
|
|||||||
player.on('shuffle', (enableShuffle) => {
|
player.on('shuffle', (enableShuffle) => {
|
||||||
if (enableShuffle) {
|
if (enableShuffle) {
|
||||||
shuffle();
|
shuffle();
|
||||||
|
requestQueueInformation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
player.on('open', (args: { uri: string }) => {
|
player.on('open', (args: { uri: string }) => {
|
||||||
win.loadURL(args.uri);
|
win.loadURL(args.uri).then(() => {
|
||||||
|
requestQueueInformation();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on('error', (error: Error) => {
|
||||||
|
console.error(LoggerPrefix, 'Error in MPRIS');
|
||||||
|
console.trace(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mprisVolNewer = false;
|
let mprisVolNewer = false;
|
||||||
@ -136,7 +296,7 @@ function registerMPRIS(win: BrowserWindow) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
player.on('volume', (newVolume) => {
|
player.on('volume', (newVolume: number) => {
|
||||||
if (config.plugins.isEnabled('precise-volume')) {
|
if (config.plugins.isEnabled('precise-volume')) {
|
||||||
// With precise volume we can set the volume to the exact value.
|
// With precise volume we can set the volume to the exact value.
|
||||||
const newVol = ~~(newVolume * 100);
|
const newVol = ~~(newVolume * 100);
|
||||||
@ -146,45 +306,46 @@ function registerMPRIS(win: BrowserWindow) {
|
|||||||
win.webContents.send('setVolume', newVol);
|
win.webContents.send('setVolume', newVol);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// With keyboard shortcuts we can only change the volume in increments of 10, so round it.
|
setVolume(newVolume * 100);
|
||||||
let deltaVolume = Math.round((newVolume - player.volume) * 10);
|
|
||||||
while (deltaVolume !== 0 && deltaVolume > 0) {
|
|
||||||
volumePlus10();
|
|
||||||
player.volume += 0.1;
|
|
||||||
deltaVolume--;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (deltaVolume !== 0 && deltaVolume < 0) {
|
|
||||||
volumeMinus10();
|
|
||||||
player.volume -= 0.1;
|
|
||||||
deltaVolume++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCallback((songInfo) => {
|
registerCallback((songInfo: SongInfo) => {
|
||||||
if (player) {
|
if (player) {
|
||||||
const data: Track = {
|
const data: Track = {
|
||||||
'mpris:length': secToMicro(songInfo.songDuration),
|
'mpris:length': secToMicro(songInfo.songDuration),
|
||||||
'mpris:artUrl': songInfo.imageSrc ?? undefined,
|
...(songInfo.imageSrc
|
||||||
|
? { 'mpris:artUrl': songInfo.imageSrc }
|
||||||
|
: undefined),
|
||||||
'xesam:title': songInfo.title,
|
'xesam:title': songInfo.title,
|
||||||
'xesam:url': songInfo.url,
|
'xesam:url': songInfo.url,
|
||||||
'xesam:artist': [songInfo.artist],
|
'xesam:artist': [songInfo.artist],
|
||||||
'mpris:trackid': '/',
|
'mpris:trackid': player.objectPath(
|
||||||
|
`Track/${correctId(songInfo.videoId)}`,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
if (songInfo.album) {
|
if (songInfo.album) {
|
||||||
data['xesam:album'] = songInfo.album;
|
data['xesam:album'] = songInfo.album;
|
||||||
}
|
}
|
||||||
|
currentSongInfo = songInfo;
|
||||||
|
|
||||||
player.metadata = data;
|
player.metadata = data;
|
||||||
player.seeked(secToMicro(songInfo.elapsedSeconds));
|
|
||||||
player.playbackStatus = songInfo.isPaused
|
const currentElapsedMicroSeconds = secToMicro(
|
||||||
? mpris.PLAYBACK_STATUS_PAUSED
|
songInfo.elapsedSeconds ?? 0,
|
||||||
: mpris.PLAYBACK_STATUS_PLAYING;
|
);
|
||||||
|
player.setPosition(currentElapsedMicroSeconds);
|
||||||
|
player.seeked(currentElapsedMicroSeconds);
|
||||||
|
|
||||||
|
player.setPlaybackStatus(
|
||||||
|
songInfo.isPaused ? PLAYBACK_STATUS_PAUSED : PLAYBACK_STATUS_PLAYING,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
requestQueueInformation();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error in MPRIS', error);
|
console.error(LoggerPrefix, 'Error in MPRIS');
|
||||||
|
console.trace(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,41 @@
|
|||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
export default createPlugin({
|
export default createPlugin<
|
||||||
|
unknown,
|
||||||
|
unknown,
|
||||||
|
{
|
||||||
|
observer?: MutationObserver;
|
||||||
|
waitForElem(selector: string): Promise<HTMLElement>;
|
||||||
|
start(): void;
|
||||||
|
stop(): void;
|
||||||
|
}
|
||||||
|
>({
|
||||||
name: () => t('plugins.skip-disliked-songs.name'),
|
name: () => t('plugins.skip-disliked-songs.name'),
|
||||||
description: () => t('plugins.skip-disliked-songs.description'),
|
description: () => t('plugins.skip-disliked-songs.description'),
|
||||||
restartNeeded: false,
|
restartNeeded: false,
|
||||||
renderer: {
|
renderer: {
|
||||||
observer: null as MutationObserver | null,
|
waitForElem(selector: string) {
|
||||||
|
return new Promise<HTMLElement>((resolve) => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const elem = document.querySelector<HTMLElement>(selector);
|
||||||
|
if (!elem) return;
|
||||||
|
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve(elem);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
start() {
|
start() {
|
||||||
this.waitForElem('#like-button-renderer').then((likeBtn) => {
|
this.waitForElem('#dislike-button-renderer').then((dislikeBtn) => {
|
||||||
this.observer = new MutationObserver(() => {
|
this.observer = new MutationObserver(() => {
|
||||||
if (likeBtn?.getAttribute('like-status') == 'DISLIKE') {
|
if (dislikeBtn?.getAttribute('like-status') == 'DISLIKE') {
|
||||||
document
|
document
|
||||||
.querySelector('tp-yt-paper-icon-button.next-button')
|
.querySelector<HTMLButtonElement>('tp-yt-paper-icon-button.next-button')
|
||||||
?.click();
|
?.click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.observer.observe(likeBtn, {
|
this.observer.observe(dislikeBtn, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
childList: false,
|
childList: false,
|
||||||
subtree: false,
|
subtree: false,
|
||||||
@ -26,16 +45,5 @@ export default createPlugin({
|
|||||||
stop() {
|
stop() {
|
||||||
this.observer?.disconnect();
|
this.observer?.disconnect();
|
||||||
},
|
},
|
||||||
waitForElem(selector) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
const elem = document.querySelector(selector);
|
|
||||||
if (!elem) return;
|
|
||||||
|
|
||||||
clearInterval(interval);
|
|
||||||
resolve(elem);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { contextBridge, ipcRenderer, IpcRendererEvent, webFrame } from 'electron';
|
import { contextBridge, ipcRenderer, IpcRendererEvent, webFrame } from 'electron';
|
||||||
import is from 'electron-is';
|
import is from 'electron-is';
|
||||||
|
|
||||||
import { injectChromeCompatToObject, chrome } from '@jellybrick/electron-chromecast';
|
|
||||||
|
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -55,8 +53,6 @@ contextBridge.exposeInMainWorld(
|
|||||||
'ELECTRON_RENDERER_URL',
|
'ELECTRON_RENDERER_URL',
|
||||||
process.env.ELECTRON_RENDERER_URL,
|
process.env.ELECTRON_RENDERER_URL,
|
||||||
);
|
);
|
||||||
injectChromeCompatToObject(global);
|
|
||||||
contextBridge.exposeInMainWorld('caster', chrome);
|
|
||||||
|
|
||||||
const [path, script] = ipcRenderer.sendSync('get-renderer-script') as [string | null, string];
|
const [path, script] = ipcRenderer.sendSync('get-renderer-script') as [string | null, string];
|
||||||
let blocked = true;
|
let blocked = true;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import getSongControls from './song-controls';
|
|||||||
|
|
||||||
export const APP_PROTOCOL = 'youtubemusic';
|
export const APP_PROTOCOL = 'youtubemusic';
|
||||||
|
|
||||||
let protocolHandler: ((cmd: string) => void) | undefined;
|
let protocolHandler: ((cmd: string, args: string[] | undefined) => void) | undefined;
|
||||||
|
|
||||||
export function setupProtocolHandler(win: BrowserWindow) {
|
export function setupProtocolHandler(win: BrowserWindow) {
|
||||||
if (process.defaultApp && process.argv.length >= 2) {
|
if (process.defaultApp && process.argv.length >= 2) {
|
||||||
@ -19,18 +19,18 @@ export function setupProtocolHandler(win: BrowserWindow) {
|
|||||||
|
|
||||||
const songControls = getSongControls(win);
|
const songControls = getSongControls(win);
|
||||||
|
|
||||||
protocolHandler = ((cmd: keyof typeof songControls) => {
|
protocolHandler = ((cmd: keyof typeof songControls, args: string[] | undefined = undefined) => {
|
||||||
if (Object.keys(songControls).includes(cmd)) {
|
if (Object.keys(songControls).includes(cmd)) {
|
||||||
songControls[cmd]();
|
songControls[cmd](args as never);
|
||||||
}
|
}
|
||||||
}) as (cmd: string) => void;
|
}) as (cmd: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleProtocol(cmd: string) {
|
export function handleProtocol(cmd: string, args: string[] | undefined) {
|
||||||
protocolHandler?.(cmd);
|
protocolHandler?.(cmd, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeProtocolHandler(f: (cmd: string) => void) {
|
export function changeProtocolHandler(f: (cmd: string, args: string[] | undefined) => void) {
|
||||||
protocolHandler = f;
|
protocolHandler = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,39 +1,82 @@
|
|||||||
// This is used for to control the songs
|
// This is used for to control the songs
|
||||||
import { BrowserWindow, ipcMain } from 'electron';
|
import { BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
// see protocol-handler.ts
|
||||||
|
type ArgsType<T> = T | string[] | undefined;
|
||||||
|
|
||||||
|
const parseNumberFromArgsType = (args: ArgsType<number>) => {
|
||||||
|
if (typeof args === 'number') {
|
||||||
|
return args;
|
||||||
|
} else if (Array.isArray(args)) {
|
||||||
|
return Number(args[0]);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseBooleanFromArgsType = (args: ArgsType<boolean>) => {
|
||||||
|
if (typeof args === 'boolean') {
|
||||||
|
return args;
|
||||||
|
} else if (Array.isArray(args)) {
|
||||||
|
return args[0] === 'true';
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default (win: BrowserWindow) => {
|
export default (win: BrowserWindow) => {
|
||||||
const commands = {
|
|
||||||
// Playback
|
|
||||||
previous: () => win.webContents.send('ytmd:previous-video'),
|
|
||||||
next: () => win.webContents.send('ytmd:next-video'),
|
|
||||||
playPause: () => win.webContents.send('ytmd:toggle-play'),
|
|
||||||
like: () => win.webContents.send('ytmd:update-like', 'LIKE'),
|
|
||||||
dislike: () => win.webContents.send('ytmd:update-like', 'DISLIKE'),
|
|
||||||
go10sBack: () => win.webContents.send('ytmd:seek-by', -10),
|
|
||||||
go10sForward: () => win.webContents.send('ytmd:seek-by', 10),
|
|
||||||
go1sBack: () => win.webContents.send('ytmd:seek-by', -1),
|
|
||||||
go1sForward: () => win.webContents.send('ytmd:seek-by', 1),
|
|
||||||
shuffle: () => win.webContents.send('ytmd:shuffle'),
|
|
||||||
switchRepeat: (n = 1) => win.webContents.send('ytmd:switch-repeat', n),
|
|
||||||
// General
|
|
||||||
volumeMinus10: () => {
|
|
||||||
ipcMain.once('ytmd:get-volume-return', (_, volume) => {
|
|
||||||
win.webContents.send('ytmd:update-volume', volume - 10);
|
|
||||||
});
|
|
||||||
win.webContents.send('ytmd:get-volume');
|
|
||||||
},
|
|
||||||
volumePlus10: () => {
|
|
||||||
ipcMain.once('ytmd:get-volume-return', (_, volume) => {
|
|
||||||
win.webContents.send('ytmd:update-volume', volume + 10);
|
|
||||||
});
|
|
||||||
win.webContents.send('ytmd:get-volume');
|
|
||||||
},
|
|
||||||
fullscreen: () => win.webContents.send('ytmd:toggle-fullscreen'),
|
|
||||||
muteUnmute: () => win.webContents.send('ytmd:toggle-mute'),
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
...commands,
|
// Playback
|
||||||
play: commands.playPause,
|
previous: () => win.webContents.send('ytmd:previous-video'),
|
||||||
pause: commands.playPause,
|
next: () => win.webContents.send('ytmd:next-video'),
|
||||||
|
playPause: () => win.webContents.send('ytmd:toggle-play'),
|
||||||
|
like: () => win.webContents.send('ytmd:update-like', 'LIKE'),
|
||||||
|
dislike: () => win.webContents.send('ytmd:update-like', 'DISLIKE'),
|
||||||
|
goBack: (seconds: ArgsType<number>) => {
|
||||||
|
const secondsNumber = parseNumberFromArgsType(seconds);
|
||||||
|
if (secondsNumber !== null) {
|
||||||
|
win.webContents.send('ytmd:seek-by', -secondsNumber);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goForward: (seconds: ArgsType<number>) => {
|
||||||
|
const secondsNumber = parseNumberFromArgsType(seconds);
|
||||||
|
if (secondsNumber !== null) {
|
||||||
|
win.webContents.send('ytmd:seek-by', seconds);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shuffle: () => win.webContents.send('ytmd:shuffle'),
|
||||||
|
switchRepeat: (n: ArgsType<number> = 1) => {
|
||||||
|
const repeat = parseNumberFromArgsType(n);
|
||||||
|
if (repeat !== null) {
|
||||||
|
win.webContents.send('ytmd:switch-repeat', n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// General
|
||||||
|
setVolume: (volume: ArgsType<number>) => {
|
||||||
|
const volumeNumber = parseNumberFromArgsType(volume);
|
||||||
|
if (volumeNumber !== null) {
|
||||||
|
win.webContents.send('ytmd:update-volume', volume);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setFullscreen: (isFullscreen: ArgsType<boolean>) => {
|
||||||
|
const isFullscreenValue = parseBooleanFromArgsType(isFullscreen);
|
||||||
|
if (isFullscreenValue !== null) {
|
||||||
|
win.setFullScreen(isFullscreenValue);
|
||||||
|
win.webContents.send('ytmd:click-fullscreen-button', isFullscreenValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requestFullscreenInformation: () => {
|
||||||
|
win.webContents.send('ytmd:get-fullscreen');
|
||||||
|
},
|
||||||
|
requestQueueInformation: () => {
|
||||||
|
win.webContents.send('ytmd:get-queue');
|
||||||
|
},
|
||||||
|
muteUnmute: () => win.webContents.send('ytmd:toggle-mute'),
|
||||||
|
search: () => {
|
||||||
|
win.webContents.sendInputEvent({
|
||||||
|
type: 'keyDown',
|
||||||
|
keyCode: '/',
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -29,8 +29,9 @@ export const setupTimeChangedListener = singleton(() => {
|
|||||||
const progressObserver = new MutationObserver((mutations) => {
|
const progressObserver = new MutationObserver((mutations) => {
|
||||||
for (const mutation of mutations) {
|
for (const mutation of mutations) {
|
||||||
const target = mutation.target as Node & { value: string };
|
const target = mutation.target as Node & { value: string };
|
||||||
window.ipcRenderer.send('ytmd:time-changed', target.value);
|
const numberValue = Number(target.value);
|
||||||
songInfo.elapsedSeconds = Number(target.value);
|
window.ipcRenderer.send('ytmd:time-changed', numberValue);
|
||||||
|
songInfo.elapsedSeconds = numberValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const progressBar = document.querySelector('#progress-bar');
|
const progressBar = document.querySelector('#progress-bar');
|
||||||
@ -61,11 +62,13 @@ export const setupRepeatChangedListener = singleton(() => {
|
|||||||
// provided by YouTube Music
|
// provided by YouTube Music
|
||||||
window.ipcRenderer.send(
|
window.ipcRenderer.send(
|
||||||
'ytmd:repeat-changed',
|
'ytmd:repeat-changed',
|
||||||
document.querySelector<
|
document
|
||||||
HTMLElement & {
|
.querySelector<
|
||||||
getState: () => GetState;
|
HTMLElement & {
|
||||||
}
|
getState: () => GetState;
|
||||||
>('ytmusic-player-bar')?.getState().queue.repeatMode,
|
}
|
||||||
|
>('ytmusic-player-bar')
|
||||||
|
?.getState().queue.repeatMode,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,6 +80,46 @@ export const setupVolumeChangedListener = singleton((api: YoutubePlayer) => {
|
|||||||
window.ipcRenderer.send('ytmd:volume-changed', api.getVolume());
|
window.ipcRenderer.send('ytmd:volume-changed', api.getVolume());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setupFullScreenChangedListener = singleton(() => {
|
||||||
|
const playerBar = document.querySelector('ytmusic-player-bar');
|
||||||
|
|
||||||
|
if (!playerBar) {
|
||||||
|
window.ipcRenderer.send('ytmd:fullscreen-changed-supported', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
window.ipcRenderer.send(
|
||||||
|
'ytmd:fullscreen-changed',
|
||||||
|
(
|
||||||
|
playerBar?.attributes.getNamedItem('player-fullscreened') ?? null
|
||||||
|
) !== null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(playerBar, {
|
||||||
|
attributes: true,
|
||||||
|
childList: false,
|
||||||
|
subtree: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setupAutoPlayChangedListener = singleton(() => {
|
||||||
|
const autoplaySlider = document.querySelector<HTMLInputElement>(
|
||||||
|
'.autoplay > tp-yt-paper-toggle-button',
|
||||||
|
);
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
window.ipcRenderer.send('ytmd:autoplay-changed');
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(autoplaySlider!, {
|
||||||
|
attributes: true,
|
||||||
|
childList: false,
|
||||||
|
subtree: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export default (api: YoutubePlayer) => {
|
export default (api: YoutubePlayer) => {
|
||||||
window.ipcRenderer.on('ytmd:setup-time-changed-listener', () => {
|
window.ipcRenderer.on('ytmd:setup-time-changed-listener', () => {
|
||||||
setupTimeChangedListener();
|
setupTimeChangedListener();
|
||||||
@ -90,6 +133,14 @@ export default (api: YoutubePlayer) => {
|
|||||||
setupVolumeChangedListener(api);
|
setupVolumeChangedListener(api);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.ipcRenderer.on('ytmd:setup-fullscreen-changed-listener', () => {
|
||||||
|
setupFullScreenChangedListener();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.ipcRenderer.on('ytmd:setup-autoplay-changed-listener', () => {
|
||||||
|
setupAutoPlayChangedListener();
|
||||||
|
});
|
||||||
|
|
||||||
window.ipcRenderer.on('ytmd:setup-seeked-listener', () => {
|
window.ipcRenderer.on('ytmd:setup-seeked-listener', () => {
|
||||||
setupSeekedListener();
|
setupSeekedListener();
|
||||||
});
|
});
|
||||||
@ -154,13 +205,13 @@ export default (api: YoutubePlayer) => {
|
|||||||
function sendSongInfo(videoData: VideoDataChangeValue) {
|
function sendSongInfo(videoData: VideoDataChangeValue) {
|
||||||
const data = api.getPlayerResponse();
|
const data = api.getPlayerResponse();
|
||||||
|
|
||||||
data.videoDetails.album =
|
data.videoDetails.album = (
|
||||||
(
|
Object.entries(videoData).find(
|
||||||
Object.entries(videoData)
|
([, value]) => value && Object.hasOwn(value, 'playerOverlays'),
|
||||||
.find(([, value]) => value && Object.hasOwn(value, 'playerOverlays')) as [string, AlbumDetails | undefined]
|
) as [string, AlbumDetails | undefined]
|
||||||
)?.[1]?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album?.runs?.at(
|
)?.[1]?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album?.runs?.at(
|
||||||
0,
|
0,
|
||||||
)?.text;
|
)?.text;
|
||||||
data.videoDetails.elapsedSeconds = 0;
|
data.videoDetails.elapsedSeconds = 0;
|
||||||
data.videoDetails.isPaused = false;
|
data.videoDetails.isPaused = false;
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,11 @@ import { BrowserWindow, ipcMain, nativeImage, net } from 'electron';
|
|||||||
|
|
||||||
import { Mutex } from 'async-mutex';
|
import { Mutex } from 'async-mutex';
|
||||||
|
|
||||||
import { cache } from './decorators';
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
|
||||||
import type { GetPlayerResponse } from '@/types/get-player-response';
|
import type { GetPlayerResponse } from '@/types/get-player-response';
|
||||||
|
|
||||||
enum MediaType {
|
export enum MediaType {
|
||||||
/**
|
/**
|
||||||
* Audio uploaded by the original artist
|
* Audio uploaded by the original artist
|
||||||
*/
|
*/
|
||||||
@ -45,19 +44,20 @@ export interface SongInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Grab the native image using the src
|
// Grab the native image using the src
|
||||||
export const getImage = cache(
|
export const getImage = async (src: string): Promise<Electron.NativeImage> => {
|
||||||
async (src: string): Promise<Electron.NativeImage> => {
|
const result = await net.fetch(src);
|
||||||
const result = await net.fetch(src);
|
const output = nativeImage.createFromBuffer(
|
||||||
const buffer = await result.arrayBuffer();
|
Buffer.from(
|
||||||
const output = nativeImage.createFromBuffer(Buffer.from(buffer));
|
await result.arrayBuffer(),
|
||||||
if (output.isEmpty() && !src.endsWith('.jpg') && src.includes('.jpg')) {
|
),
|
||||||
// Fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
|
);
|
||||||
return getImage(src.slice(0, src.lastIndexOf('.jpg') + 4));
|
if (output.isEmpty() && !src.endsWith('.jpg') && src.includes('.jpg')) {
|
||||||
}
|
// Fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
|
||||||
|
return getImage(src.slice(0, src.lastIndexOf('.jpg') + 4));
|
||||||
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
const handleData = async (
|
const handleData = async (
|
||||||
data: GetPlayerResponse,
|
data: GetPlayerResponse,
|
||||||
@ -120,11 +120,24 @@ const handleData = async (
|
|||||||
songInfo.mediaType = MediaType.PodcastEpisode;
|
songInfo.mediaType = MediaType.PodcastEpisode;
|
||||||
// HACK: Podcast's participant is not the artist
|
// HACK: Podcast's participant is not the artist
|
||||||
if (!config.get('options.usePodcastParticipantAsArtist')) {
|
if (!config.get('options.usePodcastParticipantAsArtist')) {
|
||||||
songInfo.artist = cleanupName(data.microformat.microformatDataRenderer.pageOwnerDetails.name);
|
songInfo.artist = cleanupName(
|
||||||
|
data.microformat.microformatDataRenderer.pageOwnerDetails.name,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
songInfo.mediaType = MediaType.OtherVideo;
|
songInfo.mediaType = MediaType.OtherVideo;
|
||||||
|
// HACK: This is a workaround for "podcast" types where "musicVideoType" doesn't exist. Google :facepalm:
|
||||||
|
if (
|
||||||
|
!config.get('options.usePodcastParticipantAsArtist') &&
|
||||||
|
(data.responseContext.serviceTrackingParams
|
||||||
|
?.at(0)
|
||||||
|
?.params?.find((it) => it.key === 'ipcc')?.value ?? '1') != '0'
|
||||||
|
) {
|
||||||
|
songInfo.artist = cleanupName(
|
||||||
|
data.microformat.microformatDataRenderer.pageOwnerDetails.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,10 +166,12 @@ const registerProvider = (win: BrowserWindow) => {
|
|||||||
|
|
||||||
// This will be called when the song-info-front finds a new request with song data
|
// This will be called when the song-info-front finds a new request with song data
|
||||||
ipcMain.on('ytmd:video-src-changed', async (_, data: GetPlayerResponse) => {
|
ipcMain.on('ytmd:video-src-changed', async (_, data: GetPlayerResponse) => {
|
||||||
const tempSongInfo = await dataMutex.runExclusive<SongInfo | null>(async () => {
|
const tempSongInfo = await dataMutex.runExclusive<SongInfo | null>(
|
||||||
songInfo = await handleData(data, win);
|
async () => {
|
||||||
return songInfo;
|
songInfo = await handleData(data, win);
|
||||||
});
|
return songInfo;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (tempSongInfo) {
|
if (tempSongInfo) {
|
||||||
for (const c of callbacks) {
|
for (const c of callbacks) {
|
||||||
@ -194,10 +209,19 @@ const registerProvider = (win: BrowserWindow) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const suffixesToRemove = [
|
const suffixesToRemove = [
|
||||||
' - topic',
|
// Artist names
|
||||||
'vevo',
|
/\s*(- topic)$/i,
|
||||||
' (performance video)',
|
/\s*vevo$/i,
|
||||||
' (clip official)',
|
|
||||||
|
// 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,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function cleanupName(name: string): string {
|
export function cleanupName(name: string): string {
|
||||||
@ -205,15 +229,8 @@ export function cleanupName(name: string): string {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name.replace(
|
|
||||||
/\((?:official)? ?(?:music)? ?(?:lyrics?)? ?(?:video)?\)$/i,
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
const lowCaseName = name.toLowerCase();
|
|
||||||
for (const suffix of suffixesToRemove) {
|
for (const suffix of suffixesToRemove) {
|
||||||
if (lowCaseName.endsWith(suffix)) {
|
name = name.replace(suffix, '');
|
||||||
return name.slice(0, -suffix.length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import { loadI18n, setLanguage, t as i18t } from '@/i18n';
|
|||||||
|
|
||||||
import type { PluginConfig } from '@/types/plugins';
|
import type { PluginConfig } from '@/types/plugins';
|
||||||
import type { YoutubePlayer } from '@/types/youtube-player';
|
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||||
|
import type { QueueElement } from '@/types/queue';
|
||||||
window.chrome.cast = window.caster.cast;
|
import type { QueueResponse } from '@/types/youtube-music-desktop-internal';
|
||||||
|
|
||||||
let api: (Element & YoutubePlayer) | null = null;
|
let api: (Element & YoutubePlayer) | null = null;
|
||||||
let isPluginLoaded = false;
|
let isPluginLoaded = false;
|
||||||
@ -59,22 +59,60 @@ async function onApiLoaded() {
|
|||||||
});
|
});
|
||||||
window.ipcRenderer.on('ytmd:switch-repeat', (_, repeat = 1) => {
|
window.ipcRenderer.on('ytmd:switch-repeat', (_, repeat = 1) => {
|
||||||
for (let i = 0; i < repeat; i++) {
|
for (let i = 0; i < repeat; i++) {
|
||||||
document.querySelector<HTMLElement & { onRepeatButtonTap: () => void }>('ytmusic-player-bar')?.onRepeatButtonTap();
|
document.querySelector<HTMLElement & { onRepeatButtonClick: () => void }>('ytmusic-player-bar')?.onRepeatButtonClick();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.ipcRenderer.on('ytmd:update-volume', (_, volume: number) => {
|
window.ipcRenderer.on('ytmd:update-volume', (_, volume: number) => {
|
||||||
document.querySelector<HTMLElement & { updateVolume: (volume: number) => void }>('ytmusic-player-bar')?.updateVolume(volume);
|
document
|
||||||
|
.querySelector<
|
||||||
|
HTMLElement & { updateVolume: (volume: number) => void }
|
||||||
|
>('ytmusic-player-bar')
|
||||||
|
?.updateVolume(volume);
|
||||||
});
|
});
|
||||||
window.ipcRenderer.on('ytmd:get-volume', (event) => {
|
|
||||||
event.sender.emit('ytmd:get-volume-return', api?.getVolume());
|
const isFullscreen = () => {
|
||||||
|
const isFullscreen =
|
||||||
|
document
|
||||||
|
.querySelector<HTMLElement>('ytmusic-player-bar')
|
||||||
|
?.attributes.getNamedItem('player-fullscreened') ?? null;
|
||||||
|
|
||||||
|
return isFullscreen !== null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickFullscreenButton = (isFullscreenValue: boolean) => {
|
||||||
|
const fullscreen = isFullscreen();
|
||||||
|
if (isFullscreenValue === fullscreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullscreen) {
|
||||||
|
document.querySelector<HTMLElement>('.exit-fullscreen-button')?.click();
|
||||||
|
} else {
|
||||||
|
document.querySelector<HTMLElement>('.fullscreen-button')?.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.ipcRenderer.on('ytmd:get-fullscreen', (event) => {
|
||||||
|
event.sender.send('ytmd:set-fullscreen', isFullscreen());
|
||||||
});
|
});
|
||||||
window.ipcRenderer.on('ytmd:toggle-fullscreen', (_) => {
|
|
||||||
document.querySelector<HTMLElement & { toggleFullscreen: () => void }>('ytmusic-player-bar')?.toggleFullscreen();
|
window.ipcRenderer.on('ytmd:click-fullscreen-button', (_, fullscreen: boolean | undefined) => {
|
||||||
|
clickFullscreenButton(fullscreen ?? false);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.ipcRenderer.on('ytmd:toggle-mute', (_) => {
|
window.ipcRenderer.on('ytmd:toggle-mute', (_) => {
|
||||||
document.querySelector<HTMLElement & { onVolumeTap: () => void }>('ytmusic-player-bar')?.onVolumeTap();
|
document.querySelector<HTMLElement & { onVolumeTap: () => void }>('ytmusic-player-bar')?.onVolumeTap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.ipcRenderer.on('ytmd:get-queue', (event) => {
|
||||||
|
const queue = document.querySelector<QueueElement>('#queue');
|
||||||
|
event.sender.send('ytmd:get-queue-response', {
|
||||||
|
items: queue?.queue.getItems(),
|
||||||
|
autoPlaying: queue?.queue.autoPlaying,
|
||||||
|
continuation: queue?.queue.continuation,
|
||||||
|
} satisfies QueueResponse);
|
||||||
|
});
|
||||||
|
|
||||||
const video = document.querySelector('video')!;
|
const video = document.querySelector('video')!;
|
||||||
const audioContext = new AudioContext();
|
const audioContext = new AudioContext();
|
||||||
const audioSource = audioContext.createMediaElementSource(video);
|
const audioSource = audioContext.createMediaElementSource(video);
|
||||||
@ -135,7 +173,7 @@ async function onApiLoaded() {
|
|||||||
// Remove upgrade button
|
// Remove upgrade button
|
||||||
if (window.mainConfig.get('options.removeUpgradeButton')) {
|
if (window.mainConfig.get('options.removeUpgradeButton')) {
|
||||||
const styles = document.createElement('style');
|
const styles = document.createElement('style');
|
||||||
styles.innerHTML = `ytmusic-guide-signin-promo-renderer {
|
styles.innerHTML = `ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer:last-child {
|
||||||
display: none;
|
display: none;
|
||||||
}`;
|
}`;
|
||||||
document.head.appendChild(styles);
|
document.head.appendChild(styles);
|
||||||
@ -187,6 +225,9 @@ const preload = async () => {
|
|||||||
t: i18t.bind(i18next),
|
t: i18t.bind(i18next),
|
||||||
};
|
};
|
||||||
defineYTMDTransElements();
|
defineYTMDTransElements();
|
||||||
|
if (document.body?.dataset?.os) {
|
||||||
|
document.body.dataset.os = navigator.userAgent;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
@ -238,7 +279,9 @@ const initObserver = async () => {
|
|||||||
// check document.documentElement is ready
|
// check document.documentElement is ready
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', () => resolve(), { once: true });
|
document.addEventListener('DOMContentLoaded', () => resolve(), {
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/reset.d.ts
vendored
1
src/reset.d.ts
vendored
@ -22,7 +22,6 @@ declare global {
|
|||||||
ipcRenderer: typeof electronIpcRenderer;
|
ipcRenderer: typeof electronIpcRenderer;
|
||||||
mainConfig: typeof config;
|
mainConfig: typeof config;
|
||||||
electronIs: typeof is;
|
electronIs: typeof is;
|
||||||
caster: typeof window.chrome;
|
|
||||||
ELECTRON_RENDERER_URL: string | undefined;
|
ELECTRON_RENDERER_URL: string | undefined;
|
||||||
/**
|
/**
|
||||||
* YouTube Music internal variable (Last interaction time)
|
* YouTube Music internal variable (Last interaction time)
|
||||||
|
|||||||
12
src/tray.ts
12
src/tray.ts
@ -1,4 +1,5 @@
|
|||||||
import { Menu, nativeImage, Tray } from 'electron';
|
import { Menu, screen, nativeImage, Tray } from 'electron';
|
||||||
|
import is from 'electron-is';
|
||||||
|
|
||||||
import defaultTrayIconAsset from '@assets/youtube-music-tray.png?asset&asarUnpack';
|
import defaultTrayIconAsset from '@assets/youtube-music-tray.png?asset&asarUnpack';
|
||||||
import pausedTrayIconAsset from '@assets/youtube-music-tray-paused.png?asset&asarUnpack';
|
import pausedTrayIconAsset from '@assets/youtube-music-tray-paused.png?asset&asarUnpack';
|
||||||
@ -48,13 +49,14 @@ export const setUpTray = (app: Electron.App, win: Electron.BrowserWindow) => {
|
|||||||
|
|
||||||
const { playPause, next, previous } = getSongControls(win);
|
const { playPause, next, previous } = getSongControls(win);
|
||||||
|
|
||||||
|
const pixelRatio = is.windows() ? screen.getPrimaryDisplay().scaleFactor || 1 : 1;
|
||||||
const defaultTrayIcon = nativeImage.createFromPath(defaultTrayIconAsset).resize({
|
const defaultTrayIcon = nativeImage.createFromPath(defaultTrayIconAsset).resize({
|
||||||
width: 16,
|
width: 16 * pixelRatio,
|
||||||
height: 16,
|
height: 16 * pixelRatio,
|
||||||
});
|
});
|
||||||
const pausedTrayIcon = nativeImage.createFromPath(pausedTrayIconAsset).resize({
|
const pausedTrayIcon = nativeImage.createFromPath(pausedTrayIconAsset).resize({
|
||||||
width: 16,
|
width: 16 * pixelRatio,
|
||||||
height: 16,
|
height: 16 * pixelRatio,
|
||||||
});
|
});
|
||||||
|
|
||||||
tray = new Tray(defaultTrayIcon);
|
tray = new Tray(defaultTrayIcon);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import type { PlayerConfig } from '@/types/get-player-response';
|
||||||
|
|
||||||
export interface GetState {
|
export interface GetState {
|
||||||
castStatus: CastStatus;
|
castStatus: CastStatus;
|
||||||
entities: Entities;
|
entities: Entities;
|
||||||
@ -32,17 +34,11 @@ export interface Download {
|
|||||||
export interface Entities {}
|
export interface Entities {}
|
||||||
|
|
||||||
export interface LikeStatus {
|
export interface LikeStatus {
|
||||||
videos: Videos;
|
videos: Record<string, LikeType>;
|
||||||
playlists: Entities;
|
playlists: Entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Videos {
|
export enum LikeType {
|
||||||
tNVTuUEeWP0: Kqp1PyPRBzA;
|
|
||||||
KQP1PyPrBzA: Kqp1PyPRBzA;
|
|
||||||
'o1iz4L-5zkQ': Kqp1PyPRBzA;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Kqp1PyPRBzA {
|
|
||||||
Dislike = 'DISLIKE',
|
Dislike = 'DISLIKE',
|
||||||
Indifferent = 'INDIFFERENT',
|
Indifferent = 'INDIFFERENT',
|
||||||
Like = 'LIKE',
|
Like = 'LIKE',
|
||||||
@ -195,14 +191,10 @@ export interface Target {
|
|||||||
|
|
||||||
export interface CommandWatchEndpoint {
|
export interface CommandWatchEndpoint {
|
||||||
videoId: string;
|
videoId: string;
|
||||||
params: PurpleParams;
|
params: string;
|
||||||
watchEndpointMusicSupportedConfigs: PurpleWatchEndpointMusicSupportedConfigs;
|
watchEndpointMusicSupportedConfigs: PurpleWatchEndpointMusicSupportedConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PurpleParams {
|
|
||||||
WAEB = 'wAEB',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PurpleWatchEndpointMusicSupportedConfigs {
|
export interface PurpleWatchEndpointMusicSupportedConfigs {
|
||||||
watchEndpointMusicConfig: PurpleWatchEndpointMusicConfig;
|
watchEndpointMusicConfig: PurpleWatchEndpointMusicConfig;
|
||||||
}
|
}
|
||||||
@ -381,7 +373,7 @@ export enum SharePanelType {
|
|||||||
export interface PurpleWatchEndpoint {
|
export interface PurpleWatchEndpoint {
|
||||||
videoId: string;
|
videoId: string;
|
||||||
playlistId: string;
|
playlistId: string;
|
||||||
params: PurpleParams;
|
params: string;
|
||||||
loggingContext: LoggingContext;
|
loggingContext: LoggingContext;
|
||||||
watchEndpointMusicSupportedConfigs: PurpleWatchEndpointMusicSupportedConfigs;
|
watchEndpointMusicSupportedConfigs: PurpleWatchEndpointMusicSupportedConfigs;
|
||||||
}
|
}
|
||||||
@ -466,7 +458,7 @@ export interface FeedbackEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PurpleLikeEndpoint {
|
export interface PurpleLikeEndpoint {
|
||||||
status: Kqp1PyPRBzA;
|
status: LikeType;
|
||||||
target: Target;
|
target: Target;
|
||||||
actions?: LikeEndpointAction[];
|
actions?: LikeEndpointAction[];
|
||||||
}
|
}
|
||||||
@ -488,7 +480,7 @@ export interface PurpleToggledServiceEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FluffyLikeEndpoint {
|
export interface FluffyLikeEndpoint {
|
||||||
status: Kqp1PyPRBzA;
|
status: LikeType;
|
||||||
target: Target;
|
target: Target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -690,7 +682,7 @@ export interface FluffyDefaultServiceEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TentacledLikeEndpoint {
|
export interface TentacledLikeEndpoint {
|
||||||
status: Kqp1PyPRBzA;
|
status: LikeType;
|
||||||
target: AddToPlaylistEndpoint;
|
target: AddToPlaylistEndpoint;
|
||||||
actions?: LikeEndpointAction[];
|
actions?: LikeEndpointAction[];
|
||||||
}
|
}
|
||||||
@ -702,7 +694,7 @@ export interface FluffyToggledServiceEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StickyLikeEndpoint {
|
export interface StickyLikeEndpoint {
|
||||||
status: Kqp1PyPRBzA;
|
status: LikeType;
|
||||||
target: AddToPlaylistEndpoint;
|
target: AddToPlaylistEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1185,81 +1177,6 @@ export interface PtrackingURLClass {
|
|||||||
headers: HeaderElement[];
|
headers: HeaderElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlayerConfig {
|
|
||||||
audioConfig: AudioConfig;
|
|
||||||
streamSelectionConfig: StreamSelectionConfig;
|
|
||||||
mediaCommonConfig: MediaCommonConfig;
|
|
||||||
webPlayerConfig: WebPlayerConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AudioConfig {
|
|
||||||
loudnessDb: number;
|
|
||||||
perceptualLoudnessDb: number;
|
|
||||||
enablePerFormatLoudness: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MediaCommonConfig {
|
|
||||||
dynamicReadaheadConfig: DynamicReadaheadConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DynamicReadaheadConfig {
|
|
||||||
maxReadAheadMediaTimeMs: number;
|
|
||||||
minReadAheadMediaTimeMs: number;
|
|
||||||
readAheadGrowthRateMs: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StreamSelectionConfig {
|
|
||||||
maxBitrate: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebPlayerConfig {
|
|
||||||
useCobaltTvosDash: boolean;
|
|
||||||
webPlayerActionsPorting: WebPlayerActionsPorting;
|
|
||||||
gatewayExperimentGroup: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebPlayerActionsPorting {
|
|
||||||
subscribeCommand: SubscribeCommand;
|
|
||||||
unsubscribeCommand: UnsubscribeCommand;
|
|
||||||
addToWatchLaterCommand: AddToWatchLaterCommand;
|
|
||||||
removeFromWatchLaterCommand: RemoveFromWatchLaterCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddToWatchLaterCommand {
|
|
||||||
clickTrackingParams: string;
|
|
||||||
playlistEditEndpoint: AddToWatchLaterCommandPlaylistEditEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddToWatchLaterCommandPlaylistEditEndpoint {
|
|
||||||
playlistId: string;
|
|
||||||
actions: PurpleAction[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PurpleAction {
|
|
||||||
addedVideoId: string;
|
|
||||||
action: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoveFromWatchLaterCommand {
|
|
||||||
clickTrackingParams: string;
|
|
||||||
playlistEditEndpoint: RemoveFromWatchLaterCommandPlaylistEditEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoveFromWatchLaterCommandPlaylistEditEndpoint {
|
|
||||||
playlistId: string;
|
|
||||||
actions: FluffyAction[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FluffyAction {
|
|
||||||
action: string;
|
|
||||||
removedVideoId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SubscribeCommand {
|
|
||||||
clickTrackingParams: string;
|
|
||||||
subscribeEndpoint: SubscribeEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Storyboards {
|
export interface Storyboards {
|
||||||
playerStoryboardSpecRenderer: PlayerStoryboardSpecRenderer;
|
playerStoryboardSpecRenderer: PlayerStoryboardSpecRenderer;
|
||||||
}
|
}
|
||||||
@ -1384,7 +1301,7 @@ export interface PlayerOverlayRendererAction {
|
|||||||
|
|
||||||
export interface LikeButtonRenderer {
|
export interface LikeButtonRenderer {
|
||||||
target: Target;
|
target: Target;
|
||||||
likeStatus: Kqp1PyPRBzA;
|
likeStatus: LikeType;
|
||||||
trackingParams: string;
|
trackingParams: string;
|
||||||
likesAllowed: boolean;
|
likesAllowed: boolean;
|
||||||
serviceEndpoints: ServiceEndpoint[];
|
serviceEndpoints: ServiceEndpoint[];
|
||||||
@ -1396,13 +1313,14 @@ export interface ServiceEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceEndpointLikeEndpoint {
|
export interface ServiceEndpointLikeEndpoint {
|
||||||
status: Kqp1PyPRBzA;
|
status: LikeType;
|
||||||
target: Target;
|
target: Target;
|
||||||
likeParams?: LikeParams;
|
likeParams?: LikeParams;
|
||||||
dislikeParams?: LikeParams;
|
dislikeParams?: LikeParams;
|
||||||
removeLikeParams?: LikeParams;
|
removeLikeParams?: LikeParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add more
|
||||||
export enum LikeParams {
|
export enum LikeParams {
|
||||||
Oai3D = 'OAI%3D',
|
Oai3D = 'OAI%3D',
|
||||||
}
|
}
|
||||||
@ -1467,16 +1385,12 @@ export interface CurrentVideoEndpoint {
|
|||||||
|
|
||||||
export interface CurrentVideoEndpointWatchEndpoint {
|
export interface CurrentVideoEndpointWatchEndpoint {
|
||||||
videoId: string;
|
videoId: string;
|
||||||
playlistId: PlaylistID;
|
playlistId: string;
|
||||||
index: number;
|
index: number;
|
||||||
playlistSetVideoId: string;
|
playlistSetVideoId: string;
|
||||||
loggingContext: LoggingContext;
|
loggingContext: LoggingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PlaylistID {
|
|
||||||
RDAMVMrkaNKAvksDE = 'RDAMVMrkaNKAvksDE',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlayerPageWatchNextResponseResponseContext {
|
export interface PlayerPageWatchNextResponseResponseContext {
|
||||||
serviceTrackingParams: ServiceTrackingParam[];
|
serviceTrackingParams: ServiceTrackingParam[];
|
||||||
}
|
}
|
||||||
@ -1536,6 +1450,8 @@ export interface FlagEndpoint {
|
|||||||
flagAction: string;
|
flagAction: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RepeatMode = 'NONE' | 'ONE' | 'ALL';
|
||||||
|
|
||||||
export interface Queue {
|
export interface Queue {
|
||||||
automixItems: unknown[];
|
automixItems: unknown[];
|
||||||
autoplay: boolean;
|
autoplay: boolean;
|
||||||
@ -1553,7 +1469,7 @@ export interface Queue {
|
|||||||
nextQueueItemId: number;
|
nextQueueItemId: number;
|
||||||
playbackContentMode: string;
|
playbackContentMode: string;
|
||||||
queueContextParams: string;
|
queueContextParams: string;
|
||||||
repeatMode: string;
|
repeatMode: RepeatMode;
|
||||||
responsiveSignals: ResponsiveSignals;
|
responsiveSignals: ResponsiveSignals;
|
||||||
selectedItemIndex: number;
|
selectedItemIndex: number;
|
||||||
shuffleEnabled: boolean;
|
shuffleEnabled: boolean;
|
||||||
@ -1642,23 +1558,15 @@ export interface PlaylistPanelVideoRendererNavigationEndpoint {
|
|||||||
|
|
||||||
export interface FluffyWatchEndpoint {
|
export interface FluffyWatchEndpoint {
|
||||||
videoId: string;
|
videoId: string;
|
||||||
playlistId?: PlaylistID;
|
playlistId?: string;
|
||||||
index: number;
|
index: number;
|
||||||
params: FluffyParams;
|
params: string;
|
||||||
playerParams?: PlayerParams;
|
playerParams?: string;
|
||||||
playlistSetVideoId?: string;
|
playlistSetVideoId?: string;
|
||||||
loggingContext?: LoggingContext;
|
loggingContext?: LoggingContext;
|
||||||
watchEndpointMusicSupportedConfigs: FluffyWatchEndpointMusicSupportedConfigs;
|
watchEndpointMusicSupportedConfigs: FluffyWatchEndpointMusicSupportedConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FluffyParams {
|
|
||||||
OAHyAQIIAQ3D3D = 'OAHyAQIIAQ%3D%3D',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PlayerParams {
|
|
||||||
The8Aub = '8AUB',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FluffyWatchEndpointMusicSupportedConfigs {
|
export interface FluffyWatchEndpointMusicSupportedConfigs {
|
||||||
watchEndpointMusicConfig: FluffyWatchEndpointMusicConfig;
|
watchEndpointMusicConfig: FluffyWatchEndpointMusicConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/types/queue.ts
Normal file
40
src/types/queue.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||||
|
import type { GetState, QueueItem } from '@/types/datahost-get-state';
|
||||||
|
|
||||||
|
type StoreState = GetState;
|
||||||
|
type Store = {
|
||||||
|
dispatch: (obj: {
|
||||||
|
type: string;
|
||||||
|
payload?: {
|
||||||
|
items?: QueueItem[];
|
||||||
|
};
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
getState: () => StoreState;
|
||||||
|
replaceReducer: (param1: unknown) => unknown;
|
||||||
|
subscribe: (callback: () => void) => unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueueElement = HTMLElement & {
|
||||||
|
dispatch(obj: {
|
||||||
|
type: string;
|
||||||
|
payload?: unknown;
|
||||||
|
}): void;
|
||||||
|
queue: QueueAPI;
|
||||||
|
};
|
||||||
|
export type QueueAPI = {
|
||||||
|
getItems(): QueueItem[];
|
||||||
|
store: {
|
||||||
|
store: Store,
|
||||||
|
};
|
||||||
|
continuation?: string;
|
||||||
|
autoPlaying?: boolean;
|
||||||
|
};
|
||||||
|
export type AppElement = HTMLElement & AppAPI;
|
||||||
|
export type AppAPI = {
|
||||||
|
queue_: QueueAPI;
|
||||||
|
playerApi_: YoutubePlayer;
|
||||||
|
openToast: (message: string) => void;
|
||||||
|
|
||||||
|
// TODO: Add more
|
||||||
|
};
|
||||||
7
src/types/youtube-music-desktop-internal.ts
Normal file
7
src/types/youtube-music-desktop-internal.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { QueueItem } from '@/types/datahost-get-state';
|
||||||
|
|
||||||
|
export interface QueueResponse {
|
||||||
|
items?: QueueItem[];
|
||||||
|
autoPlaying?: boolean;
|
||||||
|
continuation?: string;
|
||||||
|
}
|
||||||
@ -3,12 +3,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Allow window dragging */
|
/* Allow window dragging */
|
||||||
.center-content.ytmusic-nav-bar {
|
ytmusic-nav-bar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
ytmusic-nav-bar::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center-content.ytmusic-nav-bar > ytmusic-search-box {
|
ytmusic-nav-bar > .left-content > *,
|
||||||
|
ytmusic-nav-bar > .center-content > *,
|
||||||
|
ytmusic-nav-bar > .right-content > * {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +44,11 @@ img {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide cast button which doesn't work */
|
||||||
|
ytmusic-cast-button {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Remove useless inaccessible button on top-right corner of the video player */
|
/* Remove useless inaccessible button on top-right corner of the video player */
|
||||||
.ytp-chrome-top-buttons {
|
.ytp-chrome-top-buttons {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
@ -50,7 +64,28 @@ ytmusic-nav-bar > div.left-content > a > picture > img {
|
|||||||
tp-yt-paper-item.ytmusic-guide-entry-renderer::before {
|
tp-yt-paper-item.ytmusic-guide-entry-renderer::before {
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
/** apply fix when #av-id is exist */
|
|
||||||
ytmusic-player-page:not([video-mode]):not([player-fullscreened]) #av-id ~ #player.ytmusic-player-page {
|
/* fix video player align */
|
||||||
margin-top: calc(var(--ytmusic-player-page-vertical-padding) / 2 * -1) !important;
|
#av-id {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#av-id ~ #player.ytmusic-player-page:not([player-ui-state="FULLSCREEN"]) {
|
||||||
|
margin-top: auto !important;
|
||||||
|
margin-bottom: auto !important;
|
||||||
|
max-height: calc(100% - (var(--ytmusic-player-page-vertical-padding) * 2));
|
||||||
|
max-width: calc(100% - var(--ytmusic-player-page-vertical-padding) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-player[player-ui-state=FULLSCREEN] {
|
||||||
|
margin-top: calc(var(--menu-bar-height, 32px) * -1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* macos traffic lights fix */
|
||||||
|
:where([data-os*="Macintosh"]) ytmusic-app-layout#layout ytmusic-nav-bar {
|
||||||
|
padding-top: var(--ytmusic-nav-bar-offset, 0);
|
||||||
|
}
|
||||||
|
:where([data-os*="Macintosh"]) ytmusic-app-layout#layout {
|
||||||
|
--ytmusic-nav-bar-offset: 24px;
|
||||||
|
--ytmusic-nav-bar-height: calc(90px + var(--ytmusic-nav-bar-offset, 0));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user