Compare commits
503 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| eabb3392b4 | |||
| c78cc3a38d | |||
| 011ffd538b | |||
| f386576196 | |||
| bab3526f0f | |||
| 115923b422 | |||
| fefa8c8750 | |||
| 3aa80c0f01 | |||
| aea219994a | |||
| 74e22ccecd | |||
| db09cf5ffb | |||
| cc1d13f203 | |||
| 0cd1ce6a79 | |||
| 95254fc2ff | |||
| 205376aadf | |||
| 6f057bfbfc | |||
| bb39481666 | |||
| ddb9968195 | |||
| a309729fa4 | |||
| ba7e065ba6 | |||
| ee05893d4c | |||
| febc63edef | |||
| b3c05c8647 | |||
| cd8701d0e5 | |||
| 3b41edb62d | |||
| ab3b8495df | |||
| b04cd79bcf | |||
| c864e5764a | |||
| 1f71df6c41 | |||
| 0decda57ab | |||
| 5bfd3a4562 | |||
| d4af820ae8 | |||
| 3ec25b7779 | |||
| a9ee12b05e | |||
| a612d1c1fd | |||
| fb48d24e0d | |||
| 38d19d9ea7 | |||
| 4950abc399 | |||
| e3ad804dc4 | |||
| f2f15bc3cc | |||
| 4624a1022a | |||
| cc169da6d4 | |||
| c58ed21661 | |||
| c483733bc3 | |||
| 9738f2f6ae | |||
| ecdd8eb9f6 | |||
| 47e2052ce0 | |||
| a34eb31d9c | |||
| c03cab179e | |||
| 0d61cf906d | |||
| b3c1aa6b4b | |||
| 4904620ce6 | |||
| feaccb593d | |||
| 9ad2baea59 | |||
| df77086039 | |||
| ceb844473a | |||
| a0932b0dc4 | |||
| 0be37716ef | |||
| c07b05a7be | |||
| 94b1da9db0 | |||
| 5fa7a1273f | |||
| cf9088785b | |||
| adf3e3150e | |||
| dbeb63018e | |||
| 51a39d240c | |||
| 4061f8e0e6 | |||
| d32e60249a | |||
| b84a1e43af | |||
| f5c22b63aa | |||
| 41700799c7 | |||
| b15b421975 | |||
| cca4de8684 | |||
| 946c2790c4 | |||
| 3a033f1bab | |||
| 67a04a2840 | |||
| dd48e46854 | |||
| 99edb15c77 | |||
| 8503afeac7 | |||
| 0528637135 | |||
| bf20a2b3be | |||
| 0db55ce4f3 | |||
| 21347e9d0a | |||
| 4333a25c31 | |||
| 5b20e491bd | |||
| bc05f5849d | |||
| b1046bc28d | |||
| d5ab36f42e | |||
| 922d78dcee | |||
| de6506e6b4 | |||
| 9d136c8dd5 | |||
| 26de7f940e | |||
| 7c404ba2ea | |||
| 96d2a72bfa | |||
| 10f41bddad | |||
| 39d2f3ec80 | |||
| 512b446a3d | |||
| c9b96f0488 | |||
| c84ea257d5 | |||
| 6512f5ad2a | |||
| e5d0eced5d | |||
| f424ee5170 | |||
| ea0f6c401d | |||
| 5c5f51b3de | |||
| 7caf02ebba | |||
| f6a444b970 | |||
| d19a36b44f | |||
| aacb126fb5 | |||
| 5adf45cde2 | |||
| 0980aad060 | |||
| 069e5ac8b8 | |||
| b5f6762997 | |||
| c2abfe4b41 | |||
| 3964d03a3b | |||
| a82c4ce499 | |||
| 2f54fa19e6 | |||
| aacb4b3147 | |||
| 60980251f2 | |||
| f8e55f95df | |||
| bebd232af0 | |||
| 1a89fbe612 | |||
| e73584c2aa | |||
| 96a713074f | |||
| aedfa4ca06 | |||
| ac187f722b | |||
| cd87642384 | |||
| 4a736d3211 | |||
| e586940c57 | |||
| ab36a21583 | |||
| c0c1c3b626 | |||
| 918bd7fdb1 | |||
| 971d1a5776 | |||
| 1235d46e73 | |||
| 451b98833b | |||
| 48f9be9712 | |||
| fd8d59bada | |||
| 99ce0b7f9c | |||
| 88ace9ab35 | |||
| 63b0ea60e4 | |||
| b4ecf0f935 | |||
| c846f18086 | |||
| 8a851b06f9 | |||
| 5484dc8bd1 | |||
| 6c47ac36e3 | |||
| 3554496803 | |||
| b8a197615e | |||
| 2ed949920f | |||
| d76f4dade3 | |||
| aacd01ce7c | |||
| d501b016fc | |||
| b1503cfb87 | |||
| 8a004ae9dd | |||
| de709cc7c9 | |||
| 6b7c43925a | |||
| 5d5cc58f59 | |||
| 3e6bab7f15 | |||
| 7e17a8b73b | |||
| 129b798d8f | |||
| a3a411e197 | |||
| 6e6acd6f19 | |||
| 34cb79eeaf | |||
| 1c30a07031 | |||
| 5dd5f41ef5 | |||
| 45a3c11d51 | |||
| 8bd3b4d3f0 | |||
| 4e3cb5806d | |||
| 6563eb4ddd | |||
| 895386f6f8 | |||
| 3810955e56 | |||
| 59c521e53f | |||
| 25d266f8f9 | |||
| 0c3c380591 | |||
| a20cfa30a1 | |||
| fefe899393 | |||
| 55759e8d7a | |||
| ddb561937f | |||
| 198cb71a4c | |||
| c34b880752 | |||
| 76944e3e41 | |||
| 68cd76f2af | |||
| 81145b52b7 | |||
| 2a19dab061 | |||
| 6958d59d4f | |||
| 8a51dfad87 | |||
| 5bb4d9efbe | |||
| 927aa5f24b | |||
| d695bc93a1 | |||
| b05fb4ccbe | |||
| 299eb7e7d6 | |||
| ae26333224 | |||
| 35176469b0 | |||
| 4e74f9cbc5 | |||
| 4091b36f36 | |||
| b3f805fce6 | |||
| b129a3e8d8 | |||
| 64ea1fdb58 | |||
| 8fcf59ed0a | |||
| 9811ca63de | |||
| 9028f88299 | |||
| fd47766d93 | |||
| 26b12c7208 | |||
| 8da9b3454d | |||
| 205cbefc83 | |||
| 0e94c72eef | |||
| c055641351 | |||
| c0a3aa99de | |||
| 8a8976acef | |||
| e409165e1b | |||
| b278140796 | |||
| 397056a54d | |||
| edecd65419 | |||
| 4d2d0b7bd6 | |||
| 0ca4e34efd | |||
| 43f3226c3a | |||
| 0a6dbecc05 | |||
| f5aa179cd6 | |||
| 3140e91dda | |||
| 022f8ff65c | |||
| 5e63cc2e89 | |||
| 880ed99846 | |||
| 222e78c85b | |||
| 050d55c736 | |||
| 13ef8560ff | |||
| 78d990c079 | |||
| 4d3e2c09da | |||
| aa899d247a | |||
| ee0c512529 | |||
| 5f9b522307 | |||
| c207e29980 | |||
| df4d2d6b72 | |||
| c3dd20cabd | |||
| 7a6db95d1a | |||
| bc6825d63b | |||
| 5e79e9e0f2 | |||
| 5e303c2ba8 | |||
| 0bd9c16356 | |||
| f0f5d9da2f | |||
| f46c431f4c | |||
| 62410e9ee2 | |||
| 46f76f1408 | |||
| 5e071e16d8 | |||
| c0238588bd | |||
| 30002d660a | |||
| 48eeb6bca3 | |||
| e67699fed5 | |||
| 8aeae45965 | |||
| ce7491941b | |||
| 1dce03c4f2 | |||
| 62eae6d5d0 | |||
| 15b2b26b84 | |||
| 9664c17c47 | |||
| 8067dad2fa | |||
| 4dcaa510d9 | |||
| b6e918089d | |||
| 1c9e6b1bb8 | |||
| ebd304c252 | |||
| 36083c4173 | |||
| a084b060d8 | |||
| 432c79b606 | |||
| 0f1f0ee933 | |||
| 9b1a4b8d88 | |||
| 1a7a665915 | |||
| 623ecf7fb8 | |||
| 0dc9c6a1a9 | |||
| 72c5eaa5ff | |||
| 0f47b94b7d | |||
| 9abe15f1ad | |||
| 96afda92c8 | |||
| 5c6fd4a739 | |||
| 23b87a876d | |||
| 737fd05369 | |||
| c5bcd89f16 | |||
| 377e1be0b2 | |||
| a92049c0c9 | |||
| 27a2955bba | |||
| cc940e2020 | |||
| 203c80767d | |||
| f564039438 | |||
| d6566fb870 | |||
| 55ab17e789 | |||
| e0eeb720cd | |||
| 53ac7ff257 | |||
| a4e2f10afa | |||
| c9c9d766e6 | |||
| b82d161180 | |||
| 7cadacd8cf | |||
| fc1a7cda62 | |||
| da3bc5aeb7 | |||
| ee98344064 | |||
| da10eabf99 | |||
| ce3c63c386 | |||
| f8c3cff5a9 | |||
| 08f369f8bc | |||
| 1517230ede | |||
| 45aeadef86 | |||
| 5ec7c346ca | |||
| 4b68de1606 | |||
| 7710bcdacc | |||
| 4c19bcc983 | |||
| 2f50b1423a | |||
| 15768c9691 | |||
| 442eb75b8d | |||
| 8facd27125 | |||
| a302cb6ebf | |||
| 32422c7ba9 | |||
| ca131cb156 | |||
| 5f8262ede1 | |||
| 88db9d3693 | |||
| 0fa6f344f9 | |||
| d1642b3dfb | |||
| 7599fce5f5 | |||
| 9bd0dc9a7b | |||
| dad7cafebc | |||
| 4febb28201 | |||
| ced943bad3 | |||
| 202b8717a2 | |||
| eba7026b89 | |||
| 6b2adca1f4 | |||
| 0c9d3cc057 | |||
| 45ec11aaed | |||
| 4d47fa3fac | |||
| 6322d5bb09 | |||
| 2e8a579049 | |||
| 9db7e99d7f | |||
| ef73989995 | |||
| 61024084e9 | |||
| 1c71b2020f | |||
| 3b2b545388 | |||
| fafc7f94fd | |||
| 5fa88b5ce9 | |||
| a8fe4167db | |||
| 0e6b0ddc26 | |||
| af88db3865 | |||
| 0a1bef556c | |||
| 7030181dc1 | |||
| 98cb2f61ab | |||
| a601d0b3d2 | |||
| 8d18923065 | |||
| 4b6a7e0bd7 | |||
| 44f87853d6 | |||
| 4ff47bee03 | |||
| 7a2c28df21 | |||
| f7241d1e4a | |||
| 2b5daf3a75 | |||
| d7184cf75d | |||
| 1313d18d44 | |||
| 605e9481d9 | |||
| cf4c89f825 | |||
| f4f1d4d373 | |||
| 6557b40924 | |||
| e01a29c5d1 | |||
| d9a7e16d63 | |||
| d170199d85 | |||
| e650aae491 | |||
| 49058dbeab | |||
| 31d2d91566 | |||
| abf2a52b46 | |||
| d0ca10e1a1 | |||
| fe99a04174 | |||
| 916900ae6a | |||
| 826c9ba48e | |||
| 7783cc5f30 | |||
| 035d7f19ea | |||
| 294cf15d58 | |||
| 11fa0dcbc6 | |||
| 85b53db439 | |||
| 200df100f4 | |||
| ba202a8572 | |||
| daf05239a1 | |||
| b73b5735ec | |||
| 292248dd35 | |||
| 2e240541c8 | |||
| ab62ae3682 | |||
| 5c3f5d05d3 | |||
| c81af537b6 | |||
| 7295c73371 | |||
| 5a65e08a94 | |||
| 45e5bc7df5 | |||
| a81cb9515c | |||
| 88e5cc2728 | |||
| 1685648328 | |||
| 9334adacf6 | |||
| efbe557dce | |||
| 52b2625486 | |||
| aa0424db08 | |||
| 157619aa4f | |||
| ce637968b2 | |||
| b2a3ed7428 | |||
| ca856e4d88 | |||
| 2ef2536766 | |||
| 1b22633388 | |||
| db306ad4e0 | |||
| 34a44cb9c6 | |||
| 8c499a6e20 | |||
| 3b816a6fd9 | |||
| 35aba8e25a | |||
| 0eb6723f27 | |||
| 2250ec9372 | |||
| ef02b53284 | |||
| 57df7cf3f9 | |||
| f0cd540726 | |||
| 7e919395eb | |||
| facc7252c2 | |||
| 09b8943f69 | |||
| aa84ffc7e1 | |||
| 10162c948b | |||
| e8c1cbdd94 | |||
| 5459f623ec | |||
| 2a2cba3539 | |||
| 1ce84607e6 | |||
| 075c54d39f | |||
| aabb826cb8 | |||
| 3791b1ae1c | |||
| d4b87d098b | |||
| 57e9c13d13 | |||
| e48e570e69 | |||
| b7d4d5b022 | |||
| dc8b0173e9 | |||
| 840729126e | |||
| ed90b97a92 | |||
| bbe4f07651 | |||
| 66df83a86d | |||
| dfedd8f091 | |||
| 58ec6eebfb | |||
| 3ba4130bc6 | |||
| 7dbbf38e78 | |||
| 51e04162a7 | |||
| d3325e2490 | |||
| d2708ee32f | |||
| 781a09c717 | |||
| 11605293dd | |||
| 3f0b946190 | |||
| 74972f053a | |||
| d85190ace1 | |||
| 5755b4ac7f | |||
| 3c24f2edcd | |||
| 6828520853 | |||
| 2ba7dddb95 | |||
| 57a8922d04 | |||
| 640098860a | |||
| 53384d9f3b | |||
| 89d8d98a35 | |||
| 7b78ba6761 | |||
| 3926a9a0c0 | |||
| 0708cd5a38 | |||
| e20e9ca771 | |||
| be1038bafd | |||
| ebc087963b | |||
| 020bdc0811 | |||
| a0d1ad6a47 | |||
| 62e5679791 | |||
| 35568bd299 | |||
| 1e40b377af | |||
| 1073de1b45 | |||
| 1d5788acaf | |||
| 8fb446588d | |||
| 6cec34b2ac | |||
| 763d3e8f74 | |||
| 7da0a913f1 | |||
| ca04c4561b | |||
| 7d8fbf49a8 | |||
| 75e15b948d | |||
| 125b69fd75 | |||
| a68d6b64dd | |||
| a60d4264dc | |||
| 9e2c6b1afa | |||
| 14965a93e9 | |||
| f62664b6a5 | |||
| 60cb7f32f1 | |||
| 008b3ad710 | |||
| 8cae64f496 | |||
| 5cdc1bc762 | |||
| 15c455105b | |||
| 14407a98c9 | |||
| 0d004d5caf | |||
| 4b75a2405c |
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -24,6 +24,12 @@ body:
|
|||||||
placeholder: 2.0.0
|
placeholder: 2.0.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Checklists
|
||||||
|
options:
|
||||||
|
- 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).
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: What operating system are you using?
|
label: What operating system are you using?
|
||||||
@ -44,7 +50,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: What arch are you using?
|
label: What CPU architecture are you using?
|
||||||
options:
|
options:
|
||||||
- x64
|
- x64
|
||||||
- ia32
|
- ia32
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -15,7 +15,7 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Problem Description
|
label: Problem Description
|
||||||
description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
|
description: A clear and concise description of the problem you are seeking to solve with this feature request.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
@ -33,6 +33,6 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional Information
|
label: Additional Information
|
||||||
description: Add any other context about the problem here.
|
description: Any other context about the problem.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
2
.github/workflows/dependency-review.yml
vendored
@ -17,4 +17,4 @@ jobs:
|
|||||||
- name: "Checkout Repository"
|
- name: "Checkout Repository"
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: "Dependency Review"
|
- name: "Dependency Review"
|
||||||
uses: actions/dependency-review-action@v3
|
uses: actions/dependency-review-action@v4
|
||||||
|
|||||||
62
README.md
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://github.com/th-ch/youtube-music/releases/latest">
|
<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">
|
<img src="web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
|
||||||
@ -28,6 +29,12 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md)
|
|||||||
- Framework for custom plugins: change YouTube Music to your needs (style, content, features), enable/disable plugins in
|
- Framework for custom plugins: change YouTube Music to your needs (style, content, features), enable/disable plugins in
|
||||||
one click
|
one click
|
||||||
|
|
||||||
|
## Demo Image
|
||||||
|
|
||||||
|
| Player Screen (album color theme & ambient light) |
|
||||||
|
|:---------------------------------------------------------------------------------------------------------:|
|
||||||
|
||
|
||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
||||||
@ -49,9 +56,10 @@ this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Insta
|
|||||||
|
|
||||||
### MacOS
|
### MacOS
|
||||||
|
|
||||||
You can install the app using Homebrew:
|
You can install the app using Homebrew (see the [cask definition](https://github.com/th-ch/homebrew-youtube-music)):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master/youtube-music.rb
|
brew install th-ch/youtube-music/youtube-music
|
||||||
```
|
```
|
||||||
|
|
||||||
If you install the app manually and get an error "is damaged and can’t be opened." when launching the app, run the following in the Terminal:
|
If you install the app manually and get an error "is damaged and can’t be opened." when launching the app, run the following in the Terminal:
|
||||||
@ -102,27 +110,29 @@ winget install th-ch.YouTubeMusic
|
|||||||
## Available plugins:
|
## Available plugins:
|
||||||
|
|
||||||
- **Ad Blocker**: Block all ads and tracking out of the box
|
- **Ad Blocker**: Block all ads and tracking out of the box
|
||||||
|
|
||||||
|
- **Album Actions**: Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album
|
||||||
|
|
||||||
- **Album Color Theme**: Applies a dynamic theme and visual effects based on the album color palette
|
- **Album Color Theme**: Applies a dynamic theme and visual effects based on the album color palette
|
||||||
|
|
||||||
- **Ambient Mode**: Applies a lighting effect by casting gentle colors from the video, into your screen’s background.
|
- **Ambient Mode**: Applies a lighting effect by casting gentle colors from the video, into your screen’s background
|
||||||
|
|
||||||
- **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the
|
- **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the
|
||||||
volume of the softest parts)
|
volume of the softest parts)
|
||||||
|
|
||||||
- **Blur Nav Bar**: makes navigation bar transparent and blurry
|
- **Blur Navigation Bar**: makes navigation bar transparent and blurry
|
||||||
|
|
||||||
- **Bypass age restrictions**: bypass YouTube's age verification
|
- **Bypass Age Restrictions**: bypass YouTube's age verification
|
||||||
|
|
||||||
- **Captions selector**: Enable captions
|
- **Captions Selector**: Enable captions
|
||||||
|
|
||||||
- **Compact sidebar**: Always set the sidebar in compact mode
|
- **Compact Sidebar**: Always set the sidebar in compact mode
|
||||||
|
|
||||||
- **Crossfade**: Crossfade between songs
|
- **Crossfade**: Crossfade between songs
|
||||||
|
|
||||||
- **Disable Autoplay**: Makes every song start in "paused" mode
|
- **Disable Autoplay**: Makes every song start in "paused" mode
|
||||||
|
|
||||||
- [**Discord**](https://discord.com/): Show your friends what you listen to
|
- **[Discord](https://discord.com/) Rich Presence**: Show your friends what you listen to
|
||||||
with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||||
|
|
||||||
- **Downloader**: downloads
|
- **Downloader**: downloads
|
||||||
@ -130,19 +140,21 @@ winget install th-ch.YouTubeMusic
|
|||||||
|
|
||||||
- **Exponential Volume**: Makes the volume
|
- **Exponential Volume**: Makes the volume
|
||||||
slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to
|
slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to
|
||||||
select lower volumes.
|
select lower volumes
|
||||||
|
|
||||||
- **In-App Menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
- **In-App Menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
||||||
|
|
||||||
> (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem
|
> (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem
|
||||||
accessing the menu after enabling this plugin and hide-menu option)
|
accessing the menu after enabling this plugin and hide-menu option)
|
||||||
|
|
||||||
- [**Last.fm**](https://www.last.fm/): Scrobbles support
|
- **Scrobbler**: Adds scrobbling support for [Last.fm](https://www.last.fm/) and [ListenBrainz](https://listenbrainz.org/)
|
||||||
|
|
||||||
- **Lumia Stream**: Adds [Lumia Stream](https://lumiastream.com/) support
|
- **Lumia Stream**: Adds [Lumia Stream](https://lumiastream.com/) support
|
||||||
|
|
||||||
- **Lyrics Genius**: Adds lyrics support for most songs
|
- **Lyrics Genius**: Adds lyrics support for most songs
|
||||||
|
|
||||||
|
- **Music Together**: Share a playlist with others. When the host plays a song, everyone else will hear the same song
|
||||||
|
|
||||||
- **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser
|
- **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser
|
||||||
|
|
||||||
- **No Google Login**: Remove Google login buttons and links from the interface
|
- **No Google Login**: Remove Google login buttons and links from the interface
|
||||||
@ -151,7 +163,7 @@ winget install th-ch.YouTubeMusic
|
|||||||
playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
|
playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
|
||||||
are available on windows)
|
are available on windows)
|
||||||
|
|
||||||
- **Picture in picture**: allows to switch the app to picture-in-picture mode
|
- **Picture-in-picture**: allows to switch the app to picture-in-picture mode
|
||||||
|
|
||||||
- **Playback Speed**: Listen fast, listen
|
- **Playback Speed**: Listen fast, listen
|
||||||
slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
||||||
@ -159,17 +171,15 @@ winget install th-ch.YouTubeMusic
|
|||||||
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume
|
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume
|
||||||
steps
|
steps
|
||||||
|
|
||||||
- **Quality Changer**: Allows changing the video quality with
|
- **Shortcuts (& MPRIS)**: Allows setting global hotkeys for playback (play/pause/next/previous) +
|
||||||
a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on
|
|
||||||
the video overlay
|
|
||||||
|
|
||||||
- **Shortcuts**: Allows setting global hotkeys for playback (play/pause/next/previous) +
|
|
||||||
disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
|
disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
|
||||||
by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for
|
by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for
|
||||||
mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
|
mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
|
||||||
for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||||
|
|
||||||
- **Skip-Silences** - Automatically skip silenced sections
|
- **Skip Disliked Song**: Skips disliked songs
|
||||||
|
|
||||||
|
- **Skip Silences**: Automatically skip silenced sections
|
||||||
|
|
||||||
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or
|
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or
|
||||||
parts of music videos where the song isn't playing
|
parts of music videos where the song isn't playing
|
||||||
@ -177,11 +187,15 @@ winget install th-ch.YouTubeMusic
|
|||||||
- **Taskbar Media Control**: Control playback from
|
- **Taskbar Media Control**: Control playback from
|
||||||
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||||
|
|
||||||
- **Touchbar**: Custom TouchBar layout for macOS
|
- **TouchBar**: Custom TouchBar layout for macOS
|
||||||
|
|
||||||
- **Tuna-OBS**: Integration with [OBS](https://obsproject.com/)'s
|
- **Tuna OBS**: Integration with [OBS](https://obsproject.com/)'s
|
||||||
plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
|
plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
|
||||||
|
|
||||||
|
- **Video Quality Changer**: Allows changing the video quality with
|
||||||
|
a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on
|
||||||
|
the video overlay
|
||||||
|
|
||||||
- **Video Toggle**: Adds
|
- **Video Toggle**: Adds
|
||||||
a [button](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) to
|
a [button](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) to
|
||||||
switch between Video/Song mode. can also optionally remove the whole video tab
|
switch between Video/Song mode. can also optionally remove the whole video tab
|
||||||
@ -212,7 +226,7 @@ Using plugins, you can:
|
|||||||
|
|
||||||
### Creating a plugin
|
### Creating a plugin
|
||||||
|
|
||||||
Create a folder in `plugins/YOUR-PLUGIN-NAME`:
|
Create a folder in `src/plugins/YOUR-PLUGIN-NAME`:
|
||||||
|
|
||||||
- `index.ts`: the main file of the plugin
|
- `index.ts`: the main file of the plugin
|
||||||
```typescript
|
```typescript
|
||||||
@ -290,9 +304,9 @@ import style from './style.css?inline'; // import style as inline
|
|||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
const builder = createPlugin({
|
export default createPlugin({
|
||||||
name: 'Plugin Label',
|
name: 'Plugin Label',
|
||||||
restartNeeded: true, // if value is true, ytmusic show restart dialog
|
restartNeeded: true, // if value is true, ytmusic will show a restart dialog
|
||||||
config: {
|
config: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
}, // your custom config
|
}, // your custom config
|
||||||
@ -306,9 +320,9 @@ const builder = createPlugin({
|
|||||||
```typescript
|
```typescript
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
const builder = createPlugin({
|
export default createPlugin({
|
||||||
name: 'Plugin Label',
|
name: 'Plugin Label',
|
||||||
restartNeeded: true, // if value is true, ytmusic show restart dialog
|
restartNeeded: true, // if value is true, ytmusic will show the restart dialog
|
||||||
config: {
|
config: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
}, // your custom config
|
}, // your custom config
|
||||||
|
|||||||
BIN
assets/youtube-music-tray-paused.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
91
changelog.md
@ -2,7 +2,96 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||||
|
|
||||||
#### [v3.0.0](https://github.com/th-ch/youtube-music/compare/v2.2.0...v3.0.0)
|
#### [v3.2.2](https://github.com/th-ch/youtube-music/compare/v3.2.1...v3.2.2)
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
- chore(deps): update dependency rollup to v4.9.3 [`0c3c380`](https://github.com/th-ch/youtube-music/commit/0c3c3805918adf2a185a7f1dc67ea3af8135863d)
|
||||||
|
- chore(i18n): Translated using Weblate (Turkish) [`64ea1fd`](https://github.com/th-ch/youtube-music/commit/64ea1fdb58fdf2766ae3284ac1a51bfac8894b36)
|
||||||
|
- fix(music-together): typing [`895386f`](https://github.com/th-ch/youtube-music/commit/895386f6f8c649f77ea15c88f6fb7ecc5b775554)
|
||||||
|
|
||||||
|
#### [v3.2.1](https://github.com/th-ch/youtube-music/compare/v3.2.0...v3.2.1)
|
||||||
|
|
||||||
|
> 1 January 2024
|
||||||
|
|
||||||
|
- fix: fix #1574 [`#1574`](https://github.com/th-ch/youtube-music/issues/1574)
|
||||||
|
- fix: fix #1575 [`#1575`](https://github.com/th-ch/youtube-music/issues/1575)
|
||||||
|
- chore(i18n): Translated using Weblate [`f5aa179`](https://github.com/th-ch/youtube-music/commit/f5aa179cd639eb4b8f70f1264b5b459ebcc16695)
|
||||||
|
- chore(i18n): Translated using Weblate (English) [`e409165`](https://github.com/th-ch/youtube-music/commit/e409165e1bed85f3d1aea3a565e7b9e462b1e05b)
|
||||||
|
- chore(i18n): Translated using Weblate (Czech) [`0ca4e34`](https://github.com/th-ch/youtube-music/commit/0ca4e34efd86e877314e5a245f266065b4cf0013)
|
||||||
|
|
||||||
|
#### [v3.2.0](https://github.com/th-ch/youtube-music/compare/v3.1.1...v3.2.0)
|
||||||
|
|
||||||
|
> 1 January 2024
|
||||||
|
|
||||||
|
- feat(album-color-theme): improve `Album Color Theme` style [`#1571`](https://github.com/th-ch/youtube-music/pull/1571)
|
||||||
|
- feat(menu): add more detail in Menu [`#1570`](https://github.com/th-ch/youtube-music/pull/1570)
|
||||||
|
- feat(music-together): Add new plugin `Music Together` [`#1562`](https://github.com/th-ch/youtube-music/pull/1562)
|
||||||
|
- chore(deps): update dependency rollup to v4.9.2 [`#1567`](https://github.com/th-ch/youtube-music/pull/1567)
|
||||||
|
- fix(deps): update dependency i18next to v23.7.13 [`#1569`](https://github.com/th-ch/youtube-music/pull/1569)
|
||||||
|
- feat: Add new plugin `Album actions` [`#1515`](https://github.com/th-ch/youtube-music/pull/1515)
|
||||||
|
- fix(deps): update dependency i18next to v23.7.12 [`#1564`](https://github.com/th-ch/youtube-music/pull/1564)
|
||||||
|
- fix: Only apply scale factor on Windows [`#1565`](https://github.com/th-ch/youtube-music/pull/1565)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.16.0 [`#1556`](https://github.com/th-ch/youtube-music/pull/1556)
|
||||||
|
- chore(deps): update pnpm to v8.13.1 [`#1557`](https://github.com/th-ch/youtube-music/pull/1557)
|
||||||
|
- chore(deps): update dependency ws to v8.16.0 [`#1559`](https://github.com/th-ch/youtube-music/pull/1559)
|
||||||
|
- fix(deps): update dependency youtubei.js to v8.1.0 [`#1560`](https://github.com/th-ch/youtube-music/pull/1560)
|
||||||
|
- fix(deps): update dependency node-html-parser to v6.1.12 [`#1554`](https://github.com/th-ch/youtube-music/pull/1554)
|
||||||
|
- Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2" [`#1552`](https://github.com/th-ch/youtube-music/pull/1552)
|
||||||
|
- feat(ambient-mode): support ambient mode on `Song section` [`#1555`](https://github.com/th-ch/youtube-music/issues/1555)
|
||||||
|
- fix: fixed an issue with the download button disappearing [`#1551`](https://github.com/th-ch/youtube-music/issues/1551)
|
||||||
|
- fix: fix `homebrew cask` [`#1514`](https://github.com/th-ch/youtube-music/issues/1514)
|
||||||
|
- fix: pnpm build error [`13ef856`](https://github.com/th-ch/youtube-music/commit/13ef8560ff43353030537403be7da82542ba535e)
|
||||||
|
- chore(i18n): Translated using Weblate (Czech) [`0dc9c6a`](https://github.com/th-ch/youtube-music/commit/0dc9c6a1a90bce6505614617b827e816cbaaf875)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.15.0 [`c5bcd89`](https://github.com/th-ch/youtube-music/commit/c5bcd89f164b51d7380486a8ae35edd0caeea842)
|
||||||
|
|
||||||
|
#### [v3.1.1](https://github.com/th-ch/youtube-music/compare/v3.1.0...v3.1.1)
|
||||||
|
|
||||||
|
> 18 December 2023
|
||||||
|
|
||||||
|
- fix: fix renderer plugin load timing [`#1522`](https://github.com/th-ch/youtube-music/issues/1522)
|
||||||
|
- chore(i18n): Translated using Weblate (Lithuanian) [`fc1a7cd`](https://github.com/th-ch/youtube-music/commit/fc1a7cda62b6e33e5f5d57a5a6e0adef6a32bf9a)
|
||||||
|
- chore(i18n): Translated using Weblate (Chinese (Simplified)) [`eba7026`](https://github.com/th-ch/youtube-music/commit/eba7026b89bbfdd3ac07cf728a66ba9bdd274ec0)
|
||||||
|
- chore(deps): update dependency rollup to v4.8.0 [`a601d0b`](https://github.com/th-ch/youtube-music/commit/a601d0b3d2dee0fabad79a18e1a7dd0ca84ccf01)
|
||||||
|
|
||||||
|
#### [v3.1.0](https://github.com/th-ch/youtube-music/compare/v3.0.2...v3.1.0)
|
||||||
|
|
||||||
|
> 11 December 2023
|
||||||
|
|
||||||
|
- chore(deps): update dependency electron to v28 [`#1498`](https://github.com/th-ch/youtube-music/pull/1498)
|
||||||
|
- Enable/Disable Navigation without restart [`#1507`](https://github.com/th-ch/youtube-music/pull/1507)
|
||||||
|
- Turkish(tr)_lang_file [`#1513`](https://github.com/th-ch/youtube-music/pull/1513)
|
||||||
|
- Skip Disliked Songs [`#1505`](https://github.com/th-ch/youtube-music/pull/1505)
|
||||||
|
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.13.2 [`#1452`](https://github.com/th-ch/youtube-music/pull/1452)
|
||||||
|
- fix: Homebrew latest release url parsing [`#1496`](https://github.com/th-ch/youtube-music/pull/1496)
|
||||||
|
- fix: in-player adblocker inject timing issue [`#1478`](https://github.com/th-ch/youtube-music/issues/1478)
|
||||||
|
- fix(package.json): fix RPM version `libuuid` issue [`#1508`](https://github.com/th-ch/youtube-music/issues/1508)
|
||||||
|
- Translated using Weblate (Polish) [`7b78ba6`](https://github.com/th-ch/youtube-music/commit/7b78ba67613f14be65a45751efeb06431b405a91)
|
||||||
|
- Translated using Weblate (French) [`ebc0879`](https://github.com/th-ch/youtube-music/commit/ebc087963b23265ff00528c8305d51597abf587a)
|
||||||
|
- Translated using Weblate (Chinese (Traditional)) [`020bdc0`](https://github.com/th-ch/youtube-music/commit/020bdc0811ea45ad6c2853c62a05ae6695c5c4f9)
|
||||||
|
|
||||||
|
#### [v3.0.2](https://github.com/th-ch/youtube-music/compare/v3.0.1...v3.0.2)
|
||||||
|
|
||||||
|
> 3 December 2023
|
||||||
|
|
||||||
|
- fix(adblocker): fix In-Player adblocker [`#1478`](https://github.com/th-ch/youtube-music/issues/1478)
|
||||||
|
- fix(menu): crash on linux [`#1477`](https://github.com/th-ch/youtube-music/issues/1477)
|
||||||
|
- fix: update pnpm-lock.yaml [`9e2c6b1`](https://github.com/th-ch/youtube-music/commit/9e2c6b1afa33b5708853c8328946e68ec45b09c3)
|
||||||
|
- Translated using Weblate (Chinese (Traditional)) [`125b69f`](https://github.com/th-ch/youtube-music/commit/125b69fd75a05c3eb893886119e2d9f2332b3e56)
|
||||||
|
- Translated using Weblate (French) [`15c4551`](https://github.com/th-ch/youtube-music/commit/15c455105b5100a8ee2bd0a4631548d3d455f047)
|
||||||
|
|
||||||
|
#### [v3.0.1](https://github.com/th-ch/youtube-music/compare/v3.0.0...v3.0.1)
|
||||||
|
|
||||||
|
> 2 December 2023
|
||||||
|
|
||||||
|
- hotfix(adblocker): fix #1475 [`#1475`](https://github.com/th-ch/youtube-music/issues/1475)
|
||||||
|
- Translated using Weblate (French) [`7f02afc`](https://github.com/th-ch/youtube-music/commit/7f02afc5a6839adfe8437d4e2cc8dee13a93b311)
|
||||||
|
- Update changelog for v3.0.0 [`d8c8bd1`](https://github.com/th-ch/youtube-music/commit/d8c8bd17ecfbdf96ebd29eb4c5748c07876ee242)
|
||||||
|
- Translated using Weblate (German) [`0660f0b`](https://github.com/th-ch/youtube-music/commit/0660f0b7ce6895ef5800f48ade1da2d7f8e0c1f7)
|
||||||
|
|
||||||
|
### [v3.0.0](https://github.com/th-ch/youtube-music/compare/v2.2.0...v3.0.0)
|
||||||
|
|
||||||
|
> 2 December 2023
|
||||||
|
|
||||||
- Add text to Translation section [`#1470`](https://github.com/th-ch/youtube-music/pull/1470)
|
- Add text to Translation section [`#1470`](https://github.com/th-ch/youtube-music/pull/1470)
|
||||||
- fix(deps): update dependency youtubei.js to v8 [`#1473`](https://github.com/th-ch/youtube-music/pull/1473)
|
- fix(deps): update dependency youtubei.js to v8 [`#1473`](https://github.com/th-ch/youtube-music/pull/1473)
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -258,7 +258,7 @@ import style from './style.css?inline'; // 스타일을 인라인으로 가져
|
|||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
const builder = createPlugin({
|
export default createPlugin({
|
||||||
name: 'Plugin Label',
|
name: 'Plugin Label',
|
||||||
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
|
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
|
||||||
config: {
|
config: {
|
||||||
@ -274,7 +274,7 @@ const builder = createPlugin({
|
|||||||
```typescript
|
```typescript
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
|
|
||||||
const builder = createPlugin({
|
export default createPlugin({
|
||||||
name: 'Plugin Label',
|
name: 'Plugin Label',
|
||||||
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
|
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
import { resolve } from 'node:path';
|
import { resolve, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import { defineConfig, defineViteConfig } from 'electron-vite';
|
import { defineConfig, defineViteConfig } from 'electron-vite';
|
||||||
import builtinModules from 'builtin-modules';
|
import builtinModules from 'builtin-modules';
|
||||||
import viteResolve from 'vite-plugin-resolve';
|
import viteResolve from 'vite-plugin-resolve';
|
||||||
import Inspect from 'vite-plugin-inspect';
|
import Inspect from 'vite-plugin-inspect';
|
||||||
|
|
||||||
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer';
|
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs';
|
||||||
import pluginLoader from './vite-plugins/plugin-loader';
|
import pluginLoader from './vite-plugins/plugin-loader.mjs';
|
||||||
|
|
||||||
import type { UserConfig } from 'vite';
|
import type { UserConfig } from 'vite';
|
||||||
import { i18nImporter } from './vite-plugins/i18n-importer';
|
import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
|
||||||
|
import solidPlugin from 'vite-plugin-solid';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
const resolveAlias = {
|
const resolveAlias = {
|
||||||
'@': resolve(__dirname, './src'),
|
'@': resolve(__dirname, './src'),
|
||||||
@ -114,6 +118,7 @@ export default defineConfig({
|
|||||||
'virtual:i18n': i18nImporter(),
|
'virtual:i18n': i18nImporter(),
|
||||||
'virtual:plugins': pluginVirtualModuleGenerator('renderer'),
|
'virtual:plugins': pluginVirtualModuleGenerator('renderer'),
|
||||||
}),
|
}),
|
||||||
|
solidPlugin(),
|
||||||
],
|
],
|
||||||
root: './src/',
|
root: './src/',
|
||||||
build: {
|
build: {
|
||||||
101
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-music",
|
"name": "youtube-music",
|
||||||
"productName": "YouTube Music",
|
"productName": "YouTube Music",
|
||||||
"version": "3.0.1",
|
"version": "3.3.0",
|
||||||
"description": "YouTube Music Desktop App - including custom plugins",
|
"description": "YouTube Music Desktop App - including custom plugins",
|
||||||
"main": "./dist/main/index.js",
|
"main": "./dist/main/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -77,6 +77,11 @@
|
|||||||
"rpm"
|
"rpm"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"rpm": {
|
||||||
|
"depends": [
|
||||||
|
"/usr/lib64/libuuid.so.1"
|
||||||
|
]
|
||||||
|
},
|
||||||
"snap": {
|
"snap": {
|
||||||
"slots": [
|
"slots": [
|
||||||
{
|
{
|
||||||
@ -97,50 +102,55 @@
|
|||||||
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
|
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
|
||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
|
"start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev --watch",
|
||||||
"dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
|
"dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
|
||||||
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
|
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
|
||||||
"dist": "pnpm clean && pnpm build && electron-builder --win --mac --linux -p never",
|
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
|
||||||
"dist:linux": "pnpm clean && pnpm build && electron-builder --linux -p never",
|
"dist:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p never",
|
||||||
"dist:mac": "pnpm clean && pnpm build && electron-builder --mac dmg:x64 -p never",
|
"dist:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:x64 -p never",
|
||||||
"dist:mac:arm64": "pnpm clean && pnpm build && electron-builder --mac dmg:arm64 -p never",
|
"dist:mac:arm64": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:arm64 -p never",
|
||||||
"dist:win": "pnpm clean && pnpm build && electron-builder --win -p never",
|
"dist:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p never",
|
||||||
"dist:win:x64": "pnpm clean && pnpm build && electron-builder --win nsis-web:x64 -p never",
|
"dist:win:x64": "pnpm clean && pnpm build && pnpm electron-builder --win nsis-web:x64 -p never",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"changelog": "npx --yes auto-changelog",
|
"changelog": "npx --yes auto-changelog",
|
||||||
"release:linux": "pnpm clean && pnpm build && electron-builder --linux -p always -c.snap.publish=github",
|
"release:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p always -c.snap.publish=github",
|
||||||
"release:mac": "pnpm clean && pnpm build && electron-builder --mac -p always",
|
"release:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac -p always",
|
||||||
"release:win": "pnpm clean && pnpm build && electron-builder --win -p always",
|
"release:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p always",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"usocket": "1.0.1",
|
"usocket": "1.0.1",
|
||||||
"rollup": "4.6.1",
|
|
||||||
"node-gyp": "10.0.1",
|
"node-gyp": "10.0.1",
|
||||||
"xml2js": "0.6.2",
|
"xml2js": "0.6.2",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"@electron/universal": "2.0.0",
|
"@electron/universal": "2.0.1",
|
||||||
"@babel/runtime": "7.23.2"
|
"@babel/runtime": "7.23.8"
|
||||||
|
},
|
||||||
|
"patchedDependencies": {
|
||||||
|
"vudio@2.1.1": "patches/vudio@2.1.1.patch",
|
||||||
|
"@xhayper/discord-rpc@1.1.2": "patches/@xhayper__discord-rpc@1.1.2.patch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cliqz/adblocker-electron": "1.26.12",
|
"@cliqz/adblocker-electron": "1.26.15",
|
||||||
"@cliqz/adblocker-electron-preload": "1.26.12",
|
"@cliqz/adblocker-electron-preload": "1.26.15",
|
||||||
"@electron-toolkit/tsconfig": "1.0.1",
|
"@electron-toolkit/tsconfig": "1.0.1",
|
||||||
"@electron/remote": "2.1.0",
|
"@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",
|
||||||
"@foobar404/wave": "2.0.4",
|
"@floating-ui/dom": "1.6.3",
|
||||||
|
"@foobar404/wave": "2.0.5",
|
||||||
"@jellybrick/electron-better-web-request": "1.0.4",
|
"@jellybrick/electron-better-web-request": "1.0.4",
|
||||||
"@jellybrick/mpris-service": "2.1.4",
|
"@jellybrick/mpris-service": "2.1.4",
|
||||||
"@xhayper/discord-rpc": "1.1.1",
|
"@xhayper/discord-rpc": "1.1.2",
|
||||||
"async-mutex": "0.4.0",
|
"async-mutex": "0.4.1",
|
||||||
"butterchurn": "3.0.0-beta.4",
|
"butterchurn": "3.0.0-beta.4",
|
||||||
"butterchurn-presets": "3.0.0-beta.4",
|
"butterchurn-presets": "3.0.0-beta.4",
|
||||||
|
"color": "4.2.3",
|
||||||
"conf": "10.2.0",
|
"conf": "10.2.0",
|
||||||
"custom-electron-prompt": "1.5.7",
|
"custom-electron-prompt": "1.5.7",
|
||||||
"dbus-next": "0.10.2",
|
"dbus-next": "0.10.2",
|
||||||
@ -156,48 +166,59 @@
|
|||||||
"filenamify": "6.0.0",
|
"filenamify": "6.0.0",
|
||||||
"howler": "2.2.4",
|
"howler": "2.2.4",
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"i18next": "23.7.7",
|
"i18next": "23.8.3",
|
||||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||||
"keyboardevents-areequal": "0.2.2",
|
"keyboardevents-areequal": "0.2.2",
|
||||||
"node-html-parser": "6.1.11",
|
"node-html-parser": "6.1.12",
|
||||||
"node-id3": "0.2.6",
|
"node-id3": "0.2.6",
|
||||||
|
"peerjs": "1.5.2",
|
||||||
|
"semver": "7.6.0",
|
||||||
"serve": "14.2.1",
|
"serve": "14.2.1",
|
||||||
"simple-youtube-age-restriction-bypass": "git+https://github.com/organization/Simple-YouTube-Age-Restriction-Bypass.git#v2.5.8",
|
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
|
||||||
"ts-morph": "20.0.0",
|
"solid-floating-ui": "0.3.1",
|
||||||
|
"solid-js": "1.8.15",
|
||||||
|
"solid-styled-components": "0.28.5",
|
||||||
|
"solid-transition-group": "0.2.3",
|
||||||
|
"ts-morph": "21.0.1",
|
||||||
"vudio": "2.1.1",
|
"vudio": "2.1.1",
|
||||||
"x11": "2.3.0",
|
"x11": "2.3.0",
|
||||||
"youtubei.js": "8.0.0"
|
"youtubei.js": "9.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.40.1",
|
"@playwright/test": "1.41.2",
|
||||||
"@total-typescript/ts-reset": "0.5.1",
|
"@total-typescript/ts-reset": "0.5.1",
|
||||||
|
"@types/color": "3.0.6",
|
||||||
"@types/electron-localshortcut": "3.1.3",
|
"@types/electron-localshortcut": "3.1.3",
|
||||||
"@types/howler": "2.2.11",
|
"@types/howler": "2.2.11",
|
||||||
"@types/html-to-text": "9.0.4",
|
"@types/html-to-text": "9.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "6.13.1",
|
"@types/semver": "7.5.7",
|
||||||
|
"@typescript-eslint/eslint-plugin": "7.0.1",
|
||||||
"bufferutil": "4.0.8",
|
"bufferutil": "4.0.8",
|
||||||
"builtin-modules": "3.3.0",
|
"builtin-modules": "3.3.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"del-cli": "5.1.0",
|
"del-cli": "5.1.0",
|
||||||
"electron": "27.1.3",
|
"discord-api-types": "0.37.70",
|
||||||
|
"electron": "28.2.3",
|
||||||
"electron-builder": "24.9.1",
|
"electron-builder": "24.9.1",
|
||||||
"electron-devtools-installer": "3.2.0",
|
"electron-devtools-installer": "3.2.0",
|
||||||
"electron-vite": "1.0.29",
|
"electron-vite": "2.0.0",
|
||||||
"eslint": "8.55.0",
|
"esbuild": "0.20.0",
|
||||||
|
"eslint": "8.56.0",
|
||||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||||
"eslint-import-resolver-typescript": "3.6.1",
|
"eslint-import-resolver-typescript": "3.6.1",
|
||||||
"eslint-plugin-import": "2.29.0",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-prettier": "5.0.1",
|
"eslint-plugin-prettier": "5.1.3",
|
||||||
"glob": "10.3.10",
|
"glob": "10.3.10",
|
||||||
"node-gyp": "10.0.1",
|
"node-gyp": "10.0.1",
|
||||||
"playwright": "1.40.1",
|
"playwright": "1.41.2",
|
||||||
"rollup": "4.6.1",
|
"rollup": "4.12.0",
|
||||||
"typescript": "5.3.2",
|
"typescript": "5.3.3",
|
||||||
"utf-8-validate": "6.0.3",
|
"utf-8-validate": "6.0.3",
|
||||||
"vite": "4.5.0",
|
"vite": "5.1.3",
|
||||||
"vite-plugin-inspect": "0.8.1",
|
"vite-plugin-inspect": "0.8.3",
|
||||||
"vite-plugin-resolve": "2.5.1",
|
"vite-plugin-resolve": "2.5.1",
|
||||||
"ws": "8.14.2"
|
"vite-plugin-solid": "2.10.1",
|
||||||
|
"ws": "8.16.0"
|
||||||
},
|
},
|
||||||
"auto-changelog": {
|
"auto-changelog": {
|
||||||
"hideCredit": true,
|
"hideCredit": true,
|
||||||
@ -205,5 +226,5 @@
|
|||||||
"unreleased": true,
|
"unreleased": true,
|
||||||
"output": "changelog.md"
|
"output": "changelog.md"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.11.0"
|
"packageManager": "pnpm@8.15.3"
|
||||||
}
|
}
|
||||||
|
|||||||
17
patches/@xhayper__discord-rpc@1.1.2.patch
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index 40db5dfbd8a4455ce2987d8115eca9882e1f9f14..414fc6986b9c0cc288908eb0107b90c4bfd916b2 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -25,11 +25,7 @@
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.2",
|
||||||
|
- "ws": "^8.15.1"
|
||||||
|
- },
|
||||||
|
- "optionalDependencies": {
|
||||||
|
- "bufferutil": "^4.0.8",
|
||||||
|
- "utf-8-validate": "^6.0.3"
|
||||||
|
+ "ws": "^8.16.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^14.*",
|
||||||
20
patches/vudio@2.1.1.patch
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
diff --git a/umd/vudio.js b/umd/vudio.js
|
||||||
|
index d0d1127e57125ad4e77442af2db4a26998c7b385..c0b66bd4327c65c31dc6e588bfa4ae6ec70bd3b8 100644
|
||||||
|
--- a/umd/vudio.js
|
||||||
|
+++ b/umd/vudio.js
|
||||||
|
@@ -147,7 +147,6 @@
|
||||||
|
|
||||||
|
source.connect(this.analyser);
|
||||||
|
this.analyser.fftSize = this.option.accuracy * 2;
|
||||||
|
- this.analyser.connect(audioContext.destination);
|
||||||
|
|
||||||
|
this.freqByteData = new Uint8Array(this.analyser.frequencyBinCount);
|
||||||
|
|
||||||
|
@@ -207,7 +206,6 @@
|
||||||
|
|
||||||
|
source.connect(this.analyser);
|
||||||
|
this.analyser.fftSize = this.option.accuracy * 2;
|
||||||
|
- this.analyser.connect(audioContext.destination);
|
||||||
|
},
|
||||||
|
|
||||||
|
__rebuildData : function (freqByteData, horizontalAlign) {
|
||||||
2445
pnpm-lock.yaml
generated
@ -32,6 +32,7 @@ export interface DefaultConfig {
|
|||||||
proxy: string;
|
proxy: string;
|
||||||
startingPage: string;
|
startingPage: string;
|
||||||
overrideUserAgent: boolean;
|
overrideUserAgent: boolean;
|
||||||
|
usePodcastParticipantAsArtist: boolean;
|
||||||
themes: string[];
|
themes: string[];
|
||||||
};
|
};
|
||||||
plugins: Record<string, unknown>;
|
plugins: Record<string, unknown>;
|
||||||
@ -66,6 +67,7 @@ const defaultConfig: DefaultConfig = {
|
|||||||
proxy: '',
|
proxy: '',
|
||||||
startingPage: '',
|
startingPage: '',
|
||||||
overrideUserAgent: false,
|
overrideUserAgent: false,
|
||||||
|
usePodcastParticipantAsArtist: false,
|
||||||
themes: [],
|
themes: [],
|
||||||
},
|
},
|
||||||
'plugins': {},
|
'plugins': {},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Store from 'electron-store';
|
import Store from 'electron-store';
|
||||||
import { deepmerge } from 'deepmerge-ts';
|
import { deepmergeCustom } from 'deepmerge-ts';
|
||||||
|
|
||||||
import defaultConfig from './defaults';
|
import defaultConfig from './defaults';
|
||||||
|
|
||||||
@ -8,6 +8,10 @@ import plugins from './plugins';
|
|||||||
|
|
||||||
import { restart } from '@/providers/app-controls';
|
import { restart } from '@/providers/app-controls';
|
||||||
|
|
||||||
|
const deepmerge = deepmergeCustom({
|
||||||
|
mergeArrays: false,
|
||||||
|
});
|
||||||
|
|
||||||
const set = (key: string, value: unknown) => {
|
const set = (key: string, value: unknown) => {
|
||||||
store.set(key, value);
|
store.set(key, value);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,6 +6,55 @@ import defaults from './defaults';
|
|||||||
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
|
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
|
||||||
|
|
||||||
const migrations = {
|
const migrations = {
|
||||||
|
'>=3.3.0'(store: Conf<Record<string, unknown>>) {
|
||||||
|
const lastfmConfig = store.get('plugins.lastfm') as {
|
||||||
|
enabled?: boolean;
|
||||||
|
token?: string;
|
||||||
|
session_key?: string;
|
||||||
|
api_root?: string;
|
||||||
|
api_key?: string;
|
||||||
|
secret?: string;
|
||||||
|
};
|
||||||
|
if (lastfmConfig) {
|
||||||
|
let scrobblerConfig = store.get(
|
||||||
|
'plugins.scrobbler',
|
||||||
|
) as {
|
||||||
|
enabled?: boolean;
|
||||||
|
scrobblers?: {
|
||||||
|
lastfm?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
token?: string;
|
||||||
|
sessionKey?: string;
|
||||||
|
apiRoot?: string;
|
||||||
|
apiKey?: string;
|
||||||
|
secret?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} | undefined;
|
||||||
|
|
||||||
|
if (!scrobblerConfig) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
'>=3.0.0'(store: Conf<Record<string, unknown>>) {
|
'>=3.0.0'(store: Conf<Record<string, unknown>>) {
|
||||||
const discordConfig = store.get('plugins.discord') as Record<
|
const discordConfig = store.get('plugins.discord') as Record<
|
||||||
string,
|
string,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export interface LanguageResources {
|
export interface LanguageResources {
|
||||||
[lang: string]: {
|
[lang: string]: {
|
||||||
translation: Record<string, unknown> & {
|
translation: Record<string, unknown> & {
|
||||||
language: {
|
language?: {
|
||||||
name: string;
|
name: string;
|
||||||
'local-name': string;
|
'local-name': string;
|
||||||
code: string;
|
code: string;
|
||||||
|
|||||||
46
src/i18n/resources/bg.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Неуспешно изпълнение на плъгин {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Плъгин {{pluginName}}::{{contextName}} изпълнет в {{ms}}ms",
|
||||||
|
"initialize-failed": "Неуспешна инициализация на плъгин \"{{pluginName}}\"",
|
||||||
|
"load-all": "Зареждане на всички плъгини",
|
||||||
|
"load-failed": "Неуспешно зареждане на плъгин \"{{pluginName}}\"",
|
||||||
|
"loaded": "Плъгин \"{{pluginName}}\" зареден",
|
||||||
|
"unload-failed": "Неуспешне разрездане на плъгин \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Плъгин \"{{pluginName}}\" разреден"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "bg",
|
||||||
|
"local-name": "Български",
|
||||||
|
"name": "Bulgarian"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Завърши зареждането. DevTools отворени"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n заредено"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Получена команда чрез протокол: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS файл \"{{cssFile}}\" не съществува, ингнорира се"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Грешка без отговор!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Изчистване на кешът на аппа"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Прозореца се опита да се изрисува извън екрана, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,14 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"console": {
|
"console": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
"execute-failed": "Selhalo spuštění pluginu {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} spuštěn za {{ms}}ms",
|
||||||
|
"initialize-failed": "Selhalo zapnutí \"{{pluginName}}\" pluginu",
|
||||||
"load-all": "Načítání všech pluginů",
|
"load-all": "Načítání všech pluginů",
|
||||||
"loaded": "Plugin \"{{pluginName}}\" načten"
|
"load-failed": "Selhalo načtení \"{{pluginName}}\" pluginu",
|
||||||
|
"loaded": "Plugin \"{{pluginName}}\" načten",
|
||||||
|
"unload-failed": "Selhalo unload \"{{pluginName}}\" pluginu",
|
||||||
|
"unloaded": "Plugin {{pluginName}} byl odnačten"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -14,34 +20,52 @@
|
|||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
"console": {
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Načítání dokončeno. Vývojářské nástroje se otevřely"
|
||||||
|
},
|
||||||
"i18n": {
|
"i18n": {
|
||||||
"loaded": "i18n načteno"
|
"loaded": "i18n načteno"
|
||||||
},
|
},
|
||||||
"second-instance": {
|
"second-instance": {
|
||||||
"receive-command": "Received command přes protokol: \"{{command}}\""
|
"receive-command": "Přijmut příkaz přes protokol: \"{{command}}\""
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"css-file-not-found": "CSS soubor \"{{cssFile}}\" neexistuje, ignoring"
|
"css-file-not-found": "CSS soubor \"{{cssFile}}\" neexistuje, ignorováno"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Chyba - Aplikace nereaguje!\n{{error}}"
|
||||||
},
|
},
|
||||||
"when-ready": {
|
"when-ready": {
|
||||||
"clearing-cache-after-20s": "Čištění mezipaměti aplikace"
|
"clearing-cache-after-20s": "Čištění mezipaměti aplikace"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Okno se pokusilo vykreslit na pozadí, velikost okna = {{windowSize}}, display velikost = {{displaySize}}, pozice = {{position}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Menu je skryté, stiskněte 'Alt' k jeho zobrazení (nebo 'Escape', pokud používáte in-app-menu)",
|
||||||
|
"message": "Skrýt menu je povoleno",
|
||||||
|
"title": "Skrýt menu Povolené"
|
||||||
|
},
|
||||||
"need-to-restart": {
|
"need-to-restart": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"later": "Později",
|
"later": "Později",
|
||||||
"restart-now": "Restartovat nyní"
|
"restart-now": "Restartovat nyní"
|
||||||
}
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" plugin vyžaduje restart, aby se projevil",
|
||||||
|
"message": "\"{{pluginName}}\" potřebuje restartovat",
|
||||||
|
"title": "Restart vyžadován"
|
||||||
},
|
},
|
||||||
"unresponsive": {
|
"unresponsive": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"quit": "Ukončení",
|
"quit": "Ukončit",
|
||||||
"relaunch": "Spustit znovu",
|
"relaunch": "Spustit znovu",
|
||||||
"wait": "Počkat"
|
"wait": "Počkat"
|
||||||
},
|
},
|
||||||
"detail": "Omlouváme se za způsobené nepříjemnosti! prosím vyberte, co dělat:",
|
"detail": "Omlouváme se za způsobené nepříjemnosti! prosím vyberte, co dělat:",
|
||||||
"message": "Aplikace nereaguje"
|
"message": "Aplikace nereaguje",
|
||||||
|
"title": "Okno nereaguje"
|
||||||
},
|
},
|
||||||
"update-available": {
|
"update-available": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@ -49,8 +73,9 @@
|
|||||||
"download": "Stáhnout",
|
"download": "Stáhnout",
|
||||||
"ok": "OK"
|
"ok": "OK"
|
||||||
},
|
},
|
||||||
|
"detail": "Nová verze je k dispozici a lze ji stáhnout na {{downloadLink}}",
|
||||||
"message": "Nová verze je dostupná",
|
"message": "Nová verze je dostupná",
|
||||||
"title": "Aktualizace k dispozici"
|
"title": "Aktualizace je k dispozici"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
@ -58,9 +83,10 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"label": "Navigace",
|
"label": "Navigace",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"copy-current-url": "Kopírovat aktuální URL adresu",
|
"copy-current-url": "Zkopírovat aktuální URL adresu",
|
||||||
"go-back": "Jít zpátky",
|
"go-back": "Jít zpátky",
|
||||||
"go-forward": "Jít dopředu",
|
"go-forward": "Jít dopředu",
|
||||||
|
"quit": "Ukončit",
|
||||||
"restart": "Restartovat aplikaci"
|
"restart": "Restartovat aplikaci"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -70,22 +96,29 @@
|
|||||||
"advanced-options": {
|
"advanced-options": {
|
||||||
"label": "Pokročilé možnosti",
|
"label": "Pokročilé možnosti",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Při spuštění aplikace se resetuje její mezipaměť",
|
||||||
"disable-hardware-acceleration": "Vypnout hardware zrychlení",
|
"disable-hardware-acceleration": "Vypnout hardware zrychlení",
|
||||||
"edit-config-json": "Upravit config.json",
|
"edit-config-json": "Upravit config.json",
|
||||||
"override-user-agent": "Přepsat User-Agent",
|
"override-user-agent": "Přepsat uživatelského agenta",
|
||||||
"restart-on-config-changes": "Restartovat na změny v configu",
|
"restart-on-config-changes": "Restartovat aplikaci na změny v konfiguraci",
|
||||||
"set-proxy": {
|
"set-proxy": {
|
||||||
"label": "Nastavit proxy",
|
"label": "Nastavit proxy",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"placeholder": "Příklad: socks5://127.0.0.1:9999",
|
"label": "Zadejte adresu proxy: (k vypnutí nechte pole prázdné)",
|
||||||
|
"placeholder": "Příklad: SOCKS5://127.0.0.1:9999",
|
||||||
"title": "Nastavit proxy"
|
"title": "Nastavit proxy"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"toggle-dev-tools": "Přepínat vývojářské nástroje"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"always-on-top": "Vždy na vrchu",
|
"always-on-top": "Vždy na vrchu",
|
||||||
"auto-update": "Automatické aktualizace",
|
"auto-update": "Automatické aktualizace",
|
||||||
"hide-menu": {
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Menu bude skryto na dalším spuštěním, použijte [Alt] k jeho zobrazení (nebo backtick [`] pokud používáte in-app-menu)",
|
||||||
|
"title": "Skrýt menu Povoleno"
|
||||||
|
},
|
||||||
"label": "Skrýt menu"
|
"label": "Skrýt menu"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
@ -98,24 +131,36 @@
|
|||||||
"to-help-translate": "Chcete pomoc s překladem? Klikněte zde"
|
"to-help-translate": "Chcete pomoc s překladem? Klikněte zde"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"resume-on-start": "Při spuštění aplikace, pokračovat na poslední písničce",
|
||||||
|
"single-instance-lock": "Zámek pro jednu instanci",
|
||||||
|
"start-at-login": "Zapnutí aplikace po přihlášení",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Úvodní stránka",
|
||||||
|
"unset": "Nenastaveno"
|
||||||
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
|
"label": "Tray",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"disabled": "Vypnuto",
|
||||||
"enabled-and-hide-app": "Povolit a skrýt aplikaci",
|
"enabled-and-hide-app": "Povolit a skrýt aplikaci",
|
||||||
|
"enabled-and-show-app": "Enabled a show aplikaci",
|
||||||
"play-pause-on-click": "Přehrát/Pozastavit na kliknutí"
|
"play-pause-on-click": "Přehrát/Pozastavit na kliknutí"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"visual-tweaks": {
|
"visual-tweaks": {
|
||||||
|
"label": "Vzhledové vylepšení",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"like-buttons": {
|
"like-buttons": {
|
||||||
"default": "Výchozí",
|
"default": "Výchozí",
|
||||||
"hide": "Schovat",
|
"force-show": "Vynutit zobrazení",
|
||||||
|
"hide": "Skrýt",
|
||||||
"label": "Like tlačítka"
|
"label": "Like tlačítka"
|
||||||
},
|
},
|
||||||
"remove-upgrade-button": "Remove upgrade tlačítko",
|
"remove-upgrade-button": "Odebrat upgrade tlačítko",
|
||||||
"theme": {
|
"theme": {
|
||||||
"label": "Motiv",
|
"label": "Motiv",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"import-css-file": "Import custom CSS soubor",
|
"import-css-file": "Vložit vlastní CSS soubor",
|
||||||
"no-theme": "Žádný motiv"
|
"no-theme": "Žádný motiv"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,10 +170,16 @@
|
|||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"enabled": "Povoleno",
|
"enabled": "Povoleno",
|
||||||
"label": "Pluginy"
|
"label": "Pluginy",
|
||||||
|
"new": "NOVÉ"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
|
"label": "Zobrazení",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"force-reload": "Vynutit znovu načtení",
|
||||||
|
"reload": "Obnovit",
|
||||||
|
"reset-zoom": "Skutečná velikost",
|
||||||
|
"toggle-fullscreen": "Přepnout režim celé obrazovky",
|
||||||
"zoom-in": "Přiblížit",
|
"zoom-in": "Přiblížit",
|
||||||
"zoom-out": "Oddálit"
|
"zoom-out": "Oddálit"
|
||||||
}
|
}
|
||||||
@ -136,11 +187,11 @@
|
|||||||
},
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"next": "Další",
|
"next": "Další",
|
||||||
"play-pause": "Hrát/Zastavit",
|
"play-pause": "Přehrát/Pozastavit",
|
||||||
"previous": "Minulý",
|
"previous": "Minulý",
|
||||||
"quit": "Ukončit",
|
"quit": "Ukončit",
|
||||||
"restart": "Restartovat aplikaci",
|
"restart": "Restartovat aplikaci",
|
||||||
"show": "Ukázat okno"
|
"show": "Zobrazit okno"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
@ -151,14 +202,19 @@
|
|||||||
},
|
},
|
||||||
"name": "Blokovač reklam"
|
"name": "Blokovač reklam"
|
||||||
},
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Přidává Undislike, Dislike, Like, a Unlike tlačítka k apply this ke všem písničkám v seznamu písniček nebo albumu.",
|
||||||
|
"name": "Album akce"
|
||||||
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Použije dynamický motiv a visuální efekty based na paletě barev alba",
|
"description": "Používá dynamický motiv a vizuální efekty na základě palety barev alba",
|
||||||
"name": "Album Color Motiv"
|
"name": "Motiv podle barvy Alba"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
"description": "Applies a lighting efekty by casting gentle colors z videa, into your screen’s pozadí.",
|
"description": "Applies bleskové efekty pomocí casting jemných barev z videa, do vašeho pozadí obrazovky.",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
|
"label": "Množství rozmazání",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"pixels": "{{blurAmount}} pixelů"
|
"pixels": "{{blurAmount}} pixelů"
|
||||||
}
|
}
|
||||||
@ -190,21 +246,29 @@
|
|||||||
"smoothness-transition": {
|
"smoothness-transition": {
|
||||||
"label": "Plynulý přechod",
|
"label": "Plynulý přechod",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"during": "Během {{interpolationTime}}s"
|
"during": "Během {{interpolationTime}} s"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Používání režimu celé obrazovky"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "Ambientní režim"
|
"name": "Ambientní režim"
|
||||||
},
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Apply compression k audiu (snižuje hlasitost nejhlasitěších částí signálu and zvyšuje hlasitost nejjemnějších částí)",
|
||||||
|
"name": "Audio kompresor"
|
||||||
|
},
|
||||||
"blur-nav-bar": {
|
"blur-nav-bar": {
|
||||||
"description": "Udělá navigační panel průhledným a rozmazaným"
|
"description": "Udělá navigační panel průhledný a rozmazaný",
|
||||||
|
"name": "Rozmazaný navigační panel"
|
||||||
},
|
},
|
||||||
"bypass-age-restrictions": {
|
"bypass-age-restrictions": {
|
||||||
"description": "Obejít ověření věku na YouTube",
|
"description": "Obejít ověření věku na YouTube",
|
||||||
"name": "Obejít věková omezení"
|
"name": "Obejít věková omezení"
|
||||||
},
|
},
|
||||||
"captions-selector": {
|
"captions-selector": {
|
||||||
"description": "Titulkový selector pro YouTube Music audio tracks",
|
"description": "Titulkový selector pro zvukové stopy v YouTube Music",
|
||||||
"menu": {
|
"menu": {
|
||||||
"autoload": "Automaticky vybrat naposledy použité titulky",
|
"autoload": "Automaticky vybrat naposledy použité titulky",
|
||||||
"disable-captions": "Žádné titulky ve vychozím nastavení"
|
"disable-captions": "Žádné titulky ve vychozím nastavení"
|
||||||
@ -222,13 +286,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compact-sidebar": {
|
"compact-sidebar": {
|
||||||
"description": "Vždy set the sidebar in compact mode"
|
"description": "Vždy nastavit postranní panel do kompaktního režimu",
|
||||||
|
"name": "Kompaktní postranní panel"
|
||||||
},
|
},
|
||||||
"crossfade": {
|
"crossfade": {
|
||||||
"description": "Crossfade mezi písničkami",
|
"description": "Prolínání mezi písničkami",
|
||||||
"menu": {
|
"menu": {
|
||||||
"advanced": "Pokročilý"
|
"advanced": "Pokročilý"
|
||||||
},
|
},
|
||||||
|
"name": "Prolínání [Beta]",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"options": {
|
"options": {
|
||||||
"multi-input": {
|
"multi-input": {
|
||||||
@ -243,19 +309,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disable-autoplay": {
|
"disable-autoplay": {
|
||||||
"name": "Zrušit automatické přehrávání"
|
"description": "Spustí písničku v režimu \"pozastaveno\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Applies jenom na spuštění aplikace"
|
||||||
|
},
|
||||||
|
"name": "Vypnout automatické přehrávání"
|
||||||
},
|
},
|
||||||
"discord": {
|
"discord": {
|
||||||
"backend": {
|
"backend": {
|
||||||
|
"already-connected": "Pokusilo se spojit s aktivním spojením",
|
||||||
"connected": "Připojeno k Discordu",
|
"connected": "Připojeno k Discordu",
|
||||||
"disconnected": "Odpojeno od Discordu"
|
"disconnected": "Odpojeno od Discordu"
|
||||||
},
|
},
|
||||||
"description": "Ukažte svým přátelům, co posloucháte s Rich Presence",
|
"description": "Ukažte svým přátelům, co posloucháte s Bohatou přítomností",
|
||||||
"menu": {
|
"menu": {
|
||||||
|
"auto-reconnect": "Automaticky znovu připojit",
|
||||||
|
"clear-activity": "Vymazat aktivitu",
|
||||||
|
"clear-activity-after-timeout": "Vymazat aktivitu po timeout",
|
||||||
"connected": "Připojeno",
|
"connected": "Připojeno",
|
||||||
"disconnected": "Odpojeno",
|
"disconnected": "Odpojeno",
|
||||||
|
"hide-duration-left": "Skrýt zbývající duration",
|
||||||
"hide-github-button": "Skrýt tlačítko s odkazem na GitHub",
|
"hide-github-button": "Skrýt tlačítko s odkazem na GitHub",
|
||||||
"play-on-youtube-music": "Hrát na YouTube Music"
|
"play-on-youtube-music": "Hrát na YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Nastavit timeout pro neaktivitu"
|
||||||
|
},
|
||||||
|
"name": "Discord Bohatá přítomnost",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Zadejte timeout neaktivity v sekundách:",
|
||||||
|
"title": "Nastavit timeout pro neaktivitu"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"downloader": {
|
"downloader": {
|
||||||
@ -273,67 +356,139 @@
|
|||||||
"ok": "OK"
|
"ok": "OK"
|
||||||
},
|
},
|
||||||
"detail": "({{playlistSize}} písničky)",
|
"detail": "({{playlistSize}} písničky)",
|
||||||
"message": "Stahování Playlistu {{playlistTitle}}",
|
"message": "Stahování seznamu písniček {{playlistTitle}}",
|
||||||
"title": "Stahování začalo"
|
"title": "Stahování začalo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
|
"conversion-progress": "Konverze: {{percent}}%",
|
||||||
"done": "Hotovo: {{filePath}}",
|
"done": "Hotovo: {{filePath}}",
|
||||||
"download-info": "Stahování {{artist}} - {{title}} [{{videoId}}",
|
"download-info": "Stahování {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Stahování: {{percent}}%",
|
||||||
"downloading": "Stahování…",
|
"downloading": "Stahování…",
|
||||||
"downloading-counter": "Stahování {{current}}/{{total}}…",
|
"downloading-counter": "Stahování {{current}}/{{total}}…",
|
||||||
"downloading-playlist": "Downloading playlist \"{{playlistTitle}}\" - {{playlistSize}} písničky ({{playlistId}})",
|
"downloading-playlist": "Stahování seznamu písniček \"{{playlistTitle}}\" - {{playlistSize}} písničky ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Chyba při stahování \"{{author}} - {{title}}\": {{error}}",
|
||||||
"folder-already-exists": "Složka {{playlistFolder}} již existuje",
|
"folder-already-exists": "Složka {{playlistFolder}} již existuje",
|
||||||
|
"getting-playlist-info": "Získávání informací o seznamu písniček…",
|
||||||
"loading": "Načítání…",
|
"loading": "Načítání…",
|
||||||
"playlist-has-only-one-song": "Playlist má jenom jeden položku, downloading it directly",
|
"playlist-has-only-one-song": "Seznam písniček má pouze jednu položku, stahuje se přímo",
|
||||||
"playlist-id-not-found": "Žádný playlist ID nenalezen",
|
"playlist-id-not-found": "Žádné ID seznamu písnček nenalezeno",
|
||||||
"playlist-is-empty": "Playlist je prázdný",
|
"playlist-is-empty": "Seznam písniček je prázdný",
|
||||||
|
"playlist-is-mix-or-private": "Chyba při získávání informací o seznamu písniček: ujistite se, že se nejedná o soukromý nebo \"Namíchaný pro vás\" seznam písniček\n\n{{error}}",
|
||||||
"preparing-file": "Připravování souboru…",
|
"preparing-file": "Připravování souboru…",
|
||||||
"saving": "Ukládání…",
|
"saving": "Ukládání…",
|
||||||
"video-id-not-found": "Video nebylo nalezeno"
|
"trying-to-get-playlist-id": "Trying se získat ID seznamu písniček: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Video nebylo nalezeno",
|
||||||
|
"writing-id3": "Psaní ID3 značek…"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"description": "Stahuje MP3 / source audio přímo z rozhraní",
|
||||||
"menu": {
|
"menu": {
|
||||||
"choose-download-folder": "Vybrat download složku",
|
"choose-download-folder": "Vybrat složku pro stahování",
|
||||||
"download-playlist": "Stáhnout playlist",
|
"download-playlist": "Stáhnout seznam písniček",
|
||||||
|
"presets": "Předvolby",
|
||||||
"skip-existing": "Přeskočit existující soubory"
|
"skip-existing": "Přeskočit existující soubory"
|
||||||
},
|
},
|
||||||
"name": "Stahovač",
|
"name": "Stahovač",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Progress nemůže být aktualizován"
|
||||||
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Stáhnout"
|
"button": "Stáhnout"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exponential-volume": {
|
"exponential-volume": {
|
||||||
|
"description": "Dělá posuvník hlasitosti exponenciální, takže je snazší vybrat nižší hlasitost.",
|
||||||
"name": "Exponenciální hlasitost"
|
"name": "Exponenciální hlasitost"
|
||||||
},
|
},
|
||||||
"last-fm": {
|
"in-app-menu": {
|
||||||
"name": "Last.fm"
|
"description": "Dává menu panelům fancy, tmavý nebo album-color vzhled",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Skrýt DOM window controls"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Přidává Lumia Stream podporu",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
},
|
},
|
||||||
"lyrics-genius": {
|
"lyrics-genius": {
|
||||||
"description": "Přidat lyrics podporu pro většinu písniček"
|
"description": "Přidává lyrics podporu pro většinu písniček",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Fetched lyrics pro Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Sdílejte seznam písniček s ostatními. Když the host hraje písničku, uslyší jí i všichni ostatní.",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Zadejte Host ID"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Uložit",
|
||||||
|
"unknown-user": "Neznámý uživatel"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Zkopírovat Host ID",
|
||||||
|
"close": "Zavřít Hudba Spolu",
|
||||||
|
"connected-users": "Připojení uživatelé",
|
||||||
|
"disconnect": "Odpojit od Hudby Spolu",
|
||||||
|
"empty-user": "Žadní připojení uživatelé",
|
||||||
|
"host": "Hudba Spolu Host",
|
||||||
|
"join": "Připojit se k Hudbě Spolu",
|
||||||
|
"permission": {
|
||||||
|
"all": "Povolit hostům ovládat seznam písniček a přehrávač",
|
||||||
|
"host-only": "Jenom host může ovládat seznam písniček a přehrávač",
|
||||||
|
"playlist": "Povolit hostům ovládat seznam písniček"
|
||||||
|
},
|
||||||
|
"set-permission": "Změnit ovládací oprávnění",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Odpojen",
|
||||||
|
"guest": "Připojený/á jako Guest",
|
||||||
|
"host": "Připojený/á jako Host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Hudba Spolu [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Selhalo přidání písničky",
|
||||||
|
"closed": "Hudba Spolu zavřena",
|
||||||
|
"disconnected": "Hudba Spolu odpojena",
|
||||||
|
"host-failed": "Selhalo hostování Hudby Spolu",
|
||||||
|
"id-copied": "Host ID zkopírováno do schránky",
|
||||||
|
"join-failed": "Selhalo připojení k Hudba Spolu",
|
||||||
|
"joined": "Připojil/a jste se k Hudbě Spolu",
|
||||||
|
"permission-changed": "Oprávnění Hudby Spolu se změnilo na \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Selhalo odstranění písničky",
|
||||||
|
"user-connected": "{{name}} se připojil/a k Hudbě Spolu",
|
||||||
|
"user-disconnected": "{{name}} odpustil/a Hudba Spolu"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
|
"description": "Další/Zpátky navigační šipky přímo integrovány do rozhraní, jako ve vašem oblíbeném prohlížeči",
|
||||||
"name": "Navigace"
|
"name": "Navigace"
|
||||||
},
|
},
|
||||||
"no-google-login": {
|
"no-google-login": {
|
||||||
"description": "Odstranit Google login tlačítka a odkazy z rozhraní",
|
"description": "Odstranit tlačítka Google přihlášení a odkazy z rozhraní",
|
||||||
"name": "Žádné Google přihlášení"
|
"name": "Žádné Google přihlášení"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"description": "Display oznámení when a písnička starts hraje (interactive notifications are available on Windows)",
|
"description": "Zobrazit oznámení, když písnička začne hrát (interaktivní notifikace jsou dostupné na Windows)",
|
||||||
"menu": {
|
"menu": {
|
||||||
|
"interactive": "Interaktivní oznámení",
|
||||||
"interactive-settings": {
|
"interactive-settings": {
|
||||||
"label": "Interactive Nastavení",
|
"label": "Interactive Nastavení",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"hide-button-text": "Skrýt text tlačítka",
|
"hide-button-text": "Skrýt text tlačítka",
|
||||||
"tray-controls": "Otevřít/Zavřít on tray click"
|
"refresh-on-play-pause": "Refresh na Přehrát/Pozastavit",
|
||||||
|
"tray-controls": "Otevřít/Zavřít aplikaci na kliknutí na tray ikonu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"priority": "Priorita Oznámení"
|
"priority": "Priorita Oznámení",
|
||||||
|
"unpause-notification": "Zobrazit oznámení na unpause"
|
||||||
},
|
},
|
||||||
"name": "Oznámení"
|
"name": "Oznámení"
|
||||||
},
|
},
|
||||||
"picture-in-picture": {
|
"picture-in-picture": {
|
||||||
|
"description": "Povoluje switch aplikaci do režimu obrázek v obrázku",
|
||||||
"menu": {
|
"menu": {
|
||||||
"always-on-top": "Vždy na vrchu",
|
"always-on-top": "Vždy na vrchu",
|
||||||
"hotkey": {
|
"hotkey": {
|
||||||
@ -341,7 +496,9 @@
|
|||||||
"prompt": {
|
"prompt": {
|
||||||
"keybind-options": {
|
"keybind-options": {
|
||||||
"hotkey": "Klávesová zkratka"
|
"hotkey": "Klávesová zkratka"
|
||||||
}
|
},
|
||||||
|
"label": "Vybrat klávesovou zkratku pro přepínání obrázek v obrázku",
|
||||||
|
"title": "klávesová zkratka pro obrázek v obrázku"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"save-window-position": "Uložit pozici okna",
|
"save-window-position": "Uložit pozici okna",
|
||||||
@ -354,23 +511,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playback-speed": {
|
"playback-speed": {
|
||||||
"description": "Posloiuchej rychle, poslouchej pomalu! Adds a slider, který kontroluje rychlost písníčky",
|
"description": "Poslouchej rychle, poslouchej pomalu! Přidává slider, který kontroluje rychlost písníčky",
|
||||||
"name": "Playback rychlost",
|
"name": "Rychlost přehrávání",
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Rychlost"
|
"button": "Rychlost"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"precise-volume": {
|
"precise-volume": {
|
||||||
|
"description": "Přesná kontrola hlasitosti pomocí kolečka myši/klávesnicových zkratek, s vlastní HUD a customizable hlasitostních steps",
|
||||||
"menu": {
|
"menu": {
|
||||||
|
"custom-volume-steps": "Nastavit vlastní hlasitostní steps",
|
||||||
"global-shortcuts": "Globální klávesové zkratky"
|
"global-shortcuts": "Globální klávesové zkratky"
|
||||||
},
|
},
|
||||||
"name": "Precise hlasitost",
|
"name": "Přesná hlasitost",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"global-shortcuts": {
|
"global-shortcuts": {
|
||||||
"keybind-options": {
|
"keybind-options": {
|
||||||
"decrease": "Snížit hlasitost",
|
"decrease": "Snížit hlasitost",
|
||||||
"increase": "Zvýšit hlasitost"
|
"increase": "Zvýšit hlasitost"
|
||||||
}
|
},
|
||||||
|
"label": "Vybrat globální klávesnicové zkratky:",
|
||||||
|
"title": "Globální klávesnicové zkratky hlasitosti"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Vybrat Zvýšení/Snížení hlasitost Steps",
|
||||||
|
"title": "Hlasitostní steps"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -383,9 +548,16 @@
|
|||||||
"title": "Vybrat kvalitu videa"
|
"title": "Vybrat kvalitu videa"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "Umožňuje měnit kvalitu videa pomocí tlačítka na video overlay",
|
||||||
|
"name": "Měnič kvality videa"
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
|
"description": "Dovoluje nastavit globální klávesové zkratky pro playback (přehrát/pozastavit/další/předchozí) a vypínání media OSD pomocí přepisování media klíčů, zapínání Ctrl/CMD + F k vyhledávání, zapínání Linux MPRIS podporu pro media klíče, a vlastní klávesové zkratky pro pokročilé uživatele.",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Přepsat media klíče",
|
||||||
|
"set-keybinds": "Nastavit globální Controls písniček"
|
||||||
|
},
|
||||||
"name": "Zkratky (& MPRIS)",
|
"name": "Zkratky (& MPRIS)",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"keybind": {
|
"keybind": {
|
||||||
@ -393,33 +565,68 @@
|
|||||||
"next": "Další",
|
"next": "Další",
|
||||||
"play-pause": "Přehrát / Pozastavit",
|
"play-pause": "Přehrát / Pozastavit",
|
||||||
"previous": "Předchozí"
|
"previous": "Předchozí"
|
||||||
}
|
},
|
||||||
|
"label": "Vybrat globální klávesnicové zkratky pro ovládání písniček:",
|
||||||
|
"title": "Globální klávesnicové zkratky"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Přeskakovat disliked písničky",
|
||||||
|
"name": "Přeskočit Disliked písničky"
|
||||||
|
},
|
||||||
"skip-silences": {
|
"skip-silences": {
|
||||||
"description": "Automaticky přeskakovat tichá místa v písničkách",
|
"description": "Automaticky přeskakovat tichá místa v písničkách",
|
||||||
"name": "Přeskočit Tichá místa"
|
"name": "Přeskakovat Tichá místa"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Automaticky přeskakuje nehudební části jako intro/outro nebo části hudebních videí, kde nehraje písnčka",
|
||||||
|
"name": "SponsorBlock"
|
||||||
},
|
},
|
||||||
"taskbar-mediacontrol": {
|
"taskbar-mediacontrol": {
|
||||||
"description": "Kontrolovat playback z vašeho Windows taskbar"
|
"description": "Ovládejte přehrávání z vašeho Windows hlavního panelu",
|
||||||
|
"name": "Hlavní panel Media Control"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Přidává Touch Bar widget pro macOS uživatele",
|
||||||
|
"name": "Touch Bar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integrace s OBS's plugin Tuna",
|
||||||
|
"name": "Tuna OBS"
|
||||||
},
|
},
|
||||||
"video-toggle": {
|
"video-toggle": {
|
||||||
|
"description": "Přidává tlačítko k switch mezi video/písničko režimem. Může také odstranit celou video kartu",
|
||||||
"menu": {
|
"menu": {
|
||||||
"align": {
|
"align": {
|
||||||
"label": "Zarovnání",
|
"label": "Zarovnání",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"left": "Vlevo",
|
"left": "Vlevo",
|
||||||
|
"middle": "Uprostřed",
|
||||||
"right": "Pravo"
|
"right": "Pravo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"force-hide": "Vynutit odstranění karty videa",
|
||||||
"mode": {
|
"mode": {
|
||||||
"label": "Režim"
|
"label": "Režim",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Vlastní přepínač",
|
||||||
|
"disabled": "Vypnuto",
|
||||||
|
"native": "Původní přepínač"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"name": "Přepínač videa",
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Písnička"
|
"button": "Písnička"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Přidá vizualizér do přehrávače",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Typ vizualizéru"
|
||||||
|
},
|
||||||
|
"name": "Vizualizér"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"hide-menu-enabled": {
|
"hide-menu-enabled": {
|
||||||
"detail": "Menü ist versteckt, nutze 'Alt', um es zu zeigen (oder 'Escape' beim Verwenden des In-App-Menüs)",
|
"detail": "Das Menü ist versteckt, nutze 'Alt', um es zu aufzurufen (oder 'Escape' beim Verwenden des In-App-Menüs)",
|
||||||
"message": "Menü verstecken ist aktiviert",
|
"message": "Menü verstecken ist aktiviert",
|
||||||
"title": "Menü Verstecken Aktiviert"
|
"title": "Menü Verstecken Aktiviert"
|
||||||
},
|
},
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"label": "Proxy setzen",
|
"label": "Proxy setzen",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"label": "Proxy-Adresse eingeben: (leer lassen zum Ausschalten)",
|
"label": "Proxy-Adresse eingeben: (leer lassen zum Ausschalten)",
|
||||||
"placeholder": "Beispiel: socks5://127.0.0.1:9999",
|
"placeholder": "Beispiel: SOCKS5://127.0.0.1:9999",
|
||||||
"title": "Proxy setzen"
|
"title": "Proxy setzen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -170,7 +170,8 @@
|
|||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"enabled": "Aktiviert",
|
"enabled": "Aktiviert",
|
||||||
"label": "Erweiterungen"
|
"label": "Erweiterungen",
|
||||||
|
"new": "NEU"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"label": "Ansicht",
|
"label": "Ansicht",
|
||||||
@ -190,7 +191,11 @@
|
|||||||
"previous": "Vorheriges",
|
"previous": "Vorheriges",
|
||||||
"quit": "Beenden",
|
"quit": "Beenden",
|
||||||
"restart": "Anwendung neu starten",
|
"restart": "Anwendung neu starten",
|
||||||
"show": "Fenster anzeigen"
|
"show": "Fenster anzeigen",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Musik",
|
||||||
|
"with-song-info": "YouTube Musik: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
@ -201,12 +206,16 @@
|
|||||||
},
|
},
|
||||||
"name": "Werbeblocker"
|
"name": "Werbeblocker"
|
||||||
},
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Fügt Undislike, Dislike, Like und Unlike-Knöpfe hinzu, welche sich auf alle Lieder in einer Playlist oder Album auswirken",
|
||||||
|
"name": "Album-Aktionen"
|
||||||
|
},
|
||||||
"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",
|
||||||
"name": "Thema aus Albumfarbe"
|
"name": "Thema aus Albumfarbe"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
"description": "Fügt einen Lichteffekt durch sanftes Abstreifen der Farben des Videos in deinen Bildschirmhintergrund hinzu.",
|
"description": "Fügt einen Lichteffekt durch sanftes Abstreifen der Farben des Videos in deinen Bildschirmhintergrund hinzu",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
"label": "Unschärfemenge",
|
"label": "Unschärfemenge",
|
||||||
@ -408,10 +417,6 @@
|
|||||||
},
|
},
|
||||||
"name": "In-App Menü"
|
"name": "In-App Menü"
|
||||||
},
|
},
|
||||||
"last-fm": {
|
|
||||||
"description": "Scrobbling-Unterstützung für Last.fm hinzufügen",
|
|
||||||
"name": "Last.fm"
|
|
||||||
},
|
|
||||||
"lumiastream": {
|
"lumiastream": {
|
||||||
"description": "Fügt Unterstützung für Lumia Stream hinzu",
|
"description": "Fügt Unterstützung für Lumia Stream hinzu",
|
||||||
"name": "Lumia Stream [Beta]"
|
"name": "Lumia Stream [Beta]"
|
||||||
@ -426,6 +431,52 @@
|
|||||||
"fetched-lyrics": "Liedtexte für Genius abgerufen"
|
"fetched-lyrics": "Liedtexte für Genius abgerufen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Teile eine Wiedergabeliste mit anderen. Wenn der Host ein Lied abspielt, hören alle anderen das gleiche Lied",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Host ID eingeben"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Speichern",
|
||||||
|
"track-source": "Quelle verfolgen",
|
||||||
|
"unknown-user": "Unbekannter Nutzer"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Host ID kopieren",
|
||||||
|
"close": "Music Together schließen",
|
||||||
|
"connected-users": "Verbundene Benutzer",
|
||||||
|
"disconnect": "Verbindung zu Music Together trennen",
|
||||||
|
"empty-user": "Keine verbundenen Benutzer",
|
||||||
|
"host": "Host für Music Together",
|
||||||
|
"join": "Music Together beitreten",
|
||||||
|
"permission": {
|
||||||
|
"all": "Gästen erlauben, Wiederhabeliste und Player zu bedienen",
|
||||||
|
"host-only": "Nur der Host kann die Playlist und den Player kontrollieren",
|
||||||
|
"playlist": "Gäste das Kontrollieren der Playlist erlauben"
|
||||||
|
},
|
||||||
|
"set-permission": "Kontrollberechtigung ändern",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Verbindung getrennt",
|
||||||
|
"guest": "Als Gast verbunden",
|
||||||
|
"host": "Als Host verbunden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Song hinzufügen gescheitert",
|
||||||
|
"closed": "Music Together geschlossen",
|
||||||
|
"disconnected": "Verbindung zu Music Together getrennt",
|
||||||
|
"host-failed": "Hosten von Music Together gescheitert",
|
||||||
|
"id-copied": "Host ID in die Zwischenablage kopiert",
|
||||||
|
"id-copy-failed": "Kopieren der Host ID in die Zwischenablage gescheitert",
|
||||||
|
"join-failed": "Beitreten zu Music Together gescheitert",
|
||||||
|
"joined": "Music Together beigetreten",
|
||||||
|
"permission-changed": "Music Together-Berechtigung zu \"{{permission}}\" geändert",
|
||||||
|
"remove-song-failed": "Entfernen des Liedes gescheitert",
|
||||||
|
"user-connected": "{{name}} ist Music Together beigetreten",
|
||||||
|
"user-disconnected": "{{name}} hat Music Together verlassen"
|
||||||
|
}
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"description": "Vorwärts/Zurück Navigationspfeile direkt in die Oberfläche integriert - wie in deinem geliebten Browser",
|
"description": "Vorwärts/Zurück Navigationspfeile direkt in die Oberfläche integriert - wie in deinem geliebten Browser",
|
||||||
"name": "Navigation"
|
"name": "Navigation"
|
||||||
@ -470,9 +521,9 @@
|
|||||||
"save-window-size": "Fenstergröße speichern",
|
"save-window-size": "Fenstergröße speichern",
|
||||||
"use-native-pip": "Browsereigenes PiP verwenden"
|
"use-native-pip": "Browsereigenes PiP verwenden"
|
||||||
},
|
},
|
||||||
"name": "Bild im Bild",
|
"name": "Bild-im-Bild",
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Bild im Bild"
|
"button": "Bild-im-Bild"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playback-speed": {
|
"playback-speed": {
|
||||||
@ -518,10 +569,33 @@
|
|||||||
"description": "Erlaubt die Videoqualität über einen Knopf auf dem Video",
|
"description": "Erlaubt die Videoqualität über einen Knopf auf dem Video",
|
||||||
"name": "Videoqualitätsänderer"
|
"name": "Videoqualitätsänderer"
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"scrobbler": {
|
||||||
"description": "Ermöglicht das Festlegen globaler Hotkeys für die Wiedergabe (Abspielen/Pause/Nächstes/Vorheriges) + Deaktivieren des Medien-OSD durch Überschreiben der Medientasten + Aktivieren von Strg/CMD + F zum Suchen + Aktivieren der Linux mpris-Unterstützung für Medientasten + Angepasste Tastenkürzel für fortgeschrittene Benutzer",
|
"description": "Scrobbling-Unterstützung aktivieren (z.B. für last.fm, Listenbrainz)",
|
||||||
"menu": {
|
"menu": {
|
||||||
"override-media-keys": "Medienschlüssel überschreiben",
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API Einstellungen"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "ListenBrainz-Benutzer-Token eintragen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API-Schlüssel",
|
||||||
|
"api-secret": "Last.fm API secret"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "ListenBrainz-Benutzer-Token eintragen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Medientasten überschreiben",
|
||||||
"set-keybinds": "Globale Liedsteuerung setzen"
|
"set-keybinds": "Globale Liedsteuerung setzen"
|
||||||
},
|
},
|
||||||
"name": "Abkürzungen (& MPRIS)",
|
"name": "Abkürzungen (& MPRIS)",
|
||||||
@ -537,6 +611,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Überspringt Lieder, die ihnen nicht gefallen",
|
||||||
|
"name": "Überspring Lieder, die ihnen nicht gefallen"
|
||||||
|
},
|
||||||
"skip-silences": {
|
"skip-silences": {
|
||||||
"description": "Automatisch stille Abschnitte in Liedern überspringen",
|
"description": "Automatisch stille Abschnitte in Liedern überspringen",
|
||||||
"name": "Stille überspringen"
|
"name": "Stille überspringen"
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Αποτυχία εκτέλεσης προσθέτου {{pluginName}}::{{contextName}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"code": "el",
|
"code": "el",
|
||||||
"local-name": "Ελληνικά",
|
"local-name": "Ελληνικά",
|
||||||
@ -192,9 +199,6 @@
|
|||||||
"button": "Download"
|
"button": "Download"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"last-fm": {
|
|
||||||
"name": "Last.fm"
|
|
||||||
},
|
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"name": "Navigation"
|
"name": "Navigation"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
"main": {
|
"main": {
|
||||||
"console": {
|
"console": {
|
||||||
"did-finish-load": {
|
"did-finish-load": {
|
||||||
"dev-tools": "did finish load. dev tools opened"
|
"dev-tools": "Finished loading. DevTools opened"
|
||||||
},
|
},
|
||||||
"i18n": {
|
"i18n": {
|
||||||
"loaded": "i18n loaded"
|
"loaded": "i18n loaded"
|
||||||
@ -44,7 +44,7 @@
|
|||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"hide-menu-enabled": {
|
"hide-menu-enabled": {
|
||||||
"detail": "Menu is hidden, use 'Alt' to show it (or 'Escape' if using in-app-menu)",
|
"detail": "Menu is hidden, use 'Alt' to show it (or 'Escape' if using In-App Menu)",
|
||||||
"message": "Hide Menu is enabled",
|
"message": "Hide Menu is enabled",
|
||||||
"title": "Hide Menu Enabled"
|
"title": "Hide Menu Enabled"
|
||||||
},
|
},
|
||||||
@ -96,7 +96,7 @@
|
|||||||
"advanced-options": {
|
"advanced-options": {
|
||||||
"label": "Advanced options",
|
"label": "Advanced options",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"auto-reset-app-cache": "Reset App cache when app starts",
|
"auto-reset-app-cache": "Reset app cache when app starts",
|
||||||
"disable-hardware-acceleration": "Disable hardware acceleration",
|
"disable-hardware-acceleration": "Disable hardware acceleration",
|
||||||
"edit-config-json": "Edit config.json",
|
"edit-config-json": "Edit config.json",
|
||||||
"override-user-agent": "Override User-Agent",
|
"override-user-agent": "Override User-Agent",
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"label": "Set proxy",
|
"label": "Set proxy",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"label": "Enter Proxy Address: (leave empty to disable)",
|
"label": "Enter Proxy Address: (leave empty to disable)",
|
||||||
"placeholder": "Example: socks5://127.0.0.1:9999",
|
"placeholder": "Example: SOCKS5://127.0.0.1:9999",
|
||||||
"title": "Set proxy"
|
"title": "Set proxy"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -170,7 +170,8 @@
|
|||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"label": "Plugins"
|
"label": "Plugins",
|
||||||
|
"new": "NEW"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"label": "View",
|
"label": "View",
|
||||||
@ -190,7 +191,11 @@
|
|||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"quit": "Exit",
|
"quit": "Exit",
|
||||||
"restart": "Restart App",
|
"restart": "Restart App",
|
||||||
"show": "Show window"
|
"show": "Show window",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
@ -199,14 +204,26 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"blocker": "Blocker"
|
"blocker": "Blocker"
|
||||||
},
|
},
|
||||||
"name": "Adblocker"
|
"name": "Ad Blocker"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album",
|
||||||
|
"name": "Album Actions"
|
||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Applies a dynamic theme and visual effects based on the album color palette",
|
"description": "Applies a dynamic theme and visual effects based on the album color palette",
|
||||||
"name": "Album Color Theme"
|
"name": "Album Color Theme",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Color mix ratio",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
"description": "Applies a lighting effect by casting gentle colors from the video, into your screen’s background.",
|
"description": "Applies a lighting effect by casting gentle colors from the video, into your screen’s background",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
"label": "Blur amount",
|
"label": "Blur amount",
|
||||||
@ -241,7 +258,7 @@
|
|||||||
"smoothness-transition": {
|
"smoothness-transition": {
|
||||||
"label": "Smoothness transition",
|
"label": "Smoothness transition",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"during": "During {{interpolationTime}}s"
|
"during": "During {{interpolationTime}} s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"use-fullscreen": {
|
"use-fullscreen": {
|
||||||
@ -289,12 +306,12 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"advanced": "Advanced"
|
"advanced": "Advanced"
|
||||||
},
|
},
|
||||||
"name": "Crossfade [beta]",
|
"name": "Crossfade [Beta]",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"options": {
|
"options": {
|
||||||
"multi-input": {
|
"multi-input": {
|
||||||
"fade-in-duration": "Fade in duration (milliseconds)",
|
"fade-in-duration": "Fade in duration (ms)",
|
||||||
"fade-out-duration": "Fade out duration (milliseconds)",
|
"fade-out-duration": "Fade out duration (ms)",
|
||||||
"fade-scaling": {
|
"fade-scaling": {
|
||||||
"label": "Fade scaling",
|
"label": "Fade scaling",
|
||||||
"linear": "Linear",
|
"linear": "Linear",
|
||||||
@ -408,13 +425,9 @@
|
|||||||
},
|
},
|
||||||
"name": "In-App Menu"
|
"name": "In-App Menu"
|
||||||
},
|
},
|
||||||
"last-fm": {
|
|
||||||
"description": "Add scrobbling support for Last.fm",
|
|
||||||
"name": "Last.fm"
|
|
||||||
},
|
|
||||||
"lumiastream": {
|
"lumiastream": {
|
||||||
"description": "Adds Lumia Stream support",
|
"description": "Adds Lumia Stream support",
|
||||||
"name": "Lumia Stream [beta]"
|
"name": "Lumia Stream [Beta]"
|
||||||
},
|
},
|
||||||
"lyrics-genius": {
|
"lyrics-genius": {
|
||||||
"description": "Adds lyrics support for most songs",
|
"description": "Adds lyrics support for most songs",
|
||||||
@ -426,6 +439,52 @@
|
|||||||
"fetched-lyrics": "Fetched lyrics for Genius"
|
"fetched-lyrics": "Fetched lyrics for Genius"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Share a playlist with others. When the host plays a song, everyone else will hear the same song",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Enter Host ID"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Save",
|
||||||
|
"track-source": "Track Source",
|
||||||
|
"unknown-user": "Unknown User"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Copy Host ID",
|
||||||
|
"close": "Close Music Together",
|
||||||
|
"connected-users": "Connected Users",
|
||||||
|
"disconnect": "Disconnect Music Together",
|
||||||
|
"empty-user": "No connected users",
|
||||||
|
"host": "Music Together Host",
|
||||||
|
"join": "Join Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "Allow guests to control playlist and player",
|
||||||
|
"host-only": "Only the host can control playlist and player",
|
||||||
|
"playlist": "Allow guests to control playlist"
|
||||||
|
},
|
||||||
|
"set-permission": "Change Control Permission",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Disconnected",
|
||||||
|
"guest": "Connected as Guest",
|
||||||
|
"host": "Connected as Host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Failed to add song",
|
||||||
|
"closed": "Music Together closed",
|
||||||
|
"disconnected": "Music Together disconnected",
|
||||||
|
"host-failed": "Failed to host Music Together",
|
||||||
|
"id-copied": "Host ID copied to clipboard",
|
||||||
|
"id-copy-failed": "Failed to copy Host ID to clipboard",
|
||||||
|
"join-failed": "Failed to join Music Together",
|
||||||
|
"joined": "Joined Music Together",
|
||||||
|
"permission-changed": "Music Together permission changed to \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Failed to remove song",
|
||||||
|
"user-connected": "{{name}} joined Music Together",
|
||||||
|
"user-disconnected": "{{name}} left Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"description": "Next/Back navigation arrows directly integrated in the interface, like in your favorite browser",
|
"description": "Next/Back navigation arrows directly integrated in the interface, like in your favorite browser",
|
||||||
"name": "Navigation"
|
"name": "Navigation"
|
||||||
@ -462,17 +521,17 @@
|
|||||||
"keybind-options": {
|
"keybind-options": {
|
||||||
"hotkey": "Hotkey"
|
"hotkey": "Hotkey"
|
||||||
},
|
},
|
||||||
"label": "Choose a hotkey for toggle Picture in Picture",
|
"label": "Choose a hotkey to toggle picture-in-picture",
|
||||||
"title": "Picture in Picture Hotkey"
|
"title": "Picture-in-picture Hotkey"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"save-window-position": "Save window position",
|
"save-window-position": "Save window position",
|
||||||
"save-window-size": "Save window size",
|
"save-window-size": "Save window size",
|
||||||
"use-native-pip": "Use browser native PiP"
|
"use-native-pip": "Use browser native PiP"
|
||||||
},
|
},
|
||||||
"name": "Picture in Picture",
|
"name": "Picture-in-picture",
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Picture in Picture"
|
"button": "Picture-in-picture"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playback-speed": {
|
"playback-speed": {
|
||||||
@ -518,8 +577,33 @@
|
|||||||
"description": "Allows changing the video quality with a button on the video overlay",
|
"description": "Allows changing the video quality with a button on the video overlay",
|
||||||
"name": "Video Quality Changer"
|
"name": "Video Quality Changer"
|
||||||
},
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Add scrobbling support (etc. last.fm, Listenbrainz)",
|
||||||
|
"menu": {
|
||||||
|
"scrobble-other-media": "Scrobble other media",
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API Settings"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Enter ListenBrainz user token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API key",
|
||||||
|
"api-secret": "Last.fm API secret"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Enter your ListenBrainz user token:",
|
||||||
|
"title": "ListenBrainz token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"description": "Allows setting global hotkeys for playback (play/pause/next/previous) + disable media osd by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for mediakeys + custom hotkeys for advanced users",
|
"description": "Allows setting global hotkeys for playback (play/pause/next/previous) and turning off media OSD by overriding media keys, turning on Ctrl/CMD + F to search, turning on Linux MPRIS support for media keys, and custom hotkeys for advanced users",
|
||||||
"menu": {
|
"menu": {
|
||||||
"override-media-keys": "Override Media Keys",
|
"override-media-keys": "Override Media Keys",
|
||||||
"set-keybinds": "Set Global Song Controls"
|
"set-keybinds": "Set Global Song Controls"
|
||||||
@ -537,6 +621,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Skips disliked songs",
|
||||||
|
"name": "Skip Disliked Songs"
|
||||||
|
},
|
||||||
"skip-silences": {
|
"skip-silences": {
|
||||||
"description": "Automatically skip silences sections in songs",
|
"description": "Automatically skip silences sections in songs",
|
||||||
"name": "Skip Silences"
|
"name": "Skip Silences"
|
||||||
|
|||||||
682
src/i18n/resources/es.json
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Error al ejecutar el plugin {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin {{pluginName}}: {{contextName}} ejecutado en {{ms}}ms",
|
||||||
|
"initialize-failed": "Error al inicializar plugin \"{{pluginName}}\"",
|
||||||
|
"load-all": "Cargando todos los plugins",
|
||||||
|
"load-failed": "Error al cargar el plugin \"{{pluginName}}\"",
|
||||||
|
"loaded": "Plugin \"{{pluginName}}\" cargado",
|
||||||
|
"unload-failed": "No se ha podido descargar el plugin \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Plugin \"{{pluginName}}\" descargado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "es",
|
||||||
|
"local-name": "Inglés",
|
||||||
|
"name": "Spanish"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Carga finalizada. DevTools abiertos"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n cargado"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Comando recibido sobre el protocolo: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "El archivo CSS \"{{cssFile}}\" no existe, ignorando"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "¡Error sin repuesta!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Borrar caché de la aplicación"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "La ventana intentó mostrarse fuera de la pantalla, windowSize={{windowSize}}, displaySize={{displaySize}}, posicion={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "El menú está oculto, utiliza \"Alt\" para mostrarlo (o \"Escape\" si utilizas el menú integrado en la aplicación)",
|
||||||
|
"message": "Menu oculto esta deshabilitado",
|
||||||
|
"title": "Menú oculto activado"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Más tarde",
|
||||||
|
"restart-now": "Reiniciar ahora"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" se requiere reiniciar para que el plugin tome efecto",
|
||||||
|
"message": "\"{{pluginName}}\" necesita reiniciar",
|
||||||
|
"title": "Se requiere reinicio"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Dejar",
|
||||||
|
"relaunch": "Volver a abrir",
|
||||||
|
"wait": "Espera"
|
||||||
|
},
|
||||||
|
"detail": "Sentimos las molestias. Por favor, elija qué hacer:",
|
||||||
|
"message": "La aplicación no responde",
|
||||||
|
"title": "La ventana no responde"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Desactivar actualizaciones",
|
||||||
|
"download": "Descargar",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "Una nueva versión está disponible y puede descargarse en {{downloadLink}}",
|
||||||
|
"message": "Ya está disponible una nueva versión",
|
||||||
|
"title": "Actualización disponible"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Acerca de",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navegación",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Copiar la URL actual",
|
||||||
|
"go-back": "Regresar",
|
||||||
|
"go-forward": "Adelante",
|
||||||
|
"quit": "Salir",
|
||||||
|
"restart": "Reiniciar la aplicación"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Opciones",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Opciones avanzadas",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Restablecer la caché de la aplicación al iniciarla",
|
||||||
|
"disable-hardware-acceleration": "Desactivar la aceleración por hardware",
|
||||||
|
"edit-config-json": "Editar config.json",
|
||||||
|
"override-user-agent": "sobrescribir User-Agent",
|
||||||
|
"restart-on-config-changes": "Reinicie al cambiar la configuración",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Definir proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Introduzca la dirección del proxy: (déjela vacía para desactivarla)",
|
||||||
|
"placeholder": "Ejemplo: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Establecer proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Activar DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Siempre arriba",
|
||||||
|
"auto-update": "Actualización automática",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "El menú se ocultará la próxima vez que lo inicies, usa [Alt] para mostrarlo (o pulsa [`] si usas el menú dentro de la aplicación)",
|
||||||
|
"title": "Ocultar menú habilitado"
|
||||||
|
},
|
||||||
|
"label": "Ocultar menú"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "El idioma se cambiará después de reiniciar",
|
||||||
|
"title": "Se cambio el idioma"
|
||||||
|
},
|
||||||
|
"label": "Idioma",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "¿Quieres ayudar a traducir? Haz clic aquí"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Reanudar la última canción al iniciar la aplicación",
|
||||||
|
"single-instance-lock": "Bloquear en una instancia unica",
|
||||||
|
"start-at-login": "Comenzar al iniciar sesión",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Página de inicio",
|
||||||
|
"unset": "Sin configurar"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Bandeja",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Desactivado",
|
||||||
|
"enabled-and-hide-app": "Activar y ocultar la aplicación",
|
||||||
|
"enabled-and-show-app": "Activado y mostrar aplicación",
|
||||||
|
"play-pause-on-click": "Reproducir/Pausar al hacer clic"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Ajustes visuales",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Predeterminado",
|
||||||
|
"force-show": "Forzar la visualización",
|
||||||
|
"hide": "Ocultar",
|
||||||
|
"label": "Botones de \"Me Gusta\""
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Remover el botón de Actualización",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Importar archivo CSS personalizado",
|
||||||
|
"no-theme": "Sin temas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Habilitado",
|
||||||
|
"label": "Plugins",
|
||||||
|
"new": "NUEVO"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Ver",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Forzar la recarga",
|
||||||
|
"reload": "Recargar",
|
||||||
|
"reset-zoom": "Tamaño actual",
|
||||||
|
"toggle-fullscreen": "Alternar pantalla completa",
|
||||||
|
"zoom-in": "Acercar",
|
||||||
|
"zoom-out": "Alejar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Siguiente",
|
||||||
|
"play-pause": "Reproducir/Pausar",
|
||||||
|
"previous": "Anterior",
|
||||||
|
"quit": "Salir",
|
||||||
|
"restart": "Reiniciar la aplicación",
|
||||||
|
"show": "Mostrar ventana",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Bloquear todos los anuncios y el rastreo",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Bloqueador"
|
||||||
|
},
|
||||||
|
"name": "Bloqueador de anuncios"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Añade los botones \"No me gusta\", \"No me gusta\", \"Me gusta\" y \"No me gusta\" para aplicarlos a todas las canciones de una lista de reproducción o un álbum",
|
||||||
|
"name": "Acciones en el álbum"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Aplica un tema dinámico y efectos visuales basados en la paleta de colores del álbum",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Proporción de la mezcla de color",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Color del álbum"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Aplica un efecto de iluminación mediante la proyección de colores suaves del vídeo en el fondo de la pantalla",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Cantidad de desenfoque",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pixeles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buffer",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Transparencia",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Calidad",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} píxeles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Tamaño",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Transición suave",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Durante {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Usando Pantalla Completa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Modo ambiente"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Aplicar compresión al audio (reduce la diferencia entre las partes más fuertes y más suaves de una pista para que tenga un nivel más consistente)",
|
||||||
|
"name": "Compresor de audio"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Hace que la barra de navegación sea transparente y borrosa",
|
||||||
|
"name": "Desenfocar barra de navegación"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Saltar la verificación de edad de YouTube",
|
||||||
|
"name": "Saltar las restricciones de edad"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Selector de subtítulos para pistas de audio de YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Seleccionar automáticamente el último subtítulo utilizado",
|
||||||
|
"disable-captions": "Sin subtítulos por defecto"
|
||||||
|
},
|
||||||
|
"name": "Selector de subtítulos",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Idioma actual: {{language}}",
|
||||||
|
"none": "Ninguno",
|
||||||
|
"title": "Seleccionar idioma de los subtítulos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Abra el selector de subtítulos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Poner siempre la barra lateral en modo compacto",
|
||||||
|
"name": "Barra lateral compacta"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Crossfade entre canciones",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Avanzado"
|
||||||
|
},
|
||||||
|
"name": "Crossfade [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Duración del fundido (ms)",
|
||||||
|
"fade-out-duration": "Duración del fundido de salida (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Escala de fundido",
|
||||||
|
"linear": "Lineal",
|
||||||
|
"logarithmic": "Logarítmico"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Crossfade N segundos antes del final"
|
||||||
|
},
|
||||||
|
"title": "Opciones de crossfade"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Hace que la canción comience en modo \"pausado\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Sólo se aplica al inicio"
|
||||||
|
},
|
||||||
|
"name": "Desactivar reproducción automática"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Se intentó conectar con una conexión activa",
|
||||||
|
"connected": "Conectado a Discord",
|
||||||
|
"disconnected": "Desconectado de Discord"
|
||||||
|
},
|
||||||
|
"description": "Muestra a tus amigos lo que escuchas con Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Reconectar automáticamente",
|
||||||
|
"clear-activity": "Borrar actividad",
|
||||||
|
"clear-activity-after-timeout": "Borrar actividad después de un tiempo",
|
||||||
|
"connected": "Conectado",
|
||||||
|
"disconnected": "Desconectado",
|
||||||
|
"hide-duration-left": "Ocultar la duración restante",
|
||||||
|
"hide-github-button": "Ocultar el botón de enlace a GitHub",
|
||||||
|
"play-on-youtube-music": "Reproducir en YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Establecer tiempo de inactividad"
|
||||||
|
},
|
||||||
|
"name": "Estado de actividad de Discord",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Introduzca el tiempo de inactividad en segundos:",
|
||||||
|
"title": "Establecer tiempo de inactividad"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"message": "¡Argh! Lo siento, la descarga falló…",
|
||||||
|
"title": "¡Error en la descarga!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} canciones)",
|
||||||
|
"message": "Descargar Playlist {{playlistTitle}}",
|
||||||
|
"title": "Descarga iniciada"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Conversión: {{percent}}%",
|
||||||
|
"converting": "Convirtiendo…",
|
||||||
|
"done": "Listo: {{filePath}}",
|
||||||
|
"download-info": "Descargando {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Descarga: {{percent}}%",
|
||||||
|
"downloading": "Descargando…",
|
||||||
|
"downloading-counter": "Descargando {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Descargar lista de reproducción \"{{playlistTitle}}\" - {{playlistSize}} canciones ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Error al descargar \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "La carpeta {{playlistFolder}} ya existe",
|
||||||
|
"getting-playlist-info": "Obteniendo información de la lista de reproducción…",
|
||||||
|
"loading": "Cargando…",
|
||||||
|
"playlist-has-only-one-song": "La lista de reproducción sólo tiene un elemento, descárgala directamente",
|
||||||
|
"playlist-id-not-found": "No se ha encontrado el ID de la lista de reproducción",
|
||||||
|
"playlist-is-empty": "La lista de reproducción está vacía",
|
||||||
|
"playlist-is-mix-or-private": "Error obteniendo información de la lista de reproducción: asegúrese de que no es una lista privada o \"Mixed for you\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Preparando archivo…",
|
||||||
|
"saving": "Guardando…",
|
||||||
|
"trying-to-get-playlist-id": "Intentando obtener el ID de la lista de reproducción: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Video no encontrado",
|
||||||
|
"writing-id3": "Escribiendo las etiquetas ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Descarga MP3 / audio fuente directamente desde la interfaz",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Elija la carpeta de descarga",
|
||||||
|
"download-playlist": "Descargar lista de reproducción",
|
||||||
|
"presets": "Preajustes",
|
||||||
|
"skip-existing": "Saltar archivos existentes"
|
||||||
|
},
|
||||||
|
"name": "Descargador",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "No se puede actualizar el progreso"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Descargar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Hace que el control deslizante de volumen sea exponencial para que sea más fácil seleccionar volúmenes más bajos.",
|
||||||
|
"name": "Volumen exponencial"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Da a las barras de menú un aspecto elegante, oscuro o del color de un álbum",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Ocultar controles de ventana DOM"
|
||||||
|
},
|
||||||
|
"name": "Menú de aplicación"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Agrega soporte para Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Añade el soporte para las letras para la mayoría de las canciones",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Letras Romanizadas"
|
||||||
|
},
|
||||||
|
"name": "Lyrics Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Letras recuperadas de Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Comparte una lista de reproducción con los demás. Cuando el anfitrión reproduzca una canción, todos los demás escucharán la misma",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Introduzca el ID del host"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Guardar",
|
||||||
|
"track-source": "Fuente de la pista",
|
||||||
|
"unknown-user": "Usuario desconocido"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Copiar el ID del host",
|
||||||
|
"close": "Cerrar Music Together",
|
||||||
|
"connected-users": "Usuarios conectados",
|
||||||
|
"disconnect": "Desactivar Music Together",
|
||||||
|
"empty-user": "No hay usuarios conectados",
|
||||||
|
"host": "Host de Music Together",
|
||||||
|
"join": "Únase a Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "Permite a los invitados controlar la lista de reproducción y el reproductor",
|
||||||
|
"host-only": "Sólo el anfitrión puede controlar la lista de reproducción y el reproductor",
|
||||||
|
"playlist": "Permita que los invitados controlen la lista de reproducción"
|
||||||
|
},
|
||||||
|
"set-permission": "Permiso de control de cambios",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Desconectado",
|
||||||
|
"guest": "Conectado como invitado",
|
||||||
|
"host": "Conectado como anfitrión"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "No se puede añadir la canción",
|
||||||
|
"closed": "Music Together cerrado",
|
||||||
|
"disconnected": "Music Together desconectados",
|
||||||
|
"host-failed": "Fallo el host de Music Together",
|
||||||
|
"id-copied": "ID del host copiado en el portapapeles",
|
||||||
|
"id-copy-failed": "No se ha podido copiar el ID del host en el portapapeles",
|
||||||
|
"join-failed": "Fallo en la unión a Music Together",
|
||||||
|
"joined": "Unido a Music Together",
|
||||||
|
"permission-changed": "Permiso de Music Together cambiado a \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Error al eliminar la canción",
|
||||||
|
"user-connected": "{{name}} se unió a Music Together",
|
||||||
|
"user-disconnected": "{{name}} dejó Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Flechas de navegación Siguiente/Atrás directamente integradas en la interfaz, como en tu navegador favorito",
|
||||||
|
"name": "Navegación"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Eliminar los botones y enlaces de inicio de sesión de Google de la interfaz",
|
||||||
|
"name": "Sin inicio de sesión de Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Mostrar una notificación cuando empiece a sonar una canción (las notificaciones interactivas están disponibles en Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Notificaciones interactivas",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Ajustes interactivos",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Ocultar el texto del botón",
|
||||||
|
"refresh-on-play-pause": "Actualizar al reproducir/pausar",
|
||||||
|
"tray-controls": "Abrir/Cerrar al hacer clic en la bandeja"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Prioridad de notificación",
|
||||||
|
"toast-style": "Estilo de mensaje emergente",
|
||||||
|
"unpause-notification": "Mostrar notificación al reanudar"
|
||||||
|
},
|
||||||
|
"name": "Notificaciones"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Permite cambiar la aplicación al modo de imagen en imagen",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Siempre encima",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Tecla de acceso rápido",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Tecla de acceso rápido"
|
||||||
|
},
|
||||||
|
"label": "Elige una tecla de acceso rápido para activar la función de imagen en imagen",
|
||||||
|
"title": "Tecla de acceso directo a imagen en imagen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Guardar la posición de la ventana",
|
||||||
|
"save-window-size": "Guardar tamaño de la ventana",
|
||||||
|
"use-native-pip": "Utilizar \"Dos imágenes a la vez\" PiP nativo del navegador"
|
||||||
|
},
|
||||||
|
"name": "Imagen en imagen",
|
||||||
|
"templates": {
|
||||||
|
"button": "Imagen en imagen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Escucha rápido, escucha despacio! Añade un control deslizante que ajusta la velocidad de la canción",
|
||||||
|
"name": "Velocidad de reproducción",
|
||||||
|
"templates": {
|
||||||
|
"button": "Velocidad"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Controla el volumen de manera precisa utilizando la rueda del ratón/teclas de acceso rápido, con una interfaz personalizada y pasos de volumen personalizables",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Controles de teclas de flechas locales",
|
||||||
|
"custom-volume-steps": "Establecer niveles de volumen personalizados",
|
||||||
|
"global-shortcuts": "Teclas de acceso rápido globales"
|
||||||
|
},
|
||||||
|
"name": "Volumen preciso",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Disminuir el volumen",
|
||||||
|
"increase": "Aumentar el volumen"
|
||||||
|
},
|
||||||
|
"label": "Elija combinaciones de teclas para el volumen:",
|
||||||
|
"title": "Combinaciones de teclas para el volumen"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Escoge los pasos de aumento o disminución del volumen",
|
||||||
|
"title": "Niveles de volumen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Calidad actual: {{quality}}",
|
||||||
|
"message": "Elija la calidad de vídeo:",
|
||||||
|
"title": "Elija la calidad de vídeo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Permite cambiar la calidad del vídeo con un botón sobre puesto en el vídeo",
|
||||||
|
"name": "Ajustador de calidad de vídeo"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Añadir soporte para scrobbling (last.fm, Listenbrainz, etc.)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Ajustes de la API de Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Introduzca el token de usuario de ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Scrobble en otros medios"
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Clave de la API de Last.fm",
|
||||||
|
"api-secret": "Clave secreta de la API de Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Introduzca su token de usuario de ListenBrainz:",
|
||||||
|
"title": "Token de ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Permite configurar teclas de acceso rápido globales para la reproducción (reproducir/pausa/siguiente/anterior) y desactivar la OSD multimedia anulando las teclas multimedia, activar Ctrl/CMD + F para buscar, activar la compatibilidad con MPRIS de Linux para las teclas multimedia y teclas de acceso rápido personalizadas para usuarios avanzados",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Anular teclas de medios",
|
||||||
|
"set-keybinds": "Configurar controles globales de canciones"
|
||||||
|
},
|
||||||
|
"name": "Atajos (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Siguiente",
|
||||||
|
"play-pause": "Reproducir / Pausa",
|
||||||
|
"previous": "Anterior"
|
||||||
|
},
|
||||||
|
"label": "Elija combinaciones de teclas para el control de las canciones:",
|
||||||
|
"title": "Atajos de teclado globales"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Omite las canciones que no le gustan",
|
||||||
|
"name": "Saltar canciones que no me gustan"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Salta automáticamente las secciones silenciosas de las canciones",
|
||||||
|
"name": "Saltar silencios"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Salta automáticamente las partes no musicales como la introducción/final o secciones de videos musicales donde la canción no está sonando",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Controla la reproducción desde la barra de tareas de Windows",
|
||||||
|
"name": "Control de medios de la barra de tareas"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Añade un widget TouchBar para los usuarios de macOS",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integración con el complemento Tuna de OBS",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Añade un botón para cambiar entre el modo Vídeo/Canción. también puede eliminar opcionalmente toda la pestaña de vídeo",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Alineación",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Izquierda",
|
||||||
|
"middle": "Medio",
|
||||||
|
"right": "Derecha"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Forzar eliminación de la pestaña de vídeo",
|
||||||
|
"mode": {
|
||||||
|
"label": "Modo",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Alternador personalizado",
|
||||||
|
"disabled": "Desactivado",
|
||||||
|
"native": "Alternador nativo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Alternador de vídeo",
|
||||||
|
"templates": {
|
||||||
|
"button": "Canción"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Añadir un visualizador al reproductor",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Tipo de visualizador"
|
||||||
|
},
|
||||||
|
"name": "Visualizador"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,18 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Échec de l'exécution du plugin {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} exécuté en {{ms}}ms",
|
||||||
|
"initialize-failed": "Échec de l'initialisation du plugin \"{{pluginName}}\"",
|
||||||
|
"load-all": "Chargement des plugins",
|
||||||
|
"load-failed": "Échec du chargement du plugin \"{{pluginName}}\"",
|
||||||
|
"loaded": "Plugin \"{{pluginName}}\" chargé",
|
||||||
|
"unload-failed": "Échec du déchargement du plugin \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Plugin \"{{pluginName}}\" déchargé"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"code": "fr",
|
"code": "fr",
|
||||||
"local-name": "Français",
|
"local-name": "Français",
|
||||||
@ -6,43 +20,584 @@
|
|||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
"console": {
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Chargement terminé. DevTools ouvert"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n chargé"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Received command over protocol : \"{{command}}\""
|
||||||
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"css-file-not-found": "Le fichier de CSS \"{{cssFile}}\" n'existe pas, ignorer"
|
"css-file-not-found": "Le fichier de CSS \"{{cssFile}}\" n'existe pas, ignorer"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Erreur : ne répond pas !\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Effacement du cache de l'application"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "La fenêtre a essayé d'effectuer un rendu hors écran, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Le menu est masqué, utilisez « Alt » pour l'afficher (ou « Échap » si vous utilisez le menu de l'application)",
|
||||||
|
"message": "Le masquage du menu est activé",
|
||||||
|
"title": "Masquer le menu activé"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Plus tard",
|
||||||
|
"restart-now": "Redémarrer maintenant"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" plugin nécessite un redémarrage pour qu'il soit pris en compte",
|
||||||
|
"message": "\"{{pluginName}}\" a besoin d'un redémarrage",
|
||||||
|
"title": "Redémarrage requis"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Quitté",
|
||||||
|
"relaunch": "Relancer",
|
||||||
|
"wait": "Attendre"
|
||||||
|
},
|
||||||
|
"detail": "Nous sommes désolés du dérangement ! veuillez choisir quoi faire :",
|
||||||
|
"message": "L'application ne répond pas",
|
||||||
|
"title": "La fenêtre ne répond pas"
|
||||||
|
},
|
||||||
"update-available": {
|
"update-available": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"download": "Sauvegarder"
|
"disable": "Désactiver les mises à jour",
|
||||||
}
|
"download": "Télécharger",
|
||||||
|
"ok": "Ok"
|
||||||
|
},
|
||||||
|
"detail": "Une nouvelle version est disponible et peut-être télécharger sur {{downloadLink}}",
|
||||||
|
"message": "Une nouvelle version est disponible",
|
||||||
|
"title": "Mise à jour disponible"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
|
"about": "À-propos",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigation",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Copier l'URL actuelle",
|
||||||
|
"go-back": "Retour",
|
||||||
|
"go-forward": "Avancer",
|
||||||
|
"quit": "Quitter",
|
||||||
|
"restart": "Redémarrer l'application"
|
||||||
|
}
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"label": "Paramètres",
|
"label": "Paramètres",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"advanced-options": {
|
"advanced-options": {
|
||||||
|
"label": "Options avancée",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Réinitialiser le cache de l'application au démarrage",
|
||||||
|
"disable-hardware-acceleration": "Désactiver les accélérations matérielles",
|
||||||
"edit-config-json": "Modifier config.json",
|
"edit-config-json": "Modifier config.json",
|
||||||
|
"override-user-agent": "Remplacer le User-Agent",
|
||||||
|
"restart-on-config-changes": "Redémarrer quand la configuration change",
|
||||||
"set-proxy": {
|
"set-proxy": {
|
||||||
|
"label": "Définir un proxy",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"placeholder": "Exemple: socks5://127.0.0.1:9999"
|
"label": "Entrez l'adresse proxy : (laissez vide pour désactiver)",
|
||||||
|
"placeholder": "Exemple: socks5://127.0.0.1:9999",
|
||||||
|
"title": "Définir un proxy"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"toggle-dev-tools": "Ouvrir/fermer les outils de développement"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"always-on-top": "Toujours au dessus",
|
||||||
|
"auto-update": "Mise à jour automatique",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Le menu sera masqué au prochain lancement, utilisez [Alt] pour l'afficher (ou backtick [`] si vous utilisez le menu intégré à l'application)",
|
||||||
|
"title": "Masquer le menu activé"
|
||||||
|
},
|
||||||
|
"label": "Cacher le menu"
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"label": "Langue"
|
"dialog": {
|
||||||
|
"message": "La langue sera changée après le redémarrage",
|
||||||
|
"title": "Langue modifiée"
|
||||||
|
},
|
||||||
|
"label": "Langue",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Envie d'aider à la traduction ? Cliquer ici"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Reprendre la dernière chanson quand l'application démarre",
|
||||||
|
"single-instance-lock": "Verrouillage d'instance unique",
|
||||||
|
"start-at-login": "Démarrer à la connexion",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Page de démarrage",
|
||||||
|
"unset": "Définir à vide"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Plateau",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Désactivé",
|
||||||
|
"enabled-and-hide-app": "Activé et cacher l'app",
|
||||||
|
"enabled-and-show-app": "Activé et afficher l'application",
|
||||||
|
"play-pause-on-click": "Lecture/Pause au clic"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"visual-tweaks": {
|
"visual-tweaks": {
|
||||||
|
"label": "Ajustements visuels",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"like-buttons": {
|
"like-buttons": {
|
||||||
"hide": "Cacher"
|
"default": "Par défaut",
|
||||||
|
"force-show": "Forcer à apparaître",
|
||||||
|
"hide": "Cacher",
|
||||||
|
"label": "Boutons « J'aime »"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Supprimer le bouton de mise à niveau",
|
||||||
|
"theme": {
|
||||||
|
"label": "Thème",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Importer fichier CSS personnalisé",
|
||||||
|
"no-theme": "Pas de thème"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Activé",
|
||||||
|
"label": "Extensions",
|
||||||
|
"new": "NOUVELLE"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Vue",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Forcer l'actualisation",
|
||||||
|
"reload": "Actualiser",
|
||||||
|
"reset-zoom": "Taille réelle",
|
||||||
|
"toggle-fullscreen": "Basculer en plein écran",
|
||||||
|
"zoom-in": "Zoom avant",
|
||||||
|
"zoom-out": "Zoom arrière"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Suivant",
|
||||||
|
"play-pause": "Lecture/Pause",
|
||||||
|
"previous": "Précédent",
|
||||||
|
"quit": "Quitter",
|
||||||
|
"restart": "Redémarrer l'application",
|
||||||
|
"show": "Afficher la fenêtre",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Bloquer toutes les annonces et le suivi par défaut",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Bloqueur"
|
||||||
|
},
|
||||||
|
"name": "Bloqueur de publicités"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Ajoute les boutons Dislike, Undislike, Like, et Unlike à appliquer sur toutes les chansons dans un playlist ou un album.",
|
||||||
|
"name": "Actions d'Album"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Applique un thème dynamique et des effets visuels basés sur la palette des couleurs de l'album",
|
||||||
|
"name": "Thème de couleur d'album"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Applique un effet d'éclairage en jetant des couleurs douces de la vidéo, dans le fond de votre écran.",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Quantité de flou",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pixels"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Tampon",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Opacité",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Qualité",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} pixels"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Taille",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Transition en douceur",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Pendant {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Utilisation du mode plein écran"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Mode ambiant"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Appliquer une compression à l'audio (diminue le volume des parties les plus fortes du signal et augmente le volume des parties les plus faibles)",
|
||||||
|
"name": "Compresseur audio"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Rend la barre de navigation transparente et floue",
|
||||||
|
"name": "Barre de navigation floue"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Contourner la vérification de l'âge de YouTube",
|
||||||
|
"name": "Contourner les restrictions d’âge"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Sélecteur de sous-titres pour les pistes audio YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Sélectionner automatiquement la dernière légende utilisée",
|
||||||
|
"disable-captions": "Pas de sous-titres par défaut"
|
||||||
|
},
|
||||||
|
"name": "Sélecteur de sous-titres",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Langue de sous-titrage actuelle : {{language}}",
|
||||||
|
"none": "Aucun",
|
||||||
|
"title": "Sélectionnez la langue des sous-titres"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Ouvrir le sélecteur de sous-titres"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Toujours définir la barre latérale en mode compact",
|
||||||
|
"name": "Barre latérale compacte"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Fondu enchaîné entre les chansons",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Avancé"
|
||||||
|
},
|
||||||
|
"name": "Fondu enchaîné [Bêta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Durée du fondu (millisecondes)",
|
||||||
|
"fade-out-duration": "Durée du fondu (millisecondes)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Mise à l'échelle du fondu",
|
||||||
|
"linear": "Linéaire",
|
||||||
|
"logarithmic": "Logarithmique"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Fondu enchaîné N secondes avant la fin"
|
||||||
|
},
|
||||||
|
"title": "Options de fondu enchaîné"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Fait démarrer la chanson en mode \"pause\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "S'applique seulement au démarrage"
|
||||||
|
},
|
||||||
|
"name": "Désactiver la lecture automatique"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Tentative de connexion avec une connexion active",
|
||||||
|
"connected": "Connecté à Discord",
|
||||||
|
"disconnected": "Déconnecté de Discord"
|
||||||
|
},
|
||||||
|
"description": "Montrez à vos amis ce que vous écoutez avec Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Reconnexion automatique",
|
||||||
|
"clear-activity": "Effacer l'activité",
|
||||||
|
"clear-activity-after-timeout": "Effacer l’activité après un délai d’attente",
|
||||||
|
"connected": "Connecté",
|
||||||
|
"disconnected": "Déconnecté",
|
||||||
|
"hide-duration-left": "Masquer la durée restante",
|
||||||
|
"hide-github-button": "Masquer le bouton du lien GitHub",
|
||||||
|
"play-on-youtube-music": "Jouer sur YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Définir le délai d'inactivité"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Entrez le délai d'inactivité en secondes :",
|
||||||
|
"title": "Définir le délai d'inactivité"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Ok"
|
||||||
|
},
|
||||||
|
"message": "Argh ! Désolé, le téléchargement a échoué…",
|
||||||
|
"title": "Erreur de téléchargement !"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Ok"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} chansons)",
|
||||||
|
"message": "Téléchargement de la playlist {{playlistTitle}}",
|
||||||
|
"title": "Téléchargement a commencé"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Conversion : {{percent}} %",
|
||||||
|
"converting": "Conversion…",
|
||||||
|
"done": "Terminé : {{filePath}}",
|
||||||
|
"download-info": "Téléchargement {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Télécharger: {{percent}}%",
|
||||||
|
"downloading": "Télécharge…",
|
||||||
|
"downloading-counter": "Télécharge {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Téléchargement de la playlist \"{{playlistTitle}}\" – {{playlistSize}} chansons ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Erreur lors du téléchargement de \"{{author}} - {{title}}\" : {{error}}",
|
||||||
|
"folder-already-exists": "Le dossier {{playlistFolder}} existe déjà",
|
||||||
|
"getting-playlist-info": "Obtention d'informations sur la liste de lecture…",
|
||||||
|
"loading": "Chargement…",
|
||||||
|
"playlist-has-only-one-song": "La liste de lecture ne contient qu'un seul élément, téléchargement du morceau seul",
|
||||||
|
"playlist-id-not-found": "Aucun ID de liste de lecture trouvé",
|
||||||
|
"playlist-is-empty": "La liste de lecture est vide",
|
||||||
|
"playlist-is-mix-or-private": "Erreur lors de l'obtention des informations sur la liste de lecture : assurez-vous qu'il ne s'agit pas d'une liste privée ou \"Mixée pour vous\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Péparer des fichier…",
|
||||||
|
"saving": "Sauvegarde…",
|
||||||
|
"trying-to-get-playlist-id": "Obtention de l'ID de la liste de lecture : {{playlistId}}",
|
||||||
|
"video-id-not-found": "Vidéo non trouvée",
|
||||||
|
"writing-id3": "Écriture des balises ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Télécharge les fichiers MP3/source audio directement depuis l'interface",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Choisissez le dossier de téléchargement",
|
||||||
|
"download-playlist": "Télécharger la liste de lecture",
|
||||||
|
"presets": "Préconfigurations",
|
||||||
|
"skip-existing": "Passer les fichiers existants"
|
||||||
|
},
|
||||||
|
"name": "Téléchargeur",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Impossible de mettre à jour la progression"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Télécharger"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Rend le curseur de volume exponentiel afin qu'il soit plus facile de sélectionner des volumes plus faibles.",
|
||||||
|
"name": "Volume exponentiel"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Donne aux barres de menus un aspect élégant, sombre ou aux couleurs de l'album",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Masquer les contrôles de la fenêtre DOM"
|
||||||
|
},
|
||||||
|
"name": "Menu intégré à l'application"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Ajoute la prise en charge de Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Bêta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Ajoute la prise en charge des paroles pour la plupart des chansons",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Paroles romanisées"
|
||||||
|
},
|
||||||
|
"name": "Paroles Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Paroles récupérées pour Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Flèches de navigation Suivant/Retour directement intégrées dans l'interface, comme dans votre navigateur préféré",
|
||||||
|
"name": "Navigation"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Supprimer les boutons et liens de connexion Google de l'interface",
|
||||||
|
"name": "Pas de connexion Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Afficher une notification quand une chanson commence à jouer (les notifications interactives sont disponibles sur Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Notifications interactives",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Paramètres interactifs",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Masquer le texte du bouton",
|
||||||
|
"refresh-on-play-pause": "Actualiser lors de la lecture/pause",
|
||||||
|
"tray-controls": "Ouvrir/Fermer sur le plateau, cliquez"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Priorité des notifications",
|
||||||
|
"toast-style": "Style des notifications \"Toast\"",
|
||||||
|
"unpause-notification": "Afficher la notification lors de la reprise"
|
||||||
|
},
|
||||||
|
"name": "Notifications"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Permet de basculer l’application en mode image dans image",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Toujours en haut",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Raccourci clavier",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Raccourci clavier"
|
||||||
|
},
|
||||||
|
"label": "Choisissez un raccourci clavier pour activer l'image dans l'image",
|
||||||
|
"title": "Touche de raccourci Image dans l'image"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Enregistrer la position de la fenêtre",
|
||||||
|
"save-window-size": "Enregistrer la taille de la fenêtre",
|
||||||
|
"use-native-pip": "Utiliser le mode image dans image natif du navigateur"
|
||||||
|
},
|
||||||
|
"name": "Image dans l'image",
|
||||||
|
"templates": {
|
||||||
|
"button": "Image dans l'image"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Écoutez vite, écoutez lentement ! Ajoute un curseur qui contrôle la vitesse de la chanson",
|
||||||
|
"name": "Vitesse de lecture",
|
||||||
|
"templates": {
|
||||||
|
"button": "Vitesse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Contrôlez le volume avec précision à l'aide de la molette de la souris/des raccourcis clavier, avec une interface personnalisée et des étapes de volume personnalisables",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Contrôles avec les touches fléchées",
|
||||||
|
"custom-volume-steps": "Définir des étapes de volume personnalisées",
|
||||||
|
"global-shortcuts": "Raccourcis clavier globaux"
|
||||||
|
},
|
||||||
|
"name": "Volume précis",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Diminuer le volume",
|
||||||
|
"increase": "Augmenter le volume"
|
||||||
|
},
|
||||||
|
"label": "Choisissez les raccourcis clavier du volume global :",
|
||||||
|
"title": "Raccourcis clavier de volume global"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Choisissez les étapes d'augmentation/diminution du volume",
|
||||||
|
"title": "Étapes de volume"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Qualité actuelle : {{quality}}",
|
||||||
|
"message": "Choisissez la qualité vidéo :",
|
||||||
|
"title": "Choisissez la qualité vidéo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Permet de changer la qualité vidéo avec un bouton sur la vidéo",
|
||||||
|
"name": "Changeur de qualité vidéo"
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Permet de définir des raccourcis clavier globaux pour la lecture (lecture/pause/suivant/précédent) + désactiver l'OSD multimédia en remplaçant les touches multimédias + activer Ctrl/CMD + F pour rechercher + activer la prise en charge Linux MPRIS pour les touches multimédias + raccourcis clavier personnalisés pour les utilisateurs avancés",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Remplacer les touches multimédias",
|
||||||
|
"set-keybinds": "Définir les contrôles globaux des morceaux"
|
||||||
|
},
|
||||||
|
"name": "Raccourcis (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Suivant",
|
||||||
|
"play-pause": "Lecture / Pause",
|
||||||
|
"previous": "Précédent"
|
||||||
|
},
|
||||||
|
"label": "Choisissez les raccourcis clavier globaux pour le contrôle des morceaux :",
|
||||||
|
"title": "Raccourcis clavier globaux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Passer les musiques que je n'aime pas",
|
||||||
|
"name": "Passer Chansons Déplaisantes"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Ignorer automatiquement les sections de silence dans les chansons",
|
||||||
|
"name": "Passer les silences"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Saute automatiquement les parties non musicales comme l'intro/outro ou les parties de clips vidéo où la chanson n'est pas lue",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Contrôlez la lecture depuis votre barre des tâches Windows",
|
||||||
|
"name": "Contrôle multimédia de la barre des tâches"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Ajoute un widget TouchBar pour les utilisateurs de macOS",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Intégration avec le plugin OBS Tuna",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Ajoute un bouton pour basculer entre le mode Vidéo/Chanson. peut également supprimer tout l'onglet vidéo",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Alignement",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Gauche",
|
||||||
|
"middle": "Milieu",
|
||||||
|
"right": "Droite"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Forcer la suppression de l'onglet vidéo",
|
||||||
|
"mode": {
|
||||||
|
"label": "Mode",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Bascule personnalisée",
|
||||||
|
"disabled": "Désactivé",
|
||||||
|
"native": "Bascule native"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Basculer la vidéo",
|
||||||
|
"templates": {
|
||||||
|
"button": "Musique"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Ajoute un visualiseur au lecteur",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Type de visualiseur"
|
||||||
|
},
|
||||||
|
"name": "Visualiseur"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
682
src/i18n/resources/id.json
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Gagal saat mengeksekusi plugin {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dieksekusi pada {{ms}}ms",
|
||||||
|
"initialize-failed": "Gagal dalam menginisialisasi plugin \"{{pluginName}}\"",
|
||||||
|
"load-all": "Memuat semua plugin",
|
||||||
|
"load-failed": "Gagal memuat plugin \"{{pluginName}}\"",
|
||||||
|
"loaded": "Plugin \"{{pluginName}}\" dimuat",
|
||||||
|
"unload-failed": "Gagal untuk memuat plugin \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Plugin \"{{pluginName}}\" telah dikeluarkan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "id",
|
||||||
|
"local-name": "Bahasa Indonesia",
|
||||||
|
"name": "Indonesian"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Selesai memuat. DevTools terbuka"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n selesai dimuat"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Menerima instruksi lewat protokol: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS file \"{{cssFile}}\" tidak ada, mengabaikan"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Kesalahan Tidak Responsif!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Menghapus cache aplikasi"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Window mencoba membuat render di luar layar, windowUkuran={{windowSize}}, displaySize={{displaySize}}, posisi={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Menu tersembunyi, gunakan 'Alt' untuk menampilkannya (atau 'Escape' jika menggunakan Menu Dalam Aplikasi)",
|
||||||
|
"message": "Menu Sembunyikan diaktifkan",
|
||||||
|
"title": "Sembunyikan Menu Diaktifkan"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Kemudian",
|
||||||
|
"restart-now": "Restart Sekarang"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" Plugin memerlukan pengaktifan ulang agar dapat diterapkan",
|
||||||
|
"message": "\"{{pluginName}}\" harus dimulai ulang",
|
||||||
|
"title": "Diperlukan Restart"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Keluar",
|
||||||
|
"relaunch": "Luncurkan kembali",
|
||||||
|
"wait": "Tunggu"
|
||||||
|
},
|
||||||
|
"detail": "Kami mohon maaf atas ketidaknyamanan ini. silakan pilih apa yang harus dilakukan:",
|
||||||
|
"message": "Aplikasi Tidak Responsif",
|
||||||
|
"title": "Jendela Tidak Responsif"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Nonaktifkan Pembaruan",
|
||||||
|
"download": "Unduh",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "Versi baru tersedia dan dapat diunduh di {{downloadLink}}",
|
||||||
|
"message": "Versi baru tersedia",
|
||||||
|
"title": "Pembaruan Tersedia"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Tentang",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigasi",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Salin URL saat ini",
|
||||||
|
"go-back": "Kembali",
|
||||||
|
"go-forward": "Maju",
|
||||||
|
"quit": "Keluar",
|
||||||
|
"restart": "Restart Aplikasi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Option",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Opsi lanjutan",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Mengatur ulang cache aplikasi saat aplikasi dimulai",
|
||||||
|
"disable-hardware-acceleration": "Menonaktifkan akselerasi perangkat keras",
|
||||||
|
"edit-config-json": "Ubah config.json",
|
||||||
|
"override-user-agent": "Mengesampingkan User-Agent",
|
||||||
|
"restart-on-config-changes": "Mulai ulang pada perubahan konfigurasi",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Atur Proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Masukkan Alamat Proxy: (biarkan kosong untuk menonaktifkan)",
|
||||||
|
"placeholder": "Contoh: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Atur proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Beralih ke DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Selalu di atas",
|
||||||
|
"auto-update": "Pembaruan Otomatis",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Menu akan disembunyikan pada peluncuran berikutnya, gunakan [Alt] untuk menampilkannya (atau centang [`] jika menggunakan menu dalam aplikasi)",
|
||||||
|
"title": "Sembunyikan Menu Diaktifkan"
|
||||||
|
},
|
||||||
|
"label": "Sembunyikan Menu"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Bahasa akan berubah setelah restart",
|
||||||
|
"title": "Bahasa Berubah"
|
||||||
|
},
|
||||||
|
"label": "Bahasa",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Ingin membantu menerjemahkan? Klik di sini"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Melanjutkan lagu terakhir saat aplikasi dimulai",
|
||||||
|
"single-instance-lock": "Kunci Instance Tunggal",
|
||||||
|
"start-at-login": "Mulai saat masuk",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Halaman awal",
|
||||||
|
"unset": "Tidak ditetapkan"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Bilah",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Dinonaktifkan",
|
||||||
|
"enabled-and-hide-app": "Mengaktifkan dan menyembunyikan aplikasi",
|
||||||
|
"enabled-and-show-app": "Mengaktifkan dan menampilkan aplikasi",
|
||||||
|
"play-pause-on-click": "Putar/Jeda dengan klik"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Penyesuaian Visual",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Standar",
|
||||||
|
"force-show": "Pertunjukan paksa",
|
||||||
|
"hide": "Sembunyikan",
|
||||||
|
"label": "Tombol suka"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Hapus tombol peningkatan",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Impor file CSS khusus",
|
||||||
|
"no-theme": "Tidak ada tema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Diaktifkan",
|
||||||
|
"label": "Plugin",
|
||||||
|
"new": "Baru"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Lihat",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Paksa Reload",
|
||||||
|
"reload": "Muat ulang",
|
||||||
|
"reset-zoom": "Ukuran sebenarnya",
|
||||||
|
"toggle-fullscreen": "Alihkan Layar Penuh",
|
||||||
|
"zoom-in": "Perbesar",
|
||||||
|
"zoom-out": "Perkecil"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Selanjutnya",
|
||||||
|
"play-pause": "Putar/Jeda",
|
||||||
|
"previous": "Sebelumnya",
|
||||||
|
"quit": "Keluar",
|
||||||
|
"restart": "Restart aplikasi",
|
||||||
|
"show": "Tampilkan jendela",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Musik",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Blokir semua iklan dan pelacakan di luar kotak",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Pemblokir"
|
||||||
|
},
|
||||||
|
"name": "Pemblokir Iklan"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Menerapkan tema dinamis dan efek visual berdasarkan palet warna album",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Rasio campuran warna",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Tema Warna Album"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Menerapkan efek pencahayaan dengan memancarkan warna-warna lembut dari video, ke dalam latar belakang layar Anda",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Jumlah kabur",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} piksel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buffer",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Keburaman",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Kualitas",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} piksel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Ukuran",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Kehalusan transisi",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Selama {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Gunakan layar penuh"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Mode ambient"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Menerapkan kompresi pada audio (mengurangi volume pada bagian paling keras dari sinyal dan meningkatkan volume pada bagian paling lembut)",
|
||||||
|
"name": "Kompresi suara"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Jadikan bar navigasi blur dan transparan",
|
||||||
|
"name": "buramkan bar navigasi"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Lewati verifikasi umur dari YouTube",
|
||||||
|
"name": "Lewati batasan umur"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "pemilih caption untuk trek audio YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "pilih caption terakhir secara otomatis",
|
||||||
|
"disable-captions": "bawaannya tanpa caption"
|
||||||
|
},
|
||||||
|
"name": "pemilih caption",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "bahasa caption yang dipakai sekarang: {{language}}",
|
||||||
|
"none": "tidak ada",
|
||||||
|
"title": "pilih bahasa caption"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "buka pemilih caption"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Selalu atur sidebar dalam mode kompak",
|
||||||
|
"name": "sidebar ringkas"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Crossfade antar lagu",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Lanjutan"
|
||||||
|
},
|
||||||
|
"name": "Crossfade [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "durasi fade in (ms)",
|
||||||
|
"fade-out-duration": "durasi fade out (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "redup perlahan",
|
||||||
|
"linear": "Linear",
|
||||||
|
"logarithmic": "Logaritmik"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Crossfade N detik sebelum berakhir"
|
||||||
|
},
|
||||||
|
"title": "Pilihan crossfade"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Buat lagu mulai dalam mode \"jeda\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Hanya terapkan pada saat startup"
|
||||||
|
},
|
||||||
|
"name": "Matikan Autoplay"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Percobaan untuk terhubung dengan koneksi yang aktif",
|
||||||
|
"connected": "Terhubung dengan Discord",
|
||||||
|
"disconnected": "Terputus dari Discord"
|
||||||
|
},
|
||||||
|
"description": "tunjukan apa yang kamu dengarkan dengan Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Reconnect otomatis",
|
||||||
|
"clear-activity": "Hapus riwayat aktifitas",
|
||||||
|
"clear-activity-after-timeout": "hapus riwayat aktifitas setelah timeout",
|
||||||
|
"connected": "terhubung",
|
||||||
|
"disconnected": "tidak terhubung",
|
||||||
|
"hide-duration-left": "sembunyikan sisa durasi",
|
||||||
|
"hide-github-button": "sembunyikan tombol link GitHub",
|
||||||
|
"play-on-youtube-music": "Mainkan di YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Tetapkan batas waktu tidak aktif"
|
||||||
|
},
|
||||||
|
"name": "Rich Presence Discord",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Masukkan batas waktu tidak aktif dalam detik:",
|
||||||
|
"title": "Tetapkan batas waktu tidak aktif"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Oke"
|
||||||
|
},
|
||||||
|
"message": "Argh! Maaf, dowloadnya gagal…",
|
||||||
|
"title": "Downloadnya error!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Oke"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} lagu-lagu)",
|
||||||
|
"message": "Mengunduh Playlist {{playlistTitle}}",
|
||||||
|
"title": "Download dimulai"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Konversi: {{percent}}%",
|
||||||
|
"converting": "Mengkonversi…",
|
||||||
|
"done": "Selesai: {{filePath}}",
|
||||||
|
"download-info": "Mengunduh {{artist}} - {{title}} {{videoId}}",
|
||||||
|
"download-progress": "Mengunduh: {{percent}}%",
|
||||||
|
"downloading": "Mengunduh…",
|
||||||
|
"downloading-counter": "Mengunduh {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Mengunduh playlist \"{{playlistTitle}}\" - {{playlistSize}} lagu ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Gagal mengunduh \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "Folder {{playlistFolder}} sudah ada",
|
||||||
|
"getting-playlist-info": "Mendapatkan informasi playlist…",
|
||||||
|
"loading": "Memuat…",
|
||||||
|
"playlist-has-only-one-song": "Daftar putar hanya memiliki satu item, mengunduhnya secara langsung",
|
||||||
|
"playlist-id-not-found": "ID playlist tidak ditemukan",
|
||||||
|
"playlist-is-empty": "Playlist kosong",
|
||||||
|
"playlist-is-mix-or-private": "Kesalahan mendapatkan info playlist: pastikan bukan playlist pribadi atau \"Campuran untuk Anda\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Menyiapkan file…",
|
||||||
|
"saving": "Menyimpan…",
|
||||||
|
"trying-to-get-playlist-id": "Mencoba mendapatkan ID playlist: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Video tidak ditemukan",
|
||||||
|
"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)",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
673
src/i18n/resources/it.json
Normal file
@ -0,0 +1,673 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "È stato impossibile eseguire il plugin {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Il plugin {{pluginName}}:{{contextName}} è stato eseguito a {{ms}}ms",
|
||||||
|
"initialize-failed": "Inizializzazione del plugin \"{{pluginName}}\" fallita",
|
||||||
|
"load-all": "Carica tutti i plugin",
|
||||||
|
"load-failed": "Caricamento del plugin \"{{pluginName}}\" non riuscito",
|
||||||
|
"loaded": "Plugin \"{{pluginName}}\" caricato",
|
||||||
|
"unload-failed": "Rimozione del plugin \"{{pluginName}}\" fallita",
|
||||||
|
"unloaded": "Plugin \"{{pluginName}}\" rimosso"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "it",
|
||||||
|
"local-name": "Italiano",
|
||||||
|
"name": "Italian"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Caricamento concluso. DevTools aperto"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n caricato"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Comando ricevuto tramite protocollo: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Il file CSS \"{{cssFile}}\" non esiste, ignorato"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Errore di mancata risposta!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Sto liberando la cache dell'app"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "La finestra ha cercato di renderizzare fuori schermo, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}\""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Il menu è nascosto, utilizza 'Alt' per visualizzarlo (o 'Escape' se si utilizza il Menu In-App)\"",
|
||||||
|
"message": "'Nascondi menu' è attivo",
|
||||||
|
"title": "'Nascondi menu' attivo"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "In seguito",
|
||||||
|
"restart-now": "Riavvia ora"
|
||||||
|
},
|
||||||
|
"detail": "Riavviare per attivare il plugin\"{{pluginName}}\"",
|
||||||
|
"message": "\"{{pluginName}}\" deve essere riavviato",
|
||||||
|
"title": "Riavvio richiesto"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Arresta",
|
||||||
|
"relaunch": "Riavvia",
|
||||||
|
"wait": "Attendi"
|
||||||
|
},
|
||||||
|
"detail": "Ci dispiace per l'inconveniente! Scegli cosa fare:",
|
||||||
|
"message": "L'applicazione non risponde",
|
||||||
|
"title": "La finestra non risponde"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Disattiva gli aggiornamenti",
|
||||||
|
"download": "Download",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "È disponibile una nuova versione scaricabile all'indirizzo {{downloadLink}}",
|
||||||
|
"message": "È disponibile una nuova versione",
|
||||||
|
"title": "Aggiornamento disponibile"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Informazioni",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigazione",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Copia l'URL corrente",
|
||||||
|
"go-back": "Pagina indietro",
|
||||||
|
"go-forward": "Pagina avanti",
|
||||||
|
"quit": "Esci",
|
||||||
|
"restart": "Riavvia l'app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Opzioni",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Opzioni avanzate",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Reimposta la cache dell'app quando viene riavviata",
|
||||||
|
"disable-hardware-acceleration": "Disabilita l'accelerazione hardware",
|
||||||
|
"edit-config-json": "Modificare config.json",
|
||||||
|
"override-user-agent": "Sovrascrivi User-Agent",
|
||||||
|
"restart-on-config-changes": "Riavvia alla modifica delle impostazioni",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Imposta il proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Inserisci l'indirizzo proxy: (lascia vuoto per disabilitare)",
|
||||||
|
"placeholder": "Esempio: SOKS5://127.0.0.1:9999",
|
||||||
|
"title": "Imposta il proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Attiva/disattiva DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Sempre in cima",
|
||||||
|
"auto-update": "Aggiornamento automatico",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Il menu verrà nascosto al prossimo avvio. Utilizzare [Alt] per mostrarlo (o backtick [`] se si utilizza il Menu In-App)",
|
||||||
|
"title": "Nascondi menu abilitato"
|
||||||
|
},
|
||||||
|
"label": "Nascondi menu"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "La lingua verrà cambiata dopo il riavvio",
|
||||||
|
"title": "Lingua cambiata"
|
||||||
|
},
|
||||||
|
"label": "Lingua",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Vuoi aiutare a tradurre? Clicca qui"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Riprendi a riprodurre l'ultimo brano all'avvio dell'app",
|
||||||
|
"single-instance-lock": "Permetti una sola istanza dell'app",
|
||||||
|
"start-at-login": "Avvia al login",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Pagina iniziale",
|
||||||
|
"unset": "Non impostato"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Mostra icona nel tray",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Disabilita",
|
||||||
|
"enabled-and-hide-app": "Abilita e nascondi l'app",
|
||||||
|
"enabled-and-show-app": "Abilita e mostra l'app",
|
||||||
|
"play-pause-on-click": "Riproduci/Pausa al click sull'icona"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Miglioramenti visivi",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Predefinito",
|
||||||
|
"force-show": "Forza la visualizzazione",
|
||||||
|
"hide": "Nascondi",
|
||||||
|
"label": "Pulsanti Like"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Rimuovi il pulsante aggiorna",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Importa file CSS personalizzato",
|
||||||
|
"no-theme": "Nessun tema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Attivato",
|
||||||
|
"label": "Plugin",
|
||||||
|
"new": "NUOVO"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Visualizzazione",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Forza l'aggiornamento",
|
||||||
|
"reload": "Aggiorna",
|
||||||
|
"reset-zoom": "Ripristina dimensione",
|
||||||
|
"toggle-fullscreen": "Attiva/disattiva Schermo Intero",
|
||||||
|
"zoom-in": "Zoom in",
|
||||||
|
"zoom-out": "Zoom out"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Prossimo",
|
||||||
|
"play-pause": "Riproduci/Pausa",
|
||||||
|
"previous": "Precedente",
|
||||||
|
"quit": "Esci",
|
||||||
|
"restart": "Riavvia l'app",
|
||||||
|
"show": "Mostra finestra",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Blocca tutti gli annunci e i tracker",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Blocco"
|
||||||
|
},
|
||||||
|
"name": "Ad blocker"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Aggiunge i pulsanti Undislike, Dislike, Like e Unlike a tutti i brani di una playlist o di un album",
|
||||||
|
"name": "Azioni album"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Applica un tema dinamico e degli effetti visivi basandosi sul colore dell'album",
|
||||||
|
"name": "Tema abbinato a colore album"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Applica un effetto di illuminazione proiettando i colori delicati del video sullo sfondo dello schermo",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Intensità sfocatura",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pixel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buffer",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Trasparenza",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Qualità",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} pixel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Dimensione",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Fluidità transizione",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Per {{interpolationTime}} _s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Utilizzo di schermo intero"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Modalità Ambiente"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Attiva la compressione audio (abbassa il volume delle parti più alte e alza quello delle parti più basse del segnale)",
|
||||||
|
"name": "Compressore audio"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Rende la barra di navigazione trasparente e sfuocata",
|
||||||
|
"name": "Barra di navigazione trasparente"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Bypassa la verifica dell'età di YouTube",
|
||||||
|
"name": "Aggira i limiti d'età"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Selettore sottotitolo per le tracce audio di YouTube",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Seleziona automaticamente l'ultimo sottotitolo utilizzato",
|
||||||
|
"disable-captions": "Disattiva i sottotitoli"
|
||||||
|
},
|
||||||
|
"name": "Selettore Sottotitoli",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Lingua del sottotitolo attuale: {{language}}",
|
||||||
|
"none": "Nessuno",
|
||||||
|
"title": "Scegli la lingua del sottotitolo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Apri il selettore dei sottotitoli"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Imposta sempre la barra laterale in modalità compatta",
|
||||||
|
"name": "Barra laterale compatta"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Crossfade tra i brani",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Impostazioni avanzate"
|
||||||
|
},
|
||||||
|
"name": "Crossfade [beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Durata dissolvenza in entrata (ms)",
|
||||||
|
"fade-out-duration": "Durata dissolvenza in uscita (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Transizione dissolvenza",
|
||||||
|
"linear": "Lineare",
|
||||||
|
"logarithmic": "Logaritmica"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "N° secondi di crossfade prima della fine"
|
||||||
|
},
|
||||||
|
"title": "Opzioni crossfade"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Fa iniziare i brani in modalità \"pausa\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Solo all'avvio"
|
||||||
|
},
|
||||||
|
"name": "Disattiva autoplay"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Tenta di connettersi con connessione attiva",
|
||||||
|
"connected": "Connesso a Discord",
|
||||||
|
"disconnected": "Scollegato da Discord"
|
||||||
|
},
|
||||||
|
"description": "Mostra ai tuoi amici cosa ascolti con Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Riconnessione automatica",
|
||||||
|
"clear-activity": "Rimuovi attività",
|
||||||
|
"clear-activity-after-timeout": "Cancella attività dopo il timeout",
|
||||||
|
"connected": "Connesso",
|
||||||
|
"disconnected": "Disconnesso",
|
||||||
|
"hide-duration-left": "Nascondi la durata rimasta",
|
||||||
|
"hide-github-button": "Nascondi il pulsante link a GitHub",
|
||||||
|
"play-on-youtube-music": "Riproduci su YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Imposta il timeout di inattività"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Inserisci il timeout di inattività in secondi:",
|
||||||
|
"title": "Imposta il timeout di inattività"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"message": "Mi dispiace, download fallito…",
|
||||||
|
"title": "Errore nel download!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} canzoni)",
|
||||||
|
"message": "Scarica Playlist {{playlistTitle}}",
|
||||||
|
"title": "Download iniziato"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Conversione: {{percent}}%",
|
||||||
|
"converting": "Sto convertendo…",
|
||||||
|
"done": "Fatto: {{filePath}}",
|
||||||
|
"download-info": "Sto scaricando {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Download: {{percent}}%",
|
||||||
|
"downloading": "Sto scaricando…",
|
||||||
|
"downloading-counter": "Sto scaricando {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Sto scaricando la playlist \"{{playlistTitle}}\" - {{playlistSize}} brani({{playlistId}})",
|
||||||
|
"error-while-downloading": "Errore di download \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "La cartella {{playlistFolder}} è già esistente",
|
||||||
|
"getting-playlist-info": "Sto ottenendo le info sulla playlist…",
|
||||||
|
"loading": "Caricamento…",
|
||||||
|
"playlist-has-only-one-song": "La playlist ha un solo elemento, lo sto scaricando direttamente",
|
||||||
|
"playlist-id-not-found": "Nessun ID playlist trovato",
|
||||||
|
"playlist-is-empty": "La playlist è vuota",
|
||||||
|
"playlist-is-mix-or-private": "Errore nell'ottenere info sulla playlist: assicurati che non sia una playlist privata o un \"Mixtape per te\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Sto preparando il file…",
|
||||||
|
"saving": "Sto salvando…",
|
||||||
|
"trying-to-get-playlist-id": "Sto cercando di ottenere l'ID della playlist: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Video non trovato",
|
||||||
|
"writing-id3": "Sto scrivendo i tag ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Download MP3 / sorgenti audio direttamente dall'interfaccia",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Scegli cartella download",
|
||||||
|
"download-playlist": "Scarica la playlist",
|
||||||
|
"presets": "Preimpostazioni",
|
||||||
|
"skip-existing": "Salta i file esistenti"
|
||||||
|
},
|
||||||
|
"name": "Downloader",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Impossibile aggiornare l'avanzamento"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Scarica"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Rende esponenziale il cursore del volume, in modo da facilitare la selezione di volumi più bassi.",
|
||||||
|
"name": "Volume esponenziale"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Migliora l'aspetto delle barre del menu con un look scuro o basato sul colore dell'album",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Nascondi i controlli delle finestre DOM"
|
||||||
|
},
|
||||||
|
"name": "Menu In-App"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Aggiungi supporto per Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Aggiunge il supporto dei testi per la maggior parte delle canzoni",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Alfabeto latino per i brani con testo in caratteri orientali"
|
||||||
|
},
|
||||||
|
"name": "Lyrics Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Testi recuperati per Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Condividi una playlist con altri. Quando l'Host riproduce un brano, tutti gli altri ascolteranno lo stesso brano",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Inserisci l'ID dell'Host"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Salva",
|
||||||
|
"track-source": "Traccia sorgente",
|
||||||
|
"unknown-user": "Utente sconosciuto"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Copia l'ID dell'Host",
|
||||||
|
"close": "Chiudi Music Together",
|
||||||
|
"connected-users": "Utenti connessi",
|
||||||
|
"disconnect": "Disconetti Music Together",
|
||||||
|
"empty-user": "Utenti non connessi",
|
||||||
|
"host": "Music Together Host",
|
||||||
|
"join": "Unisciti a Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "Consenti ai Guest di controllare la playlist e il player",
|
||||||
|
"host-only": "Solo l'Host può controllare la playlist e il player",
|
||||||
|
"playlist": "Consenti ai Guest di controllare la playlist"
|
||||||
|
},
|
||||||
|
"set-permission": "Cambia autorizzazione di controllo",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Disconnesso",
|
||||||
|
"guest": "Connesso come Guest",
|
||||||
|
"host": "Connesso come Host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Impossibile aggiungere il brano",
|
||||||
|
"closed": "Music Together chiuso",
|
||||||
|
"disconnected": "Music Together disconnesso",
|
||||||
|
"host-failed": "Impossibile ospitare Music Together",
|
||||||
|
"id-copied": "L'ID dell Host è stato copiato negli appunti",
|
||||||
|
"id-copy-failed": "Impossibile copiare l'ID dell'host negli appunti",
|
||||||
|
"join-failed": "Impossibile unirsi a Music Together",
|
||||||
|
"joined": "Unito a Music Together",
|
||||||
|
"permission-changed": "L'autorizzazione di Music Together è cambiata in {{permission}}",
|
||||||
|
"remove-song-failed": "Impossibile rimuovere il brano",
|
||||||
|
"user-connected": "{{name}} si è unito a Music Together",
|
||||||
|
"user-disconnected": "{{name}} ha lasciato Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Frecce di navigazione Avanti/Indietro integrate direttamente nell'interfaccia, come nel tuo browser preferito",
|
||||||
|
"name": "Navigazione"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Rimuovi i pulsanti di accesso e i link di Google dall'interfaccia",
|
||||||
|
"name": "Nessun login di Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Mostra una notifica quando viene riprodotto un brano (le notifiche interattive sono disponibili su Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Notifiche interattive",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Impostazioni dell'interazione",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Nascondi il testo del pulsante",
|
||||||
|
"refresh-on-play-pause": "Refresh quando si preme Riproduci/Pausa",
|
||||||
|
"tray-controls": "Apri/chiudi cliccando l'icona nel tray"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Priorità di notifica",
|
||||||
|
"toast-style": "Stile Toast",
|
||||||
|
"unpause-notification": "Mostra notifica quando riprendi ascolto"
|
||||||
|
},
|
||||||
|
"name": "Notifiche"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Consente di far passare l'app alla modalità Picture-in-Picture",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Sempre in primo piano",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Hotkey",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Hotkey"
|
||||||
|
},
|
||||||
|
"label": "Scegliere un'hotkey per attivare Picture-in-picture",
|
||||||
|
"title": "Picture-in-picture Hotkey"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Salva la posizione della finestra",
|
||||||
|
"save-window-size": "Salva la dimensione della finestra",
|
||||||
|
"use-native-pip": "Usa il PiP nativo del browser"
|
||||||
|
},
|
||||||
|
"name": "Picture-in-Picture",
|
||||||
|
"templates": {
|
||||||
|
"button": "Picture-in-Picture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Ascolto veloce, ascolto lento! Aggiunge un cursore che controlla la velocità di riproduzione del brano",
|
||||||
|
"name": "Velocità riproduzione",
|
||||||
|
"templates": {
|
||||||
|
"button": "Velocità"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Controlla con precisione il volume utilizzando la rotella del mouse, le hotkey o i tasti freccia e usando incrementi di volume personalizzabili",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Controlla con i tasti freccia",
|
||||||
|
"custom-volume-steps": "Imposta incrementi di volume personalizzati",
|
||||||
|
"global-shortcuts": "Hotkey globali"
|
||||||
|
},
|
||||||
|
"name": "Volume preciso",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Diminuisci volume",
|
||||||
|
"increase": "Aumenta volume"
|
||||||
|
},
|
||||||
|
"label": "Scegli i tasti di scelta rapida regolazione volume:",
|
||||||
|
"title": "Tasti di scelta rapida regolazione volume"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Seleziona l'incremento/decremento del volume",
|
||||||
|
"title": "Incrementi volume"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Qualità attuale: {{quality}}",
|
||||||
|
"message": "Qualità Video:",
|
||||||
|
"title": "Scegli la qualità video"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Permette di cambiare la qualità del video con un pulsante in sovrimpressione",
|
||||||
|
"name": "Cambia qualità video"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Aggiunge il supporto per lo scrobbling (Last.fm, Listenbrainz ecc.)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Impostazione Last.fm API"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Inserire il token utente per ListenBrainz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "API key per Last.fm",
|
||||||
|
"api-secret": "API secret per Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Inserisci il tuo token utente ListenBrainz:",
|
||||||
|
"title": "Token ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Consente di impostare tasti di scelta rapida globali per la riproduzione (riproduci/pausa/successivo/precedente) + disabilita l'OSD multimediale sovrascrivendo i tasti multimediali + abilita Ctrl/CMD + F per la ricerca + abilita il supporto Linux MPRIS per i tasti multimediali + tasti di scelta rapida personalizzati per utenti avanzati",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Ridefinisci i tasti multimediali",
|
||||||
|
"set-keybinds": "Imposta i controlli brano globali"
|
||||||
|
},
|
||||||
|
"name": "Scorciatoie (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Prossimo",
|
||||||
|
"play-pause": "Riproduci / Pausa",
|
||||||
|
"previous": "Precedente"
|
||||||
|
},
|
||||||
|
"label": "Scegli combinazioni di tasti per il controllo dei brani:",
|
||||||
|
"title": "Combinazioni di tasti"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Salta i brani che non ti piacciono",
|
||||||
|
"name": "Salta i brani che non ti piacciono"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Salta automaticamente le parti silenziose nei brani",
|
||||||
|
"name": "Salta silenzi"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Salta automaticamente le parti non musicali, come l'intro/outro delle canzoni o le parti dei video musicali in cui non viene riprodotto il brano",
|
||||||
|
"name": "Blocco sponsor"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Controlla riproduzione dalla taskbar di Windows",
|
||||||
|
"name": "Controlli multimediali sulla taskbar"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Aggiunge un widget TouchBar per gli utenti macOS",
|
||||||
|
"name": "Touch Bar (per MacOS)"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integrazione con il plugin OBS Tuna",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Aggiunge un pulsante per passare dalla modalità Video a quella Brano. Può anche rimuovere l'intera scheda Brano/Video",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Allineamento",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Sinistra",
|
||||||
|
"middle": "Centro",
|
||||||
|
"right": "Destra"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Rimuovi la scheda Brano/Video",
|
||||||
|
"mode": {
|
||||||
|
"label": "Modalità",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Brano/Video personalizzato",
|
||||||
|
"disabled": "Disattivato",
|
||||||
|
"native": "Brano/Video nativo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Selettore Brano/Video",
|
||||||
|
"templates": {
|
||||||
|
"button": "Brano"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Sostituisce al Video un visualizzatore grafico",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Tipo di visualizzazione"
|
||||||
|
},
|
||||||
|
"name": "Visualizzatore grafico"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"label": "プロキシ",
|
"label": "プロキシ",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"label": "プロキシのアドレスを入力: (空にすると無効化)",
|
"label": "プロキシのアドレスを入力: (空にすると無効化)",
|
||||||
"placeholder": "例: socks5://127.0.0.1:9999",
|
"placeholder": "例: SOCKS5://127.0.0.1:9999",
|
||||||
"title": "プロキシ"
|
"title": "プロキシ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -170,7 +170,8 @@
|
|||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"enabled": "有効",
|
"enabled": "有効",
|
||||||
"label": "プラグイン"
|
"label": "プラグイン",
|
||||||
|
"new": "新着"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"label": "表示",
|
"label": "表示",
|
||||||
@ -190,7 +191,11 @@
|
|||||||
"previous": "前の曲",
|
"previous": "前の曲",
|
||||||
"quit": "終了",
|
"quit": "終了",
|
||||||
"restart": "アプリを再起動",
|
"restart": "アプリを再起動",
|
||||||
"show": "ウィンドウを表示"
|
"show": "ウィンドウを表示",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube ミュージック",
|
||||||
|
"with-song-info": "YouTube ミュージック: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
@ -199,14 +204,26 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"blocker": "ブロッカー"
|
"blocker": "ブロッカー"
|
||||||
},
|
},
|
||||||
"name": "Adblocker"
|
"name": "広告ブロッカー"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "「Undislike(嫌いではない)」「Dislike(嫌い)」「Like(好き)」「Unlike(好きではない)」ボタンを追加し、プレイリストやアルバム内のすべての曲にこれらを適用します",
|
||||||
|
"name": "アルバムアクション"
|
||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "アルバムカバーの色をベースにして動的テーマと視覚効果を適用します",
|
"description": "アルバムカバーの色をベースにして動的テーマと視覚効果を適用します",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "カラー混合比率",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "アルバムカラーベースのテーマ"
|
"name": "アルバムカラーベースのテーマ"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
"description": "動画の間接照明を画面背景に投射します。",
|
"description": "動画の内容に合った淡い色に画面の背景を変化させるライティング効果を適応します",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
"label": "ぼかしの強さ",
|
"label": "ぼかしの強さ",
|
||||||
@ -239,9 +256,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"smoothness-transition": {
|
"smoothness-transition": {
|
||||||
"label": "スムーズな切り替えり",
|
"label": "スムーズな切り替え",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"during": "{{interpolationTime}}秒間切り替えり"
|
"during": "{{interpolationTime}}秒間切り替え"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"use-fullscreen": {
|
"use-fullscreen": {
|
||||||
@ -289,7 +306,7 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"advanced": "詳細設定"
|
"advanced": "詳細設定"
|
||||||
},
|
},
|
||||||
"name": "クロスフェード[ベータ]",
|
"name": "クロスフェード [ベータ]",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"options": {
|
"options": {
|
||||||
"multi-input": {
|
"multi-input": {
|
||||||
@ -408,10 +425,6 @@
|
|||||||
},
|
},
|
||||||
"name": "アプリ内メニュー"
|
"name": "アプリ内メニュー"
|
||||||
},
|
},
|
||||||
"last-fm": {
|
|
||||||
"description": "Last.fmのscrobblingサポートを追加",
|
|
||||||
"name": "Last.fm"
|
|
||||||
},
|
|
||||||
"lumiastream": {
|
"lumiastream": {
|
||||||
"description": "Lumia Streamのサポートを追加",
|
"description": "Lumia Streamのサポートを追加",
|
||||||
"name": "Lumia Stream [ベータ]"
|
"name": "Lumia Stream [ベータ]"
|
||||||
@ -426,6 +439,52 @@
|
|||||||
"fetched-lyrics": "Geniusから歌詞取得完了"
|
"fetched-lyrics": "Geniusから歌詞取得完了"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "プレイリストを他の人と共有します。 ホストが曲を再生すると、他の全員にも同じ曲が聞こえます",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "ホストIDを入力"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "保存",
|
||||||
|
"track-source": "トラックソース",
|
||||||
|
"unknown-user": "不明なユーザー"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "ホストIDをコピー",
|
||||||
|
"close": "一緒に音楽を閉じる",
|
||||||
|
"connected-users": "接続されているユーザー",
|
||||||
|
"disconnect": "一緒に音楽を切断する",
|
||||||
|
"empty-user": "接続中のユーザーはいません",
|
||||||
|
"host": "Music Together ホスト",
|
||||||
|
"join": "一緒に音楽に参加",
|
||||||
|
"permission": {
|
||||||
|
"all": "ゲストがプレイリストとプレーヤーを制御できるようにする",
|
||||||
|
"host-only": "ホストのみがプレイリストとプレーヤーを制御できます",
|
||||||
|
"playlist": "ゲストによるプレイリストの制御を許可する"
|
||||||
|
},
|
||||||
|
"set-permission": "制御権限を変更",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "切断されました",
|
||||||
|
"guest": "ゲストとして接続しました",
|
||||||
|
"host": "ホストとして接続されています"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "一緒に音楽 [ベータ版]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "曲の追加に失敗しました",
|
||||||
|
"closed": "一緒に音楽が閉じられました",
|
||||||
|
"disconnected": "一緒に音楽が切断されました",
|
||||||
|
"host-failed": "一緒に音楽のホストに失敗しました",
|
||||||
|
"id-copied": "ホストIDがクリップボードにコピーされました",
|
||||||
|
"id-copy-failed": "ホストIDをクリップボードにコピー出来ませんでした",
|
||||||
|
"join-failed": "一緒に音楽に参加出来ませんでした",
|
||||||
|
"joined": "一緒に音楽に参加しました",
|
||||||
|
"permission-changed": "一緒に音楽の権限が \"{{permission}}\" に変更されました",
|
||||||
|
"remove-song-failed": "曲の削除に失敗しました",
|
||||||
|
"user-connected": "{{name}} が一緒に音楽に参加しました",
|
||||||
|
"user-disconnected": "{{name}} が一緒に音楽を退出しました"
|
||||||
|
}
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"description": "ブラウザの戻る・進むボタンのようにUIからコントロールできるボタン",
|
"description": "ブラウザの戻る・進むボタンのようにUIからコントロールできるボタン",
|
||||||
"name": "ナビゲーション"
|
"name": "ナビゲーション"
|
||||||
@ -518,8 +577,33 @@
|
|||||||
"description": "ビデオオーバーレイのボタンを使用してビデオ品質を変更できるようにします",
|
"description": "ビデオオーバーレイのボタンを使用してビデオ品質を変更できるようにします",
|
||||||
"name": "ビデオ品質チェンジャー"
|
"name": "ビデオ品質チェンジャー"
|
||||||
},
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "スクロブリング対応を追加します(例:last.fm、Listenbrainzなど)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API 設定"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "ListenBrainzユーザートークンを入力してください"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "他のメディアをScrobbleする"
|
||||||
|
},
|
||||||
|
"name": "スクロブラー",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm APIキー",
|
||||||
|
"api-secret": "Last.fm API シークレット"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "ListenBrainzのユーザートークンを入力してください:",
|
||||||
|
"title": "ListenBrainzトークン"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"description": "再生用のグローバル ホットキー (再生/一時停止/次/前) の設定 + メディア キーをオーバーライドしてメディア OSD を無効にする + Ctrl/CMD + F による検索を有効にする + メディアキーの Linux mpris サポートを有効にする + 上級ユーザー向けのカスタム ホットキー を可能にします",
|
"description": "再生用のグローバル ホットキー (再生/一時停止/次/前) の設定、メディア キーをオーバーライドしてメディア OSD を無効にする、Ctrl/CMD + F による検索を有効にする、 メディアキーの Linux mpris サポートを有効にする、 上級ユーザー向けのカスタム ホットキー を可能にします",
|
||||||
"menu": {
|
"menu": {
|
||||||
"override-media-keys": "メディアキーを上書き",
|
"override-media-keys": "メディアキーを上書き",
|
||||||
"set-keybinds": "グローバルソングコントロールを設定する"
|
"set-keybinds": "グローバルソングコントロールを設定する"
|
||||||
@ -537,6 +621,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "低評価と表示された曲をスキップします",
|
||||||
|
"name": "低評価曲をスキップ"
|
||||||
|
},
|
||||||
"skip-silences": {
|
"skip-silences": {
|
||||||
"description": "曲の無音区間を自動でスキップ",
|
"description": "曲の無音区間を自動でスキップ",
|
||||||
"name": "無音区間をスキップ"
|
"name": "無音区間をスキップ"
|
||||||
@ -547,7 +635,7 @@
|
|||||||
},
|
},
|
||||||
"taskbar-mediacontrol": {
|
"taskbar-mediacontrol": {
|
||||||
"description": "Windowsタスクバーから再生をコントロール",
|
"description": "Windowsタスクバーから再生をコントロール",
|
||||||
"name": "Taskbar Media Control"
|
"name": "タスクバーメディアコントロール"
|
||||||
},
|
},
|
||||||
"touchbar": {
|
"touchbar": {
|
||||||
"description": "masOSユーザー向けにTouchBarウィジェットを追加",
|
"description": "masOSユーザー向けにTouchBarウィジェットを追加",
|
||||||
|
|||||||
@ -105,7 +105,7 @@
|
|||||||
"label": "프록시 설정",
|
"label": "프록시 설정",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"label": "프록시 주소를 입력하세요: (비워두면 비활성화됨)",
|
"label": "프록시 주소를 입력하세요: (비워두면 비활성화됨)",
|
||||||
"placeholder": "예제: socks5://127.0.0.1:9999",
|
"placeholder": "예제: SOCKS5://127.0.0.1:9999",
|
||||||
"title": "프록시 설정"
|
"title": "프록시 설정"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -170,7 +170,8 @@
|
|||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"enabled": "활성화",
|
"enabled": "활성화",
|
||||||
"label": "확장"
|
"label": "확장",
|
||||||
|
"new": "NEW"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"label": "보기",
|
"label": "보기",
|
||||||
@ -190,23 +191,39 @@
|
|||||||
"previous": "이전",
|
"previous": "이전",
|
||||||
"quit": "종료",
|
"quit": "종료",
|
||||||
"restart": "앱 재시작",
|
"restart": "앱 재시작",
|
||||||
"show": "창 표시"
|
"show": "창 표시",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "유튜브 뮤직",
|
||||||
|
"with-song-info": "유튜브 뮤직: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"adblocker": {
|
"adblocker": {
|
||||||
"description": "모든 광고와 트래커를 즉시 차단합니다",
|
"description": "모든 광고와 트래커를 즉시 차단합니다",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blocker": "애드블록 타입"
|
"blocker": "광고 차단 타입"
|
||||||
},
|
},
|
||||||
"name": "애드블록"
|
"name": "광고 차단기"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "좋아요, 싫어요 버튼을 추가하고, 결과를 재생 목록 또는 앨범의 모든 노래에 적용합니다",
|
||||||
|
"name": "앨범 액션"
|
||||||
},
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "앨범 색상 팔레트를 기반으로 동적 테마 및 시각 효과를 적용합니다",
|
"description": "앨범 색상 팔레트를 기반으로 동적 테마 및 시각 효과를 적용합니다",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "색상 혼합 비율",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "앨범 컬러 기반 테마"
|
"name": "앨범 컬러 기반 테마"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
"description": "영상의 간접 조명을 화면 배경에 투사합니다.",
|
"description": "영상의 간접 조명을 화면 배경에 투사합니다",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
"label": "흐림 효과 강도",
|
"label": "흐림 효과 강도",
|
||||||
@ -408,10 +425,6 @@
|
|||||||
},
|
},
|
||||||
"name": "인앱 메뉴"
|
"name": "인앱 메뉴"
|
||||||
},
|
},
|
||||||
"last-fm": {
|
|
||||||
"description": "Last.fm에 대한 스크러블 지원을 추가합니다",
|
|
||||||
"name": "Last.fm"
|
|
||||||
},
|
|
||||||
"lumiastream": {
|
"lumiastream": {
|
||||||
"description": "Lumia Stream 지원을 추가합니다",
|
"description": "Lumia Stream 지원을 추가합니다",
|
||||||
"name": "Lumia Stream [베타]"
|
"name": "Lumia Stream [베타]"
|
||||||
@ -426,6 +439,52 @@
|
|||||||
"fetched-lyrics": "Genius에서 가사 불러옴"
|
"fetched-lyrics": "Genius에서 가사 불러옴"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "여러명과 함께 플레이리스트를 공유합니다. 호스트가 음악을 재생하면, 다른 사용자들도 같은 노래를 들을 수 있습니다",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "호스트 아이디를 입력하세요"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "저장",
|
||||||
|
"track-source": "재생 중인 트랙 출처",
|
||||||
|
"unknown-user": "알 수 없는 사용자"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "호스트 아이디 복사",
|
||||||
|
"close": "Music Together 닫기",
|
||||||
|
"connected-users": "연결된 사용자",
|
||||||
|
"disconnect": "Music Together 연결 끊기",
|
||||||
|
"empty-user": "연결된 사용자 없음",
|
||||||
|
"host": "Music Together 호스트",
|
||||||
|
"join": "Music Together 참여",
|
||||||
|
"permission": {
|
||||||
|
"all": "게스트가 모두 제어 가능",
|
||||||
|
"host-only": "호스트만 제어 가능",
|
||||||
|
"playlist": "게스트가 재생목록 제어 가능"
|
||||||
|
},
|
||||||
|
"set-permission": "제어 권한 변경",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "연결 끊김",
|
||||||
|
"guest": "게스트로 연결됨",
|
||||||
|
"host": "호스트로 연결됨"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [베타]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "노래 추가 실패",
|
||||||
|
"closed": "Music Together가 닫혔습니다",
|
||||||
|
"disconnected": "Music Together 연결이 끊어졌습니다",
|
||||||
|
"host-failed": "Music Together를 열 수 없습니다",
|
||||||
|
"id-copied": "호스트 아이디가 클립보드에 복사되었습니다",
|
||||||
|
"id-copy-failed": "호스트 ID를 클립보드에 복사하지 못했습니다",
|
||||||
|
"join-failed": "Music Together에 참여할 수 없습니다",
|
||||||
|
"joined": "Music Together에 참여했습니다",
|
||||||
|
"permission-changed": "Music Together 제어 권한이 \"{{permission}}\"(으)로 변경되었습니다",
|
||||||
|
"remove-song-failed": "노래 제거 실패",
|
||||||
|
"user-connected": "{{name}}님이 Music Together에 참여했습니다",
|
||||||
|
"user-disconnected": "{{name}}님이 Music Together에서 나갔습니다"
|
||||||
|
}
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"description": "브라우저에서처럼, UI에 직접 통합된 앞으로/뒤로 탐색하는 화살표",
|
"description": "브라우저에서처럼, UI에 직접 통합된 앞으로/뒤로 탐색하는 화살표",
|
||||||
"name": "탐색"
|
"name": "탐색"
|
||||||
@ -518,8 +577,33 @@
|
|||||||
"description": "영상 오버레이의 버튼으로 영상 품질을 변경할 수 있습니다",
|
"description": "영상 오버레이의 버튼으로 영상 품질을 변경할 수 있습니다",
|
||||||
"name": "영상 품질 체인저"
|
"name": "영상 품질 체인저"
|
||||||
},
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "스크로블링 지원을 추가합니다 (예: last.fm, Listenbrainz)",
|
||||||
|
"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 API 비밀 키"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "ListenBrainz 유저 토큰을 입력하세요:",
|
||||||
|
"title": "ListenBrainz 토큰"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"description": "재생을 위한 전역 단축키 설정 허용 (재생/일시 정지/다음/이전) + 미디어 키를 재정의하여 미디어 OSD 비활성화 + Ctrl/CMD + F 검색 활성화 + 미디어 키에 대한 리눅스 MPRIS 지원 활성화 + 고급 사용자를 위한 사용자 지정 단축키 지원",
|
"description": "재생을 위한 전역 단축키 설정 허용 (재생/일시 정지/다음/이전), 미디어 키를 재정의하여 미디어 OSD 비활성화, Ctrl/CMD + F 검색을 활성화, 미디어키 지원을 위해 리눅스 MPRIS 지원 활성화, 고급 사용자를 위한 사용자 지정 단축키 지원 추가",
|
||||||
"menu": {
|
"menu": {
|
||||||
"override-media-keys": "미디어 키 재정의",
|
"override-media-keys": "미디어 키 재정의",
|
||||||
"set-keybinds": "전역 노래 제어 설정"
|
"set-keybinds": "전역 노래 제어 설정"
|
||||||
@ -537,6 +621,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "싫어요 표시된 노래를 건너뜁니다",
|
||||||
|
"name": "싫어요 표시 노래 건너뛰기"
|
||||||
|
},
|
||||||
"skip-silences": {
|
"skip-silences": {
|
||||||
"description": "노래의 무음 부분을 자동으로 건너뜁니다",
|
"description": "노래의 무음 부분을 자동으로 건너뜁니다",
|
||||||
"name": "무음 건너뛰기"
|
"name": "무음 건너뛰기"
|
||||||
|
|||||||
594
src/i18n/resources/lt.json
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Nepavyko įvykdyti įskiepio {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Įskiepis {{pluginName}}::{{contextName}} įvykdytas per {{ms}}ms",
|
||||||
|
"initialize-failed": "Nepavyko inicijuoti įskiepio \"{{pluginName}}\"",
|
||||||
|
"load-all": "Kraunama visus įskiepius",
|
||||||
|
"load-failed": "Nepavyko užkrauti įskiepio \"{{pluginName}}\"",
|
||||||
|
"loaded": "Įskiepis \"{{pluginName}}\" užkrautas",
|
||||||
|
"unload-failed": "Nepavyko iškrauti įskiepio \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Įskiepis \"{{pluginName}}\" iškrautas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "lt",
|
||||||
|
"local-name": "Lietuvių kalba",
|
||||||
|
"name": "Lithuanian"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Baigta krauti. \"DevTools\" atidaryta"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "\"i18n\" užkrauta"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Gauta komanda per protokolą: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS failas \"{{cssFile}}\" neegzistuoja, ignoruojama"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Nereguojanti paklaida\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Išvaloma programos talpykla"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Langas bandė vaizduotis už ekrano ribų, langoDydis={{windowSize}}, ekranoDydis={{displaySize}}, pozicija={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Meniu yra paslėpta, naudokite 'Alt', kad ją parodyti (arba 'Escape' jei naudojama programos meniu)",
|
||||||
|
"message": "\"Paslėpti Meniu\" yra įjungta",
|
||||||
|
"title": "Įjungta \"Paslėpti Meniu\""
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Vėliau",
|
||||||
|
"restart-now": "Perkrauti Dabar"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" įskiepis reikalauja perkrovimą, kad veiktų",
|
||||||
|
"message": "\"{{pluginName}}\" reikia perkrovimo",
|
||||||
|
"title": "Reikiamas perkrovimas"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Išeiti",
|
||||||
|
"relaunch": "Perleisti",
|
||||||
|
"wait": "Palaukti"
|
||||||
|
},
|
||||||
|
"detail": "Mes apgailestaujame dėl nepatogumų! prašau pasirinkti ką daryti:",
|
||||||
|
"message": "Programa Neatsako",
|
||||||
|
"title": "Langas Neatsako"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Išjungti Atnaujinimus",
|
||||||
|
"download": "Atsisiųsti",
|
||||||
|
"ok": "Gerai"
|
||||||
|
},
|
||||||
|
"detail": "Nauja versija yra prieinama ir gali būti atsisiųsta {{downloadLink}}",
|
||||||
|
"message": "Nauja versija yra prieinama",
|
||||||
|
"title": "Prieinamas Atnaujinimas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Apie",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigacija",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Nukopijuoti dabartinį URL",
|
||||||
|
"go-back": "Grįžti Atgal",
|
||||||
|
"go-forward": "Eiti į priekį",
|
||||||
|
"quit": "Išeiti",
|
||||||
|
"restart": "Perkrauti programą"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Nustatymai",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Išplėstiniai nustatymai",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Perkrauti programos talpyklą, kai programa paleidžiama",
|
||||||
|
"disable-hardware-acceleration": "Išjungti aparatūros pagreitį",
|
||||||
|
"edit-config-json": "Redaguoti config.json",
|
||||||
|
"override-user-agent": "Perrašyti \"User-Agent\"",
|
||||||
|
"restart-on-config-changes": "Perkrauti po config pasikeitimo",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Nustatyti įgaliotajį serverį",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Įvesti Įgaliotojo serverio adresą: (palikti tuščią, kad išjungti)",
|
||||||
|
"placeholder": "Pavyzdys: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Nustatyti įgaliotajį serverį"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Įjungti/Išjungti DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Visada viršuje",
|
||||||
|
"auto-update": "Automatinis Atnaujinimas",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Meniu bus paslėpta per kitą paleidimą, naudokite [Alt], kad ją parodyti (arba kairinio kirčio ženklą [`] jei naudojama programos meniu)",
|
||||||
|
"title": "\"Paslėpti Meniu\" įjungtas"
|
||||||
|
},
|
||||||
|
"label": "Paslėpti Meniu"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Kalba bus pakeista po perkrovimo",
|
||||||
|
"title": "Kalba Pakeista"
|
||||||
|
},
|
||||||
|
"label": "Kalba",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Norite padėti išversti? Paspauskite čia"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Tęsti paskutinę dainą, kai programa bus paleista",
|
||||||
|
"single-instance-lock": "Vienkartinis užraktas",
|
||||||
|
"start-at-login": "Pradėti nuo prisijungimo",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Pradžios puslapis",
|
||||||
|
"unset": "Nenustatyta"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Padėklas",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Išjungta",
|
||||||
|
"enabled-and-hide-app": "Įjungta ir slėpti programos langą",
|
||||||
|
"enabled-and-show-app": "Įjungta ir rodyti programos langą",
|
||||||
|
"play-pause-on-click": "Paleisti/Pristabdyti ant paspaudimo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Vizualiniai patobulinimai",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Numatytasis",
|
||||||
|
"force-show": "Priversti rodyti",
|
||||||
|
"hide": "Slėpti",
|
||||||
|
"label": "\"Patinka\" mygtukai"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Nerodyti \"Patobulinti\" mygtuko",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Įkelti pasirinktinį CSS failą",
|
||||||
|
"no-theme": "Be temos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Įjungta",
|
||||||
|
"label": "Įskiepiai"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Vaizdas",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Priverstinai perkrauti",
|
||||||
|
"reload": "Perkrauti",
|
||||||
|
"reset-zoom": "Tikras dydis",
|
||||||
|
"toggle-fullscreen": "Įjungti/Išjungti Pilną Ekraną",
|
||||||
|
"zoom-in": "Priartinti",
|
||||||
|
"zoom-out": "Nutolinti"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Kitas",
|
||||||
|
"play-pause": "Paleisti/Pristabdyti",
|
||||||
|
"previous": "Ankstesnis",
|
||||||
|
"quit": "Išeiti",
|
||||||
|
"restart": "Perkrauti programą",
|
||||||
|
"show": "Rodyti langą"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Blokuoti visas reklamas ir seklius",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Blokuotojas"
|
||||||
|
},
|
||||||
|
"name": "Reklamų blokuotojas"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Pritaiko dinamišką temą ir vizualinius efektus pagal albumo spalvų paletę",
|
||||||
|
"name": "Albumo Spalvų Tema"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Pritaiko apšvietimo efektą, perteikdamas švelnias vaizdo įrašo spalvas į ekrano foną.",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Suliejimo kiekis",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pikseliai"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buferis",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Skaidrumas",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Kokybė",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} pikseliai"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Dydis",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Perliejimo švelnumas",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Per {{interpolationTime}}s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Naudojamas visas ekranas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Aplinkos rėžimas"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Pritaikyti garso kompresiją (sumažina garsiausių signalo dalių garsumą ir padidina švelniausių dalių garsumą)",
|
||||||
|
"name": "Garso Kompresorius"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Padaro navigacijos lentą permatomą ir susiliejusią",
|
||||||
|
"name": "Sulieti Navigacijos Lentą"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Apeiti \"Youtube\" amžiaus patikrinimą",
|
||||||
|
"name": "Apeiti Amžiaus Apribojimus"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "„YouTube Music“ Garso takelių antraščių parinkiklis",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Automatiškai pasirinkti paskutinę naudotą antraštę",
|
||||||
|
"disable-captions": "Pagal numatytuosius nustatymus išjungti antraštės"
|
||||||
|
},
|
||||||
|
"name": "Antraščių parinkiklis",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Dabartinė antraščių kalba: {{language}}",
|
||||||
|
"none": "Joks",
|
||||||
|
"title": "Pasirinkti antraščių kalbą"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Atidaryti antraščių parinkiklį"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Visada nustatyti šoninę juostą kompaktiniame rėžime",
|
||||||
|
"name": "Kompaktinė šoninė juosta"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Perliejimas tarp dainų",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Išplėstinė"
|
||||||
|
},
|
||||||
|
"name": "Perliejimas [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Išblukimo trukmė (ms)",
|
||||||
|
"fade-out-duration": "Išnykimo trukmė (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Išblukimo stiprumas",
|
||||||
|
"linear": "Linijinis",
|
||||||
|
"logarithmic": "Logaritminis"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Pradėti lieti dainas N sekundžių prieš pabaigą"
|
||||||
|
},
|
||||||
|
"title": "Perliejimo nustatymai"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Pradeda dainą pristabdytame rėžime",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Pritaiko tik per programos paleidimą"
|
||||||
|
},
|
||||||
|
"name": "Išjungti Automatinį leidimą"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Bandyta prisijungti naudojant aktyvų ryšį",
|
||||||
|
"connected": "Prisijungta prie \"Discord\"",
|
||||||
|
"disconnected": "Atsijungta nuo \"Discord\""
|
||||||
|
},
|
||||||
|
"description": "Parodyk savo draugams ko tu klausaisi su \"Turtingas Buvimas\" (Rich Presence)",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Automatiškai prisijungti",
|
||||||
|
"clear-activity": "Išvalyti veiklą",
|
||||||
|
"clear-activity-after-timeout": "Išvalyti veiklą po skirtojo laiko",
|
||||||
|
"connected": "Prisijungta",
|
||||||
|
"disconnected": "Atsijungta",
|
||||||
|
"hide-duration-left": "Slėpti kiek liko laiko",
|
||||||
|
"hide-github-button": "Slėpti \"GitHub\" nuorodos mygtuką",
|
||||||
|
"play-on-youtube-music": "Leisti ant \"Youtube Music\"",
|
||||||
|
"set-inactivity-timeout": "Nustatyti neveiklumo laiką"
|
||||||
|
},
|
||||||
|
"name": "\"Discord\" Turtingas Buvimas (Rich Presence)",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Įveskite neveiklumo skirtąjį laiką sekundėmis:",
|
||||||
|
"title": "Nustatyti neveiklumo laiką"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Gerai"
|
||||||
|
},
|
||||||
|
"message": "Aaa! Apgailestaujame, nepavyko atsisiųsti…",
|
||||||
|
"title": "Paklaida atsisiunčiant!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Gerai"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} dainos)",
|
||||||
|
"message": "Atsisiunčiama {{playlistTitle}} grojaraštį",
|
||||||
|
"title": "Prasidėjo atsisiuntimas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Konversija: {{percent}}%",
|
||||||
|
"converting": "Konvertuojama…",
|
||||||
|
"done": "Baigta: {{filePath}}",
|
||||||
|
"download-info": "Atsiunčiama {{artist}} - {{title}} {{videoId}}",
|
||||||
|
"download-progress": "Atsisiuntimas: {{percent}}%",
|
||||||
|
"downloading": "Atsisiunčiama…",
|
||||||
|
"downloading-counter": "Atsisiunčiama {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Atsisiunčiamas grojaraštis \"{{playlistTitle}}\" - {{playlistSize}} dainų {{playlistId}}",
|
||||||
|
"error-while-downloading": "Paklaida atsisiunčiant \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "Aplankas {{playlistFolder}} jau egzistuoja",
|
||||||
|
"getting-playlist-info": "Gaunama grojaraščio informacija…",
|
||||||
|
"loading": "Kraunama…",
|
||||||
|
"playlist-has-only-one-song": "Grojaraštis turi tik vieną elementą, jis atsisiunčiamas tiesiogiai",
|
||||||
|
"playlist-id-not-found": "Grojaraščio ID nerastas",
|
||||||
|
"playlist-is-empty": "Grojaraštis yra tuščias",
|
||||||
|
"playlist-is-mix-or-private": "Paklaida gaunant grojaraščio informaciją: Pasitikrink, kad jis nėra privatus ar \"Surinkta specialiai jums\" grojaraštis\n\n{{error}}",
|
||||||
|
"preparing-file": "Failas paruošiamas…",
|
||||||
|
"saving": "Išsaugojama…",
|
||||||
|
"trying-to-get-playlist-id": "Bandoma gauti grojaraščio ID: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Vaizdo įrašas nerastas",
|
||||||
|
"writing-id3": "Rašoma ID3 žymes…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Atsisiunčia MP3 / šaltinio garsą tiesiogiai iš sąsajos",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Pasirinkti atsisiuntimų aplanką",
|
||||||
|
"download-playlist": "Atsisiųsti grojaraštį",
|
||||||
|
"presets": "Iš anksto nustatyti nustatymai",
|
||||||
|
"skip-existing": "Praleisti egzistuojančius failus"
|
||||||
|
},
|
||||||
|
"name": "Atsiuntėjas",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Nepavyko atnaujinti eigos"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Atsisiųsti"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Padaro garsumo slankiklį eksponentinį, kad būtų lengviau pasirinkti mažesnį garsumą.",
|
||||||
|
"name": "Eksponentinis garsas"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Duoda meniu lentoms įmantrią, tamsią ar albumo spalvos išvaizdą",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Slėpti DOM lango kontroles"
|
||||||
|
},
|
||||||
|
"name": "Programos Meniu"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Prideda \"Lumia Stream\" palaikymą",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Prideda daugumai dainių žodžių tekstus",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Romanizuoti dainų tekstai"
|
||||||
|
},
|
||||||
|
"name": "\"Genius\" Žodžių tekstai",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Gauti žodžiai iš „Genius“"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Kitas/Ankstenis navigacijos rodyklės tiesiogiai integruotos sąsajoje, kaip tavo mėgstamiausioje naršyklėje",
|
||||||
|
"name": "Navigacija"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Pašalinti \"Google\" prisijungimo mygtukus ir nuorodas iš sąsjos",
|
||||||
|
"name": "Be \"Google\" Prisijungimo"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Rodyti pranešimą, kai pradeda groti daina (interaktyvūs pranešimai pasiekiami sistemoje \"Windows\")",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Interaktyvūs pranešimai",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Interaktyvūs nustatymai",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Paslėpti mygtuko tekstą",
|
||||||
|
"refresh-on-play-pause": "Atnaujinti ant Paleidimo/Pristabdymo",
|
||||||
|
"tray-controls": "Atidaryti/Uždaryti ant padėklo paspaudimo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Pranešimų pirminybė",
|
||||||
|
"toast-style": "Skrudintas stilius",
|
||||||
|
"unpause-notification": "Rodyti pranešimus po dainos paleidimo"
|
||||||
|
},
|
||||||
|
"name": "Pranešimai"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Leidžia pakeisti programą į \"picture-in-picture\" rėžimą",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Visada ant viršaus",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Spartusis klavišas",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Spartusis klavišas"
|
||||||
|
},
|
||||||
|
"label": "Pasirinkti spartujį klaviša, kad įjungti/išjungti \"picture-in-picture\"",
|
||||||
|
"title": "\"Picture-in-picture\" Spartusis klavišas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Išsaugoti lango poziciją",
|
||||||
|
"save-window-size": "Išsaugoti lango dydį",
|
||||||
|
"use-native-pip": "Naudoti naršyklės savajį PiP"
|
||||||
|
},
|
||||||
|
"name": "Picture-in-picture",
|
||||||
|
"templates": {
|
||||||
|
"button": "Picture-in-picture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Klausyk greitai, klausyk lėtai! Prideda slankiklį, kuris valdo dainos greitį",
|
||||||
|
"name": "Atkūrimo Greitis",
|
||||||
|
"templates": {
|
||||||
|
"button": "Greitis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Tiksliai valdykite garsumą naudodami pelės ratuką / sparčiuosius klavišus, naudodami pritaikytą HUD ir pritaikomus garsumo žingsnius",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Vietiniai rodyklių klavišai valdikliai",
|
||||||
|
"custom-volume-steps": "Nustatykite Pasirinktinius Garsumo Žingsnius",
|
||||||
|
"global-shortcuts": "Pasauliniai spartieji klavišai"
|
||||||
|
},
|
||||||
|
"name": "Tikslus Garsas",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Pamažinti Garsą",
|
||||||
|
"increase": "Padidinti Garsą"
|
||||||
|
},
|
||||||
|
"label": "Pasirinkti Pasaulinius garso klavišus:",
|
||||||
|
"title": "Pasauliniai Garso Klavišai"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Pasirinkti Garso Didinimo/Mažinimo Žingsnius",
|
||||||
|
"title": "Garso Žingsniai"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Dabartinė Kokybė: {{quality}}",
|
||||||
|
"message": "Pasirinkite Vaizdo Kokybę:",
|
||||||
|
"title": "Pasirinkite Vaizdo Kokybę"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Leidžia pakeisti vaizdo kokybę su mygtuku ant vaizdo perdangos",
|
||||||
|
"name": "Vaizdo Kokybės Pakeitėjas"
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Leidžia nustatyti visuotinius atkūrimo sparčiuosius klavišus (paleisti / pristabdyti / kitą / ankstesnį) ir išjungti medijos OSD nepaisant medijos klavišų, įjungti Ctrl / CMD + F ieškoti, įjungti Linux MPRIS palaikymą medijos klavišams ir pasirinktinius sparčiuosius klavišus pažengusiems vartotojams.",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Perrašyti Medijos klavišus",
|
||||||
|
"set-keybinds": "Nustatyti Pasaulines Dainų Kontroles"
|
||||||
|
},
|
||||||
|
"name": "Spartieji klavišai (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Kitas",
|
||||||
|
"play-pause": "Paleisti / Pristabdyti",
|
||||||
|
"previous": "Ankstesnis"
|
||||||
|
},
|
||||||
|
"label": "Pasirinkti Pasaulinius Klavišus Dainų Kontroliavimui:",
|
||||||
|
"title": "Pasauliniai Klavišai"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Praleidžia nepatinkančias dainas",
|
||||||
|
"name": "Praleisti Nepatinkančias Dainas"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Automatiškai praleisti tylos dalis dainose",
|
||||||
|
"name": "Praleisti Tylumas"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Automatiškai praleidžia ne muzikines dalis, pvz., įžangą/užvedimą arba muzikinių vaizdo įrašų dalis, kuriose daina negrojama",
|
||||||
|
"name": "Rėmėjų blokuotojas"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Valdykite atkūrimą iš „Windows“ užduočių juostos",
|
||||||
|
"name": "Užduočių juostos medijos valdymas"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Pridedamas jutiklinės juostos valdiklis MacOS vartotojams",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integracija su OBS papildiniu \"Tuna\"",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Pridedamas mygtukas, skirtas perjungti vaizdo įrašo/dainos režimą. taip pat galite pasirinktinai pašalinti visą vaizdo įrašo skirtuką",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Lygiavimas",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Kairė",
|
||||||
|
"middle": "Vidurys",
|
||||||
|
"right": "Dešinė"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Priverstinai pašalinti vaizdo įrašo skirtuką",
|
||||||
|
"mode": {
|
||||||
|
"label": "Rėžimas",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Pasirinktinis perjungimas",
|
||||||
|
"disabled": "Išjungta",
|
||||||
|
"native": "Vietinis perjungimas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Vaizdo įrašo perjungimas",
|
||||||
|
"templates": {
|
||||||
|
"button": "Daina"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Prie grotuvo pridedamas vizualizatorius",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Vizualizatoriaus tipas"
|
||||||
|
},
|
||||||
|
"name": "Vizualizatorius"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,25 +1,160 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Klarte ikke å kjøre programtillegg {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Det tok {{ms}} ms å kjøre {{pluginName}}::{{contextName}}",
|
||||||
|
"initialize-failed": "Klarte ikke å igangsette «{{pluginName}}»",
|
||||||
|
"load-all": "Laster inn alle programtillegg",
|
||||||
|
"load-failed": "Klarte ikke å laste inn {{pluginName}}-programtillegget",
|
||||||
|
"loaded": "Lastet inn {{pluginName}}-programtillegget",
|
||||||
|
"unload-failed": "Kunne ikke skru av {{pluginName}}-programtillegget",
|
||||||
|
"unloaded": "{{pluginName}}-programtillegg avskrudd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"code": "nb_NO",
|
"code": "nb_NO",
|
||||||
"local-name": "Norsk bokmål",
|
"local-name": "Norsk bokmål",
|
||||||
"name": "Norwegian Bokmål"
|
"name": "Norwegian Bokmål"
|
||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Innlasting fullført. Utviklerverktøy åpnet."
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "språkstøtte innlastet"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Mottok kommando over protokoll: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS-filen «{{cssFile}}» finnes ikke. Ignorerer."
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Svarer ikke\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Tømmer programhurtiglager"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Prøvde å tegne vindu utenfor skjermen. Størrelse={{windowSize}}, skjermstørrelse={{displaySize}}, posisjon={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Menyen er skjult. Bruk «Alt» for å vise den, (ller «Esc» for å bruke menyen i programmet).",
|
||||||
|
"message": "Meny skjult",
|
||||||
|
"title": "Meny vist"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Senere",
|
||||||
|
"restart-now": "Start på ny nå"
|
||||||
|
},
|
||||||
|
"detail": "{{pluginName}}-programtillegget krever programomstart før bruk.",
|
||||||
|
"message": "{{pluginName}}-programtillegget krever programomstart.",
|
||||||
|
"title": "Programomstart kreves"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Avslutt",
|
||||||
|
"relaunch": "Start igjen",
|
||||||
|
"wait": "Vent"
|
||||||
|
},
|
||||||
|
"detail": "Velg blandt følgende:",
|
||||||
|
"message": "Programmet svarer ikke",
|
||||||
|
"title": "Vinduet svarer ikke"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Skru av oppgraderinger",
|
||||||
|
"download": "Last ned",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "En ny versjon er tilgjengelig og kan lastes ned fra {{downloadLink}}",
|
||||||
|
"message": "En ny versjon er tilgjengelig",
|
||||||
|
"title": "Oppgradering tilgjengelig"
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
|
"about": "Om",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigasjon",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Kopier nåværende nettadresse",
|
||||||
|
"go-back": "Tilbake",
|
||||||
|
"go-forward": "Forover",
|
||||||
|
"quit": "Avslutt",
|
||||||
|
"restart": "Programomstart"
|
||||||
|
}
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"label": "Alternativer",
|
"label": "Alternativer",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Avanserte alternativer",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Tilbakestill programhurtiglager når programmet startes",
|
||||||
|
"disable-hardware-acceleration": "Skru av maskinvareakselerasjon",
|
||||||
|
"edit-config-json": "Rediger config.json",
|
||||||
|
"override-user-agent": "Overstyr brukeragent",
|
||||||
|
"restart-on-config-changes": "Omstart ved oppsettsendringer",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Sett mellomtjener",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Skriv inn mellomtjeneradresse: (la stå tom for å skru av)",
|
||||||
|
"placeholder": "Eksempel: socks5://127.0.0.1:9999",
|
||||||
|
"title": "Sett mellomtjener"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Skru av/på utviklerverktøy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Alltid på toppen",
|
||||||
|
"auto-update": "Auto-oppdatering",
|
||||||
"hide-menu": {
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Menyen vil bli skjult ved neste programstart. Bruk [Alt] for å vise den, eller gravistegn [`] hvis du bruker menyen i programmet).",
|
||||||
|
"title": "Skjuler meny"
|
||||||
|
},
|
||||||
"label": "Skjul meny"
|
"label": "Skjul meny"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Nytt språk vises når programmet startes på ny",
|
||||||
|
"title": "Språk endret"
|
||||||
|
},
|
||||||
|
"label": "Språk",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Klikk her for å bistå oversettelsen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Gjenoppta siste spor ved programstart",
|
||||||
|
"single-instance-lock": "Sperr én instans",
|
||||||
|
"start-at-login": "Start ved innlogging",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Startside",
|
||||||
|
"unset": "Opphev"
|
||||||
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"label": "Systemkurv",
|
"label": "Systemkurv",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"disabled": "Avskrudd"
|
"disabled": "Avskrudd",
|
||||||
|
"play-pause-on-click": "Spill av/pause ved klikk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"visual-tweaks": {
|
"visual-tweaks": {
|
||||||
|
"label": "Visuelle tilpasninger",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Forvalg",
|
||||||
|
"force-show": "Tving visning",
|
||||||
|
"hide": "Skjul",
|
||||||
|
"label": "Begunstningsknapper"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Fjern oppgraderingsknapp",
|
||||||
"theme": {
|
"theme": {
|
||||||
"label": "Drakt",
|
"label": "Drakt",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
@ -32,8 +167,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
"enabled": "Påskrudd",
|
||||||
"label": "Programtillegg"
|
"label": "Programtillegg"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Vis",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Tving gjeninnlasting",
|
||||||
|
"reload": "Gjeninnlast",
|
||||||
|
"reset-zoom": "Faktisk størrelse",
|
||||||
|
"toggle-fullscreen": "Veksle fullskjermsvisning",
|
||||||
|
"zoom-in": "Forstørr",
|
||||||
|
"zoom-out": "Forminsk"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Neste",
|
||||||
|
"play-pause": "Spill av/pause",
|
||||||
|
"previous": "Forrige",
|
||||||
|
"quit": "Avslutt",
|
||||||
|
"restart": "Programomstart",
|
||||||
|
"show": "Vis vindu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
@ -49,6 +204,7 @@
|
|||||||
"name": "Albumsfargedrakt"
|
"name": "Albumsfargedrakt"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
|
"description": "Ifører lyseffekt ved å hente myke farger fra videoen inn i skjermens bakgrunn.",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
"label": "Tilsløringsmengde",
|
"label": "Tilsløringsmengde",
|
||||||
@ -56,6 +212,12 @@
|
|||||||
"pixels": "{{blurAmount}} piksler"
|
"pixels": "{{blurAmount}} piksler"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Mellomlager",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"opacity": {
|
"opacity": {
|
||||||
"label": "Dekkevne",
|
"label": "Dekkevne",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
@ -73,10 +235,38 @@
|
|||||||
"submenu": {
|
"submenu": {
|
||||||
"percent": "{{size}}%"
|
"percent": "{{size}}%"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Mykhetsovergang",
|
||||||
|
"submenu": {
|
||||||
|
"during": "I løpet av {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Ved bruk av fullskjerm"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Omgivelsesmodus"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Anvend kompresjon for lyd (senker lydstyrken for de kraftigste delene av signalet og øker nivået i de svakeste)",
|
||||||
|
"name": "Lydkompressor"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Gjør navigeringsbjelken gjennomsiktig og tilslørt",
|
||||||
|
"name": "Tilslør navigasjonsfelt"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Omgå YouTube sin aldersgrenser",
|
||||||
|
"name": "Omgå aldersgrense"
|
||||||
},
|
},
|
||||||
"captions-selector": {
|
"captions-selector": {
|
||||||
|
"description": "Undertekstverktøy for lydspor i YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Auto-velg sist brukte undertekst",
|
||||||
|
"disable-captions": "Ingen undertekst som forvalg"
|
||||||
|
},
|
||||||
|
"name": "Undertekstvelger",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"selector": {
|
"selector": {
|
||||||
"label": "Nåværende tekstingsspråk: {{language}}",
|
"label": "Nåværende tekstingsspråk: {{language}}",
|
||||||
@ -88,27 +278,63 @@
|
|||||||
"title": "Åpne undertekstvelger"
|
"title": "Åpne undertekstvelger"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Alltid sett sidefeltet i kompakt modus",
|
||||||
|
"name": "Kompakt sidefelt"
|
||||||
|
},
|
||||||
"crossfade": {
|
"crossfade": {
|
||||||
|
"description": "Overgang mellom spor",
|
||||||
"menu": {
|
"menu": {
|
||||||
"advanced": "Avansert"
|
"advanced": "Avansert"
|
||||||
},
|
},
|
||||||
|
"name": "Overgang [Beta]",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"options": {
|
"options": {
|
||||||
"multi-input": {
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Inntoningsvarighet (ms)",
|
||||||
|
"fade-out-duration": "Uttoningsvarighet (ms)",
|
||||||
"fade-scaling": {
|
"fade-scaling": {
|
||||||
|
"label": "Overgangsskalering",
|
||||||
"linear": "Lineær",
|
"linear": "Lineær",
|
||||||
"logarithmic": "Logaritmisk"
|
"logarithmic": "Logaritmisk"
|
||||||
}
|
},
|
||||||
}
|
"seconds-before-end": "Overgang antall sekunder før slutt"
|
||||||
|
},
|
||||||
|
"title": "Overgangsalternativer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disable-autoplay": {
|
"disable-autoplay": {
|
||||||
|
"description": "Merkerer sporstart i «pauset» modus",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Har kun innvirkning ved oppstart"
|
||||||
|
},
|
||||||
"name": "Skru av autospilling"
|
"name": "Skru av autospilling"
|
||||||
},
|
},
|
||||||
"discord": {
|
"discord": {
|
||||||
"backend": {
|
"backend": {
|
||||||
|
"already-connected": "Forsøkte å koble til med aktiv tilkobling",
|
||||||
|
"connected": "Tilkoblet Discord",
|
||||||
"disconnected": "Frakoblet fra Discord"
|
"disconnected": "Frakoblet fra Discord"
|
||||||
|
},
|
||||||
|
"description": "Vis venne dine hva du lytter til med rik tilstedeværelse",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Automatisk retilkobling",
|
||||||
|
"clear-activity": "Tøm aktivitet",
|
||||||
|
"clear-activity-after-timeout": "Tøm aktivitet etter tidsavbrudd",
|
||||||
|
"connected": "Tilkoblet",
|
||||||
|
"disconnected": "Frakoblet",
|
||||||
|
"hide-duration-left": "Skjul gjenværende tid",
|
||||||
|
"hide-github-button": "Skjul GitHub-lenkeknapp",
|
||||||
|
"play-on-youtube-music": "Spill på YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Sett tid før tidsavbrudd"
|
||||||
|
},
|
||||||
|
"name": "Rik tilstedeværelse for Discord",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Skriv inn antall sekunder for inaktivitetstidsavbrudd:",
|
||||||
|
"title": "Sett inaktivitetstidsavbrudd"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"downloader": {
|
"downloader": {
|
||||||
@ -117,12 +343,16 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"ok": "OK"
|
"ok": "OK"
|
||||||
}
|
},
|
||||||
|
"message": "Nedlasting mislyktes …",
|
||||||
|
"title": "Feil i nedlastning."
|
||||||
},
|
},
|
||||||
"start-download-playlist": {
|
"start-download-playlist": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"ok": "OK"
|
"ok": "OK"
|
||||||
},
|
},
|
||||||
|
"detail": "({{playlistSize}} spor)",
|
||||||
|
"message": "Laster ned {{playlistTitle}}-spillelisten …",
|
||||||
"title": "Nedlasting startet"
|
"title": "Nedlasting startet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -130,42 +360,143 @@
|
|||||||
"conversion-progress": "Konvertering: {{percent}}%",
|
"conversion-progress": "Konvertering: {{percent}}%",
|
||||||
"converting": "Konverterer …",
|
"converting": "Konverterer …",
|
||||||
"done": "Ferdig: {{filePath}}",
|
"done": "Ferdig: {{filePath}}",
|
||||||
|
"download-info": "Laster ned {{artist}} — {{title}} [{{videoId}} …",
|
||||||
|
"download-progress": "Nedlastet: {{percent}}%",
|
||||||
"downloading": "Laster ned …",
|
"downloading": "Laster ned …",
|
||||||
|
"downloading-counter": "Laster ned {{current}}/{{total}} …",
|
||||||
|
"downloading-playlist": "Laster ned {{playlistTitle}}-spillelisten — {{playlistSize}} spor ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Kunne ikke laste ned «{{author}} — {{title}}»: {{error}}",
|
||||||
|
"folder-already-exists": "{{playlistFolder}}-mappen finnes allerede",
|
||||||
|
"getting-playlist-info": "Henter spillelisteinfo …",
|
||||||
"loading": "Laster inn …",
|
"loading": "Laster inn …",
|
||||||
|
"playlist-has-only-one-song": "Spillelisten har kun ett element. Laster ned direkte.",
|
||||||
|
"playlist-id-not-found": "Fant ingen spilleliste-ID",
|
||||||
"playlist-is-empty": "Tom spilleliste",
|
"playlist-is-empty": "Tom spilleliste",
|
||||||
|
"playlist-is-mix-or-private": "Kunne ikke hente spillelisteinfo. Forsikre deg om at den ikke er privat eller «Mikset for deg».\n\n{{error}}",
|
||||||
"preparing-file": "Forbereder fil …",
|
"preparing-file": "Forbereder fil …",
|
||||||
"saving": "Lagrer …"
|
"saving": "Lagrer …",
|
||||||
|
"trying-to-get-playlist-id": "Prøver å hente spilleliste-ID: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Fant ikke videoen",
|
||||||
|
"writing-id3": "Skriver ID3-tagger …"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"description": "Laster ned MP3/kildelyd direkte fra grensesnittet",
|
||||||
"menu": {
|
"menu": {
|
||||||
"choose-download-folder": "Velg nedlastningsmappe",
|
"choose-download-folder": "Velg nedlastningsmappe",
|
||||||
|
"download-playlist": "Last ned spilleliste",
|
||||||
"presets": "Forhåndsinnstillinger",
|
"presets": "Forhåndsinnstillinger",
|
||||||
"skip-existing": "Hopp over eksisterende filer"
|
"skip-existing": "Hopp over eksisterende filer"
|
||||||
},
|
},
|
||||||
"name": "Nedlaster",
|
"name": "Nedlaster",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Kan ikke oppdatere framdrift"
|
||||||
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Last ned"
|
"button": "Last ned"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"last-fm": {
|
"exponential-volume": {
|
||||||
"name": "Last.fm"
|
"description": "Gjør lydstyrkekontrollen eksponentiell, slik at det er enklere velge lavere lydstyrker.",
|
||||||
|
"name": "Eksponentiell lydstyrke"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Gir menybjelkene stilig, mørk, eller albumfarget utseende",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Skjul DOM-vinduskontroller"
|
||||||
|
},
|
||||||
|
"name": "Meny i programmet"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Legger til Lumia Stream-støtte",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Gir sangtekststøtte for de fleste spor",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Romaniserte sangtekster"
|
||||||
|
},
|
||||||
|
"name": "Sangtekster fra Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Henter sangtekster for Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Direkte integrering av neste/tilbake-navigasjonspilene i grensesnittet, som din favorittnettleser",
|
||||||
|
"name": "Navigasjon"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Fjern Google-innloggingsknapper og lenker fra grensesnittet",
|
||||||
|
"name": "Ingen Google-innlogging"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
|
"description": "Vis en merknad når et spor starter avspilling (interaktive merknader er tilgjengelig på Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Interaktive merknader",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Interaktive innstillinger",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Skjul knappetekst",
|
||||||
|
"refresh-on-play-pause": "Gjenoppfrisk ved avspilling/pause",
|
||||||
|
"tray-controls": "Åpne/lukk med klikk i systemkurven"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Merknadsprioritet",
|
||||||
|
"unpause-notification": "Vis merknad ved oppheving av pause"
|
||||||
|
},
|
||||||
"name": "Merknader"
|
"name": "Merknader"
|
||||||
},
|
},
|
||||||
"picture-in-picture": {
|
"picture-in-picture": {
|
||||||
|
"description": "Tillater å bytte programmet til bilde-i-bilde modus",
|
||||||
"menu": {
|
"menu": {
|
||||||
"save-window-position": "Lagre vindusposisjon"
|
"always-on-top": "Alltid på toppen",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Hurtigtast",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Hurtigtast"
|
||||||
|
},
|
||||||
|
"label": "Velg en hurtigtast for veksling av bilde-i-bilde",
|
||||||
|
"title": "Hurtigtast for bilde-i-bilde"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Lagre vindusposisjon",
|
||||||
|
"save-window-size": "Lagre vindusstørrelse",
|
||||||
|
"use-native-pip": "Bruk nettleserens innebygde bilde-i-bilde"
|
||||||
|
},
|
||||||
|
"name": "Bilde-i-bilde",
|
||||||
|
"templates": {
|
||||||
|
"button": "Bilde-i-bilde"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playback-speed": {
|
"playback-speed": {
|
||||||
|
"description": "Legger til glidebryter som kontrollerer avspillingshastighet",
|
||||||
"name": "Avspillingshastighet",
|
"name": "Avspillingshastighet",
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Hastighet"
|
"button": "Hastighet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"precise-volume": {
|
"precise-volume": {
|
||||||
"name": "Presis lydstyrkejustering"
|
"description": "Kontroller lydstyrken presist ved bruk av musehjul/hurtigtaster, med egendefinert skjermoverlag og tilpassbare steg",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Kontroller for lokale piltaster",
|
||||||
|
"custom-volume-steps": "Sett egendefinerte lydstyrkesteg",
|
||||||
|
"global-shortcuts": "Hurtigtaster i hele programmet"
|
||||||
|
},
|
||||||
|
"name": "Presis lydstyrkejustering",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Senk lydstyrke",
|
||||||
|
"increase": "Øk lydstyrke"
|
||||||
|
},
|
||||||
|
"label": "Velg tastetilknytninger for lydstyrkejusteringskontroller i hele programmet",
|
||||||
|
"title": "Tastetilknytninger for lydstyrkejusteringskontroller i hele programmet"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Velg steg for økning/senking av lydstyrke",
|
||||||
|
"title": "Lydstyrkesteg"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"quality-changer": {
|
"quality-changer": {
|
||||||
"backend": {
|
"backend": {
|
||||||
@ -176,30 +507,85 @@
|
|||||||
"title": "Velg videokvalitet"
|
"title": "Velg videokvalitet"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"description": "Tillat endring av videokvalitet med en knapp i videooverlaget",
|
||||||
|
"name": "Vindukvalitetsvelger"
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Tillater bruk av hurtigtaster for hele programmet til avspilling (spill/pause/neste/forrige) + skru av media-videooverlag ved å overstyre mediataster + skru på Ctrl+CMD+F for å søke, pluss å egge til MPRIS støtte på linux for mediataster pluss egendefinerte hurtigtaster for avanserte brukere",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Overstyr mediataster",
|
||||||
|
"set-keybinds": "Sett kontroller for spor i hele programmet"
|
||||||
|
},
|
||||||
|
"name": "Snarveier (og MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Neste",
|
||||||
|
"play-pause": "Spill av/pause",
|
||||||
|
"previous": "Forrige"
|
||||||
|
},
|
||||||
|
"label": "Velg kontroller for spor i hele programmet:",
|
||||||
|
"title": "Tastaturtilknytninger i hele programmet"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Hopper over mislikte spor",
|
||||||
|
"name": "Hopp over mislikte spor"
|
||||||
|
},
|
||||||
"skip-silences": {
|
"skip-silences": {
|
||||||
|
"description": "Hopp over stille deler av spor",
|
||||||
"name": "Hopp over pauser"
|
"name": "Hopp over pauser"
|
||||||
},
|
},
|
||||||
"sponsorblock": {
|
"sponsorblock": {
|
||||||
|
"description": "Hopper over ikke-musikalske deler, som intro/sluttsats, eller deler av musikkvideoer der ingen musikk spilles",
|
||||||
"name": "SponsorBlock"
|
"name": "SponsorBlock"
|
||||||
},
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Kontroller avspilling fra din Windows-oppgavelinje",
|
||||||
|
"name": "Oppgavelinje-mediakontroll"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Legger til et pekefelt-miniprogram for macOS-brukere",
|
||||||
|
"name": "Pekefelt"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integrasjon med Tuna-programtillegget i OBS",
|
||||||
|
"name": "OBS Tuna"
|
||||||
|
},
|
||||||
"video-toggle": {
|
"video-toggle": {
|
||||||
|
"description": "Leger til en knapp for å bytte mellom video/spormodus. Kan også alternativt fjerne hele videofanen.",
|
||||||
"menu": {
|
"menu": {
|
||||||
"align": {
|
"align": {
|
||||||
|
"label": "Justering",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"left": "Venstre",
|
"left": "Venstre",
|
||||||
"middle": "Midten",
|
"middle": "Midten",
|
||||||
"right": "Høyre"
|
"right": "Høyre"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"force-hide": "Påtving fjerning av videofane",
|
||||||
"mode": {
|
"mode": {
|
||||||
"label": "Modus"
|
"label": "Modus",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Egendefinert veksling",
|
||||||
|
"disabled": "Avskrudd",
|
||||||
|
"native": "Innebygd veksling"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"name": "Videoveksling",
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Spor"
|
"button": "Spor"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Legger til en visualisator i avspilleren",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Visualisatortype"
|
||||||
|
},
|
||||||
|
"name": "Visualisator"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
674
src/i18n/resources/pl.json
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Błąd uruchamiania wtyczki {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Wtyczka {{pluginName}}::{{contextName}} uruchomiona w {{ms}}ms",
|
||||||
|
"initialize-failed": "Błąd inicjalizacji wtyczki \"{{pluginName}}\"",
|
||||||
|
"load-all": "Ładowanie wszystkich wtyczek",
|
||||||
|
"load-failed": "Błąd w ładowaniu wtyczki \"{{pluginName}}\"",
|
||||||
|
"loaded": "Wtyczka \"{{pluginName}}\" załadowana",
|
||||||
|
"unload-failed": "Błąd w odłączaniu wtyczki \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Wtyczka \"{{pluginName}}\" odłączona"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "pl",
|
||||||
|
"local-name": "Polski",
|
||||||
|
"name": "Polish"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Ukończono ładowanie. Narzędzia deweloperskie otwarte"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n załadowane"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Otrzymano komendę przez protokół: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Plik CSS \"{{cssFile}}\" nie istnieje, ignoruję"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Błąd! Brak odpowiedzi:\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Czyszczenie pamięci podręcznej aplikacji"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Okno próbuje się renderować poza ekranem, rozmiar okna={{windowSize}}, rozmiar monitora={{displaySize}}, pozycja={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Menu jest ukryte, użyj przycisku [Alt] aby je pokazać (lub [Escape], jeśli używasz menu w aplikacji)",
|
||||||
|
"message": "Ukrywanie menu jest włączone",
|
||||||
|
"title": "Ukrywanie Menu Włączone"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Później",
|
||||||
|
"restart-now": "Uruchom ponownie teraz"
|
||||||
|
},
|
||||||
|
"detail": "Wtyczka \"{{pluginName}}\" potrzebuje ponownego uruchomienia aby była aktywna",
|
||||||
|
"message": "\"{{pluginName}}\" potrzebuje ponownego uruchomienia",
|
||||||
|
"title": "Ponowne uruchomienie potrzebne"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Wyjdź",
|
||||||
|
"relaunch": "Ponownie uruchom",
|
||||||
|
"wait": "Czekać"
|
||||||
|
},
|
||||||
|
"detail": "Przepraszamy za niedogodność! Proszę wybierz co zrobić:",
|
||||||
|
"message": "Aplikacja nie reaguje",
|
||||||
|
"title": "Okno nie reaguje"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Wyłącz aktualizacje",
|
||||||
|
"download": "Pobierz",
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "Nowa wersja jest dostępna i możesz ją pobrać tutaj {{downloadLink}}",
|
||||||
|
"message": "Nowa wersja jest dostępna",
|
||||||
|
"title": "Aktualizacja jest dostępna"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "O aplikacji",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Nawigacja",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Skopiuj aktualny adres URL",
|
||||||
|
"go-back": "Wróć",
|
||||||
|
"go-forward": "Do przodu",
|
||||||
|
"quit": "Wyjście",
|
||||||
|
"restart": "Uruchom ponownie aplikację"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Opcje",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Opcje zaawansowane",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Wyczyść pamięć podręczną aplikacji przy jej uruchomieniu",
|
||||||
|
"disable-hardware-acceleration": "Wyłącz przyspieszanie sprzętowe",
|
||||||
|
"edit-config-json": "Edytuj config.json",
|
||||||
|
"override-user-agent": "Zastąp klienta użytkownika (User-Agent)",
|
||||||
|
"restart-on-config-changes": "Uruchom ponownie po zmianie konfiguracji",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Ustaw proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Podaj adres Proxy: (zostaw pusty aby wyłączyć)",
|
||||||
|
"placeholder": "Przykład: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Ustaw proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Przełącz narzędzia deweloperskie"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Zawsze na wierzchu",
|
||||||
|
"auto-update": "Automatyczne aktualizacje",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Menu będzie ukryte po następnym uruchomieniu, użyj przycisku [Alt] aby je pokazać (lub [`], jeśli używasz menu w aplikacji)",
|
||||||
|
"title": "Ukrywanie menu włączone"
|
||||||
|
},
|
||||||
|
"label": "Ukryj menu"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Język będzie zmieniony po ponownym uruchomieniu",
|
||||||
|
"title": "Język zmieniony"
|
||||||
|
},
|
||||||
|
"label": "Język",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Chcesz pomóc w tłumaczeniu? Kliknij tutaj"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Wznów ostatni utwór po uruchomieniu aplikacji",
|
||||||
|
"single-instance-lock": "Zablokuj do jednej instancji aplikacji",
|
||||||
|
"start-at-login": "Uruchom po zalogowaniu",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Strona startowa",
|
||||||
|
"unset": "Pusty"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Ikona w zasobniku",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Wyłączone",
|
||||||
|
"enabled-and-hide-app": "Włącz i ukryj aplikację",
|
||||||
|
"enabled-and-show-app": "Włącz i pokaż aplikację",
|
||||||
|
"play-pause-on-click": "Odtwórz/Wstrzymaj po kliknięciu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Poprawki wizualne",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Domyślne",
|
||||||
|
"force-show": "Wymuś pokazywanie",
|
||||||
|
"hide": "Ukryj",
|
||||||
|
"label": "Przyciski polubienia"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Usuń przycisk subskrypcji premium",
|
||||||
|
"theme": {
|
||||||
|
"label": "Motyw",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Importuj własny plik CSS",
|
||||||
|
"no-theme": "Bez motywu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Włączone",
|
||||||
|
"label": "Wtyczki",
|
||||||
|
"new": "NOWOŚĆ"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Widok",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Wymuś ponowne ładowanie",
|
||||||
|
"reload": "Ponowne ładowanie",
|
||||||
|
"reset-zoom": "Rozmiar rzeczywisty",
|
||||||
|
"toggle-fullscreen": "Przełącz pełny ekran",
|
||||||
|
"zoom-in": "Powiększ",
|
||||||
|
"zoom-out": "Pomniejsz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Następny",
|
||||||
|
"play-pause": "Odtwórz/Pauza",
|
||||||
|
"previous": "Poprzedni",
|
||||||
|
"quit": "Wyjdź",
|
||||||
|
"restart": "Uruchom ponownie aplikację",
|
||||||
|
"show": "Pokaż okno",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "{{title}} (autorstwa {{artist}}) - YT Music"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Blokuj wszystkie reklamy i śledzenie",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Metoda przechwytywania"
|
||||||
|
},
|
||||||
|
"name": "Blokowanie reklam"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Dodaje przyciski łapek w górę i dół do wszystkich piosenek podczas w albumach lub listach odtwarzania",
|
||||||
|
"name": "Akcje albumu"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Stosuje dynamiczny motyw i efekty wizualne w oparciu o paletę kolorów albumu",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Motyw kolorów albumu"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Stosuje efekt świetlny, rzucając delikatne kolory z wideo na tło ekranu",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Ilość rozmycia",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} pikseli"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Bufor",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Nieprzezroczystość",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Jakość",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} pikseli"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Rozmiar",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Płynność przejścia",
|
||||||
|
"submenu": {
|
||||||
|
"during": "W czasie {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Podczas pełnego ekranu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Tryb otoczenia"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Zastosuj kompresję do dźwięku (obniża głośność najgłośniejszych części sygnału i zwiększa głośność najcichszych części)",
|
||||||
|
"name": "Kompresor dźwięku"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Sprawia, że pasek nawigacji jest przezroczysty i rozmazany",
|
||||||
|
"name": "Rozmycie paska nawigacji"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Pomija weryfikację wieku",
|
||||||
|
"name": "Omiń ograniczenia wiekowe"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Selektor napisów dla ścieżek audio YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Automatycznie wybierz ostatnio używanych napisów",
|
||||||
|
"disable-captions": "Domyślnie, brak napisów"
|
||||||
|
},
|
||||||
|
"name": "Selektor napisów",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Bieżący język napisów: {{language}}",
|
||||||
|
"none": "Brak",
|
||||||
|
"title": "Wybierz język napisów"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Otwórz selektor napisów"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Zawsze ustawiaj pasek boczny w trybie kompaktowym",
|
||||||
|
"name": "Kompaktowy pasek boczny"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Przenikanie pomiędzy utworami",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Zaawansowane"
|
||||||
|
},
|
||||||
|
"name": "Płynne przejście [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Czas wnikania (ms)",
|
||||||
|
"fade-out-duration": "Czas zanikania (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Skalowanie zanikania",
|
||||||
|
"linear": "Liniowe",
|
||||||
|
"logarithmic": "Logarytmiczne"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Przenikanie N sekund przed końcem"
|
||||||
|
},
|
||||||
|
"title": "Opcje przenikania"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Wyłącza automatyczne odtwarzanie utworów",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Tylko przy uruchomieniu aplikacji"
|
||||||
|
},
|
||||||
|
"name": "Wyłącz automatyczne odtwarzanie"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Próbowano połączyć się przy aktywnym połączeniu",
|
||||||
|
"connected": "Połączono z Discordem",
|
||||||
|
"disconnected": "Odłączono od Discorda"
|
||||||
|
},
|
||||||
|
"description": "Pokaż znajomym, czego słuchasz dzięki Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Automatyczne wznawianie połączenia",
|
||||||
|
"clear-activity": "Wyczyść aktywność",
|
||||||
|
"clear-activity-after-timeout": "Wyczyść aktywność po czasie",
|
||||||
|
"connected": "Połączono",
|
||||||
|
"disconnected": "Odłączono",
|
||||||
|
"hide-duration-left": "Ukryj pozostały czas trwania",
|
||||||
|
"hide-github-button": "Ukryj przycisk do GitHub",
|
||||||
|
"play-on-youtube-music": "Odtwórz w YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Ustaw limit czasu bezczynności"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Podaj limit czasu bezczynności w sekundach:",
|
||||||
|
"title": "Ustaw limit czasu bezczynności"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"message": "Argh! Przepraszamy, pobieranie nie powiodło się…",
|
||||||
|
"title": "Błąd podczas pobierania!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} utworów)",
|
||||||
|
"message": "Pobieranie playlisty {{playlistTitle}}",
|
||||||
|
"title": "Pobieranie rozpoczęte"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Konwertowanie: {{percent}}%",
|
||||||
|
"converting": "Konwertowanie…",
|
||||||
|
"done": "Gotowe: {{filePath}}",
|
||||||
|
"download-info": "Pobieranie {{artist}} - {{title}} {{videoId}}",
|
||||||
|
"download-progress": "Pobieranie: {{percent}}%",
|
||||||
|
"downloading": "Pobieranie…",
|
||||||
|
"downloading-counter": "Pobieranie {{current}}/{{total}} …",
|
||||||
|
"downloading-playlist": "Pobieranie playlisty \"{{playlistTitle}}\" - {{playlistSize}} utworów ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Błąd pobierania \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "Folder {{playlistFolder}} już istnieje",
|
||||||
|
"getting-playlist-info": "Pobieram informacje o playliście…",
|
||||||
|
"loading": "Ładowanie…",
|
||||||
|
"playlist-has-only-one-song": "Playlista zawiera tylko jeden element, zostanie pobrany bezpośrednio",
|
||||||
|
"playlist-id-not-found": "Nie znaleziono ID playlisty",
|
||||||
|
"playlist-is-empty": "Playlista jest pusta",
|
||||||
|
"playlist-is-mix-or-private": "Podczas pobierania informacji o playliście wystąpił błąd: upewnij się, że nie jest to playlista prywatna ani playlista „Składanki dla Ciebie”.\n\n{{error}}",
|
||||||
|
"preparing-file": "Przygotowuję plik…",
|
||||||
|
"saving": "Zapisuję…",
|
||||||
|
"trying-to-get-playlist-id": "Próbuję uzyskać ID playlisty: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Nie znaleziono filmu",
|
||||||
|
"writing-id3": "Zapisywanie tagów ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Pobiera MP3/ źródło audio bezpośrednio z interfejsu",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Wybierz folder pobierania",
|
||||||
|
"download-playlist": "Pobierz playlistę",
|
||||||
|
"presets": "Predefiniowane ustawienia",
|
||||||
|
"skip-existing": "Pomiń istniejące pliki"
|
||||||
|
},
|
||||||
|
"name": "Pobieranie",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Nie można zaktualizować postępu"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Pobierz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Sprawia, że suwak głośności jest proporcjonalna, dzięki czemu łatwiej jest wybrać niższą głośność.",
|
||||||
|
"name": "Proporcjonalna głośność"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Nadaje paskom menu elegancki, ciemny lub albumowy wygląd",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Ukryj kontrolki okna DOM"
|
||||||
|
},
|
||||||
|
"name": "Menu w aplikacji"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Dodaje obsługę Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Dodaje obsługę tekstów dla większości piosenek",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Teksty zromanizowane"
|
||||||
|
},
|
||||||
|
"name": "Tekst piosenek od Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Tekst dostarczony przez Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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)",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Wpisz ID hosta"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Zapisz",
|
||||||
|
"track-source": "Źródło utworu",
|
||||||
|
"unknown-user": "Nieznany użytkownik"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Kopiuj ID hosta",
|
||||||
|
"close": "Zakończ host",
|
||||||
|
"connected-users": "Połączeni użytkownicy",
|
||||||
|
"disconnect": "Rozłącz z hosta",
|
||||||
|
"empty-user": "Brak połączonych użytkowników",
|
||||||
|
"host": "Host słuchania razem",
|
||||||
|
"join": "Połącz z hostem",
|
||||||
|
"permission": {
|
||||||
|
"all": "Połączeni użytkownicy mają kontrolę nad listą odtwarzania oraz playerem",
|
||||||
|
"host-only": "Tylko host może kontrolować listę odtwarzania oraz playera",
|
||||||
|
"playlist": "Połączeni użytkownicy maja kontrolę tylko nad listą odtwarzania"
|
||||||
|
},
|
||||||
|
"set-permission": "Zmień permisję kontroli",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Rozłączony",
|
||||||
|
"guest": "Połączony jako Użytkownik",
|
||||||
|
"host": "Połączony jako Host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Słuchanie razem [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Wystąpił błąd z dodaniem muzyki",
|
||||||
|
"closed": "Host słuchania razem został zamknięty pomyślnie",
|
||||||
|
"disconnected": "Pomyślnie rozłączono z hosta słuchania razem",
|
||||||
|
"host-failed": "Wystąpił błąd z hostem słuchania razem",
|
||||||
|
"id-copied": "ID hosta został wklejony do schowka",
|
||||||
|
"id-copy-failed": "Wystąpił błąd z próbą skopiowania ID do schowka",
|
||||||
|
"join-failed": "Wystąpił błąd z dołączeniem do hosta",
|
||||||
|
"joined": "Dołączono pomyślnie do hosta słuchania razem",
|
||||||
|
"permission-changed": "Permisja hosta została zmieniona na \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Wystąpił błąd z usunięciem utworu",
|
||||||
|
"user-connected": "Do hosta dołączył {{name}}",
|
||||||
|
"user-disconnected": "{{name}} właśnie wyszedł z hosta"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Strzałki nawigacyjne Dalej/Wstecz zintegrowane bezpośrednio z interfejsem, tak jak w Twojej ulubionej przeglądarce",
|
||||||
|
"name": "Nawigacja"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Usuń przyciski i linki logowania Google z interfejsu",
|
||||||
|
"name": "Usuń logowanie do Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Wyświetl powiadomienie, gdy rozpocznie się odtwarzanie utworu (interaktywne powiadomienia są dostępne w systemie Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Interaktywne powiadomienia",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Interaktywne ustawienia",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Ukryj tekst przycisku",
|
||||||
|
"refresh-on-play-pause": "Odśwież podczas odtwarzania/pauzy",
|
||||||
|
"tray-controls": "Otwórz/zamknij po kliknięciu ikony na pasku zadań"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Priorytet powiadomień",
|
||||||
|
"toast-style": "Styl powiadomień \"Toast\"",
|
||||||
|
"unpause-notification": "Pokaż powiadomienie po wznowieniu"
|
||||||
|
},
|
||||||
|
"name": "Powiadomienia"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Umożliwia przełączenie aplikacji w tryb obrazu w obrazie",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Zawsze na wierzchu",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Klawisz skrótu",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Klawisz skrótu"
|
||||||
|
},
|
||||||
|
"label": "Wybierz klawisz skrótu do przełączania trybu obrazu w obrazie",
|
||||||
|
"title": "Klawisz skrótu obrazu w obrazie"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Zapisz pozycję okna",
|
||||||
|
"save-window-size": "Zapisz rozmiar okna",
|
||||||
|
"use-native-pip": "Użyj natywnego PiP dla przeglądarki"
|
||||||
|
},
|
||||||
|
"name": "Obraz w obrazie",
|
||||||
|
"templates": {
|
||||||
|
"button": "Obraz w obrazie"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Słuchaj szybko, słuchaj powoli! Dodaje suwak kontrolujący prędkość utworu",
|
||||||
|
"name": "Szybkość odtwarzania",
|
||||||
|
"templates": {
|
||||||
|
"button": "Szybkość"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Precyzyjnie kontroluj głośność za pomocą kółka myszy/klawiszy skrótu, z niestandardowym interfejsem HUD i konfigurowalnymi krokami głośności",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Sterowanie za pomocą klawiszy strzałek",
|
||||||
|
"custom-volume-steps": "Ustaw niestandardowe kroki głośności",
|
||||||
|
"global-shortcuts": "Globalne skróty klawiszowe"
|
||||||
|
},
|
||||||
|
"name": "Precyzyjna głośność",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Zmniejsz głośność",
|
||||||
|
"increase": "Zwiększ głośność"
|
||||||
|
},
|
||||||
|
"label": "Wybierz globalne skróty klawiaturowe głośności:",
|
||||||
|
"title": "Globalne skróty klawiszowe głośności"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Wybierz kroki zwiększania/zmniejszania głośności",
|
||||||
|
"title": "Kroki głośności"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Aktualna jakość: {{quality}}",
|
||||||
|
"message": "Wybierz jakość wideo:",
|
||||||
|
"title": "Wybierz jakość wideo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Umożliwia zmianę jakości wideo za pomocą przycisku na nakładce wideo",
|
||||||
|
"name": "Zmieniacz jakości wideo"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"menu": {
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Podaj token użytkownika ListenBrainz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "klucz API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Podaj swój token użytkownika ListenBrainz:",
|
||||||
|
"title": "Token ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Umożliwia ustawienie globalnych skrótów klawiszowych do odtwarzania (odtwarzanie/pauza/następny/poprzedni) + wyłączanie OSD multimediów poprzez zastąpienie klawiszy multimediów, włączając kombinację klawiszy Ctrl/CMD + F w celu wyszukiwania, obsługę Linux MPRIS dla klawiszy multimediów oraz niestandardowe skróty klawiszowe dla zaawansowanych użytkowników",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Zastąp klawisze multimediów",
|
||||||
|
"set-keybinds": "Ustaw globalne sterowanie utworem"
|
||||||
|
},
|
||||||
|
"name": "Skróty klawiszowe (oraz MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Następny",
|
||||||
|
"play-pause": "Odtwarzanie / Pauza",
|
||||||
|
"previous": "Poprzedni"
|
||||||
|
},
|
||||||
|
"label": "Wybierz globalne skróty klawiszowe do sterowania utworami:",
|
||||||
|
"title": "Globalne skróty klawiszowe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Pomija nieulubione piosenki (zaznaczone łapką w dół)",
|
||||||
|
"name": "Pomijanie nieulubionych piosenek"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Automatycznie pomijaj sekcje bez dźwięku w utworach",
|
||||||
|
"name": "Pomiń ciszę"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Automatycznie pomija fragmenty niebędące muzyką, takie jak wstęp/zakończenie lub fragmenty teledysków, w których utwór nie jest odtwarzany",
|
||||||
|
"name": "Pomiń nieistotne fragmenty"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Steruj odtwarzaniem z paska zadań systemu Windows",
|
||||||
|
"name": "Kontroler odtwarzania z paska zadań"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Dodaje widżet do paska dotykowego dla użytkowników systemu macOS",
|
||||||
|
"name": "Pasek dotykowy"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integracja z wtyczką OBS Tuna",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Dodaje przycisk do przełączania między trybem wideo a piosenki. Może również opcjonalnie usunąć całą kartę wideo",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Wyrównanie",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Lewo",
|
||||||
|
"middle": "Środek",
|
||||||
|
"right": "Prawo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Wymuś usunięcie zakładki wideo",
|
||||||
|
"mode": {
|
||||||
|
"label": "Tryb",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Niestandardowy przełącznik",
|
||||||
|
"disabled": "Wyłączony",
|
||||||
|
"native": "Natywny przełącznik"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Przełącznik wideo",
|
||||||
|
"templates": {
|
||||||
|
"button": "Utwór"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Dodaje wizualizator do odtwarzacza",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Typ wizualizatora"
|
||||||
|
},
|
||||||
|
"name": "Wizualizator"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
649
src/i18n/resources/pt.json
Normal file
@ -0,0 +1,649 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Falha ao executar o plugin {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} executado com {{ms}}ms",
|
||||||
|
"initialize-failed": "Falha ao inicializar o plugin \"{{pluginName}}\"",
|
||||||
|
"load-all": "Carregando todos os plugins",
|
||||||
|
"load-failed": "Falha ao carregar o plugin \"{{pluginName}}\"",
|
||||||
|
"loaded": "Plugin \"{{pluginName}}\" carregado",
|
||||||
|
"unload-failed": "Falha ao descarregar o plugin \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Plugin \"{{pluginName}}\" descarregado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "pt",
|
||||||
|
"local-name": "Português",
|
||||||
|
"name": "Portuguese"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Carregamento finalizado. DevTools aberto"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n carregado"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Comando recebido através do protocolo: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Arquivo CSS \"{{cssFile}}\" não existe, ignorando"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Erro sem resposta!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Limpando o cache do aplicativo"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Janela tentou desenhar fora do ecrã, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "O menu está oculto, use a tecla 'Alt' para mostra-lo (ou 'Esc' se estiver usando o menu do aplicativo)",
|
||||||
|
"message": "Ocultar menu está ativado",
|
||||||
|
"title": "Ocultar menu ativado"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Depois",
|
||||||
|
"restart-now": "Reiniciar agora"
|
||||||
|
},
|
||||||
|
"detail": "O plugin {{pluginName}} precisa ser reiniciado para ter efeito",
|
||||||
|
"message": "\"{{pluginName}}\" precisa ser reiniciado",
|
||||||
|
"title": "É necessário reiniciar"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Sair",
|
||||||
|
"relaunch": "Reiniciar",
|
||||||
|
"wait": "Espere"
|
||||||
|
},
|
||||||
|
"detail": "Lamentamos o inconveniente! Por favor escolha o que fazer:",
|
||||||
|
"message": "A aplicação não está respondendo",
|
||||||
|
"title": "A janela não está respondendo"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Desabilitar atualizações",
|
||||||
|
"download": "Baixar",
|
||||||
|
"ok": "Ok"
|
||||||
|
},
|
||||||
|
"detail": "Uma nova versão está disponível e pode ser baixada em {{downloadLink}}",
|
||||||
|
"message": "Uma nova versão está disponível",
|
||||||
|
"title": "Atualização disponível"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Sobre",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navegação",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Copiar URL atual",
|
||||||
|
"go-back": "Voltar",
|
||||||
|
"go-forward": "Avançar",
|
||||||
|
"quit": "Saída",
|
||||||
|
"restart": "Reiniciar aplicativo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Opções",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Opções avançadas",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Reiniciar cache do aplicativo quando o aplicativo abrir",
|
||||||
|
"disable-hardware-acceleration": "Desabilitar aceleração por hardware",
|
||||||
|
"edit-config-json": "Editar config.json",
|
||||||
|
"override-user-agent": "Substituir User-Agent",
|
||||||
|
"restart-on-config-changes": "Reinicie as alterações de configurações feitas",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Definir proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Inserir Endereço do Proxy: (deixe em branco para desativar)",
|
||||||
|
"placeholder": "Exemplo: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Definir proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Ativar DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Sempre no topo",
|
||||||
|
"auto-update": "Atualização automática",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "O menu será ocultado na próxima inicialização, use [Alt] para mostrá-lo (ou acento grave [`] se estiver usando o menu interno do aplicativo)",
|
||||||
|
"title": "Ocultar Menu Ativado"
|
||||||
|
},
|
||||||
|
"label": "Ocultar Menu"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Idioma será alterado após reiniciar",
|
||||||
|
"title": "Idioma Alterado"
|
||||||
|
},
|
||||||
|
"label": "Idioma",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Quer ajudar na tradução? Clique aqui"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Continuar última música ao iniciar o aplicativo",
|
||||||
|
"single-instance-lock": "Trava de instância única",
|
||||||
|
"start-at-login": "Iniciar no login",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Página inicial",
|
||||||
|
"unset": "Indefinido"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Bandeja",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Desabilitado",
|
||||||
|
"enabled-and-hide-app": "Ativado e esconder aplicativo",
|
||||||
|
"enabled-and-show-app": "Ativado e mostrar aplicativo",
|
||||||
|
"play-pause-on-click": "Play/Pausa ao clicar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Tweaks Visuais",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Padrão",
|
||||||
|
"force-show": "Forçar mostrar",
|
||||||
|
"hide": "Esconder",
|
||||||
|
"label": "Botões de curtida"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Remover botão upgrade",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Importar arquivo CSS personalizado",
|
||||||
|
"no-theme": "Sem tema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Ativado",
|
||||||
|
"label": "Plugins",
|
||||||
|
"new": "NOVO"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Ver",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Forçar Recarregamento",
|
||||||
|
"reload": "Recarregar",
|
||||||
|
"reset-zoom": "Tamanho Atual",
|
||||||
|
"toggle-fullscreen": "Ativar Tela Cheia",
|
||||||
|
"zoom-in": "Zoom Dentro",
|
||||||
|
"zoom-out": "Zoom Fora"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Próximo",
|
||||||
|
"play-pause": "Play/Pausa",
|
||||||
|
"previous": "Anterior",
|
||||||
|
"quit": "Sair",
|
||||||
|
"restart": "Reiniciar aplicativo",
|
||||||
|
"show": "Mostrar janela",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Bloquear todos os anúncios e rastreamento automaticamente",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Bloqueador"
|
||||||
|
},
|
||||||
|
"name": "Bloqueador de anúncios"
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"name": "Ações no álbum"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum",
|
||||||
|
"name": "Tema de cores do álbum"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Aplica um efeito de iluminação lançando cores suaves do vídeo no fundo da tela.",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Quantidade de desfoque",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} píxeis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Buffer",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Opacidade",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Qualidade",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} píxeis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Tamanho",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Transição suave",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Durante {{interpolationTime}}s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Tela cheia"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Modo Ambiente"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Aplicar compressão ao áudio (diminui o volume das partes mais altas do sinal e aumenta o volume das partes mais suaves)",
|
||||||
|
"name": "Compressor de áudio"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Torna a barra de navegação transparente e desfocada",
|
||||||
|
"name": "Barra de navegação desfocada"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Ignorar a verificação de idade do YouTube",
|
||||||
|
"name": "Ignorar restrições de idade"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Seletor de legenda para faixas de áudio do YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Selecionar automaticamente a última legenda usada",
|
||||||
|
"disable-captions": "Sem legendas por padrão"
|
||||||
|
},
|
||||||
|
"name": "Seletor de legendas",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Idioma da legenda atual: {{language}}",
|
||||||
|
"none": "Nenhuma",
|
||||||
|
"title": "Selecione o idioma da legenda"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Seletor de legendas aberto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Definir sempre a barra lateral no modo compacto",
|
||||||
|
"name": "Barra lateral compacta"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Transição entre músicas",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Avançado"
|
||||||
|
},
|
||||||
|
"name": "Transição entre músicas [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Duração da transição no início (ms)",
|
||||||
|
"fade-out-duration": "Duração da transição no final (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Escala da transição",
|
||||||
|
"linear": "Linear",
|
||||||
|
"logarithmic": "Logarítmica"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Realizar transição N segundos antes do final"
|
||||||
|
},
|
||||||
|
"title": "Opções de transição"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Faz a música começar no modo \"pausado\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Aplicar apenas na inicialização"
|
||||||
|
},
|
||||||
|
"name": "Desativar reprodução automática"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Tentativa de conexão com conexão já ativa",
|
||||||
|
"connected": "Conectado ao Discord",
|
||||||
|
"disconnected": "Desconectado do Discord"
|
||||||
|
},
|
||||||
|
"description": "Mostre aos seus amigos o que você ouve com Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Reconexão automática",
|
||||||
|
"clear-activity": "Limpar atividade",
|
||||||
|
"clear-activity-after-timeout": "Limpar atividade após o tempo limite",
|
||||||
|
"connected": "Conectado",
|
||||||
|
"disconnected": "Desconectado",
|
||||||
|
"hide-duration-left": "Ocultar duração restante",
|
||||||
|
"hide-github-button": "Ocultar botão de link do GitHub",
|
||||||
|
"play-on-youtube-music": "Reproduzir no YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Definir tempo limite de inatividade"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Insira o tempo limite de inatividade em segundos:",
|
||||||
|
"title": "Definir tempo limite de inatividade"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"message": "Poxa! Desculpe, o download falhou…",
|
||||||
|
"title": "Erro no download!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} músicas)",
|
||||||
|
"message": "Baixando lista de reprodução {{playlistTitle}}",
|
||||||
|
"title": "Download iniciado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Conversão: {{percent}}%",
|
||||||
|
"converting": "Convertendo…",
|
||||||
|
"done": "Finalizado: {{filePath}}",
|
||||||
|
"download-info": "Baixando {{artist}} - {{title}} {{videoId}}",
|
||||||
|
"download-progress": "Baixando: {{percent}}%",
|
||||||
|
"downloading": "Baixando…",
|
||||||
|
"downloading-counter": "Baixando {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Baixando lista de reprodução \"{{playlistTitle}}\" - {{playlistSize}} músicas ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Erro ao baixar \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "A pasta {{playlistFolder}} já existe",
|
||||||
|
"getting-playlist-info": "Obtendo informações da lista de reprodução…",
|
||||||
|
"loading": "Carregando…",
|
||||||
|
"playlist-has-only-one-song": "A lista de reprodução possui apenas um item, baixando-o diretamente",
|
||||||
|
"playlist-id-not-found": "ID da lista de reprodução não encontrado",
|
||||||
|
"playlist-is-empty": "Lista de reprodução vazia",
|
||||||
|
"playlist-is-mix-or-private": "Erro ao obter informações da lista de reprodução: tenha certeza que ela não está privada ou não seja \"Mixtapes criadas para você\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Preparando arquivos…",
|
||||||
|
"saving": "Salvando…",
|
||||||
|
"trying-to-get-playlist-id": "Tentando pegar ID da lista de reprodução: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Vídeo não encontrado",
|
||||||
|
"writing-id3": "Escrevendo tags ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Baixa MP3 / fonte de áudio diretamente da interface",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Escolha a pasta de download",
|
||||||
|
"download-playlist": "Baixar lista de reprodução",
|
||||||
|
"presets": "Predefinições",
|
||||||
|
"skip-existing": "Ignorar arquivos existentes"
|
||||||
|
},
|
||||||
|
"name": "Downloader",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Não é possível atualizar o progresso"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Baixar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Torna o controle deslizante de volume exponencial, facilitando a seleção de volumes mais baixos.",
|
||||||
|
"name": "Volume Exponencial"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Dá às barras de menu uma aparência sofisticada, escura ou com a cor do álbum",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Ocultar controles da janela DOM"
|
||||||
|
},
|
||||||
|
"name": "Menu no aplicativo"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Adiciona suporte Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Adiciona suporte a letras para a maioria das músicas",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Letras romanizadas"
|
||||||
|
},
|
||||||
|
"name": "Letras Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Buscar letras no Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Compartilha a playlist com outros. Quando o host tocar uma música, todos poderão ouvir a mesma canção",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Digite o ID do Host"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Salvar",
|
||||||
|
"track-source": "Fonte da faixa",
|
||||||
|
"unknown-user": "Usuário Desconhecido"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Copiar ID do Host",
|
||||||
|
"close": "Fechar o Música Juntos",
|
||||||
|
"connected-users": "Usuários Conectados",
|
||||||
|
"disconnect": "Desconectar do Música Juntos",
|
||||||
|
"empty-user": "Sem usuários conectados",
|
||||||
|
"host": "Host do Música Juntos",
|
||||||
|
"join": "Juntar ao Música Juntos",
|
||||||
|
"permission": {
|
||||||
|
"all": "Permita que outros controlem a playlist e ao player",
|
||||||
|
"host-only": "Apenas o host pode controlar a playlist e ao player",
|
||||||
|
"playlist": "Permitir que outros controlem a playlist"
|
||||||
|
},
|
||||||
|
"set-permission": "Alterar permissões de controle",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Desconectado",
|
||||||
|
"guest": "Conectado como Convidado",
|
||||||
|
"host": "Conectado como Host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Música Juntos [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Falha ao adicionar canção",
|
||||||
|
"closed": "Música Juntos encerrado",
|
||||||
|
"disconnected": "Música Juntos foi desconectado",
|
||||||
|
"host-failed": "Falha ao hospedar o Música Juntos",
|
||||||
|
"id-copied": "ID de anfitrião copiado para a área de transferência",
|
||||||
|
"id-copy-failed": "Falha ao copiar o ID de anfitrião para a área de transferência",
|
||||||
|
"join-failed": "Falha ao entrar em Música Juntos",
|
||||||
|
"joined": "Entrou em Música Juntos",
|
||||||
|
"permission-changed": "A permissão do Música Juntos foi alterada para \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Falha ao remover música",
|
||||||
|
"user-connected": "{{name}} entrou em Música Juntos",
|
||||||
|
"user-disconnected": "{{name}} saiu do Música Juntos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Setas de navegação Próximo/Voltar integradas diretamente na interface, como no seu navegador favorito",
|
||||||
|
"name": "Navegação"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Remove os botões e links de login do Google da interface",
|
||||||
|
"name": "Sem login do Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Exibir uma notificação quando uma música começar a tocar (notificações interativas estão disponíveis no Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Notificações interativas",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Configurações interativas",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Ocultar texto do botão",
|
||||||
|
"refresh-on-play-pause": "Atualizar ao reproduzir/pausar",
|
||||||
|
"tray-controls": "Abrir/Fechar no clique da bandeja"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Prioridade de notificação",
|
||||||
|
"toast-style": "Estilo de alerta",
|
||||||
|
"unpause-notification": "Mostrar notificação ao despausar"
|
||||||
|
},
|
||||||
|
"name": "Notificações"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Permite mudar o aplicativo para o modo picture-in-picture",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Sempre no topo",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Tecla de atalho",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Tecla de atalho"
|
||||||
|
},
|
||||||
|
"label": "Escolha uma tecla de atalho para alternar o picture-in-picture",
|
||||||
|
"title": "Tecla de atalho picture-in-picture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Salvar posição da janela",
|
||||||
|
"save-window-size": "Salvar tamanho da janela",
|
||||||
|
"use-native-pip": "Use PiP nativo do navegador"
|
||||||
|
},
|
||||||
|
"name": "Picture-in-picture",
|
||||||
|
"templates": {
|
||||||
|
"button": "Picture-in-picture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Ouça rápido, ouça devagar! Adiciona um controle deslizante que controla a velocidade da música",
|
||||||
|
"name": "Velocidade de reprodução",
|
||||||
|
"templates": {
|
||||||
|
"button": "Velocidade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Controle o volume com precisão usando a roda do mouse/teclas de atalho, com um HUD personalizado e etapas de volume personalizáveis",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Controles locais das teclas de seta",
|
||||||
|
"custom-volume-steps": "Definir etapas de volume personalizadas",
|
||||||
|
"global-shortcuts": "Teclas de atalho globais"
|
||||||
|
},
|
||||||
|
"name": "Volume preciso",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Diminuir o volume",
|
||||||
|
"increase": "Aumentar o volume"
|
||||||
|
},
|
||||||
|
"label": "Escolha atalhos de teclado de volume global:",
|
||||||
|
"title": "Atalhos de teclado de volume global"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Escolha as etapas de aumento/diminuição de volume",
|
||||||
|
"title": "Etapas de volume"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Qualidade atual: {{quality}}",
|
||||||
|
"message": "Escolha a qualidade do vídeo:",
|
||||||
|
"title": "Escolha a qualidade do vídeo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Permite alterar a qualidade do vídeo com um botão na sobreposição de vídeo",
|
||||||
|
"name": "Trocador de qualidade do vídeo"
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Permite definir teclas de atalho globais para reprodução (reproduzir/pausar/próximo/anterior) e desligar o OSD de mídia substituindo as teclas de mídia, ativando Ctrl/CMD + F para pesquisar, ativando o suporte Linux MPRIS para teclas de mídia e teclas de atalho personalizadas para usuários avançados.",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Substituir teclas de mídia",
|
||||||
|
"set-keybinds": "Definir controles globais de música"
|
||||||
|
},
|
||||||
|
"name": "Atalhos (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Próximo",
|
||||||
|
"play-pause": "Reproduzir/Pausar",
|
||||||
|
"previous": "Anterior"
|
||||||
|
},
|
||||||
|
"label": "Escolha atalhos de teclado globais para controle de músicas:",
|
||||||
|
"title": "Atalhos globais do teclado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Pula músicas com 'não gostei'",
|
||||||
|
"name": "Pular músicas com 'não gostei'"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Pular automaticamente seções de silêncio nas músicas",
|
||||||
|
"name": "Pular silêncios"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Ignora automaticamente partes não musicais, como introdução/final ou partes de videoclipes onde a música não está tocando",
|
||||||
|
"name": "SponsorBlock (bloqueador de patrocínios)"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Controle a reprodução na barra de tarefas do Windows",
|
||||||
|
"name": "Controle de mídia da barra de tarefas"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Adiciona um widget TouchBar para usuários do macOS",
|
||||||
|
"name": "Barra de toque"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Integração com o plugin Tuna do OBS",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Adiciona um botão para alternar entre o modo Vídeo/Música. Também pode, opcionalmente, remover toda a guia de vídeo",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Alinhamento",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Esquerda",
|
||||||
|
"middle": "Centro",
|
||||||
|
"right": "Direita"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Forçar remoção da guia de vídeo",
|
||||||
|
"mode": {
|
||||||
|
"label": "Modo",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Alternar personalizado",
|
||||||
|
"disabled": "Desabilitado",
|
||||||
|
"native": "Alternar nativo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Botão de Alternar Vídeo",
|
||||||
|
"templates": {
|
||||||
|
"button": "Música"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Adiciona um visualizador ao player",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Tipo de visualizador"
|
||||||
|
},
|
||||||
|
"name": "Visualizer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,49 +1,167 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Ошибка загрузки плагина {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Плагин {{pluginName}}::{{contextName}} загружен за {{ms}}мс",
|
||||||
|
"initialize-failed": "Ошибка инициализации плагина \"{{pluginName}}\"",
|
||||||
|
"load-all": "Загружаем все плагины",
|
||||||
|
"load-failed": "Ошибка загрузки плагина \"{{pluginName}}\"",
|
||||||
|
"loaded": "Плагин \"{{pluginName}}\" загружен",
|
||||||
|
"unload-failed": "Ошибка выгрузки плагина \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Плагин \"{{pluginName}}\" выгружен"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"code": "ru",
|
"code": "ru",
|
||||||
"local-name": "Русский",
|
"local-name": "Русский",
|
||||||
"name": "Russian"
|
"name": "Russian"
|
||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Загрузка завершена. DevTools открыт"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n загружен"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Получена команда по протоколу: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS файл \"{{cssFile}}\" не существует, игнорирую"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Приложение не отвечает!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Очищаю кеш приложения"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Окно попробовало открыться за пределами экрана, размер окна={{windowSize}}, разрешение экрана={{displaySize}}, местоположение={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Меню скрыто, 'Alt' чтобы показать его ('Escape' если используете внутреннее меню приложения)",
|
||||||
|
"message": "Скрытие меню включено",
|
||||||
|
"title": "Включено скрытие меню"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Позже",
|
||||||
|
"restart-now": "Перезапустить сейчас"
|
||||||
|
},
|
||||||
|
"detail": "Перезагрузите приложение для включения плагина {{pluginName}}",
|
||||||
|
"message": "{{pluginName}} нуждается в перезагрузке",
|
||||||
|
"title": "Нужна перезагрузка"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Выйти",
|
||||||
|
"relaunch": "Перезапустить",
|
||||||
|
"wait": "Подождать"
|
||||||
|
},
|
||||||
|
"detail": "Извините за причиненные неудобства! Пожалуйста, выберите, что делать:",
|
||||||
|
"message": "Приложение не отвечает",
|
||||||
|
"title": "Окно не отвечает"
|
||||||
|
},
|
||||||
"update-available": {
|
"update-available": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"download": "Download",
|
"disable": "Отключить обновления",
|
||||||
|
"download": "Скачать",
|
||||||
"ok": "OK"
|
"ok": "OK"
|
||||||
}
|
},
|
||||||
|
"detail": "Новая версия доступна для загрузки на {{downloadLink}}",
|
||||||
|
"message": "Доступная новая версия",
|
||||||
|
"title": "Доступно обновление"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
|
"about": "О приложении",
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"label": "Navigation"
|
"label": "Навигация",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Скопировать текущий URL",
|
||||||
|
"go-back": "Вернуться",
|
||||||
|
"go-forward": "Вперед",
|
||||||
|
"quit": "Выйти",
|
||||||
|
"restart": "Перезапустить приложение"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"label": "Options",
|
"label": "Настройки",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"advanced-options": {
|
"advanced-options": {
|
||||||
|
"label": "Расширенные настройки",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Очищать кеш при запуске приложения",
|
||||||
|
"disable-hardware-acceleration": "Отключить аппаратное ускорение",
|
||||||
|
"edit-config-json": "Редактировать config.json",
|
||||||
|
"override-user-agent": "Переопределить User-Agent",
|
||||||
|
"restart-on-config-changes": "Перезапускать при изменениях конфига",
|
||||||
"set-proxy": {
|
"set-proxy": {
|
||||||
"label": "Set proxy",
|
"label": "Задать прокси",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "Set proxy"
|
"label": "Введите прокси адрес (оставьте поле пустым для отключения)",
|
||||||
|
"placeholder": "Пример: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Задать прокси"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"toggle-dev-tools": "Включить DevTools"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"auto-update": "Auto Update",
|
"always-on-top": "Поверх остальных окон",
|
||||||
"start-at-login": "Start at login",
|
"auto-update": "Автообновление",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Меню будет скрыто при следующем запуске, используйте [Alt] чтобы показать его (или [`] если используйте меню внутри приложения)",
|
||||||
|
"title": "Скрытие меню включено"
|
||||||
|
},
|
||||||
|
"label": "Скрыть меню"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Язык будет сменён после перезапуска",
|
||||||
|
"title": "Язык изменён"
|
||||||
|
},
|
||||||
|
"label": "Язык",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Хотите помочь с переводом? Кликните сюда"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Продолжать последнюю песню при запуске приложения",
|
||||||
|
"single-instance-lock": "Запрет запуска нескольких экземпляров",
|
||||||
|
"start-at-login": "Запуск при включении компьютера",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Стартовая страница",
|
||||||
|
"unset": "Не настроено"
|
||||||
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"label": "Tray"
|
"label": "Трей",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Отключено",
|
||||||
|
"enabled-and-hide-app": "Включено и скрывать приложение",
|
||||||
|
"enabled-and-show-app": "Включено и показывать приложение",
|
||||||
|
"play-pause-on-click": "Пауза/продолжить при нажатии"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"visual-tweaks": {
|
"visual-tweaks": {
|
||||||
|
"label": "Визуальные настройки",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"like-buttons": {
|
"like-buttons": {
|
||||||
"default": "Default"
|
"default": "Default",
|
||||||
|
"force-show": "Всегда показывать",
|
||||||
|
"hide": "Скрывать",
|
||||||
|
"label": "Кнопка лайка"
|
||||||
},
|
},
|
||||||
|
"remove-upgrade-button": "Убрать кнопку Youtube Premium",
|
||||||
"theme": {
|
"theme": {
|
||||||
"label": "Theme",
|
"label": "Тема",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"no-theme": "No theme"
|
"import-css-file": "Импортировать кастомный CSS файл",
|
||||||
|
"no-theme": "Без темы"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,10 +169,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"label": "Plugins"
|
"enabled": "Включено",
|
||||||
|
"label": "Плагины",
|
||||||
|
"new": "НОВИНКА"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"label": "View"
|
"label": "Вид",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Принудительная перезагрузка",
|
||||||
|
"reload": "Перезагрузить",
|
||||||
|
"reset-zoom": "Текущий размер",
|
||||||
|
"toggle-fullscreen": "Включить полноэкранный режим",
|
||||||
|
"zoom-in": "Приблизить",
|
||||||
|
"zoom-out": "Отдалить"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Следующий",
|
||||||
|
"play-pause": "Пауза/Продолжить",
|
||||||
|
"previous": "Предыдущий",
|
||||||
|
"quit": "Выйти",
|
||||||
|
"restart": "Перезагрузить приложение",
|
||||||
|
"show": "Показать окно",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -66,12 +206,24 @@
|
|||||||
},
|
},
|
||||||
"name": "Блокировщик рекламы"
|
"name": "Блокировщик рекламы"
|
||||||
},
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Добавляет кнопки Undislike, Dislike и Unlike, чтобы применять их на все композиции в плейлисте или альбоме",
|
||||||
|
"name": "Действия с альбомом"
|
||||||
|
},
|
||||||
"album-color-theme": {
|
"album-color-theme": {
|
||||||
"description": "Применяет динамическую тему и визуальные эффекты на основе цветовой палитры альбома",
|
"description": "Применяет динамическую тему и визуальные эффекты на основе цветовой палитры альбома",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Соотношение цветов",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "Цветовая тема альбома"
|
"name": "Цветовая тема альбома"
|
||||||
},
|
},
|
||||||
"ambient-mode": {
|
"ambient-mode": {
|
||||||
"description": "Применяет световой эффект, отбрасывая мягкие цвета из видео на задний фон вашего экрана.",
|
"description": "Применяет световой эффект, отбрасывая мягкие цвета из видео на задний фон вашего экрана",
|
||||||
"menu": {
|
"menu": {
|
||||||
"blur-amount": {
|
"blur-amount": {
|
||||||
"label": "Степень размытия",
|
"label": "Степень размытия",
|
||||||
@ -106,7 +258,7 @@
|
|||||||
"smoothness-transition": {
|
"smoothness-transition": {
|
||||||
"label": "Плавный переход",
|
"label": "Плавный переход",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"during": "В течение {{interpolationTime}}s"
|
"during": "В течение {{interpolationTime}} s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"use-fullscreen": {
|
"use-fullscreen": {
|
||||||
@ -129,21 +281,78 @@
|
|||||||
},
|
},
|
||||||
"captions-selector": {
|
"captions-selector": {
|
||||||
"description": "Выбор субтитров для аудиотреков в YouTube Music",
|
"description": "Выбор субтитров для аудиотреков в YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Автоматически выбирать последние использованные субтитры",
|
||||||
|
"disable-captions": "Без субтитров по умолчанию"
|
||||||
|
},
|
||||||
"name": "Выбор субтитров",
|
"name": "Выбор субтитров",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"selector": {
|
"selector": {
|
||||||
"none": "None"
|
"label": "Текущий язык субтитров:{{language}}",
|
||||||
|
"none": "Нет",
|
||||||
|
"title": "Выберите язык субтитров"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Открыть выбор субтитров"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Боковая панель всегда в компактном режиме",
|
||||||
|
"name": "Компактная боковая панель"
|
||||||
|
},
|
||||||
"crossfade": {
|
"crossfade": {
|
||||||
|
"description": "Плавный переход между песнями",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Расширенное"
|
||||||
|
},
|
||||||
|
"name": "Плавный переход [бета]",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"options": {
|
"options": {
|
||||||
"multi-input": {
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Длительность постепенного появления (мс)",
|
||||||
|
"fade-out-duration": "Длительность затухания (мс)",
|
||||||
"fade-scaling": {
|
"fade-scaling": {
|
||||||
"linear": "Linear"
|
"label": "Сила перехода",
|
||||||
}
|
"linear": "Линейный",
|
||||||
}
|
"logarithmic": "Логарифмический"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Плавных переход за N секунд перед концом"
|
||||||
|
},
|
||||||
|
"title": "Настройки плавного перехода"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Запускает песню сразу на паузе",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Применимо только при запуске"
|
||||||
|
},
|
||||||
|
"name": "Отключить Автопроигрыш"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Попытка подключения при уже существующем соединении",
|
||||||
|
"connected": "Подключено к Discord",
|
||||||
|
"disconnected": "Отключено от Discord"
|
||||||
|
},
|
||||||
|
"description": "Покажите своим друзьям что вы слушаете с помощью Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Авто переподключение",
|
||||||
|
"clear-activity": "Очистить активность",
|
||||||
|
"clear-activity-after-timeout": "Очищать активность после таймаута",
|
||||||
|
"connected": "Подключено",
|
||||||
|
"disconnected": "Отключено",
|
||||||
|
"hide-duration-left": "Скрыть сколько осталось времени",
|
||||||
|
"hide-github-button": "Скрыть ссылку на GitHub",
|
||||||
|
"play-on-youtube-music": "Воспроизвести на YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Поставить таймер неактивности"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Введите таймер неактивности в секундах:",
|
||||||
|
"title": "Задать таймер неактивности"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -154,67 +363,320 @@
|
|||||||
"buttons": {
|
"buttons": {
|
||||||
"ok": "OK"
|
"ok": "OK"
|
||||||
},
|
},
|
||||||
|
"message": "Ой-ой! Прошу простить, загрузка дала сбой…",
|
||||||
"title": "Error in download!"
|
"title": "Error in download!"
|
||||||
},
|
},
|
||||||
"start-download-playlist": {
|
"start-download-playlist": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"ok": "OK"
|
"ok": "OK"
|
||||||
}
|
},
|
||||||
|
"detail": "({{playlistSize}} песен)",
|
||||||
|
"message": "Скачиваем плейлист {{playlistTitle}}",
|
||||||
|
"title": "Загрузка началась"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"download-progress": "Download: {{percent}}%"
|
"conversion-progress": "Конвертация: {{percent}}%",
|
||||||
|
"converting": "Конвертация…",
|
||||||
|
"done": "Выполнено: {{filePath}}",
|
||||||
|
"download-info": "Скачиваем {{artist}}-{{title}} {{videoId}}",
|
||||||
|
"download-progress": "Download: {{percent}}%",
|
||||||
|
"downloading": "Скачиваем…",
|
||||||
|
"downloading-counter": "Скачиваем {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Скачиваем плейлист \"{{playlistTitle}}\" - {{playlistSize}} песен {{playlistId}}",
|
||||||
|
"error-while-downloading": "Ошибка при скачивании \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "Папка {{playlistFolder}} уже существует",
|
||||||
|
"getting-playlist-info": "Получаем информацию о плейлисте…",
|
||||||
|
"loading": "Загрузка…",
|
||||||
|
"playlist-has-only-one-song": "В плейлисте только одна песня, скачиваем напрямую",
|
||||||
|
"playlist-id-not-found": "Плейлист не найден",
|
||||||
|
"playlist-is-empty": "Плейлист пуст",
|
||||||
|
"playlist-is-mix-or-private": "Ошибка при получении данных о плейлисте: убедитесь что он не приватный или \"Только для вас\" плейлист\n\n{{error}}",
|
||||||
|
"preparing-file": "Подготавливаем файл…",
|
||||||
|
"saving": "Сохраняем…",
|
||||||
|
"trying-to-get-playlist-id": "Пытаемся получить ID плейлиста: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Видео не найдено",
|
||||||
|
"writing-id3": "Пишем ID3 теги…"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"description": "Скачивать MP3 / исходное аудио напрямую из интерфейса",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Выберите папку для загрузок",
|
||||||
|
"download-playlist": "Скачать плейлист",
|
||||||
|
"presets": "Пресеты",
|
||||||
|
"skip-existing": "Пропускать уже существующие файлы"
|
||||||
|
},
|
||||||
|
"name": "Загрузчик",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Невозможно обновить прогресс"
|
||||||
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Download"
|
"button": "Скачать"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"last-fm": {
|
"exponential-volume": {
|
||||||
"name": "Last.fm"
|
"description": "Делает слайдер громкости расширенным чтобы было легче понижать громкость.",
|
||||||
|
"name": "Расширенная громкость"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Придает меню цветовую схему альбома",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Скрыть элементы управления окном DOM"
|
||||||
|
},
|
||||||
|
"name": "Меню в приложении"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Добавляет поддержку Lumia Stream",
|
||||||
|
"name": "Lumia Stream [бета]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Добавляет поддержку текстов для большинства композиций",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Романизированный текст (any -> en)"
|
||||||
|
},
|
||||||
|
"name": "Тексты песен от Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Текст от Genius был получен"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Поделитесь плейлистом с другими. Когда хост играет песню, все остальные слышат ту же песню",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Введите ID хоста"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Сохранить",
|
||||||
|
"track-source": "Источник трека",
|
||||||
|
"unknown-user": "Неизвестный пользователь"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Копировать 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 [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Не удалось добавить песню",
|
||||||
|
"closed": "Music Together закрыт",
|
||||||
|
"disconnected": "Music Together отключен",
|
||||||
|
"host-failed": "Не удалось запустить Music Together",
|
||||||
|
"id-copied": "ID хоста скопирован в буфер обмена",
|
||||||
|
"id-copy-failed": "Не удалось скопировать айди хоста в буфер обмена",
|
||||||
|
"join-failed": "Не удалось присоединиться к Music Together",
|
||||||
|
"joined": "Присоединился к Music Together",
|
||||||
|
"permission-changed": "Разрешения Music Together изменены на \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Не удалось удалить песню",
|
||||||
|
"user-connected": "{{name}} присоединился к Music Together",
|
||||||
|
"user-disconnected": "{{name}} отключился от Music Together"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"name": "Navigation"
|
"description": "Стрелки навигации \"вперед/назад\" интегрированы в интерфейс, как в вашем любимом браузере",
|
||||||
|
"name": "Навигация"
|
||||||
},
|
},
|
||||||
"no-google-login": {
|
"no-google-login": {
|
||||||
"name": "No Google Login"
|
"description": "Убрать из интерфейса кнопки и ссылки для входа через Google",
|
||||||
|
"name": "Нет входа в систему Google"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"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": "Сохранить положение окна",
|
||||||
|
"save-window-size": "Сохранить размер окна",
|
||||||
|
"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": "Добавьте поддержку скробблинга (например, last.fm, Listenbrainz)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Настройки API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Введите токен пользователя ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Скробблинг других медиа"
|
||||||
|
},
|
||||||
|
"name": "Скробблер",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Ключ API Last.fm",
|
||||||
|
"api-secret": "Секрет API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Введите токен пользователя ListenBrainz:",
|
||||||
|
"title": "Токен ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
|
"description": "Позволяет задать глобальные горячие клавиши для воспроизведения (play/pause/next/previous) и отключить экранное мультимедийное меню, изменяя мультимедийные клавиши, также включает Ctrl/CMD + F для поиска, добавляет поддержку Linux MPRIS для мультимедийных клавиш, а также пользовательские горячие клавиши для опытных пользователей",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Переопределение мультимедийных клавиш",
|
||||||
|
"set-keybinds": "Настройка глобальных элементов управления песней"
|
||||||
|
},
|
||||||
|
"name": "Ярлыки (и MPRIS)",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"keybind": {
|
"keybind": {
|
||||||
"keybind-options": {
|
"keybind-options": {
|
||||||
"next": "Next"
|
"next": "Следующая",
|
||||||
}
|
"play-pause": "Воспроизведение / Пауза",
|
||||||
|
"previous": "Предыдущая"
|
||||||
|
},
|
||||||
|
"label": "Выберите глобальные привязки клавиш для управления песнями:",
|
||||||
|
"title": "Глобальные привязки клавиш"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Пропускает непонравившиеся песни",
|
||||||
|
"name": "Пропустить непонравившиеся песни"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Автоматически пропускает тихие моменты в песнях",
|
||||||
|
"name": "Пропустить тишину"
|
||||||
|
},
|
||||||
"sponsorblock": {
|
"sponsorblock": {
|
||||||
|
"description": "Автоматически пропускает не музыкальные фрагменты, например интро/аутро или фрагменты музыкальных клипов, в которых песня не звучит (тишина)",
|
||||||
"name": "SponsorBlock"
|
"name": "SponsorBlock"
|
||||||
},
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Управляйте воспроизведением с панели задач Windows",
|
||||||
|
"name": "Управление мультимедиа на панели задач"
|
||||||
|
},
|
||||||
"touchbar": {
|
"touchbar": {
|
||||||
"name": "TouchBar"
|
"description": "Добавляет виджет тачбара для пользователей macOS",
|
||||||
|
"name": "Тачбар"
|
||||||
},
|
},
|
||||||
"tuna-obs": {
|
"tuna-obs": {
|
||||||
|
"description": "Интеграция с плагином Tuna от OBS",
|
||||||
"name": "Tuna OBS"
|
"name": "Tuna OBS"
|
||||||
},
|
},
|
||||||
"video-toggle": {
|
"video-toggle": {
|
||||||
|
"description": "Добавляет кнопку для переключения между режимами видео и песни. Также можно удалить всю вкладку с видео",
|
||||||
"menu": {
|
"menu": {
|
||||||
"align": {
|
"align": {
|
||||||
|
"label": "Выравнивание",
|
||||||
"submenu": {
|
"submenu": {
|
||||||
"middle": "Middle",
|
"left": "Слева",
|
||||||
"right": "Right"
|
"middle": "По центру",
|
||||||
|
"right": "Справа"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"force-hide": "Принудительное скрыть видео",
|
||||||
"mode": {
|
"mode": {
|
||||||
"label": "Mode"
|
"label": "Режим",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Кастомный переключатель",
|
||||||
|
"disabled": "Отключен",
|
||||||
|
"native": "Стандартный переключатель"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"name": "Переключатель видео",
|
||||||
"templates": {
|
"templates": {
|
||||||
"button": "Song"
|
"button": "Песня"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Заменяет обложку визуализатором музыки",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Вид визуализации"
|
||||||
|
},
|
||||||
|
"name": "Визуалайзер"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
128
src/i18n/resources/th.json
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "ปลั๊กอิน {{pluginName}}::{{contextName}} ไม่สามารถทำงานได้",
|
||||||
|
"executed-at-ms": "ปลั๊กอิน {{pluginName}}::{{contextName}} ทำงานแล้วที่ {{ms}}ms",
|
||||||
|
"initialize-failed": "ไม่สามารถเริ่มต้นปลั๊กอิน \"{{pluginName}}\"",
|
||||||
|
"load-all": "กำลังโหลดปลั๊กอินทั้งหมด",
|
||||||
|
"load-failed": "ไม่สามารถโหลดปลั๊กอิน \"{{pluginName}}\"",
|
||||||
|
"loaded": "โหลดปลั๊กอิน \"{{pluginName}}\" แล้ว",
|
||||||
|
"unload-failed": "ล้มเหลวในการยกเลิกการโหลดปลั๊กอิน \"{{pluginName}}\"",
|
||||||
|
"unloaded": "ยกเลิกการโหลดปลั๊กอิน \"{{pluginName}}\" แล้ว"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "th",
|
||||||
|
"local-name": "ภาษาไทย",
|
||||||
|
"name": "Thai"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "การโหลดเสร็จสิ้น DevTools ได้ถูกเปิดแล้ว"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "โหลด i18n แล้ว"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "คำสั่งที่ได้รับผ่านโปรโตคอล: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "กำลังเพิกเฉยไฟล์ CSS \"{{cssFile}}\" เนื่องจากไม่มีอยู่"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "มีข้อผิดพลาดจากไม่การตอบสนอง!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "กำลังล้างแคชของแอป"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "หน้าต่างพยายามแสดงผลเกินขนาดหน้าจอ windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "เลิก"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "ปิดใช้งานการอัปเดต",
|
||||||
|
"download": "ดาวน์โหลด",
|
||||||
|
"ok": "ตกลง"
|
||||||
|
},
|
||||||
|
"detail": "มีเวอร์ชันใหม่ให้ดาวน์โหลดแล้วที่ {{downloadLink}}",
|
||||||
|
"message": "มีเวอร์ชันใหม่ให้ใช้งานแล้ว",
|
||||||
|
"title": "อัปเดตพร้อมใช้งาน"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "เกี่ยวกับ",
|
||||||
|
"navigation": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"feedback": {
|
||||||
|
"download-info": "กำลังดาวน์โหลด {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "ดาวน์โหลด: {{percent}}%",
|
||||||
|
"downloading": "กำลังดาวน์โหลด…",
|
||||||
|
"downloading-counter": "กำลังดาวน์โหลด {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "กำลังดาวน์โหลดเพลย์ลีสต์ \"{{playlistTitle}}\" - {{playlistSize}} เพลง ({{playlistId}})",
|
||||||
|
"error-while-downloading": "เกิดข้อผิดพลาดในการดาวน์โหลด \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "มีโฟลเดอร์ {{playlistFolder}} อยู่แล้ว",
|
||||||
|
"getting-playlist-info": "กำลังรับข้อมูลเพลย์ลิสต์…",
|
||||||
|
"loading": "กำลังโหลด…",
|
||||||
|
"playlist-has-only-one-song": "เพลย์ลิสต์มีเพียงเพลงเดียวเท่านั้น กำลังดาวน์โหลดเพลงนั้นโดยตรง",
|
||||||
|
"playlist-id-not-found": "ไม่พบ ID เพลย์ลิสต์",
|
||||||
|
"playlist-is-empty": "เพลย์ลิสต์ว่างเปล่า",
|
||||||
|
"playlist-is-mix-or-private": "เกิดข้อผิดพลาดในการรับข้อมูลเพลย์ลิสต์: ตรวจสอบให้แน่ใจว่าไม่ใช่เพลย์ลิสต์ส่วนตัวหรือเพลย์ลิสต์ \"มิกซ์สำหรับคุณ\"\n\n{{error}}",
|
||||||
|
"preparing-file": "กำลังเตรียมไฟล์…",
|
||||||
|
"saving": "กำลังบันทึก…",
|
||||||
|
"trying-to-get-playlist-id": "กำลังพยายามรับ ID เพลย์ลิสต์: {{playlistId}}",
|
||||||
|
"video-id-not-found": "ไม่พบวิดีโอ",
|
||||||
|
"writing-id3": "กำลังเขียนแท็ก ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "ดาวน์โหลด MP3 / เสียงต้นฉบับโดยตรงจากอินเทอร์เฟซ",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "เลือกโฟลเดอร์ดาวน์โหลด",
|
||||||
|
"download-playlist": "ดาวน์โหลดเพลย์ลิสต์",
|
||||||
|
"skip-existing": "ข้ามไฟล์ที่มีอยู่แล้ว"
|
||||||
|
},
|
||||||
|
"name": "ตัวดาวน์โหลด",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "ไม่สามารถอัปเดตความคืบหน้าได้"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "ดาวน์โหลด"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
682
src/i18n/resources/tr.json
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "{{pluginName}}::{{contextName}} eklentisi çalıştırılamadı",
|
||||||
|
"executed-at-ms": "{{pluginName}}::{{contextName}} eklentisi {{ms}}ms'de çalıştırıldı",
|
||||||
|
"initialize-failed": "\"{{pluginName}}\" eklentisi başlatılamadı",
|
||||||
|
"load-all": "Tüm eklentiler yükleniyor",
|
||||||
|
"load-failed": "\"{{pluginName}}\" eklentisi yüklenemedi",
|
||||||
|
"loaded": "\"{{pluginName}}\" eklentisi yüklendi",
|
||||||
|
"unload-failed": "\"{{pluginName}}\" eklentisi çıkartılamadı",
|
||||||
|
"unloaded": "\"{{pluginName}}\" eklentisi çıkartıldı"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "tr",
|
||||||
|
"local-name": "Türkçe",
|
||||||
|
"name": "Turkish"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Yükleme tamamlandı. Geliştirici araçları açıldı"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n yüklendi"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Protokol üzerinden alınan komut: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "\"{{cssFile}}\" adlı CSS dosyası bulunamadı, yok sayılıyor"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Yanıt verilmedi!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Uygulama ön belleği temizleme"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Pencere ekranın dışında oluşturulmaya çalışıldı, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Menü gizli, göstermek için 'Alt' tuşunu kullanın (veya Uygulama İçi Menüyü kullanıyorsanız 'Escape' tuşunu kullanın)",
|
||||||
|
"message": "Menüyü gizle etkinleştirildi",
|
||||||
|
"title": "Menüyü gizle etkinleştirildi"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Daha Sonra",
|
||||||
|
"restart-now": "Şimdi yeniden başlat"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" eklentisinin çalışabilmesi için yeniden başlatman gerekiyor",
|
||||||
|
"message": "\"{{pluginName}}\" için yeniden başlatman gerekiyor",
|
||||||
|
"title": "Uygulamayı yeniden başlatman gerekiyor"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Çıkış",
|
||||||
|
"relaunch": "Tekrar Başlat",
|
||||||
|
"wait": "Bekle"
|
||||||
|
},
|
||||||
|
"detail": "Rahatsızlık için özür dileriz! Lütfen ne yapacağınızı seçin:",
|
||||||
|
"message": "Uygulama yanıt vermiyor",
|
||||||
|
"title": "Pencere yanıt vermiyor"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Güncellemeleri devre dışı bırak",
|
||||||
|
"download": "İndir",
|
||||||
|
"ok": "Tamam"
|
||||||
|
},
|
||||||
|
"detail": "Yeni bir sürüm mevcut. {{downloadLink}} adresi üzerinden indirebilirsin",
|
||||||
|
"message": "Yeni bir sürüm mevcut",
|
||||||
|
"title": "Güncelleme Mevcut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Hakkında",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Navigasyon",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Geçerli Url'yi kopyala",
|
||||||
|
"go-back": "Geri dön",
|
||||||
|
"go-forward": "İlerle",
|
||||||
|
"quit": "Çıkış",
|
||||||
|
"restart": "Uygulamayı Yeniden Başlat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Seçenekler",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Gelişmiş Seçenekler",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Uygulama başlatıldığında uygulama önbelleğini sıfırla",
|
||||||
|
"disable-hardware-acceleration": "Donanım hızlandırmayı devre dışı bırak",
|
||||||
|
"edit-config-json": "Düzenle config.json",
|
||||||
|
"override-user-agent": "\"User-Agent \"ı geçersiz kıl",
|
||||||
|
"restart-on-config-changes": "Yapılandırma değişikliğinde yeniden başlat",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Proxy ayarla",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Proxy Adresini Gir: (devre dışı bırakmak için boş bırakın)",
|
||||||
|
"placeholder": "Örnek: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Proxy ayarla"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "DevTools'u Aç / Kapat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Her zaman üstte",
|
||||||
|
"auto-update": "Otomatik Güncelleme",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Menü bir sonraki açılışta gizlenecektir, göstermek için [Alt] tuşunu kullanın (veya uygulama-içi-menü kullanıyorsanız [`] tuşuna geri basın)",
|
||||||
|
"title": "Gizli Menü Aktif"
|
||||||
|
},
|
||||||
|
"label": "Gizli Menü"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Dil değişikliği yeniden başlattıktan sonra etkinleşecektir",
|
||||||
|
"title": "Dil değiştirildi"
|
||||||
|
},
|
||||||
|
"label": "Dil",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Çeviriye yardım etmek ister misiniz? Buraya tıklayın"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Uygulama başlatıldığında son şarkıyı devam ettir",
|
||||||
|
"single-instance-lock": "Tek Örnek Kilidi",
|
||||||
|
"start-at-login": "Başlangıçta çalıştır",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Başlangıç sayfası",
|
||||||
|
"unset": "Ayarlanmadı"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Tepsi",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Devre Dışı",
|
||||||
|
"enabled-and-hide-app": "Uygulamayı etkinleştirin gizleyin",
|
||||||
|
"enabled-and-show-app": "Etkinleştir ve uygulamayı göster",
|
||||||
|
"play-pause-on-click": "Tıklandığında Oynat/Duraklat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Görsel İnce Ayarlar",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Varsayılan",
|
||||||
|
"force-show": "Zorla göster",
|
||||||
|
"hide": "Gizle",
|
||||||
|
"label": "Beğenme düğmeleri"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Yükseltme düğmesini kaldır",
|
||||||
|
"theme": {
|
||||||
|
"label": "Tema",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Özel CSS dosyanı içeri aktar",
|
||||||
|
"no-theme": "Tema Yok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Aktif",
|
||||||
|
"label": "Eklentiler",
|
||||||
|
"new": "YENİ"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Görüntü",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Zorla yeniden başlat",
|
||||||
|
"reload": "Yeniden Başlat",
|
||||||
|
"reset-zoom": "Asıl Boyut",
|
||||||
|
"toggle-fullscreen": "Tam Ekran'a Geçiş",
|
||||||
|
"zoom-in": "Yakınlaştır",
|
||||||
|
"zoom-out": "Uzaklaştır"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Sonraki",
|
||||||
|
"play-pause": "Oynat/Durdur",
|
||||||
|
"previous": "Önceki",
|
||||||
|
"quit": "Çıkış",
|
||||||
|
"restart": "Yeniden başlat",
|
||||||
|
"show": "Pencereyi görüntüle",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Müzik",
|
||||||
|
"with-song-info": "YouTube Müzik: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Tüm reklamları ve izleyicileri engelle",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Engelleyici"
|
||||||
|
},
|
||||||
|
"name": "Reklam Engelleyici"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Çalma listesindeki veya albümdeki tüm şarkılara Beğendim ve Beğenmedim düğmeleri ekler",
|
||||||
|
"name": "Albüm Eylemleri"
|
||||||
|
},
|
||||||
|
"album-color-theme": {
|
||||||
|
"description": "Albümün renk paletine dayalı dinamik bir tema ve efektler uygular",
|
||||||
|
"menu": {
|
||||||
|
"color-mix-ratio": {
|
||||||
|
"label": "Renk karışım oranı",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{ratio}}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Albüm Renk Teması"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Videodaki yumuşak renkleri ekranınızın arka planına yansıtarak bir ışık efekti uygular",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Bulanıklık miktarı",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} piksel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Önbellek",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Opaklık Miktarı",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Kalite",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} piksel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Boyut",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Yumuşak Geçiş",
|
||||||
|
"submenu": {
|
||||||
|
"during": "{{interpolationTime}} saniye boyunca"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Tam ekran kullanılıyor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Ambiyans Modu"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Ses sıkıştırma (dalganın en gürültülü bölümlerinin ses düzeyini azaltır ve daha yumuşak bölümlerin ses düzeyini artırır)",
|
||||||
|
"name": "Ses Sıkıştırma"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Gezinme çubuğunu şeffaf ve bulanık yapar",
|
||||||
|
"name": "Navigasyon barını bulanıklaştır"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "YouTube yaş doğrulamasını atla",
|
||||||
|
"name": "Yaş doğrulamasını atla"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "YouTube Music için altyazı seçici",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Son kullanılan altyazıyı otomatik olarak seç",
|
||||||
|
"disable-captions": "Varsayılan olarak altyazı yok"
|
||||||
|
},
|
||||||
|
"name": "Altyazı Seçici",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Geçerli altyazı dili: {{language}}",
|
||||||
|
"none": "Hiçbiri",
|
||||||
|
"title": "Altyazı dilini seç"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Altyazı seçiciyi aç"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Her zaman kompakt kenar çubugu",
|
||||||
|
"name": "Kompakt Kenar Çubuğu"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Şarkılar arasında Çapraz Geçiş",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Gelişmiş"
|
||||||
|
},
|
||||||
|
"name": "Çapraz Geçiş [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Güçlenme süresi (ms)",
|
||||||
|
"fade-out-duration": "Zayıflama süresi (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Zayıflama Ölçeği",
|
||||||
|
"linear": "Doğrusal",
|
||||||
|
"logarithmic": "Logaritmik"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Bitişten N saniye önce çapraz geçiş"
|
||||||
|
},
|
||||||
|
"title": "Çapraz Geçiş ayarları"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Şarkıların otomatik olarak duraklatılmasını sağlar",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Yalnızca ilk şarkı için geçerlidir"
|
||||||
|
},
|
||||||
|
"name": "Otomatik oynatmayı devre dışı bırak"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Aktif bağlantı olduğu halde bağlantı kurulmaya çalışıldı",
|
||||||
|
"connected": "Discord'a bağlandı",
|
||||||
|
"disconnected": "Discord ile bağlantı kesildi"
|
||||||
|
},
|
||||||
|
"description": "Rich Presence ile Discord'da ne dinlediğinizi gösterin",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Otomatik yeniden bağlan",
|
||||||
|
"clear-activity": "Etkinliği temizle",
|
||||||
|
"clear-activity-after-timeout": "Zaman aşımından sonra etkinliği temizle",
|
||||||
|
"connected": "Bağlı",
|
||||||
|
"disconnected": "Bağlantı kesildi",
|
||||||
|
"hide-duration-left": "Kalan süreyi gizle",
|
||||||
|
"hide-github-button": "GitHub bağlantısını gizle",
|
||||||
|
"play-on-youtube-music": "YouTube Music de oynat",
|
||||||
|
"set-inactivity-timeout": "Hareketsizlik zaman aşımını ayarla"
|
||||||
|
},
|
||||||
|
"name": "Discord Etkinlik Durumu",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Hareketsizlik zaman aşımını saniye cinsinden girin:",
|
||||||
|
"title": "Hareketsizlik zaman aşımını ayarla"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Tamam"
|
||||||
|
},
|
||||||
|
"message": "Argh! Özür dilerim, indirme başarısız oldu…",
|
||||||
|
"title": "İndirme sırasında bir hata meydana geldi!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Tamam"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} şarkı)",
|
||||||
|
"message": "Oynatma listesini indir : {{playlistTitle}}",
|
||||||
|
"title": "İndirme Başladı"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Dönüştürme : {{percent}}%",
|
||||||
|
"converting": "Dönüştürülüyor…",
|
||||||
|
"done": "Tamamlandı: {{filePath}}",
|
||||||
|
"download-info": "{{artist}} - {{title}} [{{videoId}} indiriliyor",
|
||||||
|
"download-progress": "İndirme : {{percent}}%",
|
||||||
|
"downloading": "İndiriliyor…",
|
||||||
|
"downloading-counter": "İndiriliyor {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "\"{{playlistTitle}}\" şarkı listesi indiriliyor - {{playlistSize}} şarkı ({{playlistId}})",
|
||||||
|
"error-while-downloading": "\"{{author}} - {{title}}\" indirilirken hata oluştu: {{error}}",
|
||||||
|
"folder-already-exists": "{{playlistFolder}} klasörü zaten mevcut",
|
||||||
|
"getting-playlist-info": "Oynatma listesi bilgisi alınıyor…",
|
||||||
|
"loading": "Yükleniyor…",
|
||||||
|
"playlist-has-only-one-song": "Oynatma listesinde yalnızca bir şarkı var, doğrudan indiriliyor",
|
||||||
|
"playlist-id-not-found": "Oynatma listesi ID'si bulunamadı",
|
||||||
|
"playlist-is-empty": "Oynatma listesi boş",
|
||||||
|
"playlist-is-mix-or-private": "Çalma listesi bilgisi alınırken hata oluştu: özel veya \"Size özel karışık\" bir çalma listesi olmadığından emin olun\n\n{{error}}",
|
||||||
|
"preparing-file": "Dosya Hazırlanıyor…",
|
||||||
|
"saving": "Kaydediliyor…",
|
||||||
|
"trying-to-get-playlist-id": "Çalma listesi ID'si alınmaya çalışılıyor: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Video bulunamadı",
|
||||||
|
"writing-id3": "ID3 etiketleri yazılıyor…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "MP3 / kaynak sesini doğrudan arayüzden indir",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "İndirme klasörünü seç",
|
||||||
|
"download-playlist": "Oynatma listesini indir",
|
||||||
|
"presets": "Hazır Ayarlar",
|
||||||
|
"skip-existing": "Mevcut dosyaları atla"
|
||||||
|
},
|
||||||
|
"name": "İndirici",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "İlerleme güncellenemiyor"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "İndir"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Ses seviyesi kaydırıcısını üstel hale getirir, böylece daha düşük ses seviyelerini seçmek daha kolay olur.",
|
||||||
|
"name": "Üstel Ses Seviyesi"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Menü çubuklarına süslü, koyu veya albüm renginde bir görünüm verir",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "DOM penceresi kontrollerini gizle"
|
||||||
|
},
|
||||||
|
"name": "Uygulama İçi Menü"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Lumia Stream desteği ekler",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Çoğu şarkı için şarkı sözü desteği ekler",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Romanlaştırılmış Şarkı Sözleri"
|
||||||
|
},
|
||||||
|
"name": "Genius Şarkı Sözleri",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Şarkı sözleri genius tarafından alındı"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Oynatma listesini başkalarıyla paylaşın. Sunucu sahibi bir şarkı çaldığında, diğer herkes aynı şarkıyı duyacak",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Sunucu ID'si Girin"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Kaydet",
|
||||||
|
"track-source": "Parça Kaynağı",
|
||||||
|
"unknown-user": "Bilinmeyen Kullanıcı"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Sunucu ID'sini kopyala",
|
||||||
|
"close": "Birlikte Müziği Kapat",
|
||||||
|
"connected-users": "Bağlanan Kullanıcılar",
|
||||||
|
"disconnect": "Birlikte Müzik Bağlantısını Kesin",
|
||||||
|
"empty-user": "Bağlı kullanıcı bulunmuyor",
|
||||||
|
"host": "Birlikte Müzik Sunucusu",
|
||||||
|
"join": "Birlikte Müziğe Katıl",
|
||||||
|
"permission": {
|
||||||
|
"all": "Konukların oynatma listesini ve oynatıcıyı kontrol etmesine izin ver",
|
||||||
|
"host-only": "Çalma listesini ve oynatıcıyı yalnızca yönetici kontrol edebilir",
|
||||||
|
"playlist": "Konukların oynatma listesini kontrol etmesine izin ver"
|
||||||
|
},
|
||||||
|
"set-permission": "Kontrol İznini Değiştir",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Bağlantı kesildi",
|
||||||
|
"guest": "Misafir olarak bağlandı",
|
||||||
|
"host": "Sunucu Sahibi olarak bağlandı"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Birlikte Müzik [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Şarkı eklenirken bir hata meydana geldi",
|
||||||
|
"closed": "Birlikte Müzik kapatıldı",
|
||||||
|
"disconnected": "Birlikte Müzik bağlantı kesildi",
|
||||||
|
"host-failed": "Birlikte Müzik sunucusu kurulamadı",
|
||||||
|
"id-copied": "Sunucu ID'si kopyalandı",
|
||||||
|
"id-copy-failed": "Sunucu ID'si panoya kopyalanamadı",
|
||||||
|
"join-failed": "Birlikte Müziğe katılırken bir hata meydana geldi",
|
||||||
|
"joined": "Birlikte Müziğe Katıldı",
|
||||||
|
"permission-changed": "Birlikte Müzik yetkisi \"{{permission}}\" olarak değiştirildi",
|
||||||
|
"remove-song-failed": "Şarkı kaldırılırken bir hata meydana geldi",
|
||||||
|
"user-connected": "{{name}} Birlikte Müziğe Katıldı",
|
||||||
|
"user-disconnected": "{{name}} Birlikte Müzik'ten ayrıldı"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Favori tarayıcınızdaki gibi doğrudan arayüze entegre edilmiş İleri/Geri gezinme okları",
|
||||||
|
"name": "Navigasyon"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Google giriş düğmelerini ve bağlantılarını arayüzden kaldır",
|
||||||
|
"name": "Google Girişini Kaldır"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Bir şarkı çalmaya başladığında bir bildirim görüntüler (etkileşimli bildirimler Windows'ta mevcuttur)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "İnteraktif Bildirimler",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "İnteraktif Ayarlar",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Buton metnini gizle",
|
||||||
|
"refresh-on-play-pause": "Oynat/Duraklat'ta Yenile",
|
||||||
|
"tray-controls": "Tepsi tıklamasıyla Aç/Kapat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Bildirim Önceliği",
|
||||||
|
"toast-style": "Bildirim Tarzı",
|
||||||
|
"unpause-notification": "Şarkı tekrar oynatılınca bildirim göster"
|
||||||
|
},
|
||||||
|
"name": "Bildirimler"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Uygulamayı resim-içinde-resim moduna geçirmeye izin verir",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Her zaman üstte",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Kısayol",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Kısayol"
|
||||||
|
},
|
||||||
|
"label": "Resim-içinde-resim arasında geçiş yapmak için bir kısayol tuşu seçin",
|
||||||
|
"title": "Resim-içinde-resim Kısayol Tuşu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Pencere konumunu kaydet",
|
||||||
|
"save-window-size": "Pencere boyutunu kaydet",
|
||||||
|
"use-native-pip": "Tarayıcı yerel PiP'sini kullan"
|
||||||
|
},
|
||||||
|
"name": "Resim-içinde-resim",
|
||||||
|
"templates": {
|
||||||
|
"button": "Resim-içinde-resim"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Hızlı dinle, yavaş dinle! Şarkı hızını kontrol eden bir kaydırıcı ekler",
|
||||||
|
"name": "Oynatma Hızı",
|
||||||
|
"templates": {
|
||||||
|
"button": "Hız"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Özel bir HUD ve özelleştirilebilir ses seviyesi adımları ile fare tekerleği / kısayol tuşlarını kullanarak ses seviyesini hassas bir şekilde kontrol edin",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Yerel Ok Tuşu Kontrolleri",
|
||||||
|
"custom-volume-steps": "Özel Ses Seviyesi Adımlarını Ayarlama",
|
||||||
|
"global-shortcuts": "Genel Kısayol Tuşları"
|
||||||
|
},
|
||||||
|
"name": "Hassas Ses Seviyesi",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Ses Seviyesi Azaltma",
|
||||||
|
"increase": "Ses Seviyesi Yükseltme"
|
||||||
|
},
|
||||||
|
"label": "Genel Ses Tuş Atamalarını seçin:",
|
||||||
|
"title": "Genel Ses Tuş Atamaları"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Ses Artırma/Azaltma Kademelerini Seçin",
|
||||||
|
"title": "Ses Kademeleri"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Mevcut Kalite: {{quality}}",
|
||||||
|
"message": "Video Kalitesini Seçin:",
|
||||||
|
"title": "Video Kalitesini Seçin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Video katmanı üzerindeki bir düğme ile video kalitesinin değiştirilmesine izin verir",
|
||||||
|
"name": "Video Kalitesi Değiştirici"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Listeleme desteği ekler (lastfm, listenbrainz ve benzeri)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API Ayarları"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "ListenBrainz kullanıcı kimliğinizi girin"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Diğer medya ortamlarında listele"
|
||||||
|
},
|
||||||
|
"name": "Listeleyici",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API anahtarı",
|
||||||
|
"api-secret": "Last.fm API gizli anahtar"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "ListenBrainz kullanıcı kimliğinizi girin:",
|
||||||
|
"title": "ListenBrainz kimliği"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Oynatma için global kısayol tuşları (oynat/duraklat/sonraki/önceki) ayarlamaya ve medya tuşlarını geçersiz kılarak medya OSD'sini kapatmaya, arama yapmak için Ctrl/CMD + F tuşlarını açmaya, medya tuşları için Linux MPRIS desteğini açmaya ve ileri düzey kullanıcılar için özel kısayol tuşlarına izin verir",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Medya Tuşlarını Geçersiz Kıl",
|
||||||
|
"set-keybinds": "Global Şarkı Kontrollerini Ayarla"
|
||||||
|
},
|
||||||
|
"name": "Kısayollar (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "İler",
|
||||||
|
"play-pause": "Oynat / Durdur",
|
||||||
|
"previous": "Önceki"
|
||||||
|
},
|
||||||
|
"label": "Şarkı Kontrolü için Genel Tuş Atamaları'nı seçin:",
|
||||||
|
"title": "Genel Tuş Atamaları"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Beğenmediğin şarkıları atlar",
|
||||||
|
"name": "Beğenmediklerini Atla"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Şarkılardaki sessiz bölümleri otomatik olarak atlar",
|
||||||
|
"name": "Sessizlikleri Atla"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Giriş/Çıkış gibi müzik olmayan kısımları veya müzik videolarında şarkının çalmadığı kısımları otomatik olarak atlar",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Windows görev çubuğu üzerinden oynatmayı kontrol edebilmenize olanak sağlar",
|
||||||
|
"name": "Görev Çubuğu Medya Kontrolü"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "macOS kullanıcıları için bir TouchBar widget'ı ekler",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "OBS eklentisi Tuna ile entegrasyon sağlar",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Video/Şarkı modu arasında geçiş yapmak için bir düğme ekler. ayrıca isteğe bağlı olarak tüm video sekmesini kaldırabilir",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Hizalama",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Sol",
|
||||||
|
"middle": "Orta",
|
||||||
|
"right": "Sağ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Video sekmesini kaldırmaya zorla",
|
||||||
|
"mode": {
|
||||||
|
"label": "Mod",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Özel Ayar",
|
||||||
|
"disabled": "Devre dışı",
|
||||||
|
"native": "Yerel geçiş"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Video Geçiş",
|
||||||
|
"templates": {
|
||||||
|
"button": "Şarkı"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Oynatıcıya bir görselleştirici ekler",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Görselleştirici Tipi"
|
||||||
|
},
|
||||||
|
"name": "Görselleştirici"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
682
src/i18n/resources/uk.json
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Не вдалося запустити плагін {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Плагін {{pluginName}}::{{contextName}} запущено за {{ms}}ms",
|
||||||
|
"initialize-failed": "Не вдалося ініціалізувати плагін \"{{pluginName}}\"",
|
||||||
|
"load-all": "Завантаження всіх плагінів",
|
||||||
|
"load-failed": "Не вдалося завантажити плагін \"{{pluginName}}\"",
|
||||||
|
"loaded": "Плагін \"{{pluginName}}\" завантажено",
|
||||||
|
"unload-failed": "Не вдалося вивантажити плагін \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Плагін \"{{pluginName}}\" вивантажено"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "uk",
|
||||||
|
"local-name": "Українська",
|
||||||
|
"name": "Ukrainian"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Завантаження завершено. Відкрито DevTools"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n завантажено"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Отримано команду за протоколом: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Файл CSS \"{{cssFile}}\" не існує, ігноруємо"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Невідповідна помилка!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Очищення кешу програми"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Вікно намагалися відобразитися поза екраном, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Меню приховано, використовуйте \"Alt\", щоб показати його (або \"Escape\", якщо ви використовуєте меню в додатку)",
|
||||||
|
"message": "Приховане меню увімкнено",
|
||||||
|
"title": "Увімкнути Приховане Меню"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Пізніше",
|
||||||
|
"restart-now": "Перезапустити зараз"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" плагін потребує перезапуску, щоб набути чинності",
|
||||||
|
"message": "\"{{pluginName}}\" необхідно перезапустити",
|
||||||
|
"title": "Потрібен перезапуск"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Вийти",
|
||||||
|
"relaunch": "Перезапустити",
|
||||||
|
"wait": "Чекати"
|
||||||
|
},
|
||||||
|
"detail": "Перепрошуємо за незручності! Будь ласка, оберіть, що робити:",
|
||||||
|
"message": "Програма не відповідає",
|
||||||
|
"title": "Вікно не відповідає"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Вимкнути оновлення",
|
||||||
|
"download": "Завантажити",
|
||||||
|
"ok": "Так"
|
||||||
|
},
|
||||||
|
"detail": "Доступна нова версія, яку можна завантажити за посиланням {{downloadLink}}",
|
||||||
|
"message": "Доступна нова версія",
|
||||||
|
"title": "Доступне оновлення"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Про нас",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Навігація",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Копіювати поточну URL-адресу",
|
||||||
|
"go-back": "Назад",
|
||||||
|
"go-forward": "Вперед",
|
||||||
|
"quit": "Вихід",
|
||||||
|
"restart": "Перезапустити програму"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Параметри",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Додаткові опції",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Очищення кешу програми при її запуску",
|
||||||
|
"disable-hardware-acceleration": "Вимкнути апаратне прискорення",
|
||||||
|
"edit-config-json": "Редагувати config.json",
|
||||||
|
"override-user-agent": "Змінити User-Agent",
|
||||||
|
"restart-on-config-changes": "Перезапуск після зміни конфігурації",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Задати проксі",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Введіть адресу проксі-сервера: (залиште порожнім, щоб вимкнути)",
|
||||||
|
"placeholder": "Приклад: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Задати проксі"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Перемкнути DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Завжди зверху",
|
||||||
|
"auto-update": "Автооновлення",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Меню буде приховано при наступному запуску, використовуйте [Alt], щоб показати його (або клавішу [`], якщо ви використовуєте вбудоване меню)",
|
||||||
|
"title": "Приховане меню Увімкнено"
|
||||||
|
},
|
||||||
|
"label": "Приховане меню"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Мова буде змінена після перезапуску",
|
||||||
|
"title": "Мову змінено"
|
||||||
|
},
|
||||||
|
"label": "Мова",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Хочете допомогти з перекладом? Клацніть тут"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Відновлювати останню пісню при запуску програми",
|
||||||
|
"single-instance-lock": "Заблокувати єдиним інстансом",
|
||||||
|
"start-at-login": "Запустити при вході в систему",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Початкова сторінка",
|
||||||
|
"unset": "Не задано"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Таця",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Вимкнено",
|
||||||
|
"enabled-and-hide-app": "Запустити та приховати програму",
|
||||||
|
"enabled-and-show-app": "Запустити та відобразити додаток",
|
||||||
|
"play-pause-on-click": "Відтворення/Пауза за натисканням"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Візуальні налаштування",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "За замовчуванням",
|
||||||
|
"force-show": "Завжди показувати",
|
||||||
|
"hide": "Приховати",
|
||||||
|
"label": "Як кнопки"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Прибрати кнопку оновлення",
|
||||||
|
"theme": {
|
||||||
|
"label": "Тема",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Імпортувати власний CSS файл",
|
||||||
|
"no-theme": "Без теми"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Увімкнено",
|
||||||
|
"label": "Плагіни",
|
||||||
|
"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": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{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": "Обхід перевірки віку на YouTube",
|
||||||
|
"name": "Обхід вікових обмежень"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Вибір субтитрів для аудіодоріжок YouTube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Автоматичний вибір останніх використаних субтитрів",
|
||||||
|
"disable-captions": "За замовчуванням субтитри відсутні"
|
||||||
|
},
|
||||||
|
"name": "Вибір субтитрів",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Поточна мова субтитрів: {{language}}",
|
||||||
|
"none": "Нема",
|
||||||
|
"title": "Виберіть мову субтитрів"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Відкрити селектор субтитрів"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Бічна панель завжди в компактному режимі",
|
||||||
|
"name": "Компактна бічна панель"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Плавний перехід між піснями",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Розширене"
|
||||||
|
},
|
||||||
|
"name": "Плавний перехід[Бета]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Тривалість згасання (мс)",
|
||||||
|
"fade-out-duration": "Тривалість згасання (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Згладжування масштабування",
|
||||||
|
"linear": "Лінійна",
|
||||||
|
"logarithmic": "Логарифмічна"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Плавний перехід за N секунд до кінця"
|
||||||
|
},
|
||||||
|
"title": "Параметри плавного переходу"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Запуск пісні в режимі \"пауза\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Застосовується тільки при запуску"
|
||||||
|
},
|
||||||
|
"name": "Вимкнути автовідтворення"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Спроба підключення при активному з'єднанні",
|
||||||
|
"connected": "Під'єднано до Discord",
|
||||||
|
"disconnected": "Від'єднано від Discord"
|
||||||
|
},
|
||||||
|
"description": "Покажіть друзям, що ви слухаєте за допомогою Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Автоматичне перепідключення",
|
||||||
|
"clear-activity": "Очистити активність",
|
||||||
|
"clear-activity-after-timeout": "Очистити активність після тайм-ауту",
|
||||||
|
"connected": "Під'єднано",
|
||||||
|
"disconnected": "Від'єднано",
|
||||||
|
"hide-duration-left": "Приховати тривалість, яка залишилася",
|
||||||
|
"hide-github-button": "Приховати посилання на GitHub",
|
||||||
|
"play-on-youtube-music": "Слухати на YouTube Music",
|
||||||
|
"set-inactivity-timeout": "Встановити тайм-аут бездіяльності"
|
||||||
|
},
|
||||||
|
"name": "Активність Discord",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Введіть тайм-аут бездіяльності в секундах:",
|
||||||
|
"title": "Встановлення тайм-ауту бездіяльності"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Добре"
|
||||||
|
},
|
||||||
|
"message": "Йой! При завантаженні виникла помилка…",
|
||||||
|
"title": "Помилка при завантаженні!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "OK"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} пісень)",
|
||||||
|
"message": "Завантаження списку відтворення {{playlistTitle}}",
|
||||||
|
"title": "Завантаження розпочато"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Конвертація: {{percent}}%",
|
||||||
|
"converting": "Конвертація…",
|
||||||
|
"done": "Готово: {{filePath}}",
|
||||||
|
"download-info": "Завантаження {{artist}} - {{title}} [{{videoId}}]",
|
||||||
|
"download-progress": "Завантажено: {{percent}}%",
|
||||||
|
"downloading": "Завантаження…",
|
||||||
|
"downloading-counter": "Завантажено {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Завантаження плейлисту \"{{playlistTitle}}\" - {{playlistSize}} пісень ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Помилка завантаження \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "Папка {{playlistFolder}} вже існує",
|
||||||
|
"getting-playlist-info": "Отримання інформації про плейлист…",
|
||||||
|
"loading": "Завантаження…",
|
||||||
|
"playlist-has-only-one-song": "Плейлист має лише один елемент, завантаження якого відбувається безпосередньо",
|
||||||
|
"playlist-id-not-found": "Ідентифікатор плейлиста не знайдено",
|
||||||
|
"playlist-is-empty": "Плейлист порожній",
|
||||||
|
"playlist-is-mix-or-private": "Помилка при отриманні інформації про плейлист: переконайтеся, що це не приватний плейлист або плейлист \"Мікс для вас\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Готуємо файл…",
|
||||||
|
"saving": "Збереження…",
|
||||||
|
"trying-to-get-playlist-id": "Спроба отримати ідентифікатора плейлиста: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Відео не знайдено",
|
||||||
|
"writing-id3": "Пишемо ID3-теги…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Завантажує MP3 / джерело аудіо безпосередньо з інтерфейсу",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Оберіть папку для завантаження",
|
||||||
|
"download-playlist": "Завантажити плейлист",
|
||||||
|
"presets": "Попередні налаштування",
|
||||||
|
"skip-existing": "Пропустити наявні файли"
|
||||||
|
},
|
||||||
|
"name": "Завантажувач",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Неможливо оновити прогрес"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Завантажити"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Робить регулятор гучності експоненціальним, що полегшує вибір тихих рівнів гучності.",
|
||||||
|
"name": "Експоненціальна гучність"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Надає панелям меню вишуканий темний або кольоровий вигляд, схожий на альбом",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Сховати елементи керування вікном DOM"
|
||||||
|
},
|
||||||
|
"name": "Меню в програмі"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Додано підтримку для Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Бета]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Додає підтримку текстів для більшості пісень",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Романізовані тексти"
|
||||||
|
},
|
||||||
|
"name": "Тексти з Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Тексти надано Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Поділитись музикою. Коли хост включає пісню, всі інші будуть чути ту ж пісню",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Введіть ID хоста"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Зберегти",
|
||||||
|
"track-source": "Джерело композиції",
|
||||||
|
"unknown-user": "Невідомий користувач"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Скопіювати хост ID",
|
||||||
|
"close": "Вимкнути сумісне прослуховування",
|
||||||
|
"connected-users": "Підключені користувачі",
|
||||||
|
"disconnect": "Відключитись від Music Together",
|
||||||
|
"empty-user": "Немає підключених користувачів",
|
||||||
|
"host": "Хост Music Together",
|
||||||
|
"join": "Приєднатися до Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "Дозволити гостям керувати списком відтворення та плеєром",
|
||||||
|
"host-only": "Лише хост може керувати списком відтворення та плеєром",
|
||||||
|
"playlist": "Дозволити гостям керувати списком відтворення"
|
||||||
|
},
|
||||||
|
"set-permission": "Змінити дозвіл на керування",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Відключено",
|
||||||
|
"guest": "Підключено як гість",
|
||||||
|
"host": "Підключено як хост"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [Бета]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Не вдалося додати пісню",
|
||||||
|
"closed": "Music Together закритий",
|
||||||
|
"disconnected": "Music Together відключено",
|
||||||
|
"host-failed": "Не вдалося увімкнути Music Together",
|
||||||
|
"id-copied": "ID хоста скопійовано в буфер обміну",
|
||||||
|
"id-copy-failed": "Не вдалося скопіювати ID хоста в буфер обміну",
|
||||||
|
"join-failed": "Не вдалося приєднатися до Music Together",
|
||||||
|
"joined": "Приєднано до Music Together",
|
||||||
|
"permission-changed": "Дозвіл Music Together змінено на \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Не вдалося видалити пісню",
|
||||||
|
"user-connected": "{{name}} приєднався до Music Together",
|
||||||
|
"user-disconnected": "{{name}} вийшов з Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Стрілки навігації Вперед/Назад безпосередньо інтегровані в інтерфейс, як у вашому браузері, який ви використовуєте",
|
||||||
|
"name": "Навігація"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Видалити кнопки та посилання для входу через Google з інтерфейсу",
|
||||||
|
"name": "Без входу в Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Відображати сповіщення, коли пісня починає грати (інтерактивні сповіщення доступні в Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Інтерактивні сповіщення",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Інтерактивні налаштування",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Сховати текст кнопки",
|
||||||
|
"refresh-on-play-pause": "Оновлення при відтворенні/паузі",
|
||||||
|
"tray-controls": "Відкриття/закриття при натисканні на значок в області повідомлень (tray)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Пріоритет повідомлень",
|
||||||
|
"toast-style": "Стиль спливаючих повідомлень",
|
||||||
|
"unpause-notification": "Показувати повідомлення при відновленні відтворення після паузи"
|
||||||
|
},
|
||||||
|
"name": "Сповіщення"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Дозволяє перемикати програму в режим «картинка в картинці»",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Завжди наверху",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Гаряча клавіша",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Гаряча клавіша"
|
||||||
|
},
|
||||||
|
"label": "Оберіть гарячу клавішу для перемикання режиму зображення в зображенні",
|
||||||
|
"title": "Гаряча клавіша для режиму зображення в зображенні"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Зберегти положення вікна",
|
||||||
|
"save-window-size": "Зберегти розмір вікна",
|
||||||
|
"use-native-pip": "Використовувати вбудований режим \"картинка-у-картинці\" браузера"
|
||||||
|
},
|
||||||
|
"name": "Картинка-у-картинці",
|
||||||
|
"templates": {
|
||||||
|
"button": "Картинка-у-картинці"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Додає слайдер, який керує швидкістю відтворення пісні",
|
||||||
|
"name": "Швидкість відтворення",
|
||||||
|
"templates": {
|
||||||
|
"button": "Швидкість"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Точне керування гучністю за допомогою колеса миші/гарячих клавіш, з власним інтерфейсом користувача та настроюваними кроками гучності",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Локальне керування за допомогою клавіш зі стрілками",
|
||||||
|
"custom-volume-steps": "Встановити власні кроки гучності",
|
||||||
|
"global-shortcuts": "Глобальні гарячі клавіші"
|
||||||
|
},
|
||||||
|
"name": "Точна гучність",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Зменшити гучність",
|
||||||
|
"increase": "Збільшити гучність"
|
||||||
|
},
|
||||||
|
"label": "Вибрати глобальні комбінації клавіш для зміни гучності:",
|
||||||
|
"title": "Глобальні комбінації клавіш для регулювання гучності"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Вибрати кроки збільшення/зменшення гучності",
|
||||||
|
"title": "Кроки гучності"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Поточна якість: {{quality}}",
|
||||||
|
"message": "Вибрати якість відео:",
|
||||||
|
"title": "Виберіть якість відео"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Дозволяє змінювати якість відео за допомогою кнопки на відео оверлеї",
|
||||||
|
"name": "Зміна якості відео"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Додає підтримку скроблінгу (last.fm, Listenbrainz тощо)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Налаштування API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Ввести токен користувача ListenBrainz"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "Скробилити інші медіа"
|
||||||
|
},
|
||||||
|
"name": "Скроблер",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Ключ API Last.fm",
|
||||||
|
"api-secret": "Секрет API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Введіть ваш токен користувача ListenBrainz:",
|
||||||
|
"title": "Токен ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Дозволяє встановлювати глобальні гарячі клавіші для управління відтворенням (відтворення/пауза/наступний/попередній), вимикаючи OSD для мультимедійних клавіш, увімкнення пошуку за допомогою Ctrl/CMD + F, увімкнення підтримки Linux MPRIS для мультимедійних клавіш та власних гарячих клавіш для досвідчених користувачів",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Перевизначити мультимедійні клавіші",
|
||||||
|
"set-keybinds": "Встановити глобальні комбінації клавіш"
|
||||||
|
},
|
||||||
|
"name": "Гарячі клавіші (і MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Наступний",
|
||||||
|
"play-pause": "Відтворення / Пауза",
|
||||||
|
"previous": "Попередній"
|
||||||
|
},
|
||||||
|
"label": "Виберіть глобальні комбінації клавіш для керування піснями:",
|
||||||
|
"title": "Глобальні комбінації клавіш"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Пропускає пісні що не сподобались",
|
||||||
|
"name": "Пропускати пісні що не сподобались"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Автоматично пропускати тишу в піснях",
|
||||||
|
"name": "Пропуск тиші"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Автоматично пропускати немузичні частини, такі як вступ/закінчення або частини музичних відеороликів, де не відтворюється музика",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Керування відтворенням з панелі завдань Windows",
|
||||||
|
"name": "Керування медіа на панелі завдань"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Додає віджет TouchBar для користувачів macOS",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Інтеграція з плагіном Tuna для OBS",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Додає кнопку для перемикання між режимом відео і режимом пісні. Також може опціонально видаляти вкладку відео",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Вирівнювання",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Зліва",
|
||||||
|
"middle": "По центру",
|
||||||
|
"right": "Справа"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Примусово видалити вкладку відео",
|
||||||
|
"mode": {
|
||||||
|
"label": "Режим",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Власний перемикач",
|
||||||
|
"disabled": "Вимкнено",
|
||||||
|
"native": "Вбудований перемикач"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Перемикач відео",
|
||||||
|
"templates": {
|
||||||
|
"button": "Пісня"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Додати візуалізацію до плеєра",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Тип візуалізації"
|
||||||
|
},
|
||||||
|
"name": "Візуалізація"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
673
src/i18n/resources/vi.json
Normal file
@ -0,0 +1,673 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "Lỗi khi bắt đầu phần mở rộng {{pluginName}}::{{contextName}}",
|
||||||
|
"executed-at-ms": "Phần mở rộng {{pluginName}}::{{contextName}} đã bắt đầu trong {{ms}}ms",
|
||||||
|
"initialize-failed": "Lỗi khi khởi động phần mở rộng \"{{pluginName}}\"",
|
||||||
|
"load-all": "Đang tải tất cả phần mở rộng",
|
||||||
|
"load-failed": "Lỗi khi tải phần mở rộng\"{{pluginName}}\"",
|
||||||
|
"loaded": "Đã tải phần mở rộng \"{{pluginName}}\"",
|
||||||
|
"unload-failed": "Lỗi khi hủy tải phần mở rộng \"{{pluginName}}\"",
|
||||||
|
"unloaded": "Đã hủy tải phần mở rộng \"{{pluginName}}\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "vi",
|
||||||
|
"local-name": "Tiếng Việt",
|
||||||
|
"name": "Vietnamese"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "Đã tải xong. Đã mở Công cụ dành cho nhà phát triển"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n đã được tải"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "Đã nhận được lệnh qua giao thức: \"{{command}}\""
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "Tệp tin CSS \"{{cssFile}}\"không tồn tại, đang bỏ qua"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "Lỗi không phản hồi!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "Xóa bộ nhớ đệm ứng dụng"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "Cửa sổ đã cố gắng hiển thị ngoài màn hình, windowSize={{windowSize}}, displaySize={{displaySize}}, location={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "Menu đã ẩn, ấn phím 'Alt' để hiện menu (hoặc ấn 'Escape' nếu bạn đang bật In-app Menu)",
|
||||||
|
"message": "Ẩn Menu đã được bật",
|
||||||
|
"title": "Ẩn Menu đã được bật"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "Để sau",
|
||||||
|
"restart-now": "Khởi động lại ngay"
|
||||||
|
},
|
||||||
|
"detail": "Tiện ích mở rộng \"{{pluginName}}\" yêu cầu khởi động lại ứng dụng để áp dụng",
|
||||||
|
"message": "\"{{pluginName}}\" cần khởi động lại",
|
||||||
|
"title": "Yêu cầu khởi động lại"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "Thoát",
|
||||||
|
"relaunch": "Khởi chạy lại",
|
||||||
|
"wait": "Đợi"
|
||||||
|
},
|
||||||
|
"detail": "Chúng tôi xin lỗi về sự bất tiện này! hãy chọn việc cần làm:",
|
||||||
|
"message": "Ứng dụng không phản hồi",
|
||||||
|
"title": "Cửa sổ không phản hồi"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "Tắt cập nhật",
|
||||||
|
"download": "Tải xuống",
|
||||||
|
"ok": "Đồng ý"
|
||||||
|
},
|
||||||
|
"detail": "Đã có phiên bản mới hơn, bạn có thể tải xuống tại {{downloadLink}}",
|
||||||
|
"message": "Đã có phiên bản mới",
|
||||||
|
"title": "Cập nhật có sẵn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "Giới thiệu",
|
||||||
|
"navigation": {
|
||||||
|
"label": "Điều hướng",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "Copy URL hiện tại",
|
||||||
|
"go-back": "Quay lại",
|
||||||
|
"go-forward": "Tiến về trước",
|
||||||
|
"quit": "Thoát",
|
||||||
|
"restart": "Khởi động lại ứng dụng"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "Tùy chọn",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "Tùy chọn nâng cao",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "Làm mới bộ nhớ đệm khi khởi động ứng dụng",
|
||||||
|
"disable-hardware-acceleration": "Vô hiệu hóa tăng tốc phần cứng",
|
||||||
|
"edit-config-json": "Chỉnh sửa config.json",
|
||||||
|
"override-user-agent": "Ghi đè User-Agent",
|
||||||
|
"restart-on-config-changes": "Khởi động lại khi thay đổi cấu hình",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "Cài đặt proxy",
|
||||||
|
"prompt": {
|
||||||
|
"label": "Nhập địa chỉ Proxy: (để trống nếu muốn tắt)",
|
||||||
|
"placeholder": "Ví dụ: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "Cài proxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "Bật/tắt DevTools"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "Luôn ở trên cùng",
|
||||||
|
"auto-update": "Tự động cập nhật",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Menu sẽ bị ẩn khi ứng dụng được chạy vào lần tới, dùng phím [Alt] để hiện nó (hoặc phím [`] nếu sử dụng in-app-menu)",
|
||||||
|
"title": "Ẩn Menu đã được bật"
|
||||||
|
},
|
||||||
|
"label": "Ẩn Menu"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "Ngôn ngữ sẽ được thay đổi sau khi ứng dụng khởi động lại",
|
||||||
|
"title": "Ngôn ngữ đã thay đổi"
|
||||||
|
},
|
||||||
|
"label": "Ngôn ngữ",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "Bạn muốn giúp dịch? Bấm vào đây"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "Tiếp tục bài hát cuối cùng khi ứng dụng khởi động",
|
||||||
|
"single-instance-lock": "Khóa một trường hợp",
|
||||||
|
"start-at-login": "Bắt đầu lúc đăng nhập",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "Trang bắt đầu",
|
||||||
|
"unset": "Bỏ thiết đặt"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "Khay",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "Vô hiệu hóa",
|
||||||
|
"enabled-and-hide-app": "Đã bật và ẩn ứng dụng",
|
||||||
|
"enabled-and-show-app": "Đã bật và hiển thị ứng dụng",
|
||||||
|
"play-pause-on-click": "Phát/Tạm dừng khi nhấp chuột"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "Tinh chỉnh hình ảnh",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "Mặc định",
|
||||||
|
"force-show": "Tập trung hiển thị",
|
||||||
|
"hide": "Ẩn",
|
||||||
|
"label": "Nút thích"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "Xóa nút nâng cấp",
|
||||||
|
"theme": {
|
||||||
|
"label": "Chủ đề",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "Nhập tệp CSS tùy chỉnh",
|
||||||
|
"no-theme": "Không có chủ đề"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "Đã bật",
|
||||||
|
"label": "Trình bổ sung",
|
||||||
|
"new": "MỚI"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"label": "Xem",
|
||||||
|
"submenu": {
|
||||||
|
"force-reload": "Buộc tải lại",
|
||||||
|
"reload": "Tải lại",
|
||||||
|
"reset-zoom": "Kích thước thực",
|
||||||
|
"toggle-fullscreen": "Bật chế độ toàn màn hình",
|
||||||
|
"zoom-in": "Phóng to",
|
||||||
|
"zoom-out": "Thu nhỏ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"next": "Tiếp theo",
|
||||||
|
"play-pause": "Phát/Tạm Dừng",
|
||||||
|
"previous": "Trước",
|
||||||
|
"quit": "Thoát",
|
||||||
|
"restart": "Khởi động lại ứng dụng",
|
||||||
|
"show": "Hiện cửa sổ",
|
||||||
|
"tooltip": {
|
||||||
|
"default": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"adblocker": {
|
||||||
|
"description": "Chặn toàn bộ quảng cáo và trình theo dõi",
|
||||||
|
"menu": {
|
||||||
|
"blocker": "Trình chặn"
|
||||||
|
},
|
||||||
|
"name": "Chặn quảng cáo"
|
||||||
|
},
|
||||||
|
"album-actions": {
|
||||||
|
"description": "Thêm nút hủy không thích, không thích, thích và không thích để áp dụng cho tất cả danh sách phát hoặc album",
|
||||||
|
"name": "Tác vụ với album"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"name": "Màu nền album"
|
||||||
|
},
|
||||||
|
"ambient-mode": {
|
||||||
|
"description": "Áp dụng hiệu ứng ánh sáng bằng cách truyền các màu nhẹ từ video vào nền màn hình của bạn",
|
||||||
|
"menu": {
|
||||||
|
"blur-amount": {
|
||||||
|
"label": "Lượng mờ",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{blurAmount}} điểm ảnh"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"label": "Bộ đệm",
|
||||||
|
"submenu": {
|
||||||
|
"buffer": "{{buffer}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"label": "Độ mờ",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "Chất lượng",
|
||||||
|
"submenu": {
|
||||||
|
"pixels": "{{quality}} điểm ảnh"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"label": "Kích thước",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{size}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smoothness-transition": {
|
||||||
|
"label": "Độ mượt chuyển cảnh",
|
||||||
|
"submenu": {
|
||||||
|
"during": "Trong {{interpolationTime}} s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"use-fullscreen": {
|
||||||
|
"label": "Dùng chế độ toàn màn hình"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Chế độ Môi trường xung quanh"
|
||||||
|
},
|
||||||
|
"audio-compressor": {
|
||||||
|
"description": "Áp dụng tính năng nén cho âm thanh (giảm âm lượng của phần to nhất của tín hiệu và tăng âm lượng của phần nhỏ nhất)",
|
||||||
|
"name": "Bộ nén âm thanh"
|
||||||
|
},
|
||||||
|
"blur-nav-bar": {
|
||||||
|
"description": "Làm mờ và trong suốt thanh điều hướng",
|
||||||
|
"name": "Thanh điều hướng mờ"
|
||||||
|
},
|
||||||
|
"bypass-age-restrictions": {
|
||||||
|
"description": "Bỏ qua xác minh độ tuổi của YouTube",
|
||||||
|
"name": "Bỏ qua hạn chế độ tuổi"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "Bộ lựa chọn phụ đề cho các bài hát trên Youtube Music",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "Tự động chọn phụ đề vừa sử dụng",
|
||||||
|
"disable-captions": "Không có phụ đề đặt làm mặc định"
|
||||||
|
},
|
||||||
|
"name": "Bộ lựa chọn phụ đề",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "Ngôn ngữ phụ đề hiện tại: {{language}}",
|
||||||
|
"none": "Không có",
|
||||||
|
"title": "Chọn ngôn ngữ phụ đề"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "Mở lựa chọn phụ đề"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "Luôn đặt thanh bên cạnh ở chế độ thu gọn",
|
||||||
|
"name": "Thanh bên thu gọn"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "Chuyển tiếp giữa các bài hát",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "Nâng cao"
|
||||||
|
},
|
||||||
|
"name": "Xen kẽ [thử nghiệm]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "Xuất hiện mờ dần trong khoảng thời gian (ms)",
|
||||||
|
"fade-out-duration": "Khoảng thời gian hoát ra mờ dần (ms)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "Làm mờ theo tỉ lệ",
|
||||||
|
"linear": "Trực tuyến",
|
||||||
|
"logarithmic": "Logarit"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "Xen kẽ N giây trước khi kết thúc"
|
||||||
|
},
|
||||||
|
"title": "Tùy chọn xen kẽ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "Bắt đầu bài hát khi ở chế độ \"tạm dừng\"",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "Áp dụng khi khởi động"
|
||||||
|
},
|
||||||
|
"name": "Tắt tự động phát"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "Đã cố gắng kết nối với kết nối khả dụng",
|
||||||
|
"connected": "Đã kết nối với Discord",
|
||||||
|
"disconnected": "Đã ngắt kết nối với Discord"
|
||||||
|
},
|
||||||
|
"description": "Cho bạn bè của bạn thấy những gì bạn nghe với Rich Presence",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "Tự động kết nối lại",
|
||||||
|
"clear-activity": "Xoá hoạt động",
|
||||||
|
"clear-activity-after-timeout": "Xóa hoạt động sau khi hết thời gian chờ",
|
||||||
|
"connected": "Đã kết nối",
|
||||||
|
"disconnected": "Đã ngắt kết nối",
|
||||||
|
"hide-duration-left": "Ẩn thời lượng còn lại",
|
||||||
|
"hide-github-button": "Ẩn nút liên kết GitHub",
|
||||||
|
"play-on-youtube-music": "Phát trong Youtube Music",
|
||||||
|
"set-inactivity-timeout": "Đặt thời gian chờ không hoạt động"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "Nhập thời gian chờ không hoạt động tính bằng giây:",
|
||||||
|
"title": "Đặt thời gian chờ không hoạt động"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Đồng ý"
|
||||||
|
},
|
||||||
|
"message": "Argh! Xin lỗi, tải xuống thất bại…",
|
||||||
|
"title": "Lỗi khi tải xuống!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "Đồng ý"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} bài hát)",
|
||||||
|
"message": "Đang tải danh sách phát {{playlistTitle}}",
|
||||||
|
"title": "Đã bắt đầu tải xuống"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "Chuyển đổi: {{percent}}%",
|
||||||
|
"converting": "Đang chuyển đổi…",
|
||||||
|
"done": "Đã xong: {{filePath}}",
|
||||||
|
"download-info": "Đang tải {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "Đang tải: {{percent}}%",
|
||||||
|
"downloading": "Đang tải…",
|
||||||
|
"downloading-counter": "Đang tải {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "Đang tải danh sách phát \"{{playlistTitle}}\" - {{playlistSize}} bài hát ({{playlistId}})",
|
||||||
|
"error-while-downloading": "Lỗi tải xuống \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "Thư mục {{playlistFolder}} đã tồn tại",
|
||||||
|
"getting-playlist-info": "Đang lấy thông tin danh sách phát…",
|
||||||
|
"loading": "Đang tải…",
|
||||||
|
"playlist-has-only-one-song": "Danh sách phát chỉ có một mục, tải trực tiếp",
|
||||||
|
"playlist-id-not-found": "Không tìm thấy ID danh sách phát",
|
||||||
|
"playlist-is-empty": "Danh sách phát trống",
|
||||||
|
"playlist-is-mix-or-private": "Lỗi lấy thông tin danh sách phát: đảm bảo danh sách phát không ở chế độ riêng tư hoặc là danh sách phát \"Dành cho bạn\"\n\n{{error}}",
|
||||||
|
"preparing-file": "Đang chuẩn bị thư mục…",
|
||||||
|
"saving": "Đang lưu…",
|
||||||
|
"trying-to-get-playlist-id": "Đang lấy ID danh sách phát: {{playlistId}}",
|
||||||
|
"video-id-not-found": "Không tìm thấy video",
|
||||||
|
"writing-id3": "Đang ghi thẻ ID3…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Tải xuống MP3 / âm thanh nguồn trực tiếp từ giao diện",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "Chọn thư mục tải xuống",
|
||||||
|
"download-playlist": "Tải danh sách phát",
|
||||||
|
"presets": "Cài đặt sẵn",
|
||||||
|
"skip-existing": "Bỏ qua các tập tin hiện có"
|
||||||
|
},
|
||||||
|
"name": "Trình tải xuống",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "Không thể cập nhật tiến độ"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "Tải xuống"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "Làm cho thanh trượt âm lượng theo cấp số nhân để dễ dàng chọn âm lượng thấp hơn.",
|
||||||
|
"name": "Âm lượng theo cấp số nhân"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "Mang lại cho thanh menu một giao diện lạ mắt, tối màu hoặc màu album",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "Ẩn cửa sổ điều khiển DOM"
|
||||||
|
},
|
||||||
|
"name": "Menu trong ứng dụng"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "Thêm hỗ trợ Lumia Stream",
|
||||||
|
"name": "Lumia Stream [Thử nghiệm]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "Thêm hỗ trợ lời bài hát cho hầu hết các bài hát",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "Lời bài hát La Mã"
|
||||||
|
},
|
||||||
|
"name": "Lời bài hát từ Genius",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "Lời bài hát được tìm nạp cho Genius"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "Chia sẻ danh sách phát với người khác. Khi máy chủ phát một bài hát, những người khác cũng sẽ nghe bài hát đó",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "Nhập ID máy chủ"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "Lưu",
|
||||||
|
"track-source": "Nguồn âm thanh",
|
||||||
|
"unknown-user": "Người dùng không rõ"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "Sao chép ID máy chủ",
|
||||||
|
"close": "Đóng Music Together",
|
||||||
|
"connected-users": "Người dùng đã kết nối",
|
||||||
|
"disconnect": "Ngắt kết nối Music Together",
|
||||||
|
"empty-user": "Không có người dùng đã kết nối",
|
||||||
|
"host": "Máy chủ Music Together",
|
||||||
|
"join": "Tham gia Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "Cho phép người tham gia kiểm soát danh sách phát và trình phát",
|
||||||
|
"host-only": "Chỉ người tạo máy chủ có quyền điều khiển danh sách phát và trình phát",
|
||||||
|
"playlist": "Cho phép người tham gia điều khiển danh sách phát"
|
||||||
|
},
|
||||||
|
"set-permission": "Thay đổi quyền điều khiển",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "Đã ngắt kết nối",
|
||||||
|
"guest": "Đã kết nối với tư cách Người tham gia",
|
||||||
|
"host": "Đã kết nối với tư cách Máy chủ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [Thử nghiệm]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "Thêm bài hát thất bại",
|
||||||
|
"closed": "Đã đóng Music Together",
|
||||||
|
"disconnected": "Đã ngắt kết nối Music Together",
|
||||||
|
"host-failed": "Không thể tổ chức Music Together",
|
||||||
|
"id-copied": "Đã sao chép ID máy chủ vào bộ nhớ tạm",
|
||||||
|
"id-copy-failed": "Sao chepd ID máy chủ vào bộ nhớ tạm không thành công",
|
||||||
|
"join-failed": "Không thể tham gia Music Together",
|
||||||
|
"joined": "Đã tham gia Music Together",
|
||||||
|
"permission-changed": "Quyền của Music Together đã thay đổi thành \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "Không thể xoá bài hát",
|
||||||
|
"user-connected": "{{name}} đã tham gia Music Together",
|
||||||
|
"user-disconnected": "{{name}} đã rời khỏi Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "Mũi tên điều hướng Tiếp theo/Quay lại được tích hợp trực tiếp trong giao diện, giống như trong trình duyệt yêu thích của bạn",
|
||||||
|
"name": "Điều hướng"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "Xóa các nút và liên kết đăng nhập Google khỏi giao diện",
|
||||||
|
"name": "Không đăng nhập Google"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "Hiển thị thông báo khi bài hát bắt đầu phát (thông báo tương tác có sẵn trên Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "Thông báo tương tác",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "Cài đặt tương tác",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "Ẩn tên nút",
|
||||||
|
"refresh-on-play-pause": "Làm mới khi phát/tạm dừng",
|
||||||
|
"tray-controls": "Mở/Đóng khi nhấp vào khay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "Ưu tiên thông báo",
|
||||||
|
"toast-style": "Kiểu toast",
|
||||||
|
"unpause-notification": "Hiển thị thông báo khi bỏ tạm dừng"
|
||||||
|
},
|
||||||
|
"name": "Thông báo"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "Cho phép chuyển ứng dụng sang chế độ ảnh trong ảnh",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "Luôn ở trên cùng",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "Phím nóng",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "Phím nóng"
|
||||||
|
},
|
||||||
|
"label": "Chọn phím nóng để chuyển đổi ảnh trong ảnh",
|
||||||
|
"title": "Phím nóng ảnh trong ảnh"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "Lưu vị trí cửa sổ",
|
||||||
|
"save-window-size": "Lưu kích thước cửa sổ",
|
||||||
|
"use-native-pip": "Sử dụng PiP gốc của trình duyệt"
|
||||||
|
},
|
||||||
|
"name": "Ảnh trong ảnh",
|
||||||
|
"templates": {
|
||||||
|
"button": "Ảnh trong ảnh"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "Nghe nhanh, nghe chậm! Thêm thanh trượt kiểm soát tốc độ bài hát",
|
||||||
|
"name": "Tốc độ phát lại",
|
||||||
|
"templates": {
|
||||||
|
"button": "Tốc độ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "Kiểm soát âm lượng chính xác bằng con lăn chuột/phím nóng, với HUD tùy chỉnh và các bước âm lượng có thể tùy chỉnh",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "Điều khiển phím mũi tên cục bộ",
|
||||||
|
"custom-volume-steps": "Đặt các bước âm lượng tùy chỉnh",
|
||||||
|
"global-shortcuts": "Phím nóng chung"
|
||||||
|
},
|
||||||
|
"name": "Âm lượng chính xác",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "Giảm âm lượng",
|
||||||
|
"increase": "Tăng âm lượng"
|
||||||
|
},
|
||||||
|
"label": "Chọn tổ hợp phím âm lượng chung:",
|
||||||
|
"title": "Liên kết phím âm lượng chung"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "Chọn các bước tăng/giảm âm lượng",
|
||||||
|
"title": "Bước âm lượng"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "Chất lượng hiện tại: {{quality}}",
|
||||||
|
"message": "Chọn chất lượng video:",
|
||||||
|
"title": "Chọn chất lượng video:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Cho phép thay đổi chất lượng video bằng một nút trên lớp phủ video",
|
||||||
|
"name": "Thay đổi chất lượng video"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "Thêm hỗ trợ scrobbling (v.v. Last.fm, Listenbrainz)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Cài đặt API Last.fm"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "Nhập mã người dùng ListenBrainz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Khóa API Last.fm",
|
||||||
|
"api-secret": "API Last.fm bảo mật"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "Nhập mã người dùng ListenBrainz của bạn:",
|
||||||
|
"title": "Mã ListenBrainz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "Cho phép thiết lập các phím nóng chung để phát lại (phát/tạm dừng/tiếp theo/trước) và tắt OSD media bằng cách ghi đè các phím media, bật Ctrl/CMD + F để tìm kiếm, bật hỗ trợ Linux MPRIS cho các phím media và các phím nóng tùy chỉnh cho người dùng nâng cao",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "Ghi đè khóa phương tiện",
|
||||||
|
"set-keybinds": "Đặt điều khiển bài hát chung"
|
||||||
|
},
|
||||||
|
"name": "Phím tắt (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "Tiếp theo",
|
||||||
|
"play-pause": "Phát / Tạm dừng",
|
||||||
|
"previous": "Trước đó"
|
||||||
|
},
|
||||||
|
"label": "Chọn tổ hợp phím chung để kiểm soát bài hát:",
|
||||||
|
"title": "Tổ hợp phím chung"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "Bỏ qua những bài hát không thích",
|
||||||
|
"name": "Bỏ qua những bài hát không thích"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "Tự động bỏ qua các đoạn im lặng trong bài hát",
|
||||||
|
"name": "Bỏ qua đoạn im lặng"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "Tự động bỏ qua các phần không phải âm nhạc như phần giới thiệu/kết thúc hoặc các phần của video nhạc mà bài hát không được phát",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "Kiểm soát phát lại từ thanh tác vụ Windows của bạn",
|
||||||
|
"name": "Kiểm soát phương tiện trên thanh tác vụ"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "Thêm tiện ích TouchBar cho người dùng macOS",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "Tích hợp với plugin Tuna của OBS",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "Thêm nút để chuyển giữa chế độ Video/Bài hát. Cũng có thể tùy ý xóa toàn bộ tab video",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "Căn chỉnh",
|
||||||
|
"submenu": {
|
||||||
|
"left": "Trái",
|
||||||
|
"middle": "Giữa",
|
||||||
|
"right": "Phải"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "Buộc loại bỏ tab video",
|
||||||
|
"mode": {
|
||||||
|
"label": "Chế độ",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "Chuyển đổi tùy chỉnh",
|
||||||
|
"disabled": "Vô hiệu hoá",
|
||||||
|
"native": "Chuyển đổi gốc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Chuyển đổi video",
|
||||||
|
"templates": {
|
||||||
|
"button": "Bài hát"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "Thêm trình hiển thị cho trình phát",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "Loại trình hiển thị"
|
||||||
|
},
|
||||||
|
"name": "Trình hiển thị"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
682
src/i18n/resources/zh-CN.json
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "执行插件 {{pluginName}}::{{contextName}} 失败",
|
||||||
|
"executed-at-ms": "插件 {{pluginName}}::{{contextName}} 已在 {{ms}} 毫秒内执行",
|
||||||
|
"initialize-failed": "初始化插件 “{{pluginName}}” 失败",
|
||||||
|
"load-all": "正在加载所有插件",
|
||||||
|
"load-failed": "加载插件 “{{pluginName}}” 失败",
|
||||||
|
"loaded": "插件 “{{pluginName}}” 已载入",
|
||||||
|
"unload-failed": "卸载插件 “{{pluginName}}” 失败",
|
||||||
|
"unloaded": "插件 “{{pluginName}}” 已卸载"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "zh-CN",
|
||||||
|
"local-name": "简体中文",
|
||||||
|
"name": "Simplified Chinese"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "加载完毕。开发人员工具已启动"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n 已载入"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "已从协议接收到以下指令:“{{command}}”"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS 文件 “{{cssFile}}” 不存在,将被忽略"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "无响应错误!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "正在清理应用缓存"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "窗口试图于屏幕外绘制, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "菜单已隐藏,按下 Alt 键来显示(若使用应用内菜单则按 Esc 键)",
|
||||||
|
"message": "隐藏菜单已被启用",
|
||||||
|
"title": "隐藏菜单已启用"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "稍后",
|
||||||
|
"restart-now": "现在重启"
|
||||||
|
},
|
||||||
|
"detail": "“{{pluginName}}” 插件需要重启程序后生效",
|
||||||
|
"message": "“{{pluginName}}” 需要重启",
|
||||||
|
"title": "需要重启"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "退出",
|
||||||
|
"relaunch": "重启",
|
||||||
|
"wait": "等待"
|
||||||
|
},
|
||||||
|
"detail": "对产生的不便我们表示抱歉!请选择接下来要做什么:",
|
||||||
|
"message": "应用程序无响应",
|
||||||
|
"title": "窗口无响应"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "禁用更新",
|
||||||
|
"download": "下载",
|
||||||
|
"ok": "好的"
|
||||||
|
},
|
||||||
|
"detail": "新版本现已可用,可从 {{downloadLink}} 下载",
|
||||||
|
"message": "新版本可用",
|
||||||
|
"title": "更新可用"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "关于",
|
||||||
|
"navigation": {
|
||||||
|
"label": "导航",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "复制当前 URL",
|
||||||
|
"go-back": "后退",
|
||||||
|
"go-forward": "前进",
|
||||||
|
"quit": "退出",
|
||||||
|
"restart": "重启应用"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "选项",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "高级选项",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "启动时重置应用缓存",
|
||||||
|
"disable-hardware-acceleration": "禁用硬件加速",
|
||||||
|
"edit-config-json": "编辑 config.json",
|
||||||
|
"override-user-agent": "覆盖 User-Agent",
|
||||||
|
"restart-on-config-changes": "配置改变时重启",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "设置代理",
|
||||||
|
"prompt": {
|
||||||
|
"label": "输入代理地址(留空以禁用)",
|
||||||
|
"placeholder": "例如: SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "设置代理服务器"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "切换开发人员工具"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "保持置顶",
|
||||||
|
"auto-update": "自动更新",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "菜单将在下次启动时隐藏,按下 Alt 键以显示(若使用应用内菜单则按 ` 键)",
|
||||||
|
"title": "隐藏菜单已启用"
|
||||||
|
},
|
||||||
|
"label": "隐藏菜单"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "语言会在应用重启后更改",
|
||||||
|
"title": "语言已更改"
|
||||||
|
},
|
||||||
|
"label": "语言",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "想要协助翻译?点击此处"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "应用启动时继续上次播放的歌曲",
|
||||||
|
"single-instance-lock": "单例模式",
|
||||||
|
"start-at-login": "系统登录时启动",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "启动页面",
|
||||||
|
"unset": "取消设定"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "托盘",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "已禁用",
|
||||||
|
"enabled-and-hide-app": "启用并隐藏应用",
|
||||||
|
"enabled-and-show-app": "启用并显示应用",
|
||||||
|
"play-pause-on-click": "点击时播放/暂停"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "视觉调整",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "默认",
|
||||||
|
"force-show": "强制显示",
|
||||||
|
"hide": "隐藏",
|
||||||
|
"label": "点赞按钮"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "移除升级按钮",
|
||||||
|
"theme": {
|
||||||
|
"label": "主题",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "导入自定义 CSS 文件",
|
||||||
|
"no-theme": "无主题"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "已启用",
|
||||||
|
"label": "插件",
|
||||||
|
"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": "YouTube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{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": "绕过 YouTube 年龄验证",
|
||||||
|
"name": "绕过年龄验证"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "YouTube Music 音轨字幕选择器",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "自动选择上次使用的字幕",
|
||||||
|
"disable-captions": "默认无字幕"
|
||||||
|
},
|
||||||
|
"name": "字幕选择器",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "当前字幕语言: {{language}}",
|
||||||
|
"none": "无",
|
||||||
|
"title": "选择字幕语言"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "开启字幕选择器"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "始终将侧边栏设为紧凑模式",
|
||||||
|
"name": "紧凑式侧边栏"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "在歌曲间启用交叉淡化效果",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "高级"
|
||||||
|
},
|
||||||
|
"name": "交叉淡化 [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "淡入持续时间(毫秒)",
|
||||||
|
"fade-out-duration": "淡出持续时间(毫秒)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "淡化尺度",
|
||||||
|
"linear": "线性",
|
||||||
|
"logarithmic": "对数"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "歌曲结束前持续交叉淡化时长"
|
||||||
|
},
|
||||||
|
"title": "交叉淡化选项"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "让曲目开始时处于 “暂停” 模式",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "仅在程序启动时应用"
|
||||||
|
},
|
||||||
|
"name": "禁用自动播放"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "已尝试使用活跃网络进行连接",
|
||||||
|
"connected": "已连接到 Discord",
|
||||||
|
"disconnected": "已和 Discord 断开连接"
|
||||||
|
},
|
||||||
|
"description": "使用 Rich Presence 与好友分享正在收听的音乐",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "自动重连",
|
||||||
|
"clear-activity": "清除状态",
|
||||||
|
"clear-activity-after-timeout": "超时后清除状态",
|
||||||
|
"connected": "已连接",
|
||||||
|
"disconnected": "已断开连接",
|
||||||
|
"hide-duration-left": "隐藏剩余时长",
|
||||||
|
"hide-github-button": "隐藏 GitHub 链接按钮",
|
||||||
|
"play-on-youtube-music": "转至 YouTube Music 播放",
|
||||||
|
"set-inactivity-timeout": "设置非活跃时长"
|
||||||
|
},
|
||||||
|
"name": "Discord Rich Presence 状态显示",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "设置非活跃状态超时时长,以秒为单位:",
|
||||||
|
"title": "设置非活跃超时"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "好的"
|
||||||
|
},
|
||||||
|
"message": "啊!抱歉,下载失败…",
|
||||||
|
"title": "下载时发生错误!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "好的"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} 首歌曲)",
|
||||||
|
"message": "正在下载播放列表 {{playlistTitle}}",
|
||||||
|
"title": "下载已开始"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "转换进度: {{percent}}%",
|
||||||
|
"converting": "正在转换…",
|
||||||
|
"done": "完成下载: {{filePath}}",
|
||||||
|
"download-info": "正在下载 {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "下载: {{percent}}%",
|
||||||
|
"downloading": "下载中…",
|
||||||
|
"downloading-counter": "当前正下载 {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "当前正在下载播放列表 \"{{playlistTitle}}\" - 共有 {{playlistSize}} 首歌曲 ({{playlistId}})",
|
||||||
|
"error-while-downloading": "下载 \"{{author}} - {{title}}\" 时出错: {{error}}",
|
||||||
|
"folder-already-exists": "文件夹 {{playlistFolder}} 已存在",
|
||||||
|
"getting-playlist-info": "正在获取播放列表信息…",
|
||||||
|
"loading": "加载中…",
|
||||||
|
"playlist-has-only-one-song": "播放列表只有一首歌曲,将直接下载",
|
||||||
|
"playlist-id-not-found": "未找到播放列表 ID",
|
||||||
|
"playlist-is-empty": "播放列表是空的",
|
||||||
|
"playlist-is-mix-or-private": "获取播放列表信息时出错:请确认目标不是私有或“为你推荐”列表\n\n{{error}}",
|
||||||
|
"preparing-file": "正在准备文件…",
|
||||||
|
"saving": "正在保存…",
|
||||||
|
"trying-to-get-playlist-id": "正尝试获取播放列表 ID: {{playlistId}}",
|
||||||
|
"video-id-not-found": "视频未找到",
|
||||||
|
"writing-id3": "正在写入 ID3 标签…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "在界面内直接下载 MP3 / 源音频",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "选择下载文件夹",
|
||||||
|
"download-playlist": "下载播放列表",
|
||||||
|
"presets": "预设",
|
||||||
|
"skip-existing": "跳过已存在的文件"
|
||||||
|
},
|
||||||
|
"name": "下载器",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "无法更新进度"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "下载"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "让音量滑块指数化以便选择更低的音量。",
|
||||||
|
"name": "指数化音量"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "为菜单栏启用更精美的外观,可选暗色或专辑配色",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "隐藏 DOM 窗口控件"
|
||||||
|
},
|
||||||
|
"name": "应用内菜单"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "添加 Lumia Stream 支持",
|
||||||
|
"name": "Lumia Stream [测试]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "为大多数歌曲添加歌词支持",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "罗马化字幕"
|
||||||
|
},
|
||||||
|
"name": "Genius 歌词",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "已从 Genius 获取字幕"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "与他人共享播放列表。当发起人播放歌曲时,其他人也会听到相同歌曲",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "输入发起人 ID"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "保存",
|
||||||
|
"track-source": "追踪来源",
|
||||||
|
"unknown-user": "未知用户"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "复制发起者 ID",
|
||||||
|
"close": "关闭 Music Together",
|
||||||
|
"connected-users": "已连接用户",
|
||||||
|
"disconnect": "断开 Music Together 连接",
|
||||||
|
"empty-user": "没有已连接的用户",
|
||||||
|
"host": "Music Together 发起者",
|
||||||
|
"join": "加入 Music Together",
|
||||||
|
"permission": {
|
||||||
|
"all": "允许来宾控制播放列表与播放器",
|
||||||
|
"host-only": "仅发起人可以控制播放列表与播放器",
|
||||||
|
"playlist": "允许来宾控制播放列表"
|
||||||
|
},
|
||||||
|
"set-permission": "更改控制权限",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "已断开连接",
|
||||||
|
"guest": "已作为来宾连接",
|
||||||
|
"host": "已作为发起人连接"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Music Together [测试]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "添加歌曲失败",
|
||||||
|
"closed": "Music Together 已关闭",
|
||||||
|
"disconnected": "Music Together 已断开连接",
|
||||||
|
"host-failed": "发起 Music Together 失败",
|
||||||
|
"id-copied": "已将发起者 ID 复制到剪切板",
|
||||||
|
"id-copy-failed": "复制发起者 ID 到剪贴板时失败",
|
||||||
|
"join-failed": "加入 Music Together 失败",
|
||||||
|
"joined": "已加入 Music Together",
|
||||||
|
"permission-changed": "Music Together 权限已改为 \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "移除歌曲失败",
|
||||||
|
"user-connected": "{{name}} 加入了 Music Together",
|
||||||
|
"user-disconnected": "{{name}} 离开了 Music Together"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "如同浏览器般,在应用界面内直接显示前进/后退导航按钮",
|
||||||
|
"name": "导航"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "从界面内移除 Google 登录按钮和链接",
|
||||||
|
"name": "无 Google 登录"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "歌曲开始时显示通知(交互式通知仅适用于 Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "交互式通知",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "通知交互设定",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "隐藏按钮文字",
|
||||||
|
"refresh-on-play-pause": "播放/暂停时刷新",
|
||||||
|
"tray-controls": "点击托盘时打开/关闭"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "通知优先级",
|
||||||
|
"toast-style": "弹出通知样式",
|
||||||
|
"unpause-notification": "取消暂停时显示通知"
|
||||||
|
},
|
||||||
|
"name": "通知"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "允许应用切换到画中画模式",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "保持置顶",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "热键",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "热键"
|
||||||
|
},
|
||||||
|
"label": "选择切换画中画模式的热键",
|
||||||
|
"title": "画中画热键"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "记住窗口位置",
|
||||||
|
"save-window-size": "记住窗口尺寸",
|
||||||
|
"use-native-pip": "使用浏览器原生画中画功能"
|
||||||
|
},
|
||||||
|
"name": "画中画",
|
||||||
|
"templates": {
|
||||||
|
"button": "画中画"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "快慢随心!为歌曲添加速度控制滑块",
|
||||||
|
"name": "回放速度",
|
||||||
|
"templates": {
|
||||||
|
"button": "速度"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "启用自定义的音量弹窗以及可变步长,以便采用滚轮/热键精确控制音量",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "本地方向键控件",
|
||||||
|
"custom-volume-steps": "设置自定义音量步长",
|
||||||
|
"global-shortcuts": "全局热键"
|
||||||
|
},
|
||||||
|
"name": "精确音量",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "降低音量",
|
||||||
|
"increase": "提升音量"
|
||||||
|
},
|
||||||
|
"label": "选择全局音量键绑定:",
|
||||||
|
"title": "全局音量键绑定"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "选择音量增加/减弱步长",
|
||||||
|
"title": "音量步长"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "当前质量: {{quality}}",
|
||||||
|
"message": "选择视频画质:",
|
||||||
|
"title": "选择视频画质"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "允许在视频上显示切换画质按钮",
|
||||||
|
"name": "视频画质切换器"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "添加歌曲追踪支持(如 Last.fm 和 Listenbrainz)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API 设置"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "输入 ListenBrainz 用户令牌"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "记录其他媒体文件"
|
||||||
|
},
|
||||||
|
"name": "歌曲记录器",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API 密钥(Key)",
|
||||||
|
"api-secret": "Last.fm API 密文(Secret)"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "输入您的v ListenBrainz 用户令牌:",
|
||||||
|
"title": "ListenBrainz 令牌"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "允许为音频回放操作(播放/暂停/上一曲/下一曲)设置全局热键,兼具覆盖物理键以禁用 OSD、启用 Ctrl/CMD + F 搜索、为物理键启用 Linux MPRIS 支持及自定义热键等高级功能",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "覆盖物理媒体热键",
|
||||||
|
"set-keybinds": "设置全局歌曲控件"
|
||||||
|
},
|
||||||
|
"name": "快捷键(以及 MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "下一曲",
|
||||||
|
"play-pause": "播放/暂停",
|
||||||
|
"previous": "上一曲"
|
||||||
|
},
|
||||||
|
"label": "为歌曲控制选择全局按键绑定:",
|
||||||
|
"title": "全局按键绑定"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "自动跳过不喜欢的歌曲",
|
||||||
|
"name": "跳过不喜欢的歌曲"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "自动跳过音乐中的无声片段",
|
||||||
|
"name": "跳过无声片段"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "自动跳过非音乐部分,如 MV 的介绍/结语以及歌曲未开始的部分",
|
||||||
|
"name": "SponsorBlock"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "从 Windows 任务栏控制音乐回放",
|
||||||
|
"name": "任务栏媒体控件"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "为 macOS 用户启用 TouchBar 支持",
|
||||||
|
"name": "TouchBar"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "与 OBS 的 Tuna 插件集成",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "增加视频/音频模式间的切换按钮。兼具移除整个视频页面的功能",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "位置对齐",
|
||||||
|
"submenu": {
|
||||||
|
"left": "左对齐",
|
||||||
|
"middle": "居中",
|
||||||
|
"right": "右对齐"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "强制移除视频页",
|
||||||
|
"mode": {
|
||||||
|
"label": "模式",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "自定义开关样式",
|
||||||
|
"disabled": "禁用",
|
||||||
|
"native": "原生开关样式"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "视频切换开关",
|
||||||
|
"templates": {
|
||||||
|
"button": "歌曲"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "在播放器中添加可视化效果",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "可视化类型"
|
||||||
|
},
|
||||||
|
"name": "可视化效果"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
682
src/i18n/resources/zh-TW.json
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"console": {
|
||||||
|
"plugins": {
|
||||||
|
"execute-failed": "外掛 {{pluginName}} 無法執行::{{contextName}}",
|
||||||
|
"executed-at-ms": "外掛 {{pluginName}}::{{contextName}} 用了 {{ms}} 毫秒來執行",
|
||||||
|
"initialize-failed": "初始化外掛「{{pluginName}}」失敗",
|
||||||
|
"load-all": "正在載入所有外掛",
|
||||||
|
"load-failed": "載入外掛「{{pluginName}}」失敗",
|
||||||
|
"loaded": "外掛「{{pluginName}}」已載入",
|
||||||
|
"unload-failed": "移除外掛「{{pluginName}}」失敗",
|
||||||
|
"unloaded": "外掛「{{pluginName}}」已移除"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "zh-TW",
|
||||||
|
"local-name": "正體中文",
|
||||||
|
"name": "Traditional Chinese"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"console": {
|
||||||
|
"did-finish-load": {
|
||||||
|
"dev-tools": "載入完成。開發人員工具已開啟"
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"loaded": "i18n 已載入"
|
||||||
|
},
|
||||||
|
"second-instance": {
|
||||||
|
"receive-command": "使用協定來接收指令:「{{command}}」"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"css-file-not-found": "CSS 檔案「{{cssFile}}」不存在,已忽略"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"details": "沒有回應錯誤!\n{{error}}"
|
||||||
|
},
|
||||||
|
"when-ready": {
|
||||||
|
"clearing-cache-after-20s": "清理程式的快取資料"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"tried-to-render-offscreen": "視窗正嘗試在螢幕外繪製,視窗大小 = {{windowSize}},螢幕大小 = {{displaySize}},位置 = {{position}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"hide-menu-enabled": {
|
||||||
|
"detail": "選單列已隱藏,使用 Alt 鍵來重新顯示(或是使用 Esc 鍵)",
|
||||||
|
"message": "隱藏選單列已經啟用",
|
||||||
|
"title": "隱藏選單列已啟用"
|
||||||
|
},
|
||||||
|
"need-to-restart": {
|
||||||
|
"buttons": {
|
||||||
|
"later": "稍後",
|
||||||
|
"restart-now": "立即重啟"
|
||||||
|
},
|
||||||
|
"detail": "\"{{pluginName}}\" 外掛需要重啟應用之後才會生效",
|
||||||
|
"message": "\"{{pluginName}}\" 需要重啟應用",
|
||||||
|
"title": "需要重啟應用"
|
||||||
|
},
|
||||||
|
"unresponsive": {
|
||||||
|
"buttons": {
|
||||||
|
"quit": "離開",
|
||||||
|
"relaunch": "重新啟動",
|
||||||
|
"wait": "等一下"
|
||||||
|
},
|
||||||
|
"detail": "造成不便我們深表歉意!請選擇動作:",
|
||||||
|
"message": "應用程式沒有回應",
|
||||||
|
"title": "視窗沒有回應"
|
||||||
|
},
|
||||||
|
"update-available": {
|
||||||
|
"buttons": {
|
||||||
|
"disable": "停用更新",
|
||||||
|
"download": "下載",
|
||||||
|
"ok": "確定"
|
||||||
|
},
|
||||||
|
"detail": "新版本已經推出,你可以至 {{downloadLink}} 下載",
|
||||||
|
"message": "有新版本可用",
|
||||||
|
"title": "有可用的更新"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"about": "關於",
|
||||||
|
"navigation": {
|
||||||
|
"label": "導覽列",
|
||||||
|
"submenu": {
|
||||||
|
"copy-current-url": "複製目前的網址",
|
||||||
|
"go-back": "回到上一頁",
|
||||||
|
"go-forward": "回到下一頁",
|
||||||
|
"quit": "退出",
|
||||||
|
"restart": "重啟應用"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"label": "選項",
|
||||||
|
"submenu": {
|
||||||
|
"advanced-options": {
|
||||||
|
"label": "進階選項",
|
||||||
|
"submenu": {
|
||||||
|
"auto-reset-app-cache": "當程式啟動時重設應用程式快取",
|
||||||
|
"disable-hardware-acceleration": "關閉硬體加速",
|
||||||
|
"edit-config-json": "編輯 config.json",
|
||||||
|
"override-user-agent": "覆寫使用者代理",
|
||||||
|
"restart-on-config-changes": "在設定檔更動時自動重啟應用程式",
|
||||||
|
"set-proxy": {
|
||||||
|
"label": "設定代理伺服器",
|
||||||
|
"prompt": {
|
||||||
|
"label": "輸入代理伺服器位置:(留空以停用本設定)",
|
||||||
|
"placeholder": "範例:SOCKS5://127.0.0.1:9999",
|
||||||
|
"title": "設定代理伺服器"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toggle-dev-tools": "切換開發者人員工具"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"always-on-top": "永遠顯示在最上層",
|
||||||
|
"auto-update": "自動更新",
|
||||||
|
"hide-menu": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "選單列會在程式下次啟動時隱藏,使用 Alt 鍵來重新顯示(或是使用 ` 鍵)",
|
||||||
|
"title": "隱藏選單列已啟用"
|
||||||
|
},
|
||||||
|
"label": "隱藏選單列"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"dialog": {
|
||||||
|
"message": "語言會在下一次重啟應用程式時變更",
|
||||||
|
"title": "語言已變更"
|
||||||
|
},
|
||||||
|
"label": "語言",
|
||||||
|
"submenu": {
|
||||||
|
"to-help-translate": "想要協助翻譯?按一下這裡"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resume-on-start": "應用啟動時繼續上次播放的歌曲",
|
||||||
|
"single-instance-lock": "單視窗鎖定",
|
||||||
|
"start-at-login": "開機時啟動",
|
||||||
|
"starting-page": {
|
||||||
|
"label": "啟動頁面",
|
||||||
|
"unset": "不指定"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"label": "系統閘圖式",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": "已停用",
|
||||||
|
"enabled-and-hide-app": "啟用並隱藏應用程式",
|
||||||
|
"enabled-and-show-app": "啟用並顯示應用程式",
|
||||||
|
"play-pause-on-click": "點擊時播放/暫停"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual-tweaks": {
|
||||||
|
"label": "介面設定",
|
||||||
|
"submenu": {
|
||||||
|
"like-buttons": {
|
||||||
|
"default": "預設",
|
||||||
|
"force-show": "強制顯示",
|
||||||
|
"hide": "隱藏",
|
||||||
|
"label": "按讚按鈕"
|
||||||
|
},
|
||||||
|
"remove-upgrade-button": "移除升級按鈕",
|
||||||
|
"theme": {
|
||||||
|
"label": "主題",
|
||||||
|
"submenu": {
|
||||||
|
"import-css-file": "匯入自訂 CSS 檔案",
|
||||||
|
"no-theme": "無主題"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"enabled": "啟用",
|
||||||
|
"label": "外掛功能",
|
||||||
|
"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": "Youtube Music",
|
||||||
|
"with-song-info": "YouTube Music: {{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": "繞過Youtube年齡驗證",
|
||||||
|
"name": "繞過年齡驗證"
|
||||||
|
},
|
||||||
|
"captions-selector": {
|
||||||
|
"description": "音軌標題選擇",
|
||||||
|
"menu": {
|
||||||
|
"autoload": "自動選擇最後使用的標題",
|
||||||
|
"disable-captions": "預設無標題"
|
||||||
|
},
|
||||||
|
"name": "標題選擇器",
|
||||||
|
"prompt": {
|
||||||
|
"selector": {
|
||||||
|
"label": "目前標題語言: {{language}}",
|
||||||
|
"none": "無",
|
||||||
|
"title": "選擇標題語言"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"title": "打開標題選擇器"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compact-sidebar": {
|
||||||
|
"description": "永遠讓側邊欄保持收合狀態",
|
||||||
|
"name": "收合側邊欄"
|
||||||
|
},
|
||||||
|
"crossfade": {
|
||||||
|
"description": "在歌曲之間使用交叉淡化",
|
||||||
|
"menu": {
|
||||||
|
"advanced": "進階"
|
||||||
|
},
|
||||||
|
"name": "交叉淡化 [Beta]",
|
||||||
|
"prompt": {
|
||||||
|
"options": {
|
||||||
|
"multi-input": {
|
||||||
|
"fade-in-duration": "淡入時間(毫秒)",
|
||||||
|
"fade-out-duration": "淡出時間(毫秒)",
|
||||||
|
"fade-scaling": {
|
||||||
|
"label": "淡化計算方式",
|
||||||
|
"linear": "線性",
|
||||||
|
"logarithmic": "對數"
|
||||||
|
},
|
||||||
|
"seconds-before-end": "交叉淡化持續時間 (秒)"
|
||||||
|
},
|
||||||
|
"title": "交叉淡化選項"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable-autoplay": {
|
||||||
|
"description": "讓歌曲開始時為暫停模式",
|
||||||
|
"menu": {
|
||||||
|
"apply-once": "只在啟動程式時套用"
|
||||||
|
},
|
||||||
|
"name": "停用自動播放"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"backend": {
|
||||||
|
"already-connected": "已嘗試可用連接",
|
||||||
|
"connected": "已連接至Discord",
|
||||||
|
"disconnected": "與Discord斷開連接"
|
||||||
|
},
|
||||||
|
"description": "使用Discord狀態與你的好友分享你正在收聽的音樂",
|
||||||
|
"menu": {
|
||||||
|
"auto-reconnect": "自動重新連接",
|
||||||
|
"clear-activity": "清除狀態",
|
||||||
|
"clear-activity-after-timeout": "在音樂暫停後清除狀態",
|
||||||
|
"connected": "已連接",
|
||||||
|
"disconnected": "已斷開連接",
|
||||||
|
"hide-duration-left": "隱藏音樂剩餘時間狀態",
|
||||||
|
"hide-github-button": "隱藏Github頁面按鈕",
|
||||||
|
"play-on-youtube-music": "顯示Play on YouTube Music按鈕",
|
||||||
|
"set-inactivity-timeout": "設定閒置狀態時長"
|
||||||
|
},
|
||||||
|
"name": "Discord狀態",
|
||||||
|
"prompt": {
|
||||||
|
"set-inactivity-timeout": {
|
||||||
|
"label": "設定多少秒後清除狀態:",
|
||||||
|
"title": "設定閒置狀態時長"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"downloader": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"error": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "完成"
|
||||||
|
},
|
||||||
|
"message": "啊!抱歉,下載失敗了…",
|
||||||
|
"title": "下載出現錯誤!"
|
||||||
|
},
|
||||||
|
"start-download-playlist": {
|
||||||
|
"buttons": {
|
||||||
|
"ok": "完成"
|
||||||
|
},
|
||||||
|
"detail": "({{playlistSize}} 首歌曲)",
|
||||||
|
"message": "正在下載播放清單 {{playlistTitle}}",
|
||||||
|
"title": "開始下載"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"conversion-progress": "轉檔進度: {{percent}}%",
|
||||||
|
"converting": "轉檔中…",
|
||||||
|
"done": "完成下載: {{filePath}}",
|
||||||
|
"download-info": "正在下載 {{artist}} - {{title}} [{{videoId}}",
|
||||||
|
"download-progress": "下載進度: {{percent}}%",
|
||||||
|
"downloading": "下載中…",
|
||||||
|
"downloading-counter": "正在下載第 {{current}}/{{total}}…",
|
||||||
|
"downloading-playlist": "正在下載播放清單 \"{{playlistTitle}}\" - 共 {{playlistSize}} 首歌 ({{playlistId}})",
|
||||||
|
"error-while-downloading": "無法下載 \"{{author}} - {{title}}\": {{error}}",
|
||||||
|
"folder-already-exists": "資料夾 {{playlistFolder}} 已經存在",
|
||||||
|
"getting-playlist-info": "正在獲取播放清單資訊…",
|
||||||
|
"loading": "載入中…",
|
||||||
|
"playlist-has-only-one-song": "播放清單內只有一首歌曲, 將直接下載",
|
||||||
|
"playlist-id-not-found": "沒有找到播放清單 ID",
|
||||||
|
"playlist-is-empty": "播放清單是空的",
|
||||||
|
"playlist-is-mix-or-private": "獲取播放清單資訊時發生錯誤: 請確認非私人播放清單或是\"為你推薦的合輯\"\n\n{{error}}",
|
||||||
|
"preparing-file": "正在準備檔案…",
|
||||||
|
"saving": "儲存中…",
|
||||||
|
"trying-to-get-playlist-id": "正在嘗試獲取播放清單 ID: {{playlistId}}",
|
||||||
|
"video-id-not-found": "未能找到該影片",
|
||||||
|
"writing-id3": "正在寫入 ID3 標籤…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "在應用程式內下載 MP3/原始音樂檔",
|
||||||
|
"menu": {
|
||||||
|
"choose-download-folder": "選擇下載位置",
|
||||||
|
"download-playlist": "下載播放清單",
|
||||||
|
"presets": "預設格式",
|
||||||
|
"skip-existing": "跳過已存在的檔案"
|
||||||
|
},
|
||||||
|
"name": "歌曲下載",
|
||||||
|
"renderer": {
|
||||||
|
"can-not-update-progress": "無法更新進度"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"button": "下載"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exponential-volume": {
|
||||||
|
"description": "使音量滑桿指數化,以便更容易選擇較低的音量。",
|
||||||
|
"name": "指數化音量調整"
|
||||||
|
},
|
||||||
|
"in-app-menu": {
|
||||||
|
"description": "使選單列變更為黑色或隨主題變色",
|
||||||
|
"menu": {
|
||||||
|
"hide-dom-window-controls": "隱藏DOM視窗控制"
|
||||||
|
},
|
||||||
|
"name": "程式內選單列"
|
||||||
|
},
|
||||||
|
"lumiastream": {
|
||||||
|
"description": "新增對 Lumia Stream 的支援",
|
||||||
|
"name": "Lumia Stream [Beta]"
|
||||||
|
},
|
||||||
|
"lyrics-genius": {
|
||||||
|
"description": "為更多歌曲新增字幕支援",
|
||||||
|
"menu": {
|
||||||
|
"romanized-lyrics": "羅馬拼音字幕"
|
||||||
|
},
|
||||||
|
"name": "第三方字幕",
|
||||||
|
"renderer": {
|
||||||
|
"fetched-lyrics": "為Genius獲取字幕"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music-together": {
|
||||||
|
"description": "與他人共享播放清單。當發起人播放歌曲時,其他成員也會同步收聽",
|
||||||
|
"dialog": {
|
||||||
|
"enter-host": "輸入發起人 ID"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"save": "儲存",
|
||||||
|
"track-source": "追蹤來源",
|
||||||
|
"unknown-user": "未知使用者"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"click-to-copy-id": "複製發起人 ID",
|
||||||
|
"close": "同步關閉音樂",
|
||||||
|
"connected-users": "已連接的使用者",
|
||||||
|
"disconnect": "斷開連接共享音樂",
|
||||||
|
"empty-user": "無已連接的使用者",
|
||||||
|
"host": "發起共享音樂",
|
||||||
|
"join": "加入共享音樂",
|
||||||
|
"permission": {
|
||||||
|
"all": "允許加入的使用者控制播放清單及播放控制",
|
||||||
|
"host-only": "不允許加入的使用者控制播放清單及播放控制",
|
||||||
|
"playlist": "只允許加入的使用者控制播放清單"
|
||||||
|
},
|
||||||
|
"set-permission": "切換共享音樂播放權限",
|
||||||
|
"status": {
|
||||||
|
"disconnected": "已斷開連接",
|
||||||
|
"guest": "以使用者身份加入",
|
||||||
|
"host": "以發起人身份加入"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "共享音樂 [Beta]",
|
||||||
|
"toast": {
|
||||||
|
"add-song-failed": "歌曲加入失敗",
|
||||||
|
"closed": "關閉共享音樂",
|
||||||
|
"disconnected": "共享音樂已斷開連接",
|
||||||
|
"host-failed": "發起共享音樂失敗",
|
||||||
|
"id-copied": "已複製發起人 ID",
|
||||||
|
"id-copy-failed": "複製發起人 ID 失敗",
|
||||||
|
"join-failed": "加入共享音樂失敗",
|
||||||
|
"joined": "加入共享音樂",
|
||||||
|
"permission-changed": "共享音樂播放權限已切換至 \"{{permission}}\"",
|
||||||
|
"remove-song-failed": "歌曲移除失敗",
|
||||||
|
"user-connected": "{{name}} 已加入共享音樂",
|
||||||
|
"user-disconnected": "{{name}} 離開了共享音樂"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"description": "將上一頁/下一頁按鈕新增至應用程式上方, 就像你最熟悉的瀏覽器",
|
||||||
|
"name": "導覽列"
|
||||||
|
},
|
||||||
|
"no-google-login": {
|
||||||
|
"description": "移除Google登入按鈕及連結",
|
||||||
|
"name": "停用Google登入"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"description": "在歌曲播放時發送一個系統通知 (可互動通知僅限Windows)",
|
||||||
|
"menu": {
|
||||||
|
"interactive": "可互動通知",
|
||||||
|
"interactive-settings": {
|
||||||
|
"label": "通知互動設定",
|
||||||
|
"submenu": {
|
||||||
|
"hide-button-text": "隱藏按鈕文字",
|
||||||
|
"refresh-on-play-pause": "在播放/暫停時刷新",
|
||||||
|
"tray-controls": "點擊系統閘圖示時打開/關閉"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"priority": "通知優先權",
|
||||||
|
"toast-style": "通知樣式",
|
||||||
|
"unpause-notification": "在取消暫停時發送通知"
|
||||||
|
},
|
||||||
|
"name": "歌曲播放通知"
|
||||||
|
},
|
||||||
|
"picture-in-picture": {
|
||||||
|
"description": "允許應用程式切換至子母畫面模式",
|
||||||
|
"menu": {
|
||||||
|
"always-on-top": "永遠顯示在最上層",
|
||||||
|
"hotkey": {
|
||||||
|
"label": "快捷鍵",
|
||||||
|
"prompt": {
|
||||||
|
"keybind-options": {
|
||||||
|
"hotkey": "快捷鍵"
|
||||||
|
},
|
||||||
|
"label": "選擇一個快捷鍵來切換子母畫面模式",
|
||||||
|
"title": "子母畫面模式快捷鍵"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save-window-position": "記住視窗位置",
|
||||||
|
"save-window-size": "記住視窗大小",
|
||||||
|
"use-native-pip": "使用瀏覽器原生子母畫面模式"
|
||||||
|
},
|
||||||
|
"name": "子母畫面",
|
||||||
|
"templates": {
|
||||||
|
"button": "子母畫面"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playback-speed": {
|
||||||
|
"description": "傷心的人別聽慢歌, 新增一個滑桿控制歌曲速度",
|
||||||
|
"name": "控制歌曲速度",
|
||||||
|
"templates": {
|
||||||
|
"button": "速度"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"precise-volume": {
|
||||||
|
"description": "讓你可使用滑鼠滾輪/快捷鍵控制音量,並顯示目前音量大小",
|
||||||
|
"menu": {
|
||||||
|
"arrows-shortcuts": "方向鍵音量控制",
|
||||||
|
"custom-volume-steps": "自訂音量調整時層級",
|
||||||
|
"global-shortcuts": "全域快捷鍵"
|
||||||
|
},
|
||||||
|
"name": "進階音量控制",
|
||||||
|
"prompt": {
|
||||||
|
"global-shortcuts": {
|
||||||
|
"keybind-options": {
|
||||||
|
"decrease": "降低音量",
|
||||||
|
"increase": "增加音量"
|
||||||
|
},
|
||||||
|
"label": "選擇全域音量控制快捷鍵:",
|
||||||
|
"title": "全域音量控制快捷鍵"
|
||||||
|
},
|
||||||
|
"volume-steps": {
|
||||||
|
"label": "設定音量每一次增加/降低的層級",
|
||||||
|
"title": "自訂音量調整時層級"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality-changer": {
|
||||||
|
"backend": {
|
||||||
|
"dialog": {
|
||||||
|
"quality-changer": {
|
||||||
|
"detail": "目前畫質: {{quality}}",
|
||||||
|
"message": "選擇影片畫質:",
|
||||||
|
"title": "選擇影片畫質"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "允許在影片內進行畫質更改",
|
||||||
|
"name": "允許變更影片畫質"
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"description": "額外新增 scrobbling 支援 (例如:last.fm, Listenbrainz)",
|
||||||
|
"menu": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-settings": "Last.fm API 設定"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": "輸入 ListenBrainz 使用者憑證"
|
||||||
|
},
|
||||||
|
"scrobble-other-media": "紀錄其他媒體文件"
|
||||||
|
},
|
||||||
|
"name": "Scrobbler",
|
||||||
|
"prompt": {
|
||||||
|
"lastfm": {
|
||||||
|
"api-key": "Last.fm API 金鑰",
|
||||||
|
"api-secret": "Last.fm API 密鑰"
|
||||||
|
},
|
||||||
|
"listenbrainz": {
|
||||||
|
"token": {
|
||||||
|
"label": "輸入您的 ListenBrainz 使用者憑證:",
|
||||||
|
"title": "ListenBrainz 使用者憑證"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "使用全域快捷鍵控制音樂 (播放/暫停/下一首/上一首) + 透過覆寫媒體快捷鍵停用媒體OSD + 允許Ctrl/CMD + F來搜尋 + 支援Linux MPRIS媒體快捷鍵 + 更多自訂快捷鍵給進階使用者",
|
||||||
|
"menu": {
|
||||||
|
"override-media-keys": "覆寫媒體快捷鍵",
|
||||||
|
"set-keybinds": "設定全域歌曲控制"
|
||||||
|
},
|
||||||
|
"name": "Shortcuts (& MPRIS)",
|
||||||
|
"prompt": {
|
||||||
|
"keybind": {
|
||||||
|
"keybind-options": {
|
||||||
|
"next": "下一首",
|
||||||
|
"play-pause": "播放/暫停",
|
||||||
|
"previous": "上一首"
|
||||||
|
},
|
||||||
|
"label": "選擇全域音樂控制快捷鍵:",
|
||||||
|
"title": "全域快捷鍵"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skip-disliked-songs": {
|
||||||
|
"description": "自動跳過不喜歡的歌曲",
|
||||||
|
"name": "自動跳過不喜歡的歌曲"
|
||||||
|
},
|
||||||
|
"skip-silences": {
|
||||||
|
"description": "自動跳過音樂無聲片段",
|
||||||
|
"name": "自動跳過無聲片段"
|
||||||
|
},
|
||||||
|
"sponsorblock": {
|
||||||
|
"description": "自動跳過贊助片段",
|
||||||
|
"name": "贊助阻擋"
|
||||||
|
},
|
||||||
|
"taskbar-mediacontrol": {
|
||||||
|
"description": "透過工作列應用程式圖式控制媒體播放",
|
||||||
|
"name": "工作列媒體控制"
|
||||||
|
},
|
||||||
|
"touchbar": {
|
||||||
|
"description": "為macOS使用者新增觸控列支援",
|
||||||
|
"name": "觸控列 (Touchbar) 支援"
|
||||||
|
},
|
||||||
|
"tuna-obs": {
|
||||||
|
"description": "與 OBS 的 Tuna 外掛連接",
|
||||||
|
"name": "Tuna OBS"
|
||||||
|
},
|
||||||
|
"video-toggle": {
|
||||||
|
"description": "新增一個按鈕可以控制影片/歌曲切換和完全移除整個影片頁面的功能",
|
||||||
|
"menu": {
|
||||||
|
"align": {
|
||||||
|
"label": "按鈕位置",
|
||||||
|
"submenu": {
|
||||||
|
"left": "靠左",
|
||||||
|
"middle": "中間",
|
||||||
|
"right": "靠右"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"force-hide": "強制移除整個影片頁面",
|
||||||
|
"mode": {
|
||||||
|
"label": "樣式",
|
||||||
|
"submenu": {
|
||||||
|
"custom": "自訂樣式",
|
||||||
|
"disabled": "停用",
|
||||||
|
"native": "原生樣式"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "歌曲/影片切換",
|
||||||
|
"templates": {
|
||||||
|
"button": "歌曲"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizer": {
|
||||||
|
"description": "新增一個可視化音樂效果",
|
||||||
|
"menu": {
|
||||||
|
"visualizer-type": "樣式"
|
||||||
|
},
|
||||||
|
"name": "可視化音樂效果"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
src/index.ts
@ -1,7 +1,6 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import url from 'node:url';
|
import url from 'node:url';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import process from 'node:process';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
@ -56,6 +55,13 @@ import { loadI18n, setLanguage, t } from '@/i18n';
|
|||||||
|
|
||||||
import type { PluginConfig } from '@/types/plugins';
|
import type { PluginConfig } from '@/types/plugins';
|
||||||
|
|
||||||
|
if (!is.macOS()) {
|
||||||
|
delete allPlugins['touchbar'];
|
||||||
|
}
|
||||||
|
if (!is.windows()) {
|
||||||
|
delete allPlugins['taskbar-mediacontrol'];
|
||||||
|
}
|
||||||
|
|
||||||
// Catch errors and log them
|
// Catch errors and log them
|
||||||
unhandled({
|
unhandled({
|
||||||
logger: console.error,
|
logger: console.error,
|
||||||
@ -115,18 +121,18 @@ function onClosed() {
|
|||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle('get-main-plugin-names', () => Object.keys(mainPlugins));
|
ipcMain.handle('ytmd:get-main-plugin-names', () => Object.keys(mainPlugins));
|
||||||
|
|
||||||
const initHook = (win: BrowserWindow) => {
|
const initHook = (win: BrowserWindow) => {
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'get-config',
|
'ytmd:get-config',
|
||||||
(_, id: string) =>
|
(_, id: string) =>
|
||||||
deepmerge(
|
deepmerge(
|
||||||
allPlugins[id].config ?? { enabled: false },
|
allPlugins[id].config ?? { enabled: false },
|
||||||
config.get(`plugins.${id}`) ?? {},
|
config.get(`plugins.${id}`) ?? {},
|
||||||
) as PluginConfig,
|
) as PluginConfig,
|
||||||
);
|
);
|
||||||
ipcMain.handle('set-config', (_, name: string, obj: object) =>
|
ipcMain.handle('ytmd:set-config', (_, name: string, obj: object) =>
|
||||||
config.setPartial(`plugins.${name}`, obj, allPlugins[name].config),
|
config.setPartial(`plugins.${name}`, obj, allPlugins[name].config),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -259,7 +265,7 @@ async function createMainWindow() {
|
|||||||
const windowPosition: Electron.Point = config.get('window-position');
|
const windowPosition: Electron.Point = config.get('window-position');
|
||||||
const useInlineMenu = config.plugins.isEnabled('in-app-menu');
|
const useInlineMenu = config.plugins.isEnabled('in-app-menu');
|
||||||
|
|
||||||
const defaultTitleBarOverlayOptions: Electron.TitleBarOverlayOptions = {
|
const defaultTitleBarOverlayOptions: Electron.TitleBarOverlay = {
|
||||||
color: '#00000000',
|
color: '#00000000',
|
||||||
symbolColor: '#ffffff',
|
symbolColor: '#ffffff',
|
||||||
height: 32,
|
height: 32,
|
||||||
@ -300,7 +306,7 @@ async function createMainWindow() {
|
|||||||
const { x: windowX, y: windowY } = windowPosition;
|
const { x: windowX, y: windowY } = windowPosition;
|
||||||
const winSize = win.getSize();
|
const winSize = win.getSize();
|
||||||
const display = screen.getDisplayNearestPoint(windowPosition);
|
const display = screen.getDisplayNearestPoint(windowPosition);
|
||||||
const scaleFactor = display.scaleFactor;
|
const scaleFactor = is.windows() ? display.scaleFactor: 1;
|
||||||
|
|
||||||
const scaledWidth = Math.floor(windowSize.width / scaleFactor);
|
const scaledWidth = Math.floor(windowSize.width / scaleFactor);
|
||||||
const scaledHeight = Math.floor(windowSize.height / scaleFactor);
|
const scaledHeight = Math.floor(windowSize.height / scaleFactor);
|
||||||
@ -403,53 +409,26 @@ async function createMainWindow() {
|
|||||||
|
|
||||||
removeContentSecurityPolicy();
|
removeContentSecurityPolicy();
|
||||||
|
|
||||||
win.webContents.on('dom-ready', async () => {
|
win.webContents.on('dom-ready', () => {
|
||||||
if (useInlineMenu) {
|
if (useInlineMenu && is.windows()) {
|
||||||
win.setTitleBarOverlay({
|
win.setTitleBarOverlay({
|
||||||
...defaultTitleBarOverlayOptions,
|
...defaultTitleBarOverlayOptions,
|
||||||
height: Math.floor(
|
height: Math.floor(
|
||||||
defaultTitleBarOverlayOptions.height! *
|
defaultTitleBarOverlayOptions.height! *
|
||||||
win.webContents.getZoomFactor(),
|
win.webContents.getZoomFactor(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
win.webContents.on('will-redirect', (event) => {
|
||||||
|
const url = new URL(event.url);
|
||||||
|
|
||||||
// Inject index.html file as string using insertAdjacentHTML
|
// Workarounds for regions where YTM is restricted
|
||||||
// In dev mode, get string from process.env.VITE_DEV_SERVER_URL, else use fs.readFileSync
|
if (url.hostname.endsWith('youtube.com') && url.pathname === '/premium') {
|
||||||
if (is.dev() && process.env.ELECTRON_RENDERER_URL) {
|
event.preventDefault();
|
||||||
// HACK: to make vite work with electron renderer (supports hot reload)
|
|
||||||
await win.webContents.executeJavaScript(`
|
win.webContents.loadURL(
|
||||||
console.log('Loading vite from dev server');
|
'https://accounts.google.com/ServiceLogin?ltmpl=music&service=youtube&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26next%3Dhttps%253A%252F%252Fmusic.youtube.com%252F'
|
||||||
const viteScript = document.createElement('script');
|
|
||||||
viteScript.type = 'module';
|
|
||||||
viteScript.src = '${process.env.ELECTRON_RENDERER_URL}/@vite/client';
|
|
||||||
const rendererScript = document.createElement('script');
|
|
||||||
rendererScript.type = 'module';
|
|
||||||
rendererScript.src = '${process.env.ELECTRON_RENDERER_URL}/renderer.ts';
|
|
||||||
document.body.appendChild(viteScript);
|
|
||||||
document.body.appendChild(rendererScript);
|
|
||||||
0
|
|
||||||
`);
|
|
||||||
} else {
|
|
||||||
const rendererPath = path.join(__dirname, '..', 'renderer');
|
|
||||||
const indexHTML = parse(
|
|
||||||
fs.readFileSync(path.join(rendererPath, 'index.html'), 'utf-8'),
|
|
||||||
);
|
|
||||||
const scriptSrc = indexHTML.querySelector('script')!;
|
|
||||||
const scriptPath = path.join(
|
|
||||||
rendererPath,
|
|
||||||
scriptSrc.getAttribute('src')!,
|
|
||||||
);
|
|
||||||
const scriptString = fs.readFileSync(scriptPath, 'utf-8');
|
|
||||||
await win.webContents.executeJavaScriptInIsolatedWorld(
|
|
||||||
0,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
code: scriptString + ';0',
|
|
||||||
url: url.pathToFileURL(scriptPath).toString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -607,7 +586,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
|
||||||
@ -630,6 +609,48 @@ app.whenReady().then(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.on('get-renderer-script', (event) => {
|
||||||
|
// Inject index.html file as string using insertAdjacentHTML
|
||||||
|
// In dev mode, get string from process.env.VITE_DEV_SERVER_URL, else use fs.readFileSync
|
||||||
|
if (is.dev() && process.env.ELECTRON_RENDERER_URL) {
|
||||||
|
// HACK: to make vite work with electron renderer (supports hot reload)
|
||||||
|
event.returnValue = [null, `
|
||||||
|
console.log('${LoggerPrefix}', 'Loading vite from dev server');
|
||||||
|
(async () => {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
console.log('${LoggerPrefix}', 'Waiting for DOM to load');
|
||||||
|
document.addEventListener('DOMContentLoaded', () => resolve(), { once: true });
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const viteScript = document.createElement('script');
|
||||||
|
viteScript.type = 'module';
|
||||||
|
viteScript.src = '${process.env.ELECTRON_RENDERER_URL}/@vite/client';
|
||||||
|
const rendererScript = document.createElement('script');
|
||||||
|
rendererScript.type = 'module';
|
||||||
|
rendererScript.src = '${process.env.ELECTRON_RENDERER_URL}/renderer.ts';
|
||||||
|
document.body.appendChild(viteScript);
|
||||||
|
document.body.appendChild(rendererScript);
|
||||||
|
})();
|
||||||
|
0
|
||||||
|
`];
|
||||||
|
} else {
|
||||||
|
const rendererPath = path.join(__dirname, '..', 'renderer');
|
||||||
|
const indexHTML = parse(
|
||||||
|
fs.readFileSync(path.join(rendererPath, 'index.html'), 'utf-8'),
|
||||||
|
);
|
||||||
|
const scriptSrc = indexHTML.querySelector('script')!;
|
||||||
|
const scriptPath = path.join(
|
||||||
|
rendererPath,
|
||||||
|
scriptSrc.getAttribute('src')!,
|
||||||
|
);
|
||||||
|
const scriptString = fs.readFileSync(scriptPath, 'utf-8');
|
||||||
|
event.returnValue = [url.pathToFileURL(scriptPath).toString(), scriptString + ';0'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
mainWindow = await createMainWindow();
|
mainWindow = await createMainWindow();
|
||||||
await setApplicationMenu(mainWindow);
|
await setApplicationMenu(mainWindow);
|
||||||
await refreshMenu(mainWindow);
|
await refreshMenu(mainWindow);
|
||||||
@ -685,13 +706,15 @@ app.whenReady().then(async () => {
|
|||||||
const dialogOptions: Electron.MessageBoxOptions = {
|
const dialogOptions: Electron.MessageBoxOptions = {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
buttons: [
|
buttons: [
|
||||||
t('main.dialog.update-available.buttons.download'),
|
|
||||||
t('main.dialog.update-available.buttons.ok'),
|
t('main.dialog.update-available.buttons.ok'),
|
||||||
|
t('main.dialog.update-available.buttons.download'),
|
||||||
t('main.dialog.update-available.buttons.disable'),
|
t('main.dialog.update-available.buttons.disable'),
|
||||||
],
|
],
|
||||||
title: t('main.dialog.update-available.title'),
|
title: t('main.dialog.update-available.title'),
|
||||||
message: t('main.dialog.update-available.message'),
|
message: t('main.dialog.update-available.message'),
|
||||||
detail: t('main.dialog.update-available.detail', { downloadLink }),
|
detail: t('main.dialog.update-available.detail', { downloadLink }),
|
||||||
|
defaultId: 1,
|
||||||
|
cancelId: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let dialogPromise: Promise<Electron.MessageBoxReturnValue>;
|
let dialogPromise: Promise<Electron.MessageBoxReturnValue>;
|
||||||
@ -715,7 +738,7 @@ app.whenReady().then(async () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
case 0: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,9 @@ const loadedPluginMap: Record<
|
|||||||
export const createContext = <Config extends PluginConfig>(
|
export const createContext = <Config extends PluginConfig>(
|
||||||
id: string,
|
id: string,
|
||||||
): RendererContext<Config> => ({
|
): RendererContext<Config> => ({
|
||||||
getConfig: async () => window.ipcRenderer.invoke('get-config', id),
|
getConfig: async () => window.ipcRenderer.invoke('ytmd:get-config', id),
|
||||||
setConfig: async (newConfig) => {
|
setConfig: async (newConfig) => {
|
||||||
await window.ipcRenderer.invoke('set-config', id, newConfig);
|
await window.ipcRenderer.invoke('ytmd:set-config', id, newConfig);
|
||||||
},
|
},
|
||||||
ipc: {
|
ipc: {
|
||||||
send: (event: string, ...args: unknown[]) => {
|
send: (event: string, ...args: unknown[]) => {
|
||||||
|
|||||||
29
src/menu.ts
@ -1,5 +1,3 @@
|
|||||||
import process from 'node:process';
|
|
||||||
|
|
||||||
import is from 'electron-is';
|
import is from 'electron-is';
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
@ -11,6 +9,7 @@ import {
|
|||||||
shell,
|
shell,
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
import prompt from 'custom-electron-prompt';
|
import prompt from 'custom-electron-prompt';
|
||||||
|
import { satisfies } from 'semver';
|
||||||
|
|
||||||
import { allPlugins } from 'virtual:plugins';
|
import { allPlugins } from 'virtual:plugins';
|
||||||
|
|
||||||
@ -25,6 +24,8 @@ import promptOptions from './providers/prompt-options';
|
|||||||
import { getAllMenuTemplate, loadAllMenuPlugins } from './loader/menu';
|
import { getAllMenuTemplate, loadAllMenuPlugins } from './loader/menu';
|
||||||
import { setLanguage, t } from '@/i18n';
|
import { setLanguage, t } from '@/i18n';
|
||||||
|
|
||||||
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
export type MenuTemplate = Electron.MenuItemConstructorOptions[];
|
export type MenuTemplate = Electron.MenuItemConstructorOptions[];
|
||||||
|
|
||||||
// True only if in-app-menu was loaded on launch
|
// True only if in-app-menu was loaded on launch
|
||||||
@ -33,10 +34,14 @@ const inAppMenuActive = config.plugins.isEnabled('in-app-menu');
|
|||||||
const pluginEnabledMenu = (
|
const pluginEnabledMenu = (
|
||||||
plugin: string,
|
plugin: string,
|
||||||
label = '',
|
label = '',
|
||||||
|
description: string | undefined = undefined,
|
||||||
|
isNew = false,
|
||||||
hasSubmenu = false,
|
hasSubmenu = false,
|
||||||
refreshMenu: (() => void) | undefined = undefined,
|
refreshMenu: (() => void) | undefined = undefined,
|
||||||
): Electron.MenuItemConstructorOptions => ({
|
): Electron.MenuItemConstructorOptions => ({
|
||||||
label: label || plugin,
|
label: label || plugin,
|
||||||
|
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
|
||||||
|
toolTip: description,
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: config.plugins.isEnabled(plugin),
|
checked: config.plugins.isEnabled(plugin),
|
||||||
click(item: Electron.MenuItem) {
|
click(item: Electron.MenuItem) {
|
||||||
@ -68,12 +73,15 @@ export const mainMenuTemplate = async (
|
|||||||
|
|
||||||
const menuResult = Object.entries(getAllMenuTemplate()).map(
|
const menuResult = Object.entries(getAllMenuTemplate()).map(
|
||||||
([id, template]) => {
|
([id, template]) => {
|
||||||
const pluginLabel = allPlugins[id]?.name?.() ?? id;
|
const plugin = allPlugins[id];
|
||||||
|
const pluginLabel = plugin?.name?.() ?? id;
|
||||||
|
const pluginDescription = plugin?.description?.() ?? undefined;
|
||||||
|
const isNew = plugin?.addedVersion ? satisfies(packageJson.version, plugin.addedVersion) : false;
|
||||||
|
|
||||||
if (!config.plugins.isEnabled(id)) {
|
if (!config.plugins.isEnabled(id)) {
|
||||||
return [
|
return [
|
||||||
id,
|
id,
|
||||||
pluginEnabledMenu(id, pluginLabel, true, innerRefreshMenu),
|
pluginEnabledMenu(id, pluginLabel, pluginDescription, isNew, true, innerRefreshMenu),
|
||||||
] as const;
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +89,14 @@ export const mainMenuTemplate = async (
|
|||||||
id,
|
id,
|
||||||
{
|
{
|
||||||
label: pluginLabel,
|
label: pluginLabel,
|
||||||
|
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
|
||||||
|
toolTip: pluginDescription,
|
||||||
submenu: [
|
submenu: [
|
||||||
pluginEnabledMenu(
|
pluginEnabledMenu(
|
||||||
id,
|
id,
|
||||||
t('main.menu.plugins.enabled'),
|
t('main.menu.plugins.enabled'),
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
innerRefreshMenu,
|
innerRefreshMenu,
|
||||||
),
|
),
|
||||||
@ -108,9 +120,12 @@ export const mainMenuTemplate = async (
|
|||||||
const predefinedTemplate = menuResult.find((it) => it[0] === id);
|
const predefinedTemplate = menuResult.find((it) => it[0] === id);
|
||||||
if (predefinedTemplate) return predefinedTemplate[1];
|
if (predefinedTemplate) return predefinedTemplate[1];
|
||||||
|
|
||||||
const pluginLabel = allPlugins[id]?.name?.() ?? id;
|
const plugin = allPlugins[id];
|
||||||
|
const pluginLabel = plugin?.name?.() ?? id;
|
||||||
|
const pluginDescription = plugin?.description?.() ?? undefined;
|
||||||
|
const isNew = plugin?.addedVersion ? satisfies(packageJson.version, plugin.addedVersion) : false;
|
||||||
|
|
||||||
return pluginEnabledMenu(id, pluginLabel, true, innerRefreshMenu);
|
return pluginEnabledMenu(id, pluginLabel, pluginDescription, isNew, true, innerRefreshMenu);
|
||||||
});
|
});
|
||||||
|
|
||||||
const availableLanguages = Object.keys(languageResources);
|
const availableLanguages = Object.keys(languageResources);
|
||||||
@ -377,7 +392,7 @@ export const mainMenuTemplate = async (
|
|||||||
availableLanguages
|
availableLanguages
|
||||||
.map(
|
.map(
|
||||||
(lang): Electron.MenuItemConstructorOptions => ({
|
(lang): Electron.MenuItemConstructorOptions => ({
|
||||||
label: `${languageResources[lang].translation.language.name} (${languageResources[lang].translation.language['local-name']})`,
|
label: `${languageResources[lang].translation.language?.name ?? 'Unknown'} (${languageResources[lang].translation.language?.['local-name'] ?? 'Unknown'})`,
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: (config.get('options.language') ?? 'en') === lang,
|
checked: (config.get('options.language') ?? 'en') === lang,
|
||||||
click() {
|
click() {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { contextBridge, webFrame } from 'electron';
|
||||||
|
|
||||||
import { blockers } from './types';
|
import { blockers } from './types';
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import {
|
import {
|
||||||
@ -107,31 +109,25 @@ export default createPlugin({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
preload: {
|
preload: {
|
||||||
|
script: 'window.JSON.parse = window._proxyJsonParse; window._proxyJsonParse = undefined; window.Response.prototype.json = window._proxyResponseJson; window._proxyResponseJson = undefined; 0',
|
||||||
async start({ getConfig }) {
|
async start({ getConfig }) {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
|
|
||||||
if (config.blocker === blockers.WithBlocklists) {
|
if (config.blocker === blockers.WithBlocklists) {
|
||||||
// Preload adblocker to inject scripts/styles
|
// Preload adblocker to inject scripts/styles
|
||||||
await injectCliqzPreload();
|
await injectCliqzPreload();
|
||||||
|
} else if (config.blocker === blockers.InPlayer && !isInjected()) {
|
||||||
|
inject(contextBridge);
|
||||||
|
await webFrame.executeJavaScript(this.script);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onConfigChange(newConfig) {
|
async onConfigChange(newConfig) {
|
||||||
if (newConfig.blocker === blockers.WithBlocklists) {
|
if (newConfig.blocker === blockers.WithBlocklists) {
|
||||||
await injectCliqzPreload();
|
await injectCliqzPreload();
|
||||||
|
} else if (newConfig.blocker === blockers.InPlayer && !isInjected()) {
|
||||||
|
inject(contextBridge);
|
||||||
|
await webFrame.executeJavaScript(this.script);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
renderer: {
|
|
||||||
async start({ getConfig }) {
|
|
||||||
const config = await getConfig();
|
|
||||||
if (config.blocker === blockers.InPlayer && !isInjected()) {
|
|
||||||
inject();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onConfigChange(newConfig) {
|
|
||||||
if (newConfig.blocker === blockers.InPlayer && !isInjected()) {
|
|
||||||
inject();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
4
src/plugins/adblocker/injectors/inject.d.ts
vendored
@ -1,3 +1,5 @@
|
|||||||
export const inject: () => void;
|
import type { ContextBridge } from 'electron';
|
||||||
|
|
||||||
|
export const inject: (contextBridge: ContextBridge) => void;
|
||||||
|
|
||||||
export const isInjected: () => boolean;
|
export const isInjected: () => boolean;
|
||||||
|
|||||||
@ -10,9 +10,13 @@
|
|||||||
|
|
||||||
let injected = false;
|
let injected = false;
|
||||||
|
|
||||||
export const isInjected = () => isInjected;
|
export const isInjected = () => injected;
|
||||||
|
|
||||||
export const inject = () => {
|
/**
|
||||||
|
* @param {Electron.ContextBridge} contextBridge
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export const inject = (contextBridge) => {
|
||||||
injected = true;
|
injected = true;
|
||||||
{
|
{
|
||||||
const pruner = function (o) {
|
const pruner = function (o) {
|
||||||
@ -28,17 +32,17 @@ export const inject = () => {
|
|||||||
return o;
|
return o;
|
||||||
};
|
};
|
||||||
|
|
||||||
JSON.parse = new Proxy(JSON.parse, {
|
contextBridge.exposeInMainWorld('_proxyJsonParse', new Proxy(JSON.parse, {
|
||||||
apply() {
|
apply() {
|
||||||
return pruner(Reflect.apply(...arguments));
|
return pruner(Reflect.apply(...arguments));
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
Response.prototype.json = new Proxy(Response.prototype.json, {
|
contextBridge.exposeInMainWorld('_proxyResponseJson', new Proxy(Response.prototype.json, {
|
||||||
apply() {
|
apply() {
|
||||||
return Reflect.apply(...arguments).then((o) => pruner(o));
|
return Reflect.apply(...arguments).then((o) => pruner(o));
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
|
|||||||
193
src/plugins/album-actions/index.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import { t } from '@/i18n';
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||||
|
|
||||||
|
import undislikeHTML from './templates/undislike.html?raw';
|
||||||
|
import dislikeHTML from './templates/dislike.html?raw';
|
||||||
|
import likeHTML from './templates/like.html?raw';
|
||||||
|
import unlikeHTML from './templates/unlike.html?raw';
|
||||||
|
|
||||||
|
export default createPlugin<
|
||||||
|
unknown,
|
||||||
|
unknown,
|
||||||
|
{
|
||||||
|
observer?: MutationObserver;
|
||||||
|
loadObserver?: MutationObserver;
|
||||||
|
changeObserver?: MutationObserver;
|
||||||
|
waiting: boolean;
|
||||||
|
onPageChange(): void;
|
||||||
|
waitForElem(selector: string): Promise<HTMLElement>;
|
||||||
|
loadFullList: (event: MouseEvent) => void;
|
||||||
|
applyToList(id: string, loader: HTMLElement): void;
|
||||||
|
start(): void;
|
||||||
|
stop(): void;
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
name: () => t('plugins.album-actions.name'),
|
||||||
|
description: () => t('plugins.album-actions.description'),
|
||||||
|
restartNeeded: false,
|
||||||
|
addedVersion: '3.2.X',
|
||||||
|
config: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
waiting: false,
|
||||||
|
start() {
|
||||||
|
// Waits for pagechange
|
||||||
|
this.onPageChange();
|
||||||
|
this.observer = new MutationObserver(() => {
|
||||||
|
this.onPageChange();
|
||||||
|
});
|
||||||
|
this.observer.observe(document.querySelector('#browse-page')!, {
|
||||||
|
attributes: false,
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async onPageChange() {
|
||||||
|
if (this.waiting) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.waiting = true;
|
||||||
|
}
|
||||||
|
const continuations = await this.waitForElem('#continuations');
|
||||||
|
this.waiting = false;
|
||||||
|
//Gets the for buttons
|
||||||
|
const buttons: Array<HTMLElement> = [
|
||||||
|
ElementFromHtml(undislikeHTML),
|
||||||
|
ElementFromHtml(dislikeHTML),
|
||||||
|
ElementFromHtml(likeHTML),
|
||||||
|
ElementFromHtml(unlikeHTML),
|
||||||
|
];
|
||||||
|
//Finds the playlist
|
||||||
|
const playlist =
|
||||||
|
document.querySelector('ytmusic-shelf-renderer') ??
|
||||||
|
document.querySelector('ytmusic-playlist-shelf-renderer')!;
|
||||||
|
// Adds an observer for every button, so it gets updated when one is clicked
|
||||||
|
this.changeObserver?.disconnect();
|
||||||
|
this.changeObserver = new MutationObserver(() => {
|
||||||
|
this.stop();
|
||||||
|
this.start();
|
||||||
|
});
|
||||||
|
const allButtons = playlist.querySelectorAll(
|
||||||
|
'yt-button-shape.ytmusic-like-button-renderer',
|
||||||
|
);
|
||||||
|
for (const btn of allButtons) {
|
||||||
|
this.changeObserver.observe(btn, {
|
||||||
|
attributes: true,
|
||||||
|
childList: false,
|
||||||
|
subtree: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//Determine if button is needed and colors the percentage
|
||||||
|
const listsLength = playlist.querySelectorAll(
|
||||||
|
'#button-shape-dislike > button',
|
||||||
|
).length;
|
||||||
|
if (continuations.children.length == 0 && listsLength > 0) {
|
||||||
|
const counts = [
|
||||||
|
playlist?.querySelectorAll(
|
||||||
|
'#button-shape-dislike[aria-pressed=true] > button',
|
||||||
|
).length,
|
||||||
|
playlist?.querySelectorAll(
|
||||||
|
'#button-shape-dislike[aria-pressed=false] > button',
|
||||||
|
).length,
|
||||||
|
playlist?.querySelectorAll(
|
||||||
|
'#button-shape-like[aria-pressed=false] > button',
|
||||||
|
).length,
|
||||||
|
playlist?.querySelectorAll(
|
||||||
|
'#button-shape-like[aria-pressed=true] > button',
|
||||||
|
).length,
|
||||||
|
];
|
||||||
|
let i = 0;
|
||||||
|
for (const count of counts) {
|
||||||
|
if (count == 0) {
|
||||||
|
buttons.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
} else {
|
||||||
|
(buttons[i].children[0].children[0] as HTMLElement).style.setProperty(
|
||||||
|
'-webkit-mask-size',
|
||||||
|
`100% ${100 - ((count / listsLength) * 100)}%`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const menu = document.querySelector('.detail-page-menu');
|
||||||
|
if (menu && !document.querySelector('.like-menu')) {
|
||||||
|
for (const button of buttons) {
|
||||||
|
menu.appendChild(button);
|
||||||
|
button.addEventListener('click', this.loadFullList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadFullList(event: MouseEvent) {
|
||||||
|
if (event.currentTarget instanceof Element) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const id = event.currentTarget.id;
|
||||||
|
const loader = document.getElementById('continuations')!;
|
||||||
|
this.loadObserver = new MutationObserver(() => {
|
||||||
|
this.applyToList(id, loader);
|
||||||
|
});
|
||||||
|
this.applyToList(id, loader);
|
||||||
|
this.loadObserver.observe(loader, {
|
||||||
|
attributes: true,
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
loader?.style.setProperty('top', '0');
|
||||||
|
loader?.style.setProperty('left', '50%');
|
||||||
|
loader?.style.setProperty('position', 'absolute');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applyToList(id: string, loader: HTMLElement) {
|
||||||
|
if (loader.children.length != 0) return;
|
||||||
|
this.loadObserver?.disconnect();
|
||||||
|
let playlistButtons: NodeListOf<HTMLElement> | undefined;
|
||||||
|
const playlist = document.querySelector('ytmusic-shelf-renderer')
|
||||||
|
? document.querySelector('ytmusic-shelf-renderer')
|
||||||
|
: document.querySelector('ytmusic-playlist-shelf-renderer');
|
||||||
|
switch (id) {
|
||||||
|
case 'allundislike':
|
||||||
|
playlistButtons = playlist?.querySelectorAll(
|
||||||
|
'#button-shape-dislike[aria-pressed=true] > button',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'alldislike':
|
||||||
|
playlistButtons = playlist?.querySelectorAll(
|
||||||
|
'#button-shape-dislike[aria-pressed=false] > button',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'alllike':
|
||||||
|
playlistButtons = playlist?.querySelectorAll(
|
||||||
|
'#button-shape-like[aria-pressed=false] > button',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'allunlike':
|
||||||
|
playlistButtons = playlist?.querySelectorAll(
|
||||||
|
'#button-shape-like[aria-pressed=true] > button',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
playlistButtons?.forEach((elem) => elem.click());
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
this.observer?.disconnect();
|
||||||
|
this.changeObserver?.disconnect();
|
||||||
|
for (const button of document.querySelectorAll('.like-menu')) {
|
||||||
|
button.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
waitForElem(selector: string) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const elem = document.querySelector<HTMLElement>(selector);
|
||||||
|
if (!elem) return;
|
||||||
|
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve(elem);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
74
src/plugins/album-actions/templates/dislike.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<button
|
||||||
|
id="alldislike"
|
||||||
|
data-type="dislike"
|
||||||
|
data-filled="false"
|
||||||
|
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||||
|
aria-pressed="false"
|
||||||
|
aria-label="Dislike all"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="
|
||||||
|
color: white;
|
||||||
|
-webkit-mask: linear-gradient(grey, grey);
|
||||||
|
-webkit-mask-size: 100% 50%;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||||
|
<div
|
||||||
|
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||||
|
</div>
|
||||||
|
</yt-touch-feedback-shape>
|
||||||
|
</button>
|
||||||
74
src/plugins/album-actions/templates/like.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<button
|
||||||
|
id="alllike"
|
||||||
|
data-type="like"
|
||||||
|
data-filled="false"
|
||||||
|
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||||
|
aria-pressed="false"
|
||||||
|
aria-label="Like all"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="
|
||||||
|
color: white;
|
||||||
|
-webkit-mask: linear-gradient(grey, grey);
|
||||||
|
-webkit-mask-size: 100% 50%;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||||
|
<div
|
||||||
|
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||||
|
</div>
|
||||||
|
</yt-touch-feedback-shape>
|
||||||
|
</button>
|
||||||
74
src/plugins/album-actions/templates/undislike.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<button
|
||||||
|
id="allundislike"
|
||||||
|
data-type="dislike"
|
||||||
|
data-filled="true"
|
||||||
|
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||||
|
aria-pressed="false"
|
||||||
|
aria-label="Undislike all"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="
|
||||||
|
color: white;
|
||||||
|
-webkit-mask: linear-gradient(grey, grey);
|
||||||
|
-webkit-mask-size: 100% 50%;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||||
|
<div
|
||||||
|
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||||
|
</div>
|
||||||
|
</yt-touch-feedback-shape>
|
||||||
|
</button>
|
||||||
74
src/plugins/album-actions/templates/unlike.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<button
|
||||||
|
id="allunlike"
|
||||||
|
data-type="like"
|
||||||
|
data-filled="true"
|
||||||
|
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
|
||||||
|
aria-pressed="false"
|
||||||
|
aria-label="Unlike all"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="color: var(--ytmusic-setting-item-toggle-active)"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="yt-spec-button-shape-next__icon"
|
||||||
|
style="
|
||||||
|
color: white;
|
||||||
|
-webkit-mask: linear-gradient(grey, grey);
|
||||||
|
-webkit-mask-size: 100% 50%;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 24px; height: 24px">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
focusable="false"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||||
|
>
|
||||||
|
<g class="style-scope yt-icon">
|
||||||
|
<path
|
||||||
|
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
|
||||||
|
class="style-scope yt-icon"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<yt-touch-feedback-shape style="border-radius: inherit">
|
||||||
|
<div
|
||||||
|
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__stroke"></div>
|
||||||
|
<div class="yt-spec-touch-feedback-shape__fill"></div>
|
||||||
|
</div>
|
||||||
|
</yt-touch-feedback-shape>
|
||||||
|
</button>
|
||||||
@ -1,94 +1,80 @@
|
|||||||
import { FastAverageColor } from 'fast-average-color';
|
import { FastAverageColor } from 'fast-average-color';
|
||||||
|
import Color from 'color';
|
||||||
|
|
||||||
import style from './style.css?inline';
|
import style from './style.css?inline';
|
||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
import type { VideoDataChanged } from '@/types/video-data-changed';
|
const COLOR_KEY = '--ytmusic-album-color';
|
||||||
|
const DARK_COLOR_KEY = '--ytmusic-album-color-dark';
|
||||||
|
const RATIO_KEY = '--ytmusic-album-color-ratio';
|
||||||
|
|
||||||
export default createPlugin({
|
export default createPlugin<
|
||||||
|
unknown,
|
||||||
|
unknown,
|
||||||
|
{
|
||||||
|
color?: Color;
|
||||||
|
darkColor?: Color;
|
||||||
|
|
||||||
|
playerPage: HTMLElement | null;
|
||||||
|
navBarBackground: HTMLElement | null;
|
||||||
|
ytmusicPlayerBar: HTMLElement | null;
|
||||||
|
playerBarBackground: HTMLElement | null;
|
||||||
|
sidebarBig: HTMLElement | null;
|
||||||
|
sidebarSmall: HTMLElement | null;
|
||||||
|
ytmusicAppLayout: HTMLElement | null;
|
||||||
|
|
||||||
|
getMixedColor(color: string, key: string, alpha?: number, ratioMultiply?: number): string;
|
||||||
|
updateColor(): void;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: boolean;
|
||||||
|
ratio: number;
|
||||||
|
}
|
||||||
|
>({
|
||||||
name: () => t('plugins.album-color-theme.name'),
|
name: () => t('plugins.album-color-theme.name'),
|
||||||
description: () => t('plugins.album-color-theme.description'),
|
description: () => t('plugins.album-color-theme.description'),
|
||||||
restartNeeded: true,
|
restartNeeded: false,
|
||||||
config: {
|
config: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
ratio: 0.5,
|
||||||
},
|
},
|
||||||
stylesheets: [style],
|
stylesheets: [style],
|
||||||
|
menu: async ({ getConfig, setConfig }) => {
|
||||||
|
const ratioList = [0, 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.album-color-theme.menu.color-mix-ratio.label'),
|
||||||
|
submenu: ratioList.map((ratio) => ({
|
||||||
|
label: t(
|
||||||
|
'plugins.album-color-theme.menu.color-mix-ratio.submenu.percent',
|
||||||
|
{
|
||||||
|
ratio: ratio * 100,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.ratio === ratio,
|
||||||
|
click() {
|
||||||
|
setConfig({ ratio });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
hexToHSL: (H: string) => {
|
playerPage: null,
|
||||||
// Convert hex to RGB first
|
navBarBackground: null,
|
||||||
let r = 0;
|
ytmusicPlayerBar: null,
|
||||||
let g = 0;
|
playerBarBackground: null,
|
||||||
let b = 0;
|
sidebarBig: null,
|
||||||
if (H.length == 4) {
|
sidebarSmall: null,
|
||||||
r = Number('0x' + H[1] + H[1]);
|
ytmusicAppLayout: null,
|
||||||
g = Number('0x' + H[2] + H[2]);
|
|
||||||
b = Number('0x' + H[3] + H[3]);
|
|
||||||
} else if (H.length == 7) {
|
|
||||||
r = Number('0x' + H[1] + H[2]);
|
|
||||||
g = Number('0x' + H[3] + H[4]);
|
|
||||||
b = Number('0x' + H[5] + H[6]);
|
|
||||||
}
|
|
||||||
// Then to HSL
|
|
||||||
r /= 255;
|
|
||||||
g /= 255;
|
|
||||||
b /= 255;
|
|
||||||
const cmin = Math.min(r, g, b);
|
|
||||||
const cmax = Math.max(r, g, b);
|
|
||||||
const delta = cmax - cmin;
|
|
||||||
let h: number;
|
|
||||||
let s: number;
|
|
||||||
let l: number;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
async start({ getConfig }) {
|
||||||
h = 0;
|
|
||||||
} else if (cmax == r) {
|
|
||||||
h = ((g - b) / delta) % 6;
|
|
||||||
} else if (cmax == g) {
|
|
||||||
h = ((b - r) / delta) + 2;
|
|
||||||
} else {
|
|
||||||
h = ((r - g) / delta) + 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h = Math.round(h * 60);
|
|
||||||
|
|
||||||
if (h < 0) {
|
|
||||||
h += 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
l = (cmax + cmin) / 2;
|
|
||||||
s = delta == 0 ? 0 : delta / (1 - Math.abs((2 * l) - 1));
|
|
||||||
s = +(s * 100).toFixed(1);
|
|
||||||
l = +(l * 100).toFixed(1);
|
|
||||||
|
|
||||||
//return "hsl(" + h + "," + s + "%," + l + "%)";
|
|
||||||
return [h, s, l];
|
|
||||||
},
|
|
||||||
hue: 0,
|
|
||||||
saturation: 0,
|
|
||||||
lightness: 0,
|
|
||||||
|
|
||||||
changeElementColor: (
|
|
||||||
element: HTMLElement | null,
|
|
||||||
hue: number,
|
|
||||||
saturation: number,
|
|
||||||
lightness: number,
|
|
||||||
) => {
|
|
||||||
if (element) {
|
|
||||||
element.style.backgroundColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
playerPage: null as HTMLElement | null,
|
|
||||||
navBarBackground: null as HTMLElement | null,
|
|
||||||
ytmusicPlayerBar: null as HTMLElement | null,
|
|
||||||
playerBarBackground: null as HTMLElement | null,
|
|
||||||
sidebarBig: null as HTMLElement | null,
|
|
||||||
sidebarSmall: null as HTMLElement | null,
|
|
||||||
ytmusicAppLayout: null as HTMLElement | null,
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.playerPage = document.querySelector<HTMLElement>('#player-page');
|
this.playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||||
this.navBarBackground = document.querySelector<HTMLElement>(
|
this.navBarBackground = document.querySelector<HTMLElement>(
|
||||||
'#nav-bar-background',
|
'#nav-bar-background',
|
||||||
@ -104,112 +90,102 @@ export default createPlugin({
|
|||||||
);
|
);
|
||||||
this.ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
this.ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||||
|
|
||||||
const observer = new MutationObserver((mutationsList) => {
|
const config = await getConfig();
|
||||||
for (const mutation of mutationsList) {
|
document.documentElement.style.setProperty(RATIO_KEY, `${~~(config.ratio * 100)}%`);
|
||||||
if (mutation.type === 'attributes') {
|
|
||||||
const isPageOpen =
|
|
||||||
this.ytmusicAppLayout?.hasAttribute('player-page-open');
|
|
||||||
if (isPageOpen) {
|
|
||||||
this.changeElementColor(
|
|
||||||
this.sidebarSmall,
|
|
||||||
this.hue,
|
|
||||||
this.saturation,
|
|
||||||
this.lightness - 30,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (this.sidebarSmall) {
|
|
||||||
this.sidebarSmall.style.backgroundColor = 'black';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.playerPage) {
|
|
||||||
observer.observe(this.playerPage, { attributes: true });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onPlayerApiReady(playerApi) {
|
onPlayerApiReady(playerApi) {
|
||||||
const fastAverageColor = new FastAverageColor();
|
const fastAverageColor = new FastAverageColor();
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener('videodatachange', async (event) => {
|
||||||
'videodatachange',
|
if (event.detail.name !== 'dataloaded') return;
|
||||||
(event: CustomEvent<VideoDataChanged>) => {
|
|
||||||
if (event.detail.name === 'dataloaded') {
|
const playerResponse = playerApi.getPlayerResponse();
|
||||||
const playerResponse = playerApi.getPlayerResponse();
|
const thumbnail = playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
|
||||||
const thumbnail =
|
if (!thumbnail) return;
|
||||||
playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
|
|
||||||
if (thumbnail) {
|
const albumColor = await fastAverageColor.getColorAsync(thumbnail.url)
|
||||||
fastAverageColor
|
.catch((err) => {
|
||||||
.getColorAsync(thumbnail.url)
|
console.error(err);
|
||||||
.then((albumColor) => {
|
return null;
|
||||||
if (albumColor) {
|
});
|
||||||
const [hue, saturation, lightness] = ([
|
|
||||||
this.hue,
|
if (albumColor) {
|
||||||
this.saturation,
|
const target = Color(albumColor.hex);
|
||||||
this.lightness,
|
|
||||||
] = this.hexToHSL(albumColor.hex));
|
this.darkColor = target.darken(0.3).rgb();
|
||||||
this.changeElementColor(
|
this.color = target.darken(0.15).rgb();
|
||||||
this.playerPage,
|
|
||||||
hue,
|
while (this.color.luminosity() > 0.5) {
|
||||||
saturation,
|
this.color = this.color?.darken(0.05);
|
||||||
lightness - 30,
|
this.darkColor = this.darkColor?.darken(0.05);
|
||||||
);
|
|
||||||
this.changeElementColor(
|
|
||||||
this.navBarBackground,
|
|
||||||
hue,
|
|
||||||
saturation,
|
|
||||||
lightness - 15,
|
|
||||||
);
|
|
||||||
this.changeElementColor(
|
|
||||||
this.ytmusicPlayerBar,
|
|
||||||
hue,
|
|
||||||
saturation,
|
|
||||||
lightness - 15,
|
|
||||||
);
|
|
||||||
this.changeElementColor(
|
|
||||||
this.playerBarBackground,
|
|
||||||
hue,
|
|
||||||
saturation,
|
|
||||||
lightness - 15,
|
|
||||||
);
|
|
||||||
this.changeElementColor(
|
|
||||||
this.sidebarBig,
|
|
||||||
hue,
|
|
||||||
saturation,
|
|
||||||
lightness - 15,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
this.ytmusicAppLayout?.hasAttribute('player-page-open')
|
|
||||||
) {
|
|
||||||
this.changeElementColor(
|
|
||||||
this.sidebarSmall,
|
|
||||||
hue,
|
|
||||||
saturation,
|
|
||||||
lightness - 30,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const ytRightClickList =
|
|
||||||
document.querySelector<HTMLElement>(
|
|
||||||
'tp-yt-paper-listbox',
|
|
||||||
);
|
|
||||||
this.changeElementColor(
|
|
||||||
ytRightClickList,
|
|
||||||
hue,
|
|
||||||
saturation,
|
|
||||||
lightness - 15,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (this.playerPage) {
|
|
||||||
this.playerPage.style.backgroundColor = '#000000';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => console.error(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
);
|
document.documentElement.style.setProperty(COLOR_KEY, `${~~this.color.red()}, ${~~this.color.green()}, ${~~this.color.blue()}`);
|
||||||
|
document.documentElement.style.setProperty(DARK_COLOR_KEY, `${~~this.darkColor.red()}, ${~~this.darkColor.green()}, ${~~this.darkColor.blue()}`);
|
||||||
|
} else {
|
||||||
|
document.documentElement.style.setProperty(COLOR_KEY, '0, 0, 0');
|
||||||
|
document.documentElement.style.setProperty(DARK_COLOR_KEY, '0, 0, 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateColor();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onConfigChange(config) {
|
||||||
|
document.documentElement.style.setProperty(RATIO_KEY, `${~~(config.ratio * 100)}%`);
|
||||||
|
},
|
||||||
|
getMixedColor(color: string, key: string, alpha = 1, ratioMultiply) {
|
||||||
|
const keyColor = `rgba(var(${key}), ${alpha})`;
|
||||||
|
|
||||||
|
let colorRatio = `var(${RATIO_KEY}, 50%)`;
|
||||||
|
let originalRatio = `calc(100% - var(${RATIO_KEY}, 50%))`;
|
||||||
|
if (ratioMultiply) {
|
||||||
|
colorRatio = `calc(var(${RATIO_KEY}, 50%) * ${ratioMultiply})`;
|
||||||
|
originalRatio = `calc(100% - calc(var(${RATIO_KEY}, 50%) * ${ratioMultiply}))`;
|
||||||
|
}
|
||||||
|
return `color-mix(in srgb, ${color} ${originalRatio}, ${keyColor} ${colorRatio})`;
|
||||||
|
},
|
||||||
|
updateColor() {
|
||||||
|
const variableMap = {
|
||||||
|
'--ytmusic-color-black1': '#212121',
|
||||||
|
'--ytmusic-color-black2': '#181818',
|
||||||
|
'--ytmusic-color-black3': '#030303',
|
||||||
|
'--ytmusic-color-black4': '#030303',
|
||||||
|
'--ytmusic-color-blackpure': '#000',
|
||||||
|
'--dark-theme-background-color': '#212121',
|
||||||
|
'--yt-spec-base-background': '#0f0f0f',
|
||||||
|
'--yt-spec-raised-background': '#212121',
|
||||||
|
'--yt-spec-menu-background': '#282828',
|
||||||
|
'--yt-spec-static-brand-black': '#212121',
|
||||||
|
'--yt-spec-static-overlay-background-solid': '#000',
|
||||||
|
'--yt-spec-static-overlay-background-heavy': 'rgba(0,0,0,0.8)',
|
||||||
|
'--yt-spec-static-overlay-background-medium': 'rgba(0,0,0,0.6)',
|
||||||
|
'--yt-spec-static-overlay-background-medium-light': 'rgba(0,0,0,0.3)',
|
||||||
|
'--yt-spec-static-overlay-background-light': 'rgba(0,0,0,0.1)',
|
||||||
|
'--yt-spec-general-background-a': '#181818',
|
||||||
|
'--yt-spec-general-background-b': '#0f0f0f',
|
||||||
|
'--yt-spec-general-background-c': '#030303',
|
||||||
|
'--yt-spec-snackbar-background': '#030303',
|
||||||
|
'--yt-spec-filled-button-text': '#030303',
|
||||||
|
'--yt-spec-black-1': '#282828',
|
||||||
|
'--yt-spec-black-2': '#1f1f1f',
|
||||||
|
'--yt-spec-black-3': '#161616',
|
||||||
|
'--yt-spec-black-4': '#0d0d0d',
|
||||||
|
'--yt-spec-black-pure': '#000',
|
||||||
|
'--yt-spec-black-pure-alpha-5': 'rgba(0,0,0,0.05)',
|
||||||
|
'--yt-spec-black-pure-alpha-10': 'rgba(0,0,0,0.1)',
|
||||||
|
'--yt-spec-black-pure-alpha-15': 'rgba(0,0,0,0.15)',
|
||||||
|
'--yt-spec-black-pure-alpha-30': 'rgba(0,0,0,0.3)',
|
||||||
|
'--yt-spec-black-pure-alpha-60': 'rgba(0,0,0,0.6)',
|
||||||
|
'--yt-spec-black-pure-alpha-80': 'rgba(0,0,0,0.8)',
|
||||||
|
'--yt-spec-black-1-alpha-98': 'rgba(40,40,40,0.98)',
|
||||||
|
'--yt-spec-black-1-alpha-95': 'rgba(40,40,40,0.95)',
|
||||||
|
};
|
||||||
|
Object.entries(variableMap).map(([variable, color]) => {
|
||||||
|
document.documentElement.style.setProperty(variable, this.getMixedColor(color, COLOR_KEY), 'important');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.style.setProperty('background', this.getMixedColor('#030303', COLOR_KEY), 'important');
|
||||||
|
document.documentElement.style.setProperty('--ytmusic-background', this.getMixedColor('#030303', DARK_COLOR_KEY), 'important');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,28 +4,24 @@ yt-page-navigation-progress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#player-page {
|
#player-page {
|
||||||
transition:
|
transition: transform 300ms,
|
||||||
transform 300ms,
|
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
||||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav-bar-background {
|
#nav-bar-background {
|
||||||
transition:
|
transition: opacity 200ms,
|
||||||
opacity 200ms,
|
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
||||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#mini-guide-background {
|
#mini-guide-background {
|
||||||
transition:
|
transition: opacity 200ms,
|
||||||
opacity 200ms,
|
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
||||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
|
||||||
border-right: 0px !important;
|
border-right: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guide-wrapper {
|
#guide-wrapper {
|
||||||
transition:
|
transition: opacity 200ms,
|
||||||
opacity 200ms,
|
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
||||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#img,
|
#img,
|
||||||
@ -37,3 +33,57 @@ yt-page-navigation-progress {
|
|||||||
#items {
|
#items {
|
||||||
border-radius: 10px !important;
|
border-radius: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* fix blur navigation bar */
|
||||||
|
|
||||||
|
ytmusic-app-layout > [slot='player-page'] {
|
||||||
|
padding-top: 90px;
|
||||||
|
margin-top: calc(-90px + var(--menu-bar-height, 0px)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fix icon color */
|
||||||
|
|
||||||
|
.duration.ytmusic-player-queue-item, .byline.ytmusic-player-queue-item {
|
||||||
|
color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
--yt-endpoint-color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
--yt-endpoint-hover-color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
--yt-endpoint-visited-color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.ytmusic-menu-navigation-item-renderer {
|
||||||
|
color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu.ytmusic-player-bar {
|
||||||
|
--iron-icon-fill-color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-player-bar {
|
||||||
|
color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-info.ytmusic-player-bar {
|
||||||
|
color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider.ytmusic-player-bar, .expand-volume-slider.ytmusic-player-bar {
|
||||||
|
--paper-slider-container-color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fix background image */
|
||||||
|
ytmusic-fullbleed-thumbnail-renderer img {
|
||||||
|
mask: linear-gradient(to bottom, #000 0%, #000 50%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-gradient.style-scope,
|
||||||
|
ytmusic-app-layout[is-bauhaus-sidenav-enabled] #mini-guide-background.ytmusic-app-layout {
|
||||||
|
background: var(--ytmusic-background) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-browse-response[has-background]:not([disable-gradient]) .background-gradient.ytmusic-browse-response {
|
||||||
|
background: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#background.immersive-background.style-scope.ytmusic-browse-response {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|||||||
@ -145,6 +145,73 @@ export default createPlugin({
|
|||||||
observer: null as MutationObserver | null,
|
observer: null as MutationObserver | null,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
const injectBlurImage = () => {
|
||||||
|
const songImage = document.querySelector<HTMLImageElement>(
|
||||||
|
'#song-image',
|
||||||
|
);
|
||||||
|
const image = document.querySelector<HTMLImageElement>(
|
||||||
|
'#song-image yt-img-shadow > img',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!songImage) return null;
|
||||||
|
if (!image) return null;
|
||||||
|
|
||||||
|
const blurImage = document.createElement('img');
|
||||||
|
blurImage.classList.add('html5-blur-image');
|
||||||
|
blurImage.src = image.src;
|
||||||
|
|
||||||
|
const applyImageAttribute = () => {
|
||||||
|
const rect = image.getBoundingClientRect();
|
||||||
|
|
||||||
|
const newWidth = Math.floor(image.width || rect.width);
|
||||||
|
const newHeight = Math.floor(image.height || rect.height);
|
||||||
|
|
||||||
|
if (newWidth === 0 || newHeight === 0) return;
|
||||||
|
|
||||||
|
if (this.isFullscreen) blurImage.classList.add('fullscreen');
|
||||||
|
else blurImage.classList.remove('fullscreen');
|
||||||
|
|
||||||
|
const leftOffset = (newWidth * (this.sizeRatio - 1)) / 2;
|
||||||
|
const topOffset = (newHeight * (this.sizeRatio - 1)) / 2;
|
||||||
|
blurImage.style.setProperty('--left', `${-1 * leftOffset}px`);
|
||||||
|
blurImage.style.setProperty('--top', `${-1 * topOffset}px`);
|
||||||
|
blurImage.style.setProperty('--width', `${newWidth * this.sizeRatio}px`);
|
||||||
|
blurImage.style.setProperty('--height', `${newHeight * this.sizeRatio}px`);
|
||||||
|
blurImage.style.setProperty('--blur', `${this.blur}px`);
|
||||||
|
blurImage.style.setProperty('--opacity', `${this.opacity}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update = applyImageAttribute;
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'attributes') {
|
||||||
|
applyImageAttribute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
applyImageAttribute();
|
||||||
|
});
|
||||||
|
|
||||||
|
applyImageAttribute();
|
||||||
|
observer.observe(songImage, { attributes: true });
|
||||||
|
resizeObserver.observe(songImage);
|
||||||
|
window.addEventListener('resize', applyImageAttribute);
|
||||||
|
|
||||||
|
/* injecting */
|
||||||
|
songImage.prepend(blurImage);
|
||||||
|
|
||||||
|
/* cleanup */
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
window.removeEventListener('resize', applyImageAttribute);
|
||||||
|
|
||||||
|
if (blurImage.isConnected) blurImage.remove();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const injectBlurVideo = (): (() => void) | null => {
|
const injectBlurVideo = (): (() => void) | null => {
|
||||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||||
const video = document.querySelector<HTMLVideoElement>(
|
const video = document.querySelector<HTMLVideoElement>(
|
||||||
@ -172,7 +239,6 @@ export default createPlugin({
|
|||||||
cancelAnimationFrame(lastEffectWorkId);
|
cancelAnimationFrame(lastEffectWorkId);
|
||||||
|
|
||||||
lastEffectWorkId = requestAnimationFrame(() => {
|
lastEffectWorkId = requestAnimationFrame(() => {
|
||||||
// console.log('context', context);
|
|
||||||
if (!context) return;
|
if (!context) return;
|
||||||
|
|
||||||
const width = this.qualityRatio;
|
const width = this.qualityRatio;
|
||||||
@ -280,13 +346,20 @@ export default createPlugin({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isVideoMode = () => {
|
||||||
|
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||||
|
if (!songVideo) return false;
|
||||||
|
|
||||||
|
return getComputedStyle(songVideo).display !== 'none';
|
||||||
|
};
|
||||||
|
|
||||||
const playerPage = document.querySelector<HTMLElement>('#player-page');
|
const playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||||
|
|
||||||
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
|
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
|
||||||
if (isPageOpen) {
|
if (isPageOpen) {
|
||||||
this.unregister?.();
|
this.unregister?.();
|
||||||
this.unregister = injectBlurVideo() ?? null;
|
this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const observer = new MutationObserver((mutationsList) => {
|
const observer = new MutationObserver((mutationsList) => {
|
||||||
@ -296,7 +369,7 @@ export default createPlugin({
|
|||||||
ytmusicAppLayout?.hasAttribute('player-page-open');
|
ytmusicAppLayout?.hasAttribute('player-page-open');
|
||||||
if (isPageOpen) {
|
if (isPageOpen) {
|
||||||
this.unregister?.();
|
this.unregister?.();
|
||||||
this.unregister = injectBlurVideo() ?? null;
|
this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||||
} else {
|
} else {
|
||||||
this.unregister?.();
|
this.unregister?.();
|
||||||
this.unregister = null;
|
this.unregister = null;
|
||||||
|
|||||||
@ -24,3 +24,17 @@
|
|||||||
#song-video .html5-video-container > video {
|
#song-video .html5-video-container > video {
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#song-image .html5-blur-image {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
left: var(--left, 0px);
|
||||||
|
top: var(--top, 0px);
|
||||||
|
width: var(--width, 100%) !important;
|
||||||
|
height: var(--height, 100%) !important;
|
||||||
|
|
||||||
|
filter: blur(var(--blur, 100px));
|
||||||
|
opacity: var(--opacity, 1);
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export default createPlugin({
|
|||||||
|
|
||||||
renderer() {
|
renderer() {
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
'audioCanPlay',
|
'ytmd:audio-can-play',
|
||||||
({ detail: { audioSource, audioContext } }) => {
|
({ detail: { audioSource, audioContext } }) => {
|
||||||
const compressor = audioContext.createDynamicsCompressor();
|
const compressor = audioContext.createDynamicsCompressor();
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,24 @@
|
|||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import style from './style.css?inline';
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
|
import style from './style.css?inline';
|
||||||
|
|
||||||
export default createPlugin({
|
export default createPlugin({
|
||||||
name: () => t('plugins.blur-nav-bar.name'),
|
name: () => t('plugins.blur-nav-bar.name'),
|
||||||
description: () => t('plugins.blur-nav-bar.description'),
|
description: () => t('plugins.blur-nav-bar.description'),
|
||||||
restartNeeded: true,
|
restartNeeded: false,
|
||||||
stylesheets: [style],
|
renderer: {
|
||||||
renderer() {},
|
styleSheet: null as CSSStyleSheet | null,
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
this.styleSheet = new CSSStyleSheet();
|
||||||
|
await this.styleSheet.replace(style);
|
||||||
|
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, this.styleSheet];
|
||||||
|
},
|
||||||
|
async stop() {
|
||||||
|
await this.styleSheet?.replace('');
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,18 @@
|
|||||||
#nav-bar-background,
|
#nav-bar-background,
|
||||||
#header.ytmusic-item-section-renderer,
|
#header.ytmusic-item-section-renderer {
|
||||||
ytmusic-tabs {
|
|
||||||
background: rgba(0, 0, 0, 0.3) !important;
|
background: rgba(0, 0, 0, 0.3) !important;
|
||||||
backdrop-filter: blur(8px) !important;
|
backdrop-filter: blur(8px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ytmusic-tabs {
|
||||||
|
top: calc(var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px));
|
||||||
|
backdrop-filter: blur(8px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-tabs.stuck {
|
||||||
|
background: rgba(0, 0, 0, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
#nav-bar-divider {
|
#nav-bar-divider {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { inject } from 'simple-youtube-age-restriction-bypass';
|
||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
@ -6,6 +8,6 @@ export default createPlugin({
|
|||||||
description: () => t('plugins.bypass-age-restrictions.description'),
|
description: () => t('plugins.bypass-age-restrictions.description'),
|
||||||
restartNeeded: true,
|
restartNeeded: true,
|
||||||
|
|
||||||
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript
|
// See https://github.com/organization/Simple-YouTube-Age-Restriction-Bypass#userscript
|
||||||
renderer: () => import('simple-youtube-age-restriction-bypass'),
|
renderer: () => inject(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
declare module 'simple-youtube-age-restriction-bypass' {
|
declare module 'simple-youtube-age-restriction-bypass' {
|
||||||
const nothing: never;
|
export const inject: () => void;
|
||||||
export default nothing;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,7 +121,7 @@ export default createRenderer<
|
|||||||
?.unloadModule('captions');
|
?.unloadModule('captions');
|
||||||
document
|
document
|
||||||
.querySelector('video')
|
.querySelector('video')
|
||||||
?.removeEventListener('srcChanged', this.videoChangeListener);
|
?.removeEventListener('ytmd:src-changed', this.videoChangeListener);
|
||||||
this.captionsSettingsButton.removeEventListener(
|
this.captionsSettingsButton.removeEventListener(
|
||||||
'click',
|
'click',
|
||||||
this.captionsButtonClickListener,
|
this.captionsButtonClickListener,
|
||||||
@ -139,7 +139,7 @@ export default createRenderer<
|
|||||||
|
|
||||||
document
|
document
|
||||||
.querySelector('video')
|
.querySelector('video')
|
||||||
?.addEventListener('srcChanged', this.videoChangeListener);
|
?.addEventListener('ytmd:src-changed', this.videoChangeListener);
|
||||||
this.captionsSettingsButton.addEventListener(
|
this.captionsSettingsButton.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
this.captionsButtonClickListener,
|
this.captionsButtonClickListener,
|
||||||
|
|||||||
@ -26,8 +26,8 @@ export default createPlugin<
|
|||||||
unknown,
|
unknown,
|
||||||
unknown,
|
unknown,
|
||||||
{
|
{
|
||||||
config: CrossfadePluginConfig | null;
|
config?: CrossfadePluginConfig;
|
||||||
ipc: RendererContext<CrossfadePluginConfig>['ipc'] | null;
|
ipc?: RendererContext<CrossfadePluginConfig>['ipc'];
|
||||||
},
|
},
|
||||||
CrossfadePluginConfig
|
CrossfadePluginConfig
|
||||||
>({
|
>({
|
||||||
@ -178,10 +178,8 @@ export default createPlugin<
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderer: {
|
renderer: {
|
||||||
config: null,
|
async start({ ipc, getConfig }) {
|
||||||
ipc: null,
|
this.config = await getConfig();
|
||||||
|
|
||||||
start({ ipc }) {
|
|
||||||
this.ipc = ipc;
|
this.ipc = ipc;
|
||||||
},
|
},
|
||||||
onConfigChange(newConfig) {
|
onConfigChange(newConfig) {
|
||||||
@ -271,7 +269,7 @@ export default createPlugin<
|
|||||||
const transitionBeforeEnd = () => {
|
const transitionBeforeEnd = () => {
|
||||||
if (
|
if (
|
||||||
video.currentTime >=
|
video.currentTime >=
|
||||||
video.duration - this.config!.secondsBeforeEnd &&
|
video.duration - (this.config?.secondsBeforeEnd ?? 0) &&
|
||||||
isReadyToCrossfade()
|
isReadyToCrossfade()
|
||||||
) {
|
) {
|
||||||
video.removeEventListener('timeupdate', transitionBeforeEnd);
|
video.removeEventListener('timeupdate', transitionBeforeEnd);
|
||||||
|
|||||||
@ -2,14 +2,12 @@ import { app, dialog, ipcMain } from 'electron';
|
|||||||
import { Client as DiscordClient } from '@xhayper/discord-rpc';
|
import { Client as DiscordClient } from '@xhayper/discord-rpc';
|
||||||
import { dev } from 'electron-is';
|
import { dev } from 'electron-is';
|
||||||
|
|
||||||
import { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
|
|
||||||
|
|
||||||
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
||||||
|
|
||||||
import { createBackend, LoggerPrefix } from '@/utils';
|
import { createBackend, LoggerPrefix } from '@/utils';
|
||||||
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
|
import type { GatewayActivityButton } from 'discord-api-types/v10';
|
||||||
|
import type { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
|
||||||
import type { DiscordPluginConfig } from './index';
|
import type { DiscordPluginConfig } from './index';
|
||||||
|
|
||||||
// Application ID registered by @th-ch/youtube-music dev team
|
// Application ID registered by @th-ch/youtube-music dev team
|
||||||
@ -163,24 +161,30 @@ export const backend = createBackend<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see https://github.com/th-ch/youtube-music/issues/1664
|
||||||
|
let buttons: GatewayActivityButton[] | undefined = [];
|
||||||
|
if (config.playOnYouTubeMusic) {
|
||||||
|
buttons.push({
|
||||||
|
label: 'Play on YouTube Music',
|
||||||
|
url: songInfo.url ?? 'https://music.youtube.com',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!config.hideGitHubButton) {
|
||||||
|
buttons.push({
|
||||||
|
label: 'View App On GitHub',
|
||||||
|
url: 'https://github.com/th-ch/youtube-music',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (buttons.length === 0) {
|
||||||
|
buttons = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const activityInfo: SetActivity = {
|
const activityInfo: SetActivity = {
|
||||||
details: songInfo.title,
|
details: songInfo.title,
|
||||||
state: songInfo.artist,
|
state: songInfo.artist,
|
||||||
largeImageKey: songInfo.imageSrc ?? '',
|
largeImageKey: songInfo.imageSrc ?? '',
|
||||||
largeImageText: songInfo.album ?? '',
|
largeImageText: songInfo.album ?? '',
|
||||||
buttons: [
|
buttons,
|
||||||
...(config.playOnYouTubeMusic
|
|
||||||
? [{ label: 'Play on YouTube Music', url: songInfo.url ?? '' }]
|
|
||||||
: []),
|
|
||||||
...(config.hideGitHubButton
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
label: 'View App On GitHub',
|
|
||||||
url: 'https://github.com/th-ch/youtube-music',
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (songInfo.isPaused) {
|
if (songInfo.isPaused) {
|
||||||
@ -244,7 +248,7 @@ export const backend = createBackend<
|
|||||||
});
|
});
|
||||||
connect();
|
connect();
|
||||||
let lastSent = Date.now();
|
let lastSent = Date.now();
|
||||||
ipcMain.on('timeChanged', (_, t: number) => {
|
ipcMain.on('ytmd:time-changed', (_, t: number) => {
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
// if lastSent is more than 5 seconds ago, send the new time
|
// if lastSent is more than 5 seconds ago, send the new time
|
||||||
if (currentTime - lastSent > 5000) {
|
if (currentTime - lastSent > 5000) {
|
||||||
|
|||||||
@ -84,7 +84,7 @@ export const onMenu = async ({
|
|||||||
checked: config.hideDurationLeft,
|
checked: config.hideDurationLeft,
|
||||||
click(item: Electron.MenuItem) {
|
click(item: Electron.MenuItem) {
|
||||||
setConfig({
|
setConfig({
|
||||||
hideGitHubButton: item.checked,
|
hideDurationLeft: item.checked,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -30,7 +30,7 @@ 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 { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ export const onMainLoad = async ({
|
|||||||
fetch: getNetFetchAsFetch(),
|
fetch: getNetFetchAsFetch(),
|
||||||
});
|
});
|
||||||
ipc.handle('download-song', (url: string) => downloadSong(url));
|
ipc.handle('download-song', (url: string) => downloadSong(url));
|
||||||
ipc.on('video-src-changed', (data: GetPlayerResponse) => {
|
ipc.on('ytmd:video-src-changed', (data: GetPlayerResponse) => {
|
||||||
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
|
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
|
||||||
});
|
});
|
||||||
ipc.handle('download-playlist-request', async (url: string) =>
|
ipc.handle('download-playlist-request', async (url: string) =>
|
||||||
@ -686,6 +686,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
|
||||||
|
|||||||
@ -32,38 +32,63 @@ const menuObserver = new MutationObserver(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuUrl = document.querySelector<HTMLAnchorElement>(
|
// check for video (or music)
|
||||||
'tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint',
|
let menuUrl = document.querySelector<HTMLAnchorElement>(
|
||||||
|
'tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint',
|
||||||
)?.href;
|
)?.href;
|
||||||
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
|
if (!menuUrl?.includes('watch?')) {
|
||||||
|
menuUrl = undefined;
|
||||||
|
// check for podcast
|
||||||
|
for (const it of document.querySelectorAll('tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint')) {
|
||||||
|
if (it.getAttribute('href')?.includes('podcast/')) {
|
||||||
|
menuUrl = it.getAttribute('href')!;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!menuUrl && doneFirstLoad) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.prepend(downloadButton);
|
menu.prepend(downloadButton);
|
||||||
progress = document.querySelector('#ytmcustom-download');
|
progress = document.querySelector('#ytmcustom-download');
|
||||||
|
|
||||||
if (doneFirstLoad) {
|
if (!doneFirstLoad) {
|
||||||
return;
|
setTimeout(() => (doneFirstLoad ||= true), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => (doneFirstLoad ||= true), 500);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const onRendererLoad = ({
|
export const onRendererLoad = ({
|
||||||
ipc,
|
ipc,
|
||||||
}: RendererContext<DownloaderPluginConfig>) => {
|
}: RendererContext<DownloaderPluginConfig>) => {
|
||||||
window.download = () => {
|
window.download = () => {
|
||||||
let videoUrl = getSongMenu()
|
const songMenu = getSongMenu();
|
||||||
|
let videoUrl = songMenu
|
||||||
// Selector of first button which is always "Start Radio"
|
// Selector of first button which is always "Start Radio"
|
||||||
?.querySelector(
|
?.querySelector(
|
||||||
'ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint',
|
'ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint',
|
||||||
)
|
)
|
||||||
?.getAttribute('href');
|
?.getAttribute('href');
|
||||||
|
|
||||||
|
if (!videoUrl && songMenu) {
|
||||||
|
for (const it of songMenu.querySelectorAll('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint')) {
|
||||||
|
if (it.getAttribute('href')?.includes('podcast/')) {
|
||||||
|
videoUrl = it.getAttribute('href');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (videoUrl) {
|
if (videoUrl) {
|
||||||
if (videoUrl.startsWith('watch?')) {
|
if (videoUrl.startsWith('watch?')) {
|
||||||
videoUrl = defaultConfig.url + '/' + videoUrl;
|
videoUrl = defaultConfig.url + '/' + videoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (videoUrl.startsWith('podcast/')) {
|
||||||
|
videoUrl = defaultConfig.url + '/watch?' + videoUrl.replace('podcast/', 'v=');
|
||||||
|
}
|
||||||
|
|
||||||
if (videoUrl.includes('?playlist=')) {
|
if (videoUrl.includes('?playlist=')) {
|
||||||
ipc.invoke('download-playlist-request', videoUrl);
|
ipc.invoke('download-playlist-request', videoUrl);
|
||||||
return;
|
return;
|
||||||
@ -77,7 +102,7 @@ export const onRendererLoad = ({
|
|||||||
|
|
||||||
ipc.on('downloader-feedback', (feedback: string) => {
|
ipc.on('downloader-feedback', (feedback: string) => {
|
||||||
if (progress) {
|
if (progress) {
|
||||||
progress.innerHTML = feedback || 'Download';
|
progress.innerHTML = feedback || t('plugins.downloader.templates.button');
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
LoggerPrefix,
|
LoggerPrefix,
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#ffffff" d="m4.21 4.387.083-.094a1 1 0 0 1 1.32-.083l.094.083L12 10.585l6.293-6.292a1 1 0 1 1 1.414 1.414L13.415 12l6.292 6.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083L12 13.415l-6.293 6.292a1 1 0 0 1-1.414-1.414L10.585 12 4.293 5.707a1 1 0 0 1-.083-1.32l.083-.094-.083.094Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 392 B |
@ -1,3 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#ffffff" d="M6 3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3Zm0 2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 252 B |
@ -1,3 +0,0 @@
|
|||||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M3 17h12a1 1 0 0 1 .117 1.993L15 19H3a1 1 0 0 1-.117-1.993L3 17h12H3Zm0-6h18a1 1 0 0 1 .117 1.993L21 13H3a1 1 0 0 1-.117-1.993L3 11h18H3Zm0-6h15a1 1 0 0 1 .117 1.993L18 7H3a1 1 0 0 1-.117-1.993L3 5h15H3Z" fill="#ffffff"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 338 B |
@ -1,3 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#ffffff" d="M3.755 12.5h16.492a.75.75 0 0 0 0-1.5H3.755a.75.75 0 0 0 0 1.5Z" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 174 B |
@ -1,3 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#ffffff" d="M7.518 5H6.009a3.25 3.25 0 0 1 3.24-3h8.001A4.75 4.75 0 0 1 22 6.75v8a3.25 3.25 0 0 1-3 3.24v-1.508a1.75 1.75 0 0 0 1.5-1.732v-8a3.25 3.25 0 0 0-3.25-3.25h-8A1.75 1.75 0 0 0 7.518 5ZM5.25 6A3.25 3.25 0 0 0 2 9.25v9.5A3.25 3.25 0 0 0 5.25 22h9.5A3.25 3.25 0 0 0 18 18.75v-9.5A3.25 3.25 0 0 0 14.75 6h-9.5ZM3.5 9.25c0-.966.784-1.75 1.75-1.75h9.5c.967 0 1.75.784 1.75 1.75v9.5a1.75 1.75 0 0 1-1.75 1.75h-9.5a1.75 1.75 0 0 1-1.75-1.75v-9.5Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 546 B |
27
src/plugins/in-app-menu/constants.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export interface InAppMenuConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
hideDOMWindowControls: boolean;
|
||||||
|
}
|
||||||
|
export const defaultInAppMenuConfig: InAppMenuConfig = {
|
||||||
|
enabled:
|
||||||
|
(
|
||||||
|
(
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
!window.navigator?.userAgent?.includes('mac')
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
typeof global !== 'undefined' &&
|
||||||
|
global.process?.platform !== 'darwin'
|
||||||
|
)
|
||||||
|
) && (
|
||||||
|
(
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
!window.navigator?.userAgent?.includes('linux')
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
typeof global !== 'undefined' &&
|
||||||
|
global.process?.platform !== 'linux'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
hideDOMWindowControls: false,
|
||||||
|
};
|
||||||
@ -2,24 +2,15 @@ import titlebarStyle from './titlebar.css?inline';
|
|||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import { onMainLoad } from './main';
|
import { onMainLoad } from './main';
|
||||||
import { onMenu } from './menu';
|
import { onMenu } from './menu';
|
||||||
import { onPlayerApiReady, onRendererLoad } from './renderer';
|
import { onConfigChange, onPlayerApiReady, onRendererLoad } from './renderer';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
import { defaultInAppMenuConfig } from './constants';
|
||||||
|
|
||||||
export interface InAppMenuConfig {
|
|
||||||
enabled: boolean;
|
|
||||||
hideDOMWindowControls: boolean;
|
|
||||||
}
|
|
||||||
export default createPlugin({
|
export default createPlugin({
|
||||||
name: () => t('plugins.in-app-menu.name'),
|
name: () => t('plugins.in-app-menu.name'),
|
||||||
description: () => t('plugins.in-app-menu.description'),
|
description: () => t('plugins.in-app-menu.description'),
|
||||||
restartNeeded: true,
|
restartNeeded: true,
|
||||||
config: {
|
config: defaultInAppMenuConfig,
|
||||||
enabled:
|
|
||||||
(typeof window !== 'undefined' &&
|
|
||||||
!window.navigator?.userAgent?.includes('mac')) ||
|
|
||||||
(typeof global !== 'undefined' && global.process?.platform !== 'darwin'),
|
|
||||||
hideDOMWindowControls: false,
|
|
||||||
} as InAppMenuConfig,
|
|
||||||
stylesheets: [titlebarStyle],
|
stylesheets: [titlebarStyle],
|
||||||
menu: onMenu,
|
menu: onMenu,
|
||||||
|
|
||||||
@ -27,5 +18,6 @@ export default createPlugin({
|
|||||||
renderer: {
|
renderer: {
|
||||||
start: onRendererLoad,
|
start: onRendererLoad,
|
||||||
onPlayerApiReady,
|
onPlayerApiReady,
|
||||||
|
onConfigChange,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { register } from 'electron-localshortcut';
|
|||||||
import { BrowserWindow, Menu, MenuItem, ipcMain, nativeImage } from 'electron';
|
import { BrowserWindow, Menu, MenuItem, ipcMain, nativeImage } from 'electron';
|
||||||
|
|
||||||
import type { BackendContext } from '@/types/contexts';
|
import type { BackendContext } from '@/types/contexts';
|
||||||
import type { InAppMenuConfig } from './index';
|
import type { InAppMenuConfig } from './constants';
|
||||||
|
|
||||||
export const onMainLoad = ({
|
export const onMainLoad = ({
|
||||||
window: win,
|
window: win,
|
||||||
@ -47,7 +47,7 @@ export const onMainLoad = ({
|
|||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcMain.handle('menu-event', (event, commandId: number) => {
|
ipcMain.handle('ytmd:menu-event', (event, commandId: number) => {
|
||||||
const target = getMenuItemById(commandId);
|
const target = getMenuItemById(commandId);
|
||||||
if (target)
|
if (target)
|
||||||
target.click(
|
target.click(
|
||||||
|
|||||||
@ -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';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ export const onMenu = async ({
|
|||||||
if (is.linux()) {
|
if (is.linux()) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: t('plugins.in-app-menu.hide-dom-window-controls'),
|
label: t('plugins.in-app-menu.menu.hide-dom-window-controls'),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: config.hideDOMWindowControls,
|
checked: config.hideDOMWindowControls,
|
||||||
click(item) {
|
click(item) {
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
const Icons = {
|
|
||||||
submenu:
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><polyline points="9 6 15 12 9 18" /></svg>',
|
|
||||||
checkbox:
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg>',
|
|
||||||
radio: {
|
|
||||||
checked:
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" style="padding: 2px"><path fill="currentColor" d="M10,5 C7.2,5 5,7.2 5,10 C5,12.8 7.2,15 10,15 C12.8,15 15,12.8 15,10 C15,7.2 12.8,5 10,5 L10,5 Z M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" /></svg>',
|
|
||||||
unchecked:
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" style="padding: 2px"><path fill="currentColor" d="M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" /></svg>',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Icons;
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
import Icons from './icons';
|
|
||||||
|
|
||||||
import { ElementFromHtml } from '../../utils/renderer';
|
|
||||||
|
|
||||||
import type { MenuItem } from 'electron';
|
|
||||||
|
|
||||||
interface PanelOptions {
|
|
||||||
placement?: 'bottom' | 'right';
|
|
||||||
order?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createPanel = (
|
|
||||||
parent: HTMLElement,
|
|
||||||
anchor: HTMLElement,
|
|
||||||
items: MenuItem[],
|
|
||||||
options: PanelOptions = { placement: 'bottom', order: 0 },
|
|
||||||
) => {
|
|
||||||
const childPanels: HTMLElement[] = [];
|
|
||||||
const panel = document.createElement('menu-panel');
|
|
||||||
panel.style.zIndex = `${options.order}`;
|
|
||||||
|
|
||||||
const updateIconState = async (iconWrapper: HTMLElement, item: MenuItem) => {
|
|
||||||
if (item.type === 'checkbox') {
|
|
||||||
if (item.checked) iconWrapper.innerHTML = Icons.checkbox;
|
|
||||||
else iconWrapper.innerHTML = '';
|
|
||||||
} else if (item.type === 'radio') {
|
|
||||||
if (item.checked) iconWrapper.innerHTML = Icons.radio.checked;
|
|
||||||
else iconWrapper.innerHTML = Icons.radio.unchecked;
|
|
||||||
} else {
|
|
||||||
const iconURL =
|
|
||||||
typeof item.icon === 'string'
|
|
||||||
? ((await window.ipcRenderer.invoke(
|
|
||||||
'image-path-to-data-url',
|
|
||||||
)) as string)
|
|
||||||
: item.icon?.toDataURL();
|
|
||||||
|
|
||||||
if (iconURL) iconWrapper.style.background = `url(${iconURL})`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const radioGroups: [MenuItem, HTMLElement][] = [];
|
|
||||||
items.map((item) => {
|
|
||||||
if (!item.visible) return;
|
|
||||||
if (item.type === 'separator')
|
|
||||||
return panel.appendChild(document.createElement('menu-separator'));
|
|
||||||
|
|
||||||
const menu = document.createElement('menu-item');
|
|
||||||
const iconWrapper = document.createElement('menu-icon');
|
|
||||||
|
|
||||||
updateIconState(iconWrapper, item);
|
|
||||||
menu.appendChild(iconWrapper);
|
|
||||||
menu.append(item.label);
|
|
||||||
|
|
||||||
menu.addEventListener('click', async () => {
|
|
||||||
await window.ipcRenderer.invoke('menu-event', item.commandId);
|
|
||||||
const menuItem = (await window.ipcRenderer.invoke(
|
|
||||||
'get-menu-by-id',
|
|
||||||
item.commandId,
|
|
||||||
)) as MenuItem | null;
|
|
||||||
|
|
||||||
if (menuItem) {
|
|
||||||
updateIconState(iconWrapper, menuItem);
|
|
||||||
|
|
||||||
if (menuItem.type === 'radio') {
|
|
||||||
await Promise.all(
|
|
||||||
radioGroups.map(async ([item, iconWrapper]) => {
|
|
||||||
if (item.commandId === menuItem.commandId) return;
|
|
||||||
const newItem = (await window.ipcRenderer.invoke(
|
|
||||||
'get-menu-by-id',
|
|
||||||
item.commandId,
|
|
||||||
)) as MenuItem | null;
|
|
||||||
|
|
||||||
if (newItem) updateIconState(iconWrapper, newItem);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (item.type === 'radio') {
|
|
||||||
radioGroups.push([item, iconWrapper]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === 'submenu') {
|
|
||||||
const subMenuIcon = document.createElement('menu-icon');
|
|
||||||
subMenuIcon.appendChild(ElementFromHtml(Icons.submenu));
|
|
||||||
menu.appendChild(subMenuIcon);
|
|
||||||
|
|
||||||
const [child, , children] = createPanel(
|
|
||||||
parent,
|
|
||||||
menu,
|
|
||||||
item.submenu?.items ?? [],
|
|
||||||
{
|
|
||||||
placement: 'right',
|
|
||||||
order: (options?.order ?? 0) + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
childPanels.push(child);
|
|
||||||
children.push(...children);
|
|
||||||
}
|
|
||||||
|
|
||||||
return panel.appendChild(menu);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* methods */
|
|
||||||
const isOpened = () => panel.getAttribute('open') === 'true';
|
|
||||||
const close = () => panel.setAttribute('open', 'false');
|
|
||||||
const open = () => {
|
|
||||||
const rect = anchor.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (options.placement === 'bottom') {
|
|
||||||
panel.style.setProperty('--x', `${rect.x}px`);
|
|
||||||
panel.style.setProperty('--y', `${rect.y + rect.height}px`);
|
|
||||||
} else {
|
|
||||||
panel.style.setProperty('--x', `${rect.x + rect.width}px`);
|
|
||||||
panel.style.setProperty('--y', `${rect.y}px`);
|
|
||||||
}
|
|
||||||
|
|
||||||
panel.setAttribute('open', 'true');
|
|
||||||
|
|
||||||
// Children are placed below their parent item, which can cause
|
|
||||||
// long lists to squeeze their children at the bottom of the screen
|
|
||||||
// (This needs to be done *after* setAttribute)
|
|
||||||
panel.classList.remove('position-by-bottom');
|
|
||||||
if (
|
|
||||||
options.placement === 'right' &&
|
|
||||||
panel.scrollHeight > panel.clientHeight
|
|
||||||
) {
|
|
||||||
panel.style.setProperty('--y', `${rect.y + rect.height}px`);
|
|
||||||
panel.classList.add('position-by-bottom');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
anchor.addEventListener('click', () => {
|
|
||||||
if (isOpened()) close();
|
|
||||||
else open();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.addEventListener('click', (event) => {
|
|
||||||
const path = event.composedPath();
|
|
||||||
const isInside = path.some(
|
|
||||||
(it) =>
|
|
||||||
it === panel ||
|
|
||||||
it === anchor ||
|
|
||||||
childPanels.includes(it as HTMLElement),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isInside) close();
|
|
||||||
});
|
|
||||||
|
|
||||||
parent.appendChild(panel);
|
|
||||||
|
|
||||||
return [panel, { isOpened, close, open }, childPanels] as const;
|
|
||||||
};
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
import { createPanel } from './menu/panel';
|
|
||||||
|
|
||||||
import logoRaw from './assets/menu.svg?inline';
|
|
||||||
import closeRaw from './assets/close.svg?inline';
|
|
||||||
import minimizeRaw from './assets/minimize.svg?inline';
|
|
||||||
import maximizeRaw from './assets/maximize.svg?inline';
|
|
||||||
import unmaximizeRaw from './assets/unmaximize.svg?inline';
|
|
||||||
|
|
||||||
import type { Menu } from 'electron';
|
|
||||||
|
|
||||||
import type { RendererContext } from '@/types/contexts';
|
|
||||||
import type { InAppMenuConfig } from '@/plugins/in-app-menu/index';
|
|
||||||
|
|
||||||
const isMacOS = navigator.userAgent.includes('Macintosh');
|
|
||||||
const isNotWindowsOrMacOS =
|
|
||||||
!navigator.userAgent.includes('Windows') && !isMacOS;
|
|
||||||
|
|
||||||
export const onRendererLoad = async ({
|
|
||||||
getConfig,
|
|
||||||
ipc: { invoke, on },
|
|
||||||
}: RendererContext<InAppMenuConfig>) => {
|
|
||||||
const config = await getConfig();
|
|
||||||
|
|
||||||
const hideDOMWindowControls = config.hideDOMWindowControls;
|
|
||||||
|
|
||||||
let hideMenu = window.mainConfig.get('options.hideMenu');
|
|
||||||
const titleBar = document.createElement('title-bar');
|
|
||||||
const navBar = document.querySelector<HTMLDivElement>('#nav-bar-background');
|
|
||||||
let maximizeButton: HTMLButtonElement;
|
|
||||||
let panelClosers: (() => void)[] = [];
|
|
||||||
if (isMacOS) titleBar.style.setProperty('--offset-left', '70px');
|
|
||||||
|
|
||||||
const logo = document.createElement('img');
|
|
||||||
const close = document.createElement('img');
|
|
||||||
const minimize = document.createElement('img');
|
|
||||||
const maximize = document.createElement('img');
|
|
||||||
const unmaximize = document.createElement('img');
|
|
||||||
|
|
||||||
if (window.ELECTRON_RENDERER_URL) {
|
|
||||||
logo.src = window.ELECTRON_RENDERER_URL + '/' + logoRaw;
|
|
||||||
close.src = window.ELECTRON_RENDERER_URL + '/' + closeRaw;
|
|
||||||
minimize.src = window.ELECTRON_RENDERER_URL + '/' + minimizeRaw;
|
|
||||||
maximize.src = window.ELECTRON_RENDERER_URL + '/' + maximizeRaw;
|
|
||||||
unmaximize.src = window.ELECTRON_RENDERER_URL + '/' + unmaximizeRaw;
|
|
||||||
} else {
|
|
||||||
logo.src = logoRaw;
|
|
||||||
close.src = closeRaw;
|
|
||||||
minimize.src = minimizeRaw;
|
|
||||||
maximize.src = maximizeRaw;
|
|
||||||
unmaximize.src = unmaximizeRaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
logo.classList.add('title-bar-icon');
|
|
||||||
const logoClick = () => {
|
|
||||||
hideMenu = !hideMenu;
|
|
||||||
let visibilityStyle: string;
|
|
||||||
if (hideMenu) {
|
|
||||||
visibilityStyle = 'hidden';
|
|
||||||
} else {
|
|
||||||
visibilityStyle = 'visible';
|
|
||||||
}
|
|
||||||
const menus = document.querySelectorAll<HTMLElement>('menu-button');
|
|
||||||
menus.forEach((menu) => {
|
|
||||||
menu.style.visibility = visibilityStyle;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
logo.onclick = logoClick;
|
|
||||||
|
|
||||||
on('toggle-in-app-menu', logoClick);
|
|
||||||
|
|
||||||
if (!isMacOS) titleBar.appendChild(logo);
|
|
||||||
document.body.appendChild(titleBar);
|
|
||||||
|
|
||||||
titleBar.appendChild(logo);
|
|
||||||
|
|
||||||
const addWindowControls = async () => {
|
|
||||||
// Create window control buttons
|
|
||||||
const minimizeButton = document.createElement('button');
|
|
||||||
minimizeButton.classList.add('window-control');
|
|
||||||
minimizeButton.appendChild(minimize);
|
|
||||||
minimizeButton.onclick = () => invoke('window-minimize');
|
|
||||||
|
|
||||||
maximizeButton = document.createElement('button');
|
|
||||||
if (await invoke('window-is-maximized')) {
|
|
||||||
maximizeButton.classList.add('window-control');
|
|
||||||
maximizeButton.appendChild(unmaximize);
|
|
||||||
} else {
|
|
||||||
maximizeButton.classList.add('window-control');
|
|
||||||
maximizeButton.appendChild(maximize);
|
|
||||||
}
|
|
||||||
maximizeButton.onclick = async () => {
|
|
||||||
if (await invoke('window-is-maximized')) {
|
|
||||||
// change icon to maximize
|
|
||||||
maximizeButton.removeChild(maximizeButton.firstChild!);
|
|
||||||
maximizeButton.appendChild(maximize);
|
|
||||||
|
|
||||||
// call unmaximize
|
|
||||||
await invoke('window-unmaximize');
|
|
||||||
} else {
|
|
||||||
// change icon to unmaximize
|
|
||||||
maximizeButton.removeChild(maximizeButton.firstChild!);
|
|
||||||
maximizeButton.appendChild(unmaximize);
|
|
||||||
|
|
||||||
// call maximize
|
|
||||||
await invoke('window-maximize');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeButton = document.createElement('button');
|
|
||||||
closeButton.classList.add('window-control');
|
|
||||||
closeButton.appendChild(close);
|
|
||||||
closeButton.onclick = () => invoke('window-close');
|
|
||||||
|
|
||||||
// Create a container div for the window control buttons
|
|
||||||
const windowControlsContainer = document.createElement('div');
|
|
||||||
windowControlsContainer.classList.add('window-controls-container');
|
|
||||||
windowControlsContainer.appendChild(minimizeButton);
|
|
||||||
windowControlsContainer.appendChild(maximizeButton);
|
|
||||||
windowControlsContainer.appendChild(closeButton);
|
|
||||||
|
|
||||||
// Add window control buttons to the title bar
|
|
||||||
titleBar.appendChild(windowControlsContainer);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isNotWindowsOrMacOS && !hideDOMWindowControls) await addWindowControls();
|
|
||||||
|
|
||||||
if (navBar) {
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach(() => {
|
|
||||||
titleBar.style.setProperty(
|
|
||||||
'--titlebar-background-color',
|
|
||||||
navBar.style.backgroundColor,
|
|
||||||
);
|
|
||||||
document
|
|
||||||
.querySelector('html')!
|
|
||||||
.style.setProperty(
|
|
||||||
'--titlebar-background-color',
|
|
||||||
navBar.style.backgroundColor,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(navBar, { attributes: true, attributeFilter: ['style'] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateMenu = async () => {
|
|
||||||
const children = [...titleBar.children];
|
|
||||||
children.forEach((child) => {
|
|
||||||
if (child !== logo) child.remove();
|
|
||||||
});
|
|
||||||
panelClosers = [];
|
|
||||||
|
|
||||||
const menu = (await invoke('get-menu')) as Menu | null;
|
|
||||||
if (!menu) return;
|
|
||||||
|
|
||||||
menu.items.forEach((menuItem) => {
|
|
||||||
const menu = document.createElement('menu-button');
|
|
||||||
const [, { close: closer }] = createPanel(
|
|
||||||
titleBar,
|
|
||||||
menu,
|
|
||||||
menuItem.submenu?.items ?? [],
|
|
||||||
);
|
|
||||||
panelClosers.push(closer);
|
|
||||||
|
|
||||||
menu.append(menuItem.label);
|
|
||||||
titleBar.appendChild(menu);
|
|
||||||
if (hideMenu) {
|
|
||||||
menu.style.visibility = 'hidden';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (isNotWindowsOrMacOS && !hideDOMWindowControls)
|
|
||||||
await addWindowControls();
|
|
||||||
};
|
|
||||||
await updateMenu();
|
|
||||||
|
|
||||||
document.title = 'Youtube Music';
|
|
||||||
|
|
||||||
on('close-all-in-app-menu-panel', () => {
|
|
||||||
panelClosers.forEach((closer) => closer());
|
|
||||||
});
|
|
||||||
on('refresh-in-app-menu', () => updateMenu());
|
|
||||||
on('window-maximize', () => {
|
|
||||||
if (
|
|
||||||
isNotWindowsOrMacOS &&
|
|
||||||
!hideDOMWindowControls &&
|
|
||||||
maximizeButton.firstChild
|
|
||||||
) {
|
|
||||||
maximizeButton.removeChild(maximizeButton.firstChild);
|
|
||||||
maximizeButton.appendChild(unmaximize);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
on('window-unmaximize', () => {
|
|
||||||
if (
|
|
||||||
isNotWindowsOrMacOS &&
|
|
||||||
!hideDOMWindowControls &&
|
|
||||||
maximizeButton.firstChild
|
|
||||||
) {
|
|
||||||
maximizeButton.removeChild(maximizeButton.firstChild);
|
|
||||||
maximizeButton.appendChild(unmaximize);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.mainConfig.plugins.isEnabled('picture-in-picture')) {
|
|
||||||
on('pip-toggle', () => {
|
|
||||||
updateMenu();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const onPlayerApiReady = () => {
|
|
||||||
const htmlHeadStyle = document.querySelector('head > div > style');
|
|
||||||
if (htmlHeadStyle) {
|
|
||||||
// HACK: This is a hack to remove the scrollbar width
|
|
||||||
htmlHeadStyle.innerHTML = htmlHeadStyle.innerHTML.replace(
|
|
||||||
'html::-webkit-scrollbar {width: var(--ytmusic-scrollbar-width);',
|
|
||||||
'html::-webkit-scrollbar {',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
57
src/plugins/in-app-menu/renderer.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { createSignal } from 'solid-js';
|
||||||
|
import { render } from 'solid-js/web';
|
||||||
|
|
||||||
|
import { TitleBar } from './renderer/TitleBar';
|
||||||
|
import { defaultInAppMenuConfig, InAppMenuConfig } from './constants';
|
||||||
|
|
||||||
|
import type { RendererContext } from '@/types/contexts';
|
||||||
|
|
||||||
|
const scrollStyle = `
|
||||||
|
html::-webkit-scrollbar {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const isMacOS = navigator.userAgent.includes('Macintosh');
|
||||||
|
const isNotWindowsOrMacOS =
|
||||||
|
!navigator.userAgent.includes('Windows') && !isMacOS;
|
||||||
|
|
||||||
|
|
||||||
|
const [config, setConfig] = createSignal<InAppMenuConfig>(defaultInAppMenuConfig);
|
||||||
|
export const onRendererLoad = async ({
|
||||||
|
getConfig,
|
||||||
|
ipc,
|
||||||
|
}: RendererContext<InAppMenuConfig>) => {
|
||||||
|
setConfig(await getConfig());
|
||||||
|
|
||||||
|
document.title = 'YouTube Music';
|
||||||
|
const stylesheet = new CSSStyleSheet();
|
||||||
|
stylesheet.replaceSync(scrollStyle);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
|
||||||
|
|
||||||
|
render(() => (
|
||||||
|
<TitleBar
|
||||||
|
ipc={ipc}
|
||||||
|
isMacOS={isMacOS}
|
||||||
|
enableController={isNotWindowsOrMacOS && !config().hideDOMWindowControls}
|
||||||
|
initialCollapsed={window.mainConfig.get('options.hideMenu')}
|
||||||
|
/>
|
||||||
|
), document.body);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPlayerApiReady = () => {
|
||||||
|
// NOT WORKING AFTER YTM UPDATE (last checked 2024-02-04)
|
||||||
|
//
|
||||||
|
// const htmlHeadStyle = document.querySelector('head > div > style');
|
||||||
|
// if (htmlHeadStyle) {
|
||||||
|
// // HACK: This is a hack to remove the scrollbar width
|
||||||
|
// htmlHeadStyle.innerHTML = htmlHeadStyle.innerHTML.replace(
|
||||||
|
// 'html::-webkit-scrollbar {width: var(--ytmusic-scrollbar-width);',
|
||||||
|
// 'html::-webkit-scrollbar { width: 0;',
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onConfigChange = (newConfig: InAppMenuConfig) => {
|
||||||
|
setConfig(newConfig);
|
||||||
|
};
|
||||||
44
src/plugins/in-app-menu/renderer/IconButton.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { JSX } from 'solid-js';
|
||||||
|
import { css } from 'solid-styled-components';
|
||||||
|
|
||||||
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
|
const iconButton = cache(() => css`
|
||||||
|
-webkit-app-region: none;
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
scale: 0.9;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
type CollapseIconButtonProps = JSX.HTMLAttributes<HTMLButtonElement>;
|
||||||
|
export const IconButton = (props: CollapseIconButtonProps) => {
|
||||||
|
return (
|
||||||
|
<button {...props} class={iconButton()}>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
44
src/plugins/in-app-menu/renderer/MenuButton.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { JSX, splitProps } from 'solid-js';
|
||||||
|
import { css } from 'solid-styled-components';
|
||||||
|
|
||||||
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
|
const menuStyle = cache(() => css`
|
||||||
|
-webkit-app-region: none;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
|
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
scale: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-selected="true"] {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type MenuButtonProps = JSX.HTMLAttributes<HTMLLIElement> & {
|
||||||
|
text?: string;
|
||||||
|
selected?: boolean;
|
||||||
|
};
|
||||||
|
export const MenuButton = (props: MenuButtonProps) => {
|
||||||
|
const [local, leftProps] = splitProps(props, ['text']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li {...leftProps} class={menuStyle()} data-selected={props.selected}>
|
||||||
|
{local.text}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
151
src/plugins/in-app-menu/renderer/Panel.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { createSignal, JSX, Show, splitProps } from 'solid-js';
|
||||||
|
import { mergeProps, Portal } from 'solid-js/web';
|
||||||
|
import { css } from 'solid-styled-components';
|
||||||
|
import { Transition } from 'solid-transition-group';
|
||||||
|
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
|
||||||
|
import { useFloating } from 'solid-floating-ui';
|
||||||
|
|
||||||
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
|
const panelStyle = cache(() => css`
|
||||||
|
position: fixed;
|
||||||
|
top: var(--offset-y, 0);
|
||||||
|
left: var(--offset-x, 0);
|
||||||
|
|
||||||
|
max-width: var(--max-width, 100%);
|
||||||
|
max-height: var(--max-height, 100%);
|
||||||
|
|
||||||
|
z-index: 10000;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
background-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--titlebar-background-color, #030303) 50%,
|
||||||
|
rgba(0, 0, 0, 0.1)
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05),
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
|
||||||
|
`);
|
||||||
|
|
||||||
|
const animationStyle = cache(() => ({
|
||||||
|
enter: css`
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
`,
|
||||||
|
enterActive: css`
|
||||||
|
transition: opacity 0.225s cubic-bezier(0.33, 1, 0.68, 1), transform 0.225s cubic-bezier(0.33, 1, 0.68, 1);
|
||||||
|
`,
|
||||||
|
exitTo: css`
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
`,
|
||||||
|
exitActive: css`
|
||||||
|
transition: opacity 0.225s cubic-bezier(0.32, 0, 0.67, 0), transform 0.225s cubic-bezier(0.32, 0, 0.67, 0);
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export type Placement =
|
||||||
|
'top'
|
||||||
|
| 'bottom'
|
||||||
|
| 'left'
|
||||||
|
| 'right'
|
||||||
|
| 'top-start'
|
||||||
|
| 'top-end'
|
||||||
|
| 'bottom-start'
|
||||||
|
| 'bottom-end'
|
||||||
|
| 'right-start'
|
||||||
|
| 'right-end'
|
||||||
|
| 'left-start'
|
||||||
|
| 'left-end';
|
||||||
|
export type PanelProps = JSX.HTMLAttributes<HTMLUListElement> & {
|
||||||
|
open?: boolean;
|
||||||
|
anchor?: HTMLElement | null;
|
||||||
|
children: JSX.Element;
|
||||||
|
|
||||||
|
placement?: Placement;
|
||||||
|
offset?: OffsetOptions;
|
||||||
|
};
|
||||||
|
export const Panel = (props: PanelProps) => {
|
||||||
|
const [elements, local, leftProps] = splitProps(
|
||||||
|
mergeProps({ placement: 'bottom' }, props),
|
||||||
|
['anchor', 'children'],
|
||||||
|
['open', 'placement', 'offset'],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [panel, setPanel] = createSignal<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const position = useFloating(() => elements.anchor, panel, {
|
||||||
|
whileElementsMounted: autoUpdate,
|
||||||
|
placement: local.placement as Placement,
|
||||||
|
strategy: 'fixed',
|
||||||
|
middleware: [
|
||||||
|
offset(local.offset),
|
||||||
|
size({
|
||||||
|
padding: 8,
|
||||||
|
apply({ elements, availableWidth, availableHeight }) {
|
||||||
|
elements.floating.style.setProperty('--max-width', `${Math.max(200, availableWidth)}px`);
|
||||||
|
elements.floating.style.setProperty('--max-height', `${Math.max(200, availableHeight)}px`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
flip({ fallbackStrategy: 'initialPlacement' }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const originX = () => {
|
||||||
|
if (position.placement.includes('left')) return '100%';
|
||||||
|
if (position.placement.includes('right')) return '0';
|
||||||
|
if (position.placement.includes('top') || position.placement.includes('bottom')) {
|
||||||
|
if (position.placement.includes('start')) return '0';
|
||||||
|
if (position.placement.includes('end')) return '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '50%';
|
||||||
|
};
|
||||||
|
const originY = () => {
|
||||||
|
if (position.placement.includes('top')) return '100%';
|
||||||
|
if (position.placement.includes('bottom')) return '0';
|
||||||
|
if (position.placement.includes('left') || position.placement.includes('right')) {
|
||||||
|
if (position.placement.includes('start')) return '0';
|
||||||
|
if (position.placement.includes('end')) return '100%';
|
||||||
|
}
|
||||||
|
return '50%';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Transition
|
||||||
|
appear
|
||||||
|
enterClass={animationStyle().enter}
|
||||||
|
enterActiveClass={animationStyle().enterActive}
|
||||||
|
exitToClass={animationStyle().exitTo}
|
||||||
|
exitActiveClass={animationStyle().exitActive}
|
||||||
|
>
|
||||||
|
<Show when={local.open}>
|
||||||
|
<ul
|
||||||
|
{...leftProps}
|
||||||
|
id={'sub-panel'}
|
||||||
|
ref={setPanel}
|
||||||
|
class={panelStyle()}
|
||||||
|
style={{
|
||||||
|
'--offset-x': `${position.x}px`,
|
||||||
|
'--offset-y': `${position.y}px`,
|
||||||
|
'--origin-x': originX(),
|
||||||
|
'--origin-y': originY(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{elements.children}
|
||||||
|
</ul>
|
||||||
|
</Show>
|
||||||
|
</Transition>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
335
src/plugins/in-app-menu/renderer/PanelItem.tsx
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
import { createSignal, Match, Show, Switch } from 'solid-js';
|
||||||
|
import { JSX } from 'solid-js/jsx-runtime';
|
||||||
|
import { css } from 'solid-styled-components';
|
||||||
|
import { Portal } from 'solid-js/web';
|
||||||
|
|
||||||
|
import { Transition } from 'solid-transition-group';
|
||||||
|
import { useFloating } from 'solid-floating-ui';
|
||||||
|
import { autoUpdate, offset, size } from '@floating-ui/dom';
|
||||||
|
|
||||||
|
import { Panel } from './Panel';
|
||||||
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
|
const itemStyle = cache(() => css`
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
-webkit-app-region: none;
|
||||||
|
min-height: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 32px 1fr auto minmax(32px, auto);
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-selected="true"] {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
& * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const itemIconStyle = cache(() => css`
|
||||||
|
height: 32px;
|
||||||
|
padding: 4px;
|
||||||
|
color: white;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const itemLabelStyle = cache(() => css`
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const itemChipStyle = cache(() => css`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
color: #f1f1f1;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const toolTipStyle = cache(() => css`
|
||||||
|
min-width: 32px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
max-width: calc(var(--max-width, 100%) - 8px);
|
||||||
|
max-height: calc(var(--max-height, 100%) - 8px);
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(25, 25, 25, 0.8);
|
||||||
|
color: #f1f1f1;
|
||||||
|
font-size: 10px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const popupStyle = cache(() => css`
|
||||||
|
position: fixed;
|
||||||
|
top: var(--offset-y, 0);
|
||||||
|
left: var(--offset-x, 0);
|
||||||
|
|
||||||
|
max-width: var(--max-width, 100%);
|
||||||
|
max-height: var(--max-height, 100%);
|
||||||
|
|
||||||
|
z-index: 100000000;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
`);
|
||||||
|
|
||||||
|
const animationStyle = cache(() => ({
|
||||||
|
enter: css`
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
`,
|
||||||
|
enterActive: css`
|
||||||
|
transition: opacity 0.225s cubic-bezier(0.33, 1, 0.68, 1), transform 0.225s cubic-bezier(0.33, 1, 0.68, 1);
|
||||||
|
`,
|
||||||
|
exitTo: css`
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
`,
|
||||||
|
exitActive: css`
|
||||||
|
transition: opacity 0.225s cubic-bezier(0.32, 0, 0.67, 0), transform 0.225s cubic-bezier(0.32, 0, 0.67, 0);
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getParents = (element: Element | null): (HTMLElement | null)[] => {
|
||||||
|
const parents: (HTMLElement | null)[] = [];
|
||||||
|
let now = element;
|
||||||
|
|
||||||
|
while (now) {
|
||||||
|
parents.push(now as HTMLElement | null);
|
||||||
|
now = now.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parents;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BasePanelItemProps = {
|
||||||
|
name: string;
|
||||||
|
label?: string;
|
||||||
|
chip?: string;
|
||||||
|
toolTip?: string;
|
||||||
|
commandId?: number;
|
||||||
|
};
|
||||||
|
type NormalPanelItemProps = BasePanelItemProps & {
|
||||||
|
type: 'normal';
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
type SubmenuItemProps = BasePanelItemProps & {
|
||||||
|
type: 'submenu';
|
||||||
|
level: number[];
|
||||||
|
children: JSX.Element;
|
||||||
|
};
|
||||||
|
type RadioPanelItemProps = BasePanelItemProps & {
|
||||||
|
type: 'radio';
|
||||||
|
checked: boolean;
|
||||||
|
onChange?: (checked: boolean) => void;
|
||||||
|
};
|
||||||
|
type CheckboxPanelItemProps = BasePanelItemProps & {
|
||||||
|
type: 'checkbox';
|
||||||
|
checked: boolean;
|
||||||
|
onChange?: (checked: boolean) => void;
|
||||||
|
};
|
||||||
|
export type PanelItemProps = NormalPanelItemProps | SubmenuItemProps | RadioPanelItemProps | CheckboxPanelItemProps;
|
||||||
|
export const PanelItem = (props: PanelItemProps) => {
|
||||||
|
const [open, setOpen] = createSignal(false);
|
||||||
|
const [toolTipOpen, setToolTipOpen] = createSignal(false);
|
||||||
|
const [toolTip, setToolTip] = createSignal<HTMLElement | null>(null);
|
||||||
|
const [anchor, setAnchor] = createSignal<HTMLElement | null>(null);
|
||||||
|
const [child, setChild] = createSignal<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const position = useFloating(anchor, toolTip, {
|
||||||
|
whileElementsMounted: autoUpdate,
|
||||||
|
placement: 'bottom-start',
|
||||||
|
strategy: 'fixed',
|
||||||
|
middleware: [
|
||||||
|
offset({ mainAxis: 8 }),
|
||||||
|
size({
|
||||||
|
apply({ rects, elements }) {
|
||||||
|
elements.floating.style.setProperty('--max-width', `${rects.reference.width}px`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleHover = (event: MouseEvent) => {
|
||||||
|
setToolTipOpen(true);
|
||||||
|
event.target?.addEventListener('mouseleave', () => {
|
||||||
|
setToolTipOpen(false);
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
if (props.type === 'submenu') {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setOpen(true);
|
||||||
|
|
||||||
|
let mouseX = event.clientX;
|
||||||
|
let mouseY = event.clientY;
|
||||||
|
const onMouseMove = (event: MouseEvent) => {
|
||||||
|
mouseX = event.clientX;
|
||||||
|
mouseY = event.clientY;
|
||||||
|
};
|
||||||
|
document.addEventListener('mousemove', onMouseMove);
|
||||||
|
|
||||||
|
event.target?.addEventListener('mouseleave', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
document.removeEventListener('mousemove', onMouseMove);
|
||||||
|
const parents = getParents(document.elementFromPoint(mouseX, mouseY));
|
||||||
|
|
||||||
|
if (!parents.includes(child())) {
|
||||||
|
setOpen(false);
|
||||||
|
} else {
|
||||||
|
const onOtherHover = (event: MouseEvent) => {
|
||||||
|
const parents = getParents(event.target as HTMLElement);
|
||||||
|
const closestLevel = parents.find((it) => it?.dataset?.level)?.dataset.level ?? '';
|
||||||
|
const path = event.composedPath();
|
||||||
|
|
||||||
|
const isOtherItem = path.some((it) => it instanceof HTMLElement && it.classList.contains(itemStyle()));
|
||||||
|
const isChild = closestLevel.startsWith(props.level.join('/'));
|
||||||
|
|
||||||
|
if (isOtherItem && !isChild) {
|
||||||
|
setOpen(false);
|
||||||
|
document.removeEventListener('mousemove', onOtherHover);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('mousemove', onOtherHover);
|
||||||
|
}
|
||||||
|
}, 225);
|
||||||
|
}, { once: true });
|
||||||
|
}, 225);
|
||||||
|
|
||||||
|
event.target?.addEventListener('mouseleave', () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
await window.ipcRenderer.invoke('ytmd:menu-event', props.commandId);
|
||||||
|
if (props.type === 'radio') {
|
||||||
|
props.onChange?.(!props.checked);
|
||||||
|
} else if (props.type === 'checkbox') {
|
||||||
|
props.onChange?.(!props.checked);
|
||||||
|
} else if (props.type === 'normal') {
|
||||||
|
props.onClick?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
ref={setAnchor}
|
||||||
|
class={itemStyle()}
|
||||||
|
onMouseEnter={handleHover}
|
||||||
|
onClick={handleClick}
|
||||||
|
data-selected={open()}
|
||||||
|
>
|
||||||
|
<Switch fallback={<div class={itemIconStyle()}/>}>
|
||||||
|
<Match when={props.type === 'checkbox' && props.checked}>
|
||||||
|
<svg class={itemIconStyle()} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor" fill="none"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M5 12l5 5l10 -10"/>
|
||||||
|
</svg>
|
||||||
|
</Match>
|
||||||
|
<Match when={props.type === 'radio' && props.checked}>
|
||||||
|
<svg class={itemIconStyle()} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||||
|
style={{ padding: '6px' }}>
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M10,5 C7.2,5 5,7.2 5,10 C5,12.8 7.2,15 10,15 C12.8,15 15,12.8 15,10 C15,7.2 12.8,5 10,5 L10,5 Z M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z"/>
|
||||||
|
</svg>
|
||||||
|
</Match>
|
||||||
|
<Match when={props.type === 'radio' && !props.checked}>
|
||||||
|
<svg class={itemIconStyle()} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||||
|
style={{ padding: '6px' }}>
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z"/>
|
||||||
|
</svg>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
<span class={itemLabelStyle()}>
|
||||||
|
{props.name}
|
||||||
|
</span>
|
||||||
|
<Show when={props.chip} fallback={<div/>}>
|
||||||
|
<span class={itemChipStyle()}>
|
||||||
|
{props.chip}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
<Show when={props.type === 'submenu'}>
|
||||||
|
<svg class={itemIconStyle()} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<polyline points="9 6 15 12 9 18"/>
|
||||||
|
</svg>
|
||||||
|
<Panel
|
||||||
|
ref={setChild}
|
||||||
|
open={open()}
|
||||||
|
anchor={anchor()}
|
||||||
|
placement={'right-start'}
|
||||||
|
data-level={props.type === 'submenu' && props.level.join('/')}
|
||||||
|
offset={{ mainAxis: 8 }}
|
||||||
|
>
|
||||||
|
{props.type === 'submenu' && props.children}
|
||||||
|
</Panel>
|
||||||
|
</Show>
|
||||||
|
<Show when={props.toolTip}>
|
||||||
|
<Portal>
|
||||||
|
<div
|
||||||
|
ref={setToolTip}
|
||||||
|
class={popupStyle()}
|
||||||
|
style={{
|
||||||
|
'--offset-x': `${position.x}px`,
|
||||||
|
'--offset-y': `${position.y}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Transition
|
||||||
|
appear
|
||||||
|
enterClass={animationStyle().enter}
|
||||||
|
enterActiveClass={animationStyle().enterActive}
|
||||||
|
exitToClass={animationStyle().exitTo}
|
||||||
|
exitActiveClass={animationStyle().exitActive}
|
||||||
|
>
|
||||||
|
<Show when={toolTipOpen()}>
|
||||||
|
<div class={toolTipStyle()}>
|
||||||
|
{props.toolTip}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
</Show>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
357
src/plugins/in-app-menu/renderer/TitleBar.tsx
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
import { Menu, MenuItem } from 'electron';
|
||||||
|
import { createEffect, createResource, createSignal, Index, Match, onMount, Show, Switch } from 'solid-js';
|
||||||
|
import { css } from 'solid-styled-components';
|
||||||
|
import { TransitionGroup } from 'solid-transition-group';
|
||||||
|
|
||||||
|
import { MenuButton } from './MenuButton';
|
||||||
|
import { Panel } from './Panel';
|
||||||
|
import { PanelItem } from './PanelItem';
|
||||||
|
import { IconButton } from './IconButton';
|
||||||
|
import { WindowController } from './WindowController';
|
||||||
|
|
||||||
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
|
import type { RendererContext } from '@/types/contexts';
|
||||||
|
import type { InAppMenuConfig } from '../constants';
|
||||||
|
|
||||||
|
const titleStyle = cache(() => css`
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10000000;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: var(--menu-bar-height, 32px);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
color: #f1f1f1;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 4px 4px 4px var(--offset-left, 4px);
|
||||||
|
background-color: var(--titlebar-background-color, #030303);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
transition: opacity 200ms ease 0s,
|
||||||
|
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
||||||
|
|
||||||
|
&[data-macos="true"] {
|
||||||
|
padding: 4px 4px 4px 74px;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const separatorStyle = cache(() => css`
|
||||||
|
min-height: 1px;
|
||||||
|
height: 1px;
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
`);
|
||||||
|
|
||||||
|
const animationStyle = cache(() => ({
|
||||||
|
enter: css`
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) scale(0.8);
|
||||||
|
`,
|
||||||
|
enterActive: css`
|
||||||
|
transition: opacity 0.1s cubic-bezier(0.33, 1, 0.68, 1), transform 0.1s cubic-bezier(0.33, 1, 0.68, 1);
|
||||||
|
`,
|
||||||
|
exitTo: css`
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) scale(0.8);
|
||||||
|
`,
|
||||||
|
exitActive: css`
|
||||||
|
transition: opacity 0.1s cubic-bezier(0.32, 0, 0.67, 0), transform 0.1s cubic-bezier(0.32, 0, 0.67, 0);
|
||||||
|
`,
|
||||||
|
move: css`
|
||||||
|
transition: all 0.1s cubic-bezier(0.65, 0, 0.35, 1);
|
||||||
|
`,
|
||||||
|
fakeTarget: css`
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
`,
|
||||||
|
fake: css`
|
||||||
|
transition: all 0.00000000001s;
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export type PanelRendererProps = {
|
||||||
|
items: Electron.Menu['items'];
|
||||||
|
level?: number[];
|
||||||
|
onClick?: (commandId: number, radioGroup?: MenuItem[]) => void;
|
||||||
|
}
|
||||||
|
const PanelRenderer = (props: PanelRendererProps) => {
|
||||||
|
const radioGroup = () => props.items.filter((it) => it.type === 'radio');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Index each={props.items}>
|
||||||
|
{(subItem) => (
|
||||||
|
<Show when={subItem().visible}>
|
||||||
|
<Switch>
|
||||||
|
<Match when={subItem().type === 'normal'}>
|
||||||
|
<PanelItem
|
||||||
|
type={'normal'}
|
||||||
|
name={subItem().label}
|
||||||
|
chip={subItem().sublabel}
|
||||||
|
toolTip={subItem().toolTip}
|
||||||
|
commandId={subItem().commandId}
|
||||||
|
onClick={() => props.onClick?.(subItem().commandId)}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
<Match when={subItem().type === 'submenu'}>
|
||||||
|
<PanelItem
|
||||||
|
type={'submenu'}
|
||||||
|
name={subItem().label}
|
||||||
|
chip={subItem().sublabel}
|
||||||
|
toolTip={subItem().toolTip}
|
||||||
|
level={[...props.level ?? [], subItem().commandId]}
|
||||||
|
commandId={subItem().commandId}
|
||||||
|
>
|
||||||
|
<PanelRenderer
|
||||||
|
items={subItem().submenu?.items ?? []}
|
||||||
|
level={[...props.level ?? [], subItem().commandId]}
|
||||||
|
onClick={props.onClick}
|
||||||
|
/>
|
||||||
|
</PanelItem>
|
||||||
|
</Match>
|
||||||
|
<Match when={subItem().type === 'checkbox'}>
|
||||||
|
<PanelItem
|
||||||
|
type={'checkbox'}
|
||||||
|
name={subItem().label}
|
||||||
|
checked={subItem().checked}
|
||||||
|
chip={subItem().sublabel}
|
||||||
|
toolTip={subItem().toolTip}
|
||||||
|
commandId={subItem().commandId}
|
||||||
|
onChange={() => props.onClick?.(subItem().commandId)}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
<Match when={subItem().type === 'radio'}>
|
||||||
|
<PanelItem
|
||||||
|
type={'radio'}
|
||||||
|
name={subItem().label}
|
||||||
|
checked={subItem().checked}
|
||||||
|
chip={subItem().sublabel}
|
||||||
|
toolTip={subItem().toolTip}
|
||||||
|
commandId={subItem().commandId}
|
||||||
|
onChange={() => props.onClick?.(subItem().commandId, radioGroup())}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
<Match when={subItem().type === 'separator'}>
|
||||||
|
<hr class={separatorStyle()}/>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</Show>
|
||||||
|
)}
|
||||||
|
</Index>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TitleBarProps = {
|
||||||
|
ipc: RendererContext<InAppMenuConfig>['ipc'];
|
||||||
|
isMacOS?: boolean;
|
||||||
|
enableController?: boolean;
|
||||||
|
initialCollapsed?: boolean;
|
||||||
|
};
|
||||||
|
export const TitleBar = (props: TitleBarProps) => {
|
||||||
|
const [collapsed, setCollapsed] = createSignal(props.initialCollapsed);
|
||||||
|
const [ignoreTransition, setIgnoreTransition] = createSignal(false);
|
||||||
|
const [openTarget, setOpenTarget] = createSignal<HTMLElement | null>(null);
|
||||||
|
const [menu, setMenu] = createSignal<Menu | null>(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 handleToggleMaximize = async () => {
|
||||||
|
if (isMaximized()) {
|
||||||
|
await props.ipc.invoke('window-unmaximize');
|
||||||
|
} else {
|
||||||
|
await props.ipc.invoke('window-maximize');
|
||||||
|
}
|
||||||
|
await refetchMaximize();
|
||||||
|
};
|
||||||
|
const handleMinimize = async () => {
|
||||||
|
await props.ipc.invoke('window-minimize');
|
||||||
|
};
|
||||||
|
const handleClose = async () => {
|
||||||
|
await props.ipc.invoke('window-close');
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshMenuItem = async (originalMenu: Menu, commandId: number) => {
|
||||||
|
const menuItem = (await window.ipcRenderer.invoke(
|
||||||
|
'get-menu-by-id',
|
||||||
|
commandId,
|
||||||
|
)) as MenuItem | null;
|
||||||
|
|
||||||
|
const newMenu = structuredClone(originalMenu);
|
||||||
|
const stack = [...newMenu?.items ?? []];
|
||||||
|
let now: MenuItem | undefined = stack.pop();
|
||||||
|
while (now) {
|
||||||
|
const index = now?.submenu?.items?.findIndex((it) => it.commandId === commandId) ?? -1;
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
if (menuItem) now?.submenu?.items?.splice(index, 1, menuItem);
|
||||||
|
else now?.submenu?.items?.splice(index, 1);
|
||||||
|
}
|
||||||
|
if (now?.submenu) {
|
||||||
|
stack.push(...now.submenu.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
now = stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMenu;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleItemClick = async (commandId: number, radioGroup?: MenuItem[]) => {
|
||||||
|
const menuData = menu();
|
||||||
|
if (!menuData) return;
|
||||||
|
|
||||||
|
if (Array.isArray(radioGroup)) {
|
||||||
|
let newMenu = menuData;
|
||||||
|
for await (const item of radioGroup) {
|
||||||
|
newMenu = await refreshMenuItem(newMenu, item.commandId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenu(newMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenu(await refreshMenuItem(menuData, commandId));
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
props.ipc.on('close-all-in-app-menu-panel', async () => {
|
||||||
|
setIgnoreTransition(true);
|
||||||
|
setMenu(null);
|
||||||
|
await refetch();
|
||||||
|
setMenu(data() ?? null);
|
||||||
|
setIgnoreTransition(false);
|
||||||
|
});
|
||||||
|
props.ipc.on('refresh-in-app-menu', async () => {
|
||||||
|
setIgnoreTransition(true);
|
||||||
|
await refetch();
|
||||||
|
setMenu(data() ?? null);
|
||||||
|
setIgnoreTransition(false);
|
||||||
|
});
|
||||||
|
props.ipc.on('toggle-in-app-menu', () => {
|
||||||
|
setCollapsed(!collapsed());
|
||||||
|
});
|
||||||
|
|
||||||
|
props.ipc.on('window-maximize', 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('#main-panel') ||
|
||||||
|
e.target.closest('#sub-panel')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setOpenTarget(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!menu() && data()) {
|
||||||
|
setMenu(data() ?? null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav id={'main-panel'} class={titleStyle()} data-macos={props.isMacOS}>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setCollapsed(!collapsed())}
|
||||||
|
style={{
|
||||||
|
'border-top-left-radius': '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width={16} height={16} viewBox={'0 0 24 24'}>
|
||||||
|
<path
|
||||||
|
d="M3 17h12a1 1 0 0 1 .117 1.993L15 19H3a1 1 0 0 1-.117-1.993L3 17h12H3Zm0-6h18a1 1 0 0 1 .117 1.993L21 13H3a1 1 0 0 1-.117-1.993L3 11h18H3Zm0-6h15a1 1 0 0 1 .117 1.993L18 7H3a1 1 0 0 1-.117-1.993L3 5h15H3Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</IconButton>
|
||||||
|
<TransitionGroup
|
||||||
|
enterClass={ignoreTransition() ? animationStyle().fakeTarget : animationStyle().enter}
|
||||||
|
enterActiveClass={ignoreTransition() ? animationStyle().fake : animationStyle().enterActive}
|
||||||
|
exitToClass={ignoreTransition() ? animationStyle().fakeTarget : animationStyle().exitTo}
|
||||||
|
exitActiveClass={ignoreTransition() ? animationStyle().fake : animationStyle().exitActive}
|
||||||
|
onBeforeEnter={(element) => {
|
||||||
|
if (ignoreTransition()) return;
|
||||||
|
const index = Number(element.getAttribute('data-index') ?? 0);
|
||||||
|
|
||||||
|
(element as HTMLElement).style.setProperty('transition-delay', `${(index * 0.025)}s`);
|
||||||
|
}}
|
||||||
|
onAfterEnter={(element) => {
|
||||||
|
(element as HTMLElement).style.removeProperty('transition-delay');
|
||||||
|
}}
|
||||||
|
onBeforeExit={(element) => {
|
||||||
|
if (ignoreTransition()) return;
|
||||||
|
const index = Number(element.getAttribute('data-index') ?? 0);
|
||||||
|
const length = Number(element.getAttribute('data-length') ?? 1);
|
||||||
|
|
||||||
|
(element as HTMLElement).style.setProperty('transition-delay', `${(length * 0.025) - (index * 0.025)}s`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Show when={!collapsed()}>
|
||||||
|
<Index each={menu()?.items}>
|
||||||
|
{(item, index) => {
|
||||||
|
const [anchor, setAnchor] = createSignal<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (openTarget() === anchor()) {
|
||||||
|
setOpenTarget(null);
|
||||||
|
} else {
|
||||||
|
setOpenTarget(anchor());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuButton
|
||||||
|
ref={setAnchor}
|
||||||
|
text={item().label}
|
||||||
|
onClick={handleClick}
|
||||||
|
selected={openTarget() === anchor()}
|
||||||
|
data-index={index}
|
||||||
|
data-length={data()?.items.length}
|
||||||
|
/>
|
||||||
|
<Panel
|
||||||
|
open={openTarget() === anchor()}
|
||||||
|
anchor={anchor()}
|
||||||
|
placement={'bottom-start'}
|
||||||
|
offset={{ mainAxis: 8 }}
|
||||||
|
>
|
||||||
|
<PanelRenderer
|
||||||
|
items={item().submenu?.items ?? []}
|
||||||
|
onClick={handleItemClick}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Index>
|
||||||
|
</Show>
|
||||||
|
</TransitionGroup>
|
||||||
|
<Show when={props.enableController}>
|
||||||
|
<div style={{ flex: 1 }}/>
|
||||||
|
<WindowController
|
||||||
|
isMaximize={isMaximized()}
|
||||||
|
onToggleMaximize={handleToggleMaximize}
|
||||||
|
onMinimize={handleMinimize}
|
||||||
|
onClose={handleClose}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
66
src/plugins/in-app-menu/renderer/WindowController.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { css } from 'solid-styled-components';
|
||||||
|
import { Show } from 'solid-js';
|
||||||
|
|
||||||
|
import { IconButton } from './IconButton';
|
||||||
|
import { cache } from '@/providers/decorators';
|
||||||
|
|
||||||
|
const containerStyle = cache(() => css`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& > *:last-of-type {
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type WindowControllerProps = {
|
||||||
|
isMaximize?: boolean;
|
||||||
|
|
||||||
|
onToggleMaximize?: () => void;
|
||||||
|
onMinimize?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
export const WindowController = (props: WindowControllerProps) => {
|
||||||
|
return (
|
||||||
|
<div class={containerStyle()}>
|
||||||
|
<IconButton onClick={props.onMinimize}>
|
||||||
|
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="currentColor" d="M3.755 12.5h16.492a.75.75 0 0 0 0-1.5H3.755a.75.75 0 0 0 0 1.5Z"/>
|
||||||
|
</svg>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={props.onToggleMaximize}>
|
||||||
|
<Show
|
||||||
|
when={props.isMaximize}
|
||||||
|
fallback={
|
||||||
|
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M6 3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3Zm0 2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M7.518 5H6.009a3.25 3.25 0 0 1 3.24-3h8.001A4.75 4.75 0 0 1 22 6.75v8a3.25 3.25 0 0 1-3 3.24v-1.508a1.75 1.75 0 0 0 1.5-1.732v-8a3.25 3.25 0 0 0-3.25-3.25h-8A1.75 1.75 0 0 0 7.518 5ZM5.25 6A3.25 3.25 0 0 0 2 9.25v9.5A3.25 3.25 0 0 0 5.25 22h9.5A3.25 3.25 0 0 0 18 18.75v-9.5A3.25 3.25 0 0 0 14.75 6h-9.5ZM3.5 9.25c0-.966.784-1.75 1.75-1.75h9.5c.967 0 1.75.784 1.75 1.75v9.5a1.75 1.75 0 0 1-1.75 1.75h-9.5a1.75 1.75 0 0 1-1.75-1.75v-9.5Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Show>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={props.onClose}>
|
||||||
|
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m4.21 4.387.083-.094a1 1 0 0 1 1.32-.083l.094.083L12 10.585l6.293-6.292a1 1 0 1 1 1.414 1.414L13.415 12l6.292 6.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083L12 13.415l-6.293 6.292a1 1 0 0 1-1.414-1.414L10.585 12 4.293 5.707a1 1 0 0 1-.083-1.32l.083-.094-.083.094Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,168 +1,8 @@
|
|||||||
:root {
|
:root {
|
||||||
--titlebar-background-color: #030303;
|
--titlebar-background-color: var(--ytmusic-color-black3);
|
||||||
--menu-bar-height: 32px;
|
--menu-bar-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
title-bar {
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10000000;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: var(--menu-bar-height, 36px);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
color: #f1f1f1;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
padding-left: var(--offset-left, 12px);
|
|
||||||
background-color: var(--titlebar-background-color, #030303);
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
transition:
|
|
||||||
opacity 200ms ease 0s,
|
|
||||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu-button {
|
|
||||||
-webkit-app-region: none;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
align-self: stretch;
|
|
||||||
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
menu-button:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu-panel {
|
|
||||||
position: fixed;
|
|
||||||
top: var(--y, 0);
|
|
||||||
left: var(--x, 0);
|
|
||||||
|
|
||||||
max-height: calc(100vh - var(--menu-bar-height, 36px) - 16px - var(--y, 0));
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 0;
|
|
||||||
|
|
||||||
overflow: auto;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 8px;
|
|
||||||
pointer-events: none;
|
|
||||||
background-color: color-mix(
|
|
||||||
in srgb,
|
|
||||||
var(--titlebar-background-color, #030303) 50%,
|
|
||||||
rgba(0, 0, 0, 0.1)
|
|
||||||
);
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 1px rgba(0, 0, 0, 0.05),
|
|
||||||
0 2px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
|
|
||||||
z-index: 0;
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.8);
|
|
||||||
transform-origin: top left;
|
|
||||||
|
|
||||||
transition:
|
|
||||||
opacity 200ms ease 0s,
|
|
||||||
transform 200ms ease 0s;
|
|
||||||
}
|
|
||||||
menu-panel[open='true'] {
|
|
||||||
pointer-events: all;
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
menu-panel.position-by-bottom {
|
|
||||||
top: unset;
|
|
||||||
bottom: calc(100vh - var(--y, 100%));
|
|
||||||
max-height: calc(var(--y, 0) - var(--menu-bar-height, 36px) - 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu-item {
|
|
||||||
-webkit-app-region: none;
|
|
||||||
min-height: 32px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 32px 1fr minmax(32px, auto);
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
menu-item:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu-item > menu-icon {
|
|
||||||
height: 32px;
|
|
||||||
padding: 4px;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu-separator {
|
|
||||||
min-height: 1px;
|
|
||||||
height: 1px;
|
|
||||||
margin: 4px 0;
|
|
||||||
|
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* classes */
|
|
||||||
|
|
||||||
.title-bar-icon {
|
|
||||||
height: calc(100% - 8px);
|
|
||||||
object-fit: cover;
|
|
||||||
margin-left: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Window control container */
|
|
||||||
|
|
||||||
.window-controls-container {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end; /* Align to the right end of the title-bar */
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px; /* Add spacing between the window control buttons */
|
|
||||||
position: absolute; /* Position it absolutely within title-bar */
|
|
||||||
right: 4px; /* Adjust the right position as needed */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Window control buttons */
|
|
||||||
|
|
||||||
.window-control {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #f1f1f1;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* youtube-music style */
|
/* youtube-music style */
|
||||||
|
|
||||||
ytmusic-app-layout {
|
ytmusic-app-layout {
|
||||||
@ -185,6 +25,19 @@ ytmusic-app[is-bauhaus-sidenav-enabled] #mini-guide-spacer.ytmusic-app {
|
|||||||
) !important;
|
) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 935px) {
|
||||||
|
ytmusic-app[is-bauhaus-sidenav-enabled] #guide-spacer.ytmusic-app {
|
||||||
|
margin-top: calc(
|
||||||
|
var(--menu-bar-height, 36px)
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
ytmusic-app[is-bauhaus-sidenav-enabled] #mini-guide-spacer.ytmusic-app {
|
||||||
|
margin-top: calc(
|
||||||
|
var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px)
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ytmusic-app-layout > [slot='player-page'] {
|
ytmusic-app-layout > [slot='player-page'] {
|
||||||
margin-top: var(--menu-bar-height);
|
margin-top: var(--menu-bar-height);
|
||||||
height: calc(
|
height: calc(
|
||||||
|
|||||||
@ -1,80 +0,0 @@
|
|||||||
import { createPlugin } from '@/utils';
|
|
||||||
import registerCallback from '@/providers/song-info';
|
|
||||||
import { addScrobble, getAndSetSessionKey, setNowPlaying } from './main';
|
|
||||||
import { t } from '@/i18n';
|
|
||||||
|
|
||||||
export interface LastFmPluginConfig {
|
|
||||||
enabled: boolean;
|
|
||||||
/**
|
|
||||||
* Token used for authentication
|
|
||||||
*/
|
|
||||||
token?: string;
|
|
||||||
/**
|
|
||||||
* Session key used for scrabbling
|
|
||||||
*/
|
|
||||||
session_key?: string;
|
|
||||||
/**
|
|
||||||
* Root of the Last.fm API
|
|
||||||
*
|
|
||||||
* @default 'http://ws.audioscrobbler.com/2.0/'
|
|
||||||
*/
|
|
||||||
api_root: string;
|
|
||||||
/**
|
|
||||||
* Last.fm api key registered by @semvis123
|
|
||||||
*
|
|
||||||
* @default '04d76faaac8726e60988e14c105d421a'
|
|
||||||
*/
|
|
||||||
api_key: string;
|
|
||||||
/**
|
|
||||||
* Last.fm api secret registered by @semvis123
|
|
||||||
*
|
|
||||||
* @default 'a5d2a36fdf64819290f6982481eaffa2'
|
|
||||||
*/
|
|
||||||
secret: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createPlugin({
|
|
||||||
name: () => t('plugins.last-fm.name'),
|
|
||||||
description: () => t('plugins.last-fm.description'),
|
|
||||||
restartNeeded: true,
|
|
||||||
config: {
|
|
||||||
enabled: false,
|
|
||||||
api_root: 'http://ws.audioscrobbler.com/2.0/',
|
|
||||||
api_key: '04d76faaac8726e60988e14c105d421a',
|
|
||||||
secret: 'a5d2a36fdf64819290f6982481eaffa2',
|
|
||||||
} as LastFmPluginConfig,
|
|
||||||
async backend({ getConfig, setConfig }) {
|
|
||||||
let config = await getConfig();
|
|
||||||
// This will store the timeout that will trigger addScrobble
|
|
||||||
let scrobbleTimer: number | undefined;
|
|
||||||
|
|
||||||
if (!config.api_root) {
|
|
||||||
config.enabled = true;
|
|
||||||
setConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.session_key) {
|
|
||||||
// Not authenticated
|
|
||||||
config = await getAndSetSessionKey(config, setConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCallback((songInfo) => {
|
|
||||||
// Set remove the old scrobble timer
|
|
||||||
clearTimeout(scrobbleTimer);
|
|
||||||
if (!songInfo.isPaused) {
|
|
||||||
setNowPlaying(songInfo, config, setConfig);
|
|
||||||
// Scrobble when the song is halfway through, or has passed the 4-minute mark
|
|
||||||
const scrobbleTime = Math.min(
|
|
||||||
Math.ceil(songInfo.songDuration / 2),
|
|
||||||
4 * 60,
|
|
||||||
);
|
|
||||||
if (scrobbleTime > (songInfo.elapsedSeconds ?? 0)) {
|
|
||||||
// Scrobble still needs to happen
|
|
||||||
const timeToWait =
|
|
||||||
(scrobbleTime - (songInfo.elapsedSeconds ?? 0)) * 1000;
|
|
||||||
scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,207 +0,0 @@
|
|||||||
import crypto from 'node:crypto';
|
|
||||||
|
|
||||||
import { net, shell } from 'electron';
|
|
||||||
|
|
||||||
import type { LastFmPluginConfig } from './index';
|
|
||||||
import type { SongInfo } from '@/providers/song-info';
|
|
||||||
|
|
||||||
interface LastFmData {
|
|
||||||
method: string;
|
|
||||||
timestamp?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LastFmSongData {
|
|
||||||
track?: string;
|
|
||||||
duration?: number;
|
|
||||||
artist?: string;
|
|
||||||
album?: string;
|
|
||||||
api_key: string;
|
|
||||||
sk?: string;
|
|
||||||
format: string;
|
|
||||||
method: string;
|
|
||||||
timestamp?: number;
|
|
||||||
api_sig?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createFormData = (parameters: LastFmSongData) => {
|
|
||||||
// Creates the body for in the post request
|
|
||||||
const formData = new URLSearchParams();
|
|
||||||
for (const key in parameters) {
|
|
||||||
formData.append(key, String(parameters[key as keyof LastFmSongData]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createQueryString = (
|
|
||||||
parameters: Record<string, unknown>,
|
|
||||||
apiSignature: string,
|
|
||||||
) => {
|
|
||||||
// Creates a querystring
|
|
||||||
const queryData = [];
|
|
||||||
parameters.api_sig = apiSignature;
|
|
||||||
for (const key in parameters) {
|
|
||||||
queryData.push(
|
|
||||||
`${encodeURIComponent(key)}=${encodeURIComponent(
|
|
||||||
String(parameters[key]),
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return '?' + queryData.join('&');
|
|
||||||
};
|
|
||||||
|
|
||||||
const createApiSig = (parameters: LastFmSongData, secret: string) => {
|
|
||||||
// This function creates the api signature, see: https://www.last.fm/api/authspec
|
|
||||||
const keys = Object.keys(parameters);
|
|
||||||
|
|
||||||
keys.sort();
|
|
||||||
let sig = '';
|
|
||||||
for (const key of keys) {
|
|
||||||
if (key === 'format') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
sig += `${key}${parameters[key as keyof LastFmSongData]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
sig += secret;
|
|
||||||
sig = crypto.createHash('md5').update(sig, 'utf-8').digest('hex');
|
|
||||||
return sig;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createToken = async ({
|
|
||||||
api_key: apiKey,
|
|
||||||
api_root: apiRoot,
|
|
||||||
secret,
|
|
||||||
}: LastFmPluginConfig) => {
|
|
||||||
// Creates and stores the auth token
|
|
||||||
const data = {
|
|
||||||
method: 'auth.gettoken',
|
|
||||||
api_key: apiKey,
|
|
||||||
format: 'json',
|
|
||||||
};
|
|
||||||
const apiSigature = createApiSig(data, secret);
|
|
||||||
const response = await net.fetch(
|
|
||||||
`${apiRoot}${createQueryString(data, apiSigature)}`,
|
|
||||||
);
|
|
||||||
const json = (await response.json()) as Record<string, string>;
|
|
||||||
return json?.token;
|
|
||||||
};
|
|
||||||
|
|
||||||
const authenticate = async (config: LastFmPluginConfig) => {
|
|
||||||
// Asks the user for authentication
|
|
||||||
await shell.openExternal(
|
|
||||||
`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type SetConfType = (
|
|
||||||
conf: Partial<Omit<LastFmPluginConfig, 'enabled'>>,
|
|
||||||
) => void | Promise<void>;
|
|
||||||
|
|
||||||
export const getAndSetSessionKey = async (
|
|
||||||
config: LastFmPluginConfig,
|
|
||||||
setConfig: SetConfType,
|
|
||||||
) => {
|
|
||||||
// Get and store the session key
|
|
||||||
const data = {
|
|
||||||
api_key: config.api_key,
|
|
||||||
format: 'json',
|
|
||||||
method: 'auth.getsession',
|
|
||||||
token: config.token,
|
|
||||||
};
|
|
||||||
const apiSignature = createApiSig(data, config.secret);
|
|
||||||
const response = await net.fetch(
|
|
||||||
`${config.api_root}${createQueryString(data, apiSignature)}`,
|
|
||||||
);
|
|
||||||
const json = (await response.json()) as {
|
|
||||||
error?: string;
|
|
||||||
session?: {
|
|
||||||
key: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if (json.error) {
|
|
||||||
config.token = await createToken(config);
|
|
||||||
await authenticate(config);
|
|
||||||
setConfig(config);
|
|
||||||
}
|
|
||||||
if (json.session) {
|
|
||||||
config.session_key = json.session.key;
|
|
||||||
}
|
|
||||||
setConfig(config);
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
const postSongDataToAPI = async (
|
|
||||||
songInfo: SongInfo,
|
|
||||||
config: LastFmPluginConfig,
|
|
||||||
data: LastFmData,
|
|
||||||
setConfig: SetConfType,
|
|
||||||
) => {
|
|
||||||
// This sends a post request to the api, and adds the common data
|
|
||||||
if (!config.session_key) {
|
|
||||||
await getAndSetSessionKey(config, setConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
const postData: LastFmSongData = {
|
|
||||||
track: songInfo.title,
|
|
||||||
duration: songInfo.songDuration,
|
|
||||||
artist: songInfo.artist,
|
|
||||||
...(songInfo.album ? { album: songInfo.album } : undefined), // Will be undefined if current song is a video
|
|
||||||
api_key: config.api_key,
|
|
||||||
sk: config.session_key,
|
|
||||||
format: 'json',
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
postData.api_sig = createApiSig(postData, config.secret);
|
|
||||||
const formData = createFormData(postData);
|
|
||||||
net
|
|
||||||
.fetch('https://ws.audioscrobbler.com/2.0/', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.catch(
|
|
||||||
async (error: {
|
|
||||||
response?: {
|
|
||||||
data?: {
|
|
||||||
error: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}) => {
|
|
||||||
if (error?.response?.data?.error === 9) {
|
|
||||||
// Session key is invalid, so remove it from the config and reauthenticate
|
|
||||||
config.session_key = undefined;
|
|
||||||
config.token = await createToken(config);
|
|
||||||
await authenticate(config);
|
|
||||||
setConfig(config);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addScrobble = (
|
|
||||||
songInfo: SongInfo,
|
|
||||||
config: LastFmPluginConfig,
|
|
||||||
setConfig: SetConfType,
|
|
||||||
) => {
|
|
||||||
// This adds one scrobbled song to last.fm
|
|
||||||
const data = {
|
|
||||||
method: 'track.scrobble',
|
|
||||||
timestamp: Math.trunc((Date.now() - (songInfo.elapsedSeconds ?? 0)) / 1000),
|
|
||||||
};
|
|
||||||
postSongDataToAPI(songInfo, config, data, setConfig);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setNowPlaying = (
|
|
||||||
songInfo: SongInfo,
|
|
||||||
config: LastFmPluginConfig,
|
|
||||||
setConfig: SetConfType,
|
|
||||||
) => {
|
|
||||||
// This sets the now playing status in last.fm
|
|
||||||
const data = {
|
|
||||||
method: 'track.updateNowPlaying',
|
|
||||||
};
|
|
||||||
postSongDataToAPI(songInfo, config, data, setConfig);
|
|
||||||
};
|
|
||||||
@ -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;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export const onRendererLoad = ({
|
|||||||
|
|
||||||
let unregister: (() => void) | null = null;
|
let unregister: (() => void) | null = null;
|
||||||
|
|
||||||
on('update-song-info', (extractedSongInfo: SongInfo) => {
|
on('ytmd:update-song-info', (extractedSongInfo: SongInfo) => {
|
||||||
unregister?.();
|
unregister?.();
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
|||||||
149
src/plugins/music-together/connection.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { DataConnection, Peer } from 'peerjs';
|
||||||
|
|
||||||
|
import type { Permission, Profile, VideoData } from './types';
|
||||||
|
|
||||||
|
export type ConnectionEventMap = {
|
||||||
|
ADD_SONGS: { videoList: VideoData[], index?: number };
|
||||||
|
REMOVE_SONG: { index: number };
|
||||||
|
MOVE_SONG: { fromIndex: number; toIndex: number };
|
||||||
|
IDENTIFY: { profile: Profile } | undefined;
|
||||||
|
SYNC_PROFILE: { profiles: Record<string, Profile> } | undefined;
|
||||||
|
SYNC_QUEUE: { videoList: VideoData[] } | undefined;
|
||||||
|
SYNC_PROGRESS: { progress?: number; state?: number; index?: number; } | undefined;
|
||||||
|
PERMISSION: Permission | undefined;
|
||||||
|
};
|
||||||
|
export type ConnectionEventUnion = {
|
||||||
|
[Event in keyof ConnectionEventMap]: {
|
||||||
|
type: Event;
|
||||||
|
payload: ConnectionEventMap[Event];
|
||||||
|
after?: ConnectionEventUnion[];
|
||||||
|
};
|
||||||
|
}[keyof ConnectionEventMap];
|
||||||
|
|
||||||
|
type PromiseUtil<T> = {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve: (id: T) => void;
|
||||||
|
reject: (err: unknown) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConnectionListener = (event: ConnectionEventUnion, conn: DataConnection) => void;
|
||||||
|
export type ConnectionMode = 'host' | 'guest' | 'disconnected';
|
||||||
|
export class Connection {
|
||||||
|
private peer: Peer;
|
||||||
|
private _mode: ConnectionMode = 'disconnected';
|
||||||
|
private connections: Record<string, DataConnection> = {};
|
||||||
|
|
||||||
|
private waitOpen: PromiseUtil<string> = {} as PromiseUtil<string>;
|
||||||
|
private listeners: ConnectionListener[] = [];
|
||||||
|
private connectionListeners: ((connection?: DataConnection) => void)[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.peer = new Peer({ debug: 0 });
|
||||||
|
|
||||||
|
this.waitOpen.promise = new Promise<string>((resolve, reject) => {
|
||||||
|
this.waitOpen.resolve = resolve;
|
||||||
|
this.waitOpen.reject = reject;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.peer.on('open', (id) => {
|
||||||
|
this._mode = 'host';
|
||||||
|
this.waitOpen.resolve(id);
|
||||||
|
});
|
||||||
|
this.peer.on('connection', (conn) => {
|
||||||
|
this._mode = 'host';
|
||||||
|
this.registerConnection(conn);
|
||||||
|
});
|
||||||
|
this.peer.on('error', (err) => {
|
||||||
|
this._mode = 'disconnected';
|
||||||
|
|
||||||
|
this.waitOpen.reject(err);
|
||||||
|
this.connectionListeners.forEach((listener) => listener());
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public */
|
||||||
|
async waitForReady() {
|
||||||
|
return this.waitOpen.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(id: string) {
|
||||||
|
this._mode = 'guest';
|
||||||
|
const conn = this.peer.connect(id);
|
||||||
|
await this.registerConnection(conn);
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this._mode === 'disconnected') throw new Error('Already disconnected');
|
||||||
|
|
||||||
|
this._mode = 'disconnected';
|
||||||
|
this.connections = {};
|
||||||
|
this.peer.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* utils */
|
||||||
|
public get id() {
|
||||||
|
return this.peer.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get mode() {
|
||||||
|
return this._mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnections() {
|
||||||
|
return Object.values(this.connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async broadcast<Event extends keyof ConnectionEventMap>(type: Event, payload: ConnectionEventMap[Event]) {
|
||||||
|
await Promise.all(
|
||||||
|
this.getConnections().map((conn) => conn.send({ type, payload }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(listener: ConnectionListener) {
|
||||||
|
this.listeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onConnections(listener: (connections?: DataConnection) => void) {
|
||||||
|
this.connectionListeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* privates */
|
||||||
|
private async registerConnection(conn: DataConnection) {
|
||||||
|
return new Promise<DataConnection>((resolve, reject) => {
|
||||||
|
this.peer.once('error', (err) => {
|
||||||
|
this._mode = 'disconnected';
|
||||||
|
|
||||||
|
reject(err);
|
||||||
|
this.connectionListeners.forEach((listener) => listener());
|
||||||
|
});
|
||||||
|
|
||||||
|
conn.on('open', () => {
|
||||||
|
this.connections[conn.connectionId] = conn;
|
||||||
|
resolve(conn);
|
||||||
|
this.connectionListeners.forEach((listener) => listener(conn));
|
||||||
|
|
||||||
|
conn.on('data', (data) => {
|
||||||
|
if (!data || typeof data !== 'object' || !('type' in data) || !('payload' in data) || !data.type) {
|
||||||
|
console.warn('Music Together: Invalid data', data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
listener(data as ConnectionEventUnion, conn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClose = (err?: Error) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
|
||||||
|
delete this.connections[conn.connectionId];
|
||||||
|
this.connectionListeners.forEach((listener) => listener(conn));
|
||||||
|
};
|
||||||
|
conn.on('error', onClose);
|
||||||
|
conn.on('close', onClose);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/plugins/music-together/element.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||||
|
|
||||||
|
import itemHTML from './templates/item.html?raw';
|
||||||
|
import popupHTML from './templates/popup.html?raw';
|
||||||
|
|
||||||
|
type Placement =
|
||||||
|
'top'
|
||||||
|
| 'bottom'
|
||||||
|
| 'right'
|
||||||
|
| 'left'
|
||||||
|
| 'center'
|
||||||
|
| 'middle'
|
||||||
|
| 'center-middle'
|
||||||
|
| 'top-left'
|
||||||
|
| 'top-right'
|
||||||
|
| 'bottom-left'
|
||||||
|
| 'bottom-right';
|
||||||
|
type PopupItem = (ItemRendererProps & { type: 'item'; })
|
||||||
|
| { type: 'divider'; }
|
||||||
|
| { type: 'custom'; element: HTMLElement; };
|
||||||
|
|
||||||
|
type PopupProps = {
|
||||||
|
data: PopupItem[];
|
||||||
|
anchorAt?: Placement;
|
||||||
|
popupAt?: Placement;
|
||||||
|
}
|
||||||
|
export const Popup = (props: PopupProps) => {
|
||||||
|
const popup = ElementFromHtml(popupHTML);
|
||||||
|
const container = popup.querySelector<HTMLElement>('.music-together-popup-container')!;
|
||||||
|
const items = props.data
|
||||||
|
.map((props) => {
|
||||||
|
if (props.type === 'item') return {
|
||||||
|
type: 'item' as const,
|
||||||
|
...ItemRenderer(props),
|
||||||
|
};
|
||||||
|
if (props.type === 'divider') return {
|
||||||
|
type: 'divider' as const,
|
||||||
|
element: ElementFromHtml('<div class="music-together-divider horizontal"></div>'),
|
||||||
|
};
|
||||||
|
if (props.type === 'custom') return {
|
||||||
|
type: 'custom' as const,
|
||||||
|
element: props.element,
|
||||||
|
};
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
container.append(...items.map(({ element }) => element));
|
||||||
|
popup.style.setProperty('opacity', '0');
|
||||||
|
popup.style.setProperty('pointer-events', 'none');
|
||||||
|
|
||||||
|
document.body.append(popup);
|
||||||
|
|
||||||
|
return {
|
||||||
|
element: popup,
|
||||||
|
container,
|
||||||
|
items,
|
||||||
|
|
||||||
|
show(x: number, y: number, anchor?: HTMLElement) {
|
||||||
|
let left = x;
|
||||||
|
let top = y;
|
||||||
|
|
||||||
|
if (anchor) {
|
||||||
|
if (props.anchorAt?.includes('right')) left += anchor.clientWidth;
|
||||||
|
if (props.anchorAt?.includes('bottom')) top += anchor.clientHeight;
|
||||||
|
if (props.anchorAt?.includes('center')) left += anchor.clientWidth / 2;
|
||||||
|
if (props.anchorAt?.includes('middle')) top += anchor.clientHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.popupAt?.includes('right')) left -= popup.clientWidth;
|
||||||
|
if (props.popupAt?.includes('bottom')) top -= popup.clientHeight;
|
||||||
|
if (props.popupAt?.includes('center')) left -= popup.clientWidth / 2;
|
||||||
|
if (props.popupAt?.includes('middle')) top -= popup.clientHeight / 2;
|
||||||
|
|
||||||
|
popup.style.setProperty('left', `${left}px`);
|
||||||
|
popup.style.setProperty('top', `${top}px`);
|
||||||
|
popup.style.setProperty('opacity', '1');
|
||||||
|
popup.style.setProperty('pointer-events', 'unset');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const onClose = (event: MouseEvent) => {
|
||||||
|
const isPopupClick = event.composedPath().some((element) => element === popup);
|
||||||
|
if (!isPopupClick) {
|
||||||
|
this.dismiss();
|
||||||
|
document.removeEventListener('click', onClose);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', onClose);
|
||||||
|
}, 16);
|
||||||
|
},
|
||||||
|
showAtAnchor(anchor: HTMLElement) {
|
||||||
|
const { x, y } = anchor.getBoundingClientRect();
|
||||||
|
this.show(x, y, anchor);
|
||||||
|
},
|
||||||
|
|
||||||
|
isShowing() {
|
||||||
|
return popup.style.getPropertyValue('opacity') === '1';
|
||||||
|
},
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
popup.style.setProperty('opacity', '0');
|
||||||
|
popup.style.setProperty('pointer-events', 'none');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ItemRendererProps = {
|
||||||
|
id?: string;
|
||||||
|
icon?: Element;
|
||||||
|
text: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
export const ItemRenderer = (props: ItemRendererProps) => {
|
||||||
|
const element = ElementFromHtml(itemHTML);
|
||||||
|
const iconContainer = element.querySelector<HTMLElement>('div.icon')!;
|
||||||
|
const textContainer = element.querySelector<HTMLElement>('div.text')!;
|
||||||
|
if (props.icon) iconContainer.appendChild(props.icon);
|
||||||
|
textContainer.append(props.text);
|
||||||
|
|
||||||
|
if (props.onClick) {
|
||||||
|
element.addEventListener('click', () => {
|
||||||
|
props.onClick?.();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (props.id) element.id = props.id;
|
||||||
|
|
||||||
|
return {
|
||||||
|
element,
|
||||||
|
setIcon(icon: Element) {
|
||||||
|
iconContainer.replaceChildren(icon);
|
||||||
|
},
|
||||||
|
setText(text: string) {
|
||||||
|
textContainer.replaceChildren(text);
|
||||||
|
},
|
||||||
|
id: props.id
|
||||||
|
};
|
||||||
|
};
|
||||||
3
src/plugins/music-together/icons/connect.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||||
|
<path d="M480-640 280-440l56 56 104-103v407h80v-407l104 103 56-56-200-200ZM146-260q-32-49-49-105T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 59-17 115t-49 105l-58-58q22-37 33-78t11-84q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 43 11 84t33 78l-58 58Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 408 B |
4
src/plugins/music-together/icons/key.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||||
|
<path
|
||||||
|
d="M280-400q-33 0-56.5-23.5T200-480q0-33 23.5-56.5T280-560q33 0 56.5 23.5T360-480q0 33-23.5 56.5T280-400Zm0 160q-100 0-170-70T40-480q0-100 70-170t170-70q67 0 121.5 33t86.5 87h352l120 120-180 180-80-60-80 60-85-60h-47q-32 54-86.5 87T280-240Zm0-80q56 0 98.5-34t56.5-86h125l58 41 82-61 71 55 75-75-40-40H435q-14-52-56.5-86T280-640q-66 0-113 47t-47 113q0 66 47 113t113 47Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 480 B |
3
src/plugins/music-together/icons/music-cast.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||||
|
<path d="M560-160q-66 0-113-47t-47-113q0-66 47-113t113-47q23 0 42.5 5.5T640-458v-342h240v120H720v360q0 66-47 113t-113 47ZM80-320q0-99 38-186.5T221-659q65-65 152.5-103T560-800v80q-82 0-155 31.5t-127.5 86q-54.5 54.5-86 127T160-320H80Zm160 0q0-66 25.5-124.5t69-102Q378-590 436-615t124-25v80q-100 0-170 70t-70 170h-80Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 416 B |
4
src/plugins/music-together/icons/off.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||||
|
<path
|
||||||
|
d="M792-56 686-160H260q-92 0-156-64T40-380q0-77 47.5-137T210-594q3-8 6-15.5t6-16.5L56-792l56-56 736 736-56 56ZM260-240h346L284-562q-2 11-3 21t-1 21h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm185-161Zm419 191-58-56q17-14 25.5-32.5T840-340q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-27 0-52 6.5T380-693l-58-58q35-24 74.5-36.5T480-800q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 39-15 72.5T864-210ZM593-479Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 529 B |
3
src/plugins/music-together/icons/tune.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||||
|
<path d="M440-120v-240h80v80h320v80H520v80h-80Zm-320-80v-80h240v80H120Zm160-160v-80H120v-80h160v-80h80v240h-80Zm160-80v-80h400v80H440Zm160-160v-240h80v80h160v80H680v80h-80Zm-480-80v-80h400v80H120Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 298 B |
717
src/plugins/music-together/index.ts
Normal file
@ -0,0 +1,717 @@
|
|||||||
|
import prompt from 'custom-electron-prompt';
|
||||||
|
|
||||||
|
import { DataConnection } from 'peerjs';
|
||||||
|
|
||||||
|
import { t } from '@/i18n';
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
import promptOptions from '@/providers/prompt-options';
|
||||||
|
|
||||||
|
import { AppAPI, getDefaultProfile, Permission, Profile, VideoData } from './types';
|
||||||
|
import { Queue } from './queue';
|
||||||
|
import { Connection, ConnectionEventUnion } from './connection';
|
||||||
|
import { createHostPopup } from './ui/host';
|
||||||
|
import { createGuestPopup } from './ui/guest';
|
||||||
|
import { createSettingPopup } from './ui/setting';
|
||||||
|
|
||||||
|
import settingHTML from './templates/setting.html?raw';
|
||||||
|
import style from './style.css?inline';
|
||||||
|
|
||||||
|
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||||
|
import type { RendererContext } from '@/types/contexts';
|
||||||
|
import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||||
|
|
||||||
|
type RawAccountData = {
|
||||||
|
accountName: {
|
||||||
|
runs: { text: string }[];
|
||||||
|
};
|
||||||
|
accountPhoto: {
|
||||||
|
thumbnails: { url: string; width: number; height: number; }[];
|
||||||
|
};
|
||||||
|
settingsEndpoint: unknown;
|
||||||
|
manageAccountTitle: unknown;
|
||||||
|
trackingParams: string;
|
||||||
|
channelHandle: {
|
||||||
|
runs: { text: string }[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createPlugin<
|
||||||
|
unknown,
|
||||||
|
unknown,
|
||||||
|
{
|
||||||
|
connection?: Connection;
|
||||||
|
ipc?: RendererContext<never>['ipc'];
|
||||||
|
api: HTMLElement & AppAPI | null;
|
||||||
|
queue?: Queue;
|
||||||
|
playerApi?: YoutubePlayer;
|
||||||
|
showPrompt: (title: string, label: string) => Promise<string>;
|
||||||
|
popups: {
|
||||||
|
host: ReturnType<typeof createHostPopup>;
|
||||||
|
guest: ReturnType<typeof createGuestPopup>;
|
||||||
|
setting: ReturnType<typeof createSettingPopup>;
|
||||||
|
};
|
||||||
|
elements: {
|
||||||
|
setting: HTMLElement;
|
||||||
|
icon: SVGElement;
|
||||||
|
spinner: HTMLElement;
|
||||||
|
};
|
||||||
|
stateInterval?: number;
|
||||||
|
updateNext: boolean;
|
||||||
|
ignoreChange: boolean;
|
||||||
|
rollbackInjector?: (() => void);
|
||||||
|
me?: Omit<Profile, 'id'>;
|
||||||
|
profiles: Record<string, Profile>;
|
||||||
|
permission: Permission;
|
||||||
|
videoChangeListener: (event: CustomEvent<VideoDataChanged>) => void;
|
||||||
|
videoStateChangeListener: () => void;
|
||||||
|
onHost: () => Promise<boolean>;
|
||||||
|
onJoin: () => Promise<boolean>;
|
||||||
|
onStop: () => void;
|
||||||
|
putProfile: (id: string, profile?: Profile) => void;
|
||||||
|
showSpinner: () => void;
|
||||||
|
hideSpinner: () => void;
|
||||||
|
initMyProfile: () => void;
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
name: () => t('plugins.music-together.name'),
|
||||||
|
description: () => t('plugins.music-together.description'),
|
||||||
|
restartNeeded: false,
|
||||||
|
addedVersion: '3.2.X',
|
||||||
|
config: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stylesheets: [style],
|
||||||
|
backend({ ipc }) {
|
||||||
|
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
|
||||||
|
title,
|
||||||
|
label,
|
||||||
|
type: 'input',
|
||||||
|
...promptOptions()
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
updateNext: false,
|
||||||
|
ignoreChange: false,
|
||||||
|
permission: 'playlist',
|
||||||
|
popups: {} as {
|
||||||
|
host: ReturnType<typeof createHostPopup>;
|
||||||
|
guest: ReturnType<typeof createGuestPopup>;
|
||||||
|
setting: ReturnType<typeof createSettingPopup>;
|
||||||
|
},
|
||||||
|
elements: {} as {
|
||||||
|
setting: HTMLElement;
|
||||||
|
icon: SVGElement;
|
||||||
|
spinner: HTMLElement;
|
||||||
|
},
|
||||||
|
profiles: {},
|
||||||
|
showPrompt: () => Promise.resolve(''),
|
||||||
|
api: null,
|
||||||
|
|
||||||
|
/* events */
|
||||||
|
videoChangeListener(event: CustomEvent<VideoDataChanged>) {
|
||||||
|
if (event.detail.name === 'dataloaded' || this.updateNext) {
|
||||||
|
if (this.connection?.mode === 'host') {
|
||||||
|
const videoList: VideoData[] = this.queue?.flatItems.map((it) => ({
|
||||||
|
videoId: it!.videoId,
|
||||||
|
ownerId: this.connection!.id
|
||||||
|
} satisfies VideoData)) ?? [];
|
||||||
|
|
||||||
|
this.queue?.setVideoList(videoList, false);
|
||||||
|
this.queue?.syncQueueOwner();
|
||||||
|
this.connection.broadcast('SYNC_QUEUE', {
|
||||||
|
videoList
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateNext = event.detail.name === 'dataloaded';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
videoStateChangeListener() {
|
||||||
|
if (this.connection?.mode !== 'guest') return;
|
||||||
|
if (this.ignoreChange) return;
|
||||||
|
if (this.permission !== 'all') return;
|
||||||
|
|
||||||
|
const state = this.playerApi?.getPlayerState();
|
||||||
|
if (state !== 1 && state !== 2) return;
|
||||||
|
|
||||||
|
this.connection.broadcast('SYNC_PROGRESS', {
|
||||||
|
// progress: this.playerApi?.getCurrentTime(),
|
||||||
|
state: this.playerApi?.getPlayerState()
|
||||||
|
// index: this.queue?.selectedIndex ?? 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* connection */
|
||||||
|
async onHost() {
|
||||||
|
this.connection = new Connection();
|
||||||
|
const wait = await this.connection.waitForReady().catch(() => null);
|
||||||
|
if (!wait) return false;
|
||||||
|
|
||||||
|
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
||||||
|
const rawItems = this.queue?.flatItems?.map((it) => ({
|
||||||
|
videoId: it!.videoId,
|
||||||
|
ownerId: this.connection!.id
|
||||||
|
} satisfies VideoData)) ?? [];
|
||||||
|
this.queue?.setOwner({
|
||||||
|
id: this.connection.id,
|
||||||
|
...this.me
|
||||||
|
});
|
||||||
|
this.queue?.setVideoList(rawItems, false);
|
||||||
|
this.queue?.syncQueueOwner();
|
||||||
|
this.queue?.initQueue();
|
||||||
|
this.queue?.injection();
|
||||||
|
|
||||||
|
this.profiles = {};
|
||||||
|
this.connection.onConnections((connection) => {
|
||||||
|
if (!connection) {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
|
||||||
|
this.onStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connection.open) {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.user-disconnected', {
|
||||||
|
name: this.profiles[connection.peer]?.name
|
||||||
|
}));
|
||||||
|
this.putProfile(connection.peer, undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.putProfile(this.connection.id, {
|
||||||
|
id: this.connection.id,
|
||||||
|
...this.me
|
||||||
|
});
|
||||||
|
|
||||||
|
const listener = async (event: ConnectionEventUnion, conn?: DataConnection) => {
|
||||||
|
this.ignoreChange = true;
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case 'ADD_SONGS': {
|
||||||
|
if (conn && this.permission === 'host-only') return;
|
||||||
|
|
||||||
|
await this.queue?.addVideos(event.payload.videoList, event.payload.index);
|
||||||
|
await this.connection?.broadcast('ADD_SONGS', event.payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'REMOVE_SONG': {
|
||||||
|
if (conn && this.permission === 'host-only') return;
|
||||||
|
|
||||||
|
this.queue?.removeVideo(event.payload.index);
|
||||||
|
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'MOVE_SONG': {
|
||||||
|
if (conn && this.permission === 'host-only') {
|
||||||
|
await this.connection?.broadcast('SYNC_QUEUE', {
|
||||||
|
videoList: this.queue?.videoList ?? []
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
||||||
|
await this.connection?.broadcast('MOVE_SONG', event.payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'IDENTIFY': {
|
||||||
|
if (!event.payload || !conn) {
|
||||||
|
console.warn('Music Together [Host]: Received "IDENTIFY" event without payload or connection');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.user-connected', { name: event.payload.profile.name }));
|
||||||
|
this.putProfile(conn.peer, event.payload.profile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SYNC_PROFILE': {
|
||||||
|
await this.connection?.broadcast('SYNC_PROFILE', { profiles: this.profiles });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'PERMISSION': {
|
||||||
|
await this.connection?.broadcast('PERMISSION', this.permission);
|
||||||
|
this.popups.guest.setPermission(this.permission);
|
||||||
|
this.popups.host.setPermission(this.permission);
|
||||||
|
this.popups.setting.setPermission(this.permission);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SYNC_QUEUE': {
|
||||||
|
await this.connection?.broadcast('SYNC_QUEUE', {
|
||||||
|
videoList: this.queue?.videoList ?? []
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SYNC_PROGRESS': {
|
||||||
|
let permissionLevel = 0;
|
||||||
|
if (this.permission === 'all') permissionLevel = 2;
|
||||||
|
if (this.permission === 'playlist') permissionLevel = 1;
|
||||||
|
if (this.permission === 'host-only') permissionLevel = 0;
|
||||||
|
if (!conn) permissionLevel = 3;
|
||||||
|
|
||||||
|
if (permissionLevel >= 2) {
|
||||||
|
if (typeof event.payload?.progress === 'number') {
|
||||||
|
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
|
||||||
|
if (Math.abs(event.payload.progress - currentTime) > 3) this.playerApi?.seekTo(event.payload.progress);
|
||||||
|
}
|
||||||
|
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
|
||||||
|
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
|
||||||
|
if (event.payload?.state === 1) this.playerApi?.playVideo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (permissionLevel >= 1) {
|
||||||
|
if (typeof event.payload?.index === 'number') {
|
||||||
|
const nowIndex = this.queue?.selectedIndex ?? 0;
|
||||||
|
|
||||||
|
if (nowIndex !== event.payload.index) {
|
||||||
|
this.queue?.setIndex(event.payload.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.warn('Music Together [Host]: Unknown Event', event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.after) {
|
||||||
|
const now = event.after.shift();
|
||||||
|
if (now) {
|
||||||
|
now.after = event.after;
|
||||||
|
await listener(now, conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.connection.on(listener);
|
||||||
|
this.queue?.on(listener);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.ignoreChange = false;
|
||||||
|
}, 16); // wait 1 frame
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
async onJoin() {
|
||||||
|
this.connection = new Connection();
|
||||||
|
const wait = await this.connection.waitForReady().catch(() => null);
|
||||||
|
if (!wait) return false;
|
||||||
|
|
||||||
|
this.profiles = {};
|
||||||
|
|
||||||
|
const id = await this.showPrompt(t('plugins.music-together.name'), t('plugins.music-together.dialog.enter-host'));
|
||||||
|
if (typeof id !== 'string') return false;
|
||||||
|
|
||||||
|
const connection = await this.connection.connect(id).catch(() => false);
|
||||||
|
if (!connection) return false;
|
||||||
|
this.connection.onConnections((connection) => {
|
||||||
|
if (!connection?.open) {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
|
||||||
|
this.onStop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let resolveIgnore: number | null = null;
|
||||||
|
const listener = async (event: ConnectionEventUnion) => {
|
||||||
|
this.ignoreChange = true;
|
||||||
|
switch (event.type) {
|
||||||
|
case 'ADD_SONGS': {
|
||||||
|
await this.queue?.addVideos(event.payload.videoList, event.payload.index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'REMOVE_SONG': {
|
||||||
|
this.queue?.removeVideo(event.payload.index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'MOVE_SONG': {
|
||||||
|
this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'IDENTIFY': {
|
||||||
|
console.warn('Music Together [Guest]: Received "IDENTIFY" event from guest');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SYNC_QUEUE': {
|
||||||
|
if (Array.isArray(event.payload?.videoList)) {
|
||||||
|
await this.queue?.setVideoList(event.payload.videoList);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SYNC_PROFILE': {
|
||||||
|
if (!event.payload) {
|
||||||
|
console.warn('Music Together [Guest]: Received "SYNC_PROFILE" event without payload');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(event.payload.profiles).forEach(([id, profile]) => {
|
||||||
|
this.putProfile(id, profile);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SYNC_PROGRESS': {
|
||||||
|
if (typeof event.payload?.progress === 'number') {
|
||||||
|
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
|
||||||
|
if (Math.abs(event.payload.progress - currentTime) > 3) this.playerApi?.seekTo(event.payload.progress);
|
||||||
|
}
|
||||||
|
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
|
||||||
|
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
|
||||||
|
if (event.payload?.state === 1) this.playerApi?.playVideo();
|
||||||
|
}
|
||||||
|
if (typeof event.payload?.index === 'number') {
|
||||||
|
const nowIndex = this.queue?.selectedIndex ?? 0;
|
||||||
|
|
||||||
|
if (nowIndex !== event.payload.index) {
|
||||||
|
this.queue?.setIndex(event.payload.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'PERMISSION': {
|
||||||
|
if (!event.payload) {
|
||||||
|
console.warn('Music Together [Guest]: Received "PERMISSION" event without payload');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.permission = event.payload;
|
||||||
|
this.popups.guest.setPermission(this.permission);
|
||||||
|
this.popups.host.setPermission(this.permission);
|
||||||
|
this.popups.setting.setPermission(this.permission);
|
||||||
|
|
||||||
|
const permissionLabel = t(`plugins.music-together.menu.permission.${this.permission}`);
|
||||||
|
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.warn('Music Together [Guest]: Unknown Event', event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
|
||||||
|
resolveIgnore = window.setTimeout(() => {
|
||||||
|
this.ignoreChange = false;
|
||||||
|
}, 16); // wait 1 frame
|
||||||
|
};
|
||||||
|
|
||||||
|
this.connection.on(listener);
|
||||||
|
this.queue?.on(async (event: ConnectionEventUnion) => {
|
||||||
|
this.ignoreChange = true;
|
||||||
|
switch (event.type) {
|
||||||
|
case 'ADD_SONGS': {
|
||||||
|
await this.connection?.broadcast('ADD_SONGS', event.payload);
|
||||||
|
await this.connection?.broadcast('SYNC_QUEUE', undefined);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'REMOVE_SONG': {
|
||||||
|
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'MOVE_SONG': {
|
||||||
|
await this.connection?.broadcast('MOVE_SONG', event.payload);
|
||||||
|
await this.connection?.broadcast('SYNC_QUEUE', undefined);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SYNC_PROGRESS': {
|
||||||
|
if (this.permission === 'host-only') await this.connection?.broadcast('SYNC_QUEUE', undefined);
|
||||||
|
else await this.connection?.broadcast('SYNC_PROGRESS', event.payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
|
||||||
|
resolveIgnore = window.setTimeout(() => {
|
||||||
|
this.ignoreChange = false;
|
||||||
|
}, 16); // wait 1 frame
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
||||||
|
this.queue?.injection();
|
||||||
|
this.queue?.setOwner({
|
||||||
|
id: this.connection.id,
|
||||||
|
...this.me
|
||||||
|
});
|
||||||
|
|
||||||
|
const progress = Array.from(document.querySelectorAll<HTMLElement & {
|
||||||
|
_update: (...args: unknown[]) => void
|
||||||
|
}>('tp-yt-paper-progress'));
|
||||||
|
const rollbackList = progress.map((progress) => {
|
||||||
|
const original = progress._update;
|
||||||
|
progress._update = (...args) => {
|
||||||
|
const now = args[0];
|
||||||
|
|
||||||
|
if (this.permission === 'all' && typeof now === 'number') {
|
||||||
|
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
|
||||||
|
if (Math.abs(now - currentTime) > 3) this.connection?.broadcast('SYNC_PROGRESS', {
|
||||||
|
progress: now,
|
||||||
|
state: this.playerApi?.getPlayerState()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
original.call(progress, ...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
progress._update = original;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.rollbackInjector = () => {
|
||||||
|
rollbackList.forEach((rollback) => rollback());
|
||||||
|
};
|
||||||
|
|
||||||
|
this.connection.broadcast('IDENTIFY', {
|
||||||
|
profile: {
|
||||||
|
id: this.connection.id,
|
||||||
|
handleId: this.me.handleId,
|
||||||
|
name: this.me.name,
|
||||||
|
thumbnail: this.me.thumbnail
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.broadcast('SYNC_PROFILE', undefined);
|
||||||
|
this.connection.broadcast('PERMISSION', undefined);
|
||||||
|
|
||||||
|
this.queue?.clear();
|
||||||
|
this.queue?.syncQueueOwner();
|
||||||
|
this.queue?.initQueue();
|
||||||
|
|
||||||
|
this.connection.broadcast('SYNC_QUEUE', undefined);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onStop() {
|
||||||
|
this.connection?.disconnect();
|
||||||
|
this.queue?.rollbackInjection();
|
||||||
|
this.queue?.removeQueueOwner();
|
||||||
|
if (this.rollbackInjector) {
|
||||||
|
this.rollbackInjector();
|
||||||
|
this.rollbackInjector = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.profiles = {};
|
||||||
|
this.popups.host.setUsers(Object.values(this.profiles));
|
||||||
|
this.popups.guest.setUsers(Object.values(this.profiles));
|
||||||
|
|
||||||
|
this.popups.host.dismiss();
|
||||||
|
this.popups.guest.dismiss();
|
||||||
|
this.popups.setting.dismiss();
|
||||||
|
},
|
||||||
|
|
||||||
|
/* methods */
|
||||||
|
putProfile(id: string, profile?: Profile) {
|
||||||
|
if (profile === undefined) {
|
||||||
|
delete this.profiles[id];
|
||||||
|
} else {
|
||||||
|
this.profiles[id] = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.popups.host.setUsers(Object.values(this.profiles));
|
||||||
|
this.popups.guest.setUsers(Object.values(this.profiles));
|
||||||
|
},
|
||||||
|
|
||||||
|
showSpinner() {
|
||||||
|
this.elements.icon.style.setProperty('display', 'none');
|
||||||
|
this.elements.spinner.removeAttribute('hidden');
|
||||||
|
this.elements.spinner.setAttribute('active', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
hideSpinner() {
|
||||||
|
this.elements.icon.style.removeProperty('display');
|
||||||
|
this.elements.spinner.removeAttribute('active');
|
||||||
|
this.elements.spinner.setAttribute('hidden', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
initMyProfile() {
|
||||||
|
const accountButton = document.querySelector<HTMLElement & {
|
||||||
|
onButtonTap: () => void
|
||||||
|
}>('ytmusic-settings-button');
|
||||||
|
|
||||||
|
accountButton?.onButtonTap();
|
||||||
|
setTimeout(() => {
|
||||||
|
accountButton?.onButtonTap();
|
||||||
|
const renderer = document.querySelector<HTMLElement & { data: unknown }>('ytd-active-account-header-renderer');
|
||||||
|
if (!accountButton || !renderer) {
|
||||||
|
console.warn('Music Together: Cannot find account');
|
||||||
|
this.me = getDefaultProfile(this.connection?.id ?? '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountData = renderer.data as RawAccountData;
|
||||||
|
this.me = {
|
||||||
|
handleId: accountData.channelHandle.runs[0].text,
|
||||||
|
name: accountData.accountName.runs[0].text,
|
||||||
|
thumbnail: accountData.accountPhoto.thumbnails[0].url
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.me.thumbnail) {
|
||||||
|
this.popups.host.setProfile(this.me.thumbnail);
|
||||||
|
this.popups.guest.setProfile(this.me.thumbnail);
|
||||||
|
this.popups.setting.setProfile(this.me.thumbnail);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
/* hooks */
|
||||||
|
|
||||||
|
start({ ipc }) {
|
||||||
|
this.ipc = ipc;
|
||||||
|
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
|
||||||
|
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
|
||||||
|
|
||||||
|
/* setup */
|
||||||
|
document.querySelector('#right-content > ytmusic-settings-button')?.insertAdjacentHTML('beforebegin', settingHTML);
|
||||||
|
const setting = document.querySelector<HTMLElement>('#music-together-setting-button');
|
||||||
|
const icon = document.querySelector<SVGElement>('#music-together-setting-button > svg');
|
||||||
|
const spinner = document.querySelector<HTMLElement>('#music-together-setting-button > tp-yt-paper-spinner-lite');
|
||||||
|
if (!setting || !icon || !spinner) {
|
||||||
|
console.warn('Music Together: Cannot inject html');
|
||||||
|
console.log(setting, icon, spinner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.elements = {
|
||||||
|
setting,
|
||||||
|
icon,
|
||||||
|
spinner
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stateInterval = window.setInterval(() => {
|
||||||
|
if (this.connection?.mode !== 'host') return;
|
||||||
|
const index = this.queue?.selectedIndex ?? 0;
|
||||||
|
|
||||||
|
this.connection.broadcast('SYNC_PROGRESS', {
|
||||||
|
progress: this.playerApi?.getCurrentTime(),
|
||||||
|
state: this.playerApi?.getPlayerState(),
|
||||||
|
index
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
/* UI */
|
||||||
|
const hostPopup = createHostPopup({
|
||||||
|
onItemClick: (id) => {
|
||||||
|
if (id === 'music-together-close') {
|
||||||
|
this.onStop();
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.closed'));
|
||||||
|
hostPopup.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id === 'music-together-copy-id') {
|
||||||
|
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||||
|
.then(() => {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||||
|
hostPopup.dismiss();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||||
|
hostPopup.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id === 'music-together-permission') {
|
||||||
|
if (this.permission === 'all') this.permission = 'host-only';
|
||||||
|
else if (this.permission === 'playlist') this.permission = 'all';
|
||||||
|
else if (this.permission === 'host-only') this.permission = 'playlist';
|
||||||
|
this.connection?.broadcast('PERMISSION', this.permission);
|
||||||
|
|
||||||
|
hostPopup.setPermission(this.permission);
|
||||||
|
guestPopup.setPermission(this.permission);
|
||||||
|
settingPopup.setPermission(this.permission);
|
||||||
|
|
||||||
|
const permissionLabel = t(`plugins.music-together.menu.permission.${this.permission}`);
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.permission-changed', { permission: permissionLabel }));
|
||||||
|
const item = hostPopup.items.find((it) => it?.element.id === id);
|
||||||
|
if (item?.type === 'item') {
|
||||||
|
item.setText(t('plugins.music-together.menu.set-permission'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const guestPopup = createGuestPopup({
|
||||||
|
onItemClick: (id) => {
|
||||||
|
if (id === 'music-together-disconnect') {
|
||||||
|
this.onStop();
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.disconnected'));
|
||||||
|
guestPopup.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const settingPopup = createSettingPopup({
|
||||||
|
onItemClick: async (id) => {
|
||||||
|
if (id === 'music-together-host') {
|
||||||
|
settingPopup.dismiss();
|
||||||
|
this.showSpinner();
|
||||||
|
const result = await this.onHost();
|
||||||
|
this.hideSpinner();
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||||
|
.then(() => {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||||
|
hostPopup.showAtAnchor(setting);
|
||||||
|
}).catch(() => {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||||
|
hostPopup.showAtAnchor(setting);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.host-failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id === 'music-together-join') {
|
||||||
|
settingPopup.dismiss();
|
||||||
|
this.showSpinner();
|
||||||
|
const result = await this.onJoin();
|
||||||
|
this.hideSpinner();
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.joined'));
|
||||||
|
guestPopup.showAtAnchor(setting);
|
||||||
|
} else {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.join-failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.popups = {
|
||||||
|
host: hostPopup,
|
||||||
|
guest: guestPopup,
|
||||||
|
setting: settingPopup
|
||||||
|
};
|
||||||
|
setting.addEventListener('click', () => {
|
||||||
|
let popup = settingPopup;
|
||||||
|
if (this.connection?.mode === 'host') popup = hostPopup;
|
||||||
|
if (this.connection?.mode === 'guest') popup = guestPopup;
|
||||||
|
|
||||||
|
if (popup.isShowing()) popup.dismiss();
|
||||||
|
else popup.showAtAnchor(setting);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* account data getter */
|
||||||
|
this.initMyProfile();
|
||||||
|
},
|
||||||
|
onPlayerApiReady(playerApi) {
|
||||||
|
this.queue = new Queue({
|
||||||
|
owner: {
|
||||||
|
id: this.connection?.id ?? '',
|
||||||
|
...this.me!
|
||||||
|
},
|
||||||
|
getProfile: (id) => this.profiles[id]
|
||||||
|
});
|
||||||
|
this.playerApi = playerApi;
|
||||||
|
|
||||||
|
this.playerApi.addEventListener('onStateChange', this.videoStateChangeListener);
|
||||||
|
document.addEventListener('videodatachange', this.videoChangeListener);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
const dividers = Array.from(document.querySelectorAll('.music-together-divider'));
|
||||||
|
dividers.forEach((divider) => divider.remove());
|
||||||
|
|
||||||
|
this.elements.setting?.remove();
|
||||||
|
this.onStop();
|
||||||
|
if (typeof this.stateInterval === 'number') clearInterval(this.stateInterval);
|
||||||
|
if (this.playerApi) this.playerApi.removeEventListener('onStateChange', this.videoStateChangeListener);
|
||||||
|
if (this.videoChangeListener) document.removeEventListener('videodatachange', this.videoChangeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
37
src/plugins/music-together/queue/client.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { SHA1Hash } from './sha1hash';
|
||||||
|
|
||||||
|
export const extractToken = (cookie = document.cookie) => cookie.match(/SAPISID=([^;]+);/)?.[1] ?? cookie.match(/__Secure-3PAPISID=([^;]+);/)?.[1];
|
||||||
|
|
||||||
|
export const getHash = async (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') =>
|
||||||
|
(await SHA1Hash(`${millis} ${papisid} ${origin}`)).toLowerCase();
|
||||||
|
|
||||||
|
export const getAuthorizationHeader = async (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') => {
|
||||||
|
return `SAPISIDHASH ${millis}_${await getHash(papisid, millis, origin)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClient = () => {
|
||||||
|
return {
|
||||||
|
hl: navigator.language.split('-')[0] ?? 'en',
|
||||||
|
gl: navigator.language.split('-')[1] ?? 'US',
|
||||||
|
deviceMake: '',
|
||||||
|
deviceModel: '',
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
clientName: 'WEB_REMIX',
|
||||||
|
clientVersion: '1.20231208.05.02',
|
||||||
|
osName: '',
|
||||||
|
osVersion: '',
|
||||||
|
platform: 'DESKTOP',
|
||||||
|
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
locationInfo: {
|
||||||
|
locationPermissionAuthorizationStatus: 'LOCATION_PERMISSION_AUTHORIZATION_STATUS_UNSUPPORTED',
|
||||||
|
},
|
||||||
|
musicAppInfo: {
|
||||||
|
pwaInstallabilityStatus: 'PWA_INSTALLABILITY_STATUS_UNKNOWN',
|
||||||
|
webDisplayMode: 'WEB_DISPLAY_MODE_BROWSER',
|
||||||
|
storeDigitalGoodsApiSupportStatus: {
|
||||||
|
playStoreDigitalGoodsApiSupportStatus: 'DIGITAL_GOODS_API_SUPPORT_STATUS_UNSUPPORTED',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
utcOffsetMinutes: -1 * (new Date()).getTimezoneOffset(),
|
||||||
|
};
|
||||||
|
};
|
||||||
1
src/plugins/music-together/queue/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './queue';
|
||||||
474
src/plugins/music-together/queue/queue.ts
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
import { getMusicQueueRenderer } from './song';
|
||||||
|
import { mapQueueItem } from './utils';
|
||||||
|
|
||||||
|
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
||||||
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
|
import type { Profile, QueueAPI, VideoData } from '../types';
|
||||||
|
import type { QueueItem } from '@/types/datahost-get-state';
|
||||||
|
|
||||||
|
const getHeaderPayload = (() => {
|
||||||
|
let payload: {
|
||||||
|
items?: QueueItem[] | undefined;
|
||||||
|
title: {
|
||||||
|
runs: {
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
subtitle: {
|
||||||
|
runs: {
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
buttons: {
|
||||||
|
chipCloudChipRenderer: {
|
||||||
|
style: {
|
||||||
|
styleType: string;
|
||||||
|
};
|
||||||
|
text: {
|
||||||
|
runs: {
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
navigationEndpoint: {
|
||||||
|
saveQueueToPlaylistCommand: unknown;
|
||||||
|
};
|
||||||
|
icon: {
|
||||||
|
iconType: string;
|
||||||
|
};
|
||||||
|
accessibilityData: {
|
||||||
|
accessibilityData: {
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
isSelected: boolean;
|
||||||
|
uniqueId: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!payload) {
|
||||||
|
payload = {
|
||||||
|
title: {
|
||||||
|
runs: [
|
||||||
|
{
|
||||||
|
text: t('plugins.music-together.internal.track-source')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
runs: [
|
||||||
|
{
|
||||||
|
text: t('plugins.music-together.name')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
chipCloudChipRenderer: {
|
||||||
|
style: {
|
||||||
|
styleType: 'STYLE_TRANSPARENT'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
runs: [
|
||||||
|
{
|
||||||
|
text: t('plugins.music-together.internal.save')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
navigationEndpoint: {
|
||||||
|
saveQueueToPlaylistCommand: {}
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
iconType: 'ADD_TO_PLAYLIST'
|
||||||
|
},
|
||||||
|
accessibilityData: {
|
||||||
|
accessibilityData: {
|
||||||
|
label: t('plugins.music-together.internal.save')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isSelected: false,
|
||||||
|
uniqueId: t('plugins.music-together.internal.save')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
export type QueueOptions = {
|
||||||
|
videoList?: VideoData[];
|
||||||
|
owner?: Profile;
|
||||||
|
queue?: HTMLElement & QueueAPI;
|
||||||
|
getProfile: (id: string) => Profile | undefined;
|
||||||
|
}
|
||||||
|
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
||||||
|
|
||||||
|
export class Queue {
|
||||||
|
private queue: (HTMLElement & QueueAPI);
|
||||||
|
private originalDispatch?: (obj: {
|
||||||
|
type: string;
|
||||||
|
payload?: { items?: QueueItem[] | undefined; };
|
||||||
|
}) => void;
|
||||||
|
private internalDispatch = false;
|
||||||
|
private ignoreFlag = false;
|
||||||
|
private listeners: QueueEventListener[] = [];
|
||||||
|
private owner: Profile | null = null;
|
||||||
|
private getProfile: (id: string) => Profile | undefined;
|
||||||
|
|
||||||
|
constructor(options: QueueOptions) {
|
||||||
|
this.getProfile = options.getProfile;
|
||||||
|
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue')!;
|
||||||
|
this.owner = options.owner ?? null;
|
||||||
|
this._videoList = options.videoList ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _videoList: VideoData[] = [];
|
||||||
|
|
||||||
|
/* utils */
|
||||||
|
get videoList() {
|
||||||
|
return this._videoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedIndex() {
|
||||||
|
return mapQueueItem((it) => it?.selected, this.queue.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get rawItems() {
|
||||||
|
return this.queue?.store.getState().queue.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
get flatItems() {
|
||||||
|
return mapQueueItem((it) => it, this.rawItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOwner(owner: Profile) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public */
|
||||||
|
async setVideoList(videoList: VideoData[], sync = true) {
|
||||||
|
this._videoList = videoList;
|
||||||
|
|
||||||
|
if (sync) await this.syncVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addVideos(videos: VideoData[], index?: number) {
|
||||||
|
const response = await getMusicQueueRenderer(videos.map((it) => it.videoId));
|
||||||
|
if (!response) return false;
|
||||||
|
|
||||||
|
const items = response.queueDatas.map((it) => it?.content).filter(Boolean);
|
||||||
|
if (!items) return false;
|
||||||
|
|
||||||
|
this.internalDispatch = true;
|
||||||
|
this._videoList.push(...videos);
|
||||||
|
this.queue?.dispatch({
|
||||||
|
type: 'ADD_ITEMS',
|
||||||
|
payload: {
|
||||||
|
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
|
||||||
|
index: index ?? this.queue.store.getState().queue.items.length ?? 0,
|
||||||
|
items,
|
||||||
|
shuffleEnabled: false,
|
||||||
|
shouldAssignIds: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.internalDispatch = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initQueue();
|
||||||
|
this.syncQueueOwner();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeVideo(index: number) {
|
||||||
|
this.internalDispatch = true;
|
||||||
|
this._videoList.splice(index, 1);
|
||||||
|
this.queue?.dispatch({
|
||||||
|
type: 'REMOVE_ITEM',
|
||||||
|
payload: index
|
||||||
|
});
|
||||||
|
this.internalDispatch = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initQueue();
|
||||||
|
this.syncQueueOwner();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIndex(index: number) {
|
||||||
|
this.internalDispatch = true;
|
||||||
|
this.queue?.dispatch({
|
||||||
|
type: 'SET_INDEX',
|
||||||
|
payload: index
|
||||||
|
});
|
||||||
|
this.internalDispatch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveItem(fromIndex: number, toIndex: number) {
|
||||||
|
this.internalDispatch = true;
|
||||||
|
const data = this._videoList.splice(fromIndex, 1)[0];
|
||||||
|
this._videoList.splice(toIndex, 0, data);
|
||||||
|
this.queue?.dispatch({
|
||||||
|
type: 'MOVE_ITEM',
|
||||||
|
payload: {
|
||||||
|
fromIndex,
|
||||||
|
toIndex
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.internalDispatch = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initQueue();
|
||||||
|
this.syncQueueOwner();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.internalDispatch = true;
|
||||||
|
this._videoList = [];
|
||||||
|
this.queue?.dispatch({
|
||||||
|
type: 'CLEAR'
|
||||||
|
});
|
||||||
|
this.internalDispatch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(listener: QueueEventListener) {
|
||||||
|
this.listeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(listener: QueueEventListener) {
|
||||||
|
this.listeners = this.listeners.filter((it) => it !== listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
rollbackInjection() {
|
||||||
|
if (!this.queue) {
|
||||||
|
console.error('Queue is not initialized!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.originalDispatch) this.queue.store.dispatch = this.originalDispatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
injection() {
|
||||||
|
if (!this.queue) {
|
||||||
|
console.error('Queue is not initialized!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.originalDispatch = this.queue.store.dispatch;
|
||||||
|
this.queue.store.dispatch = (event) => {
|
||||||
|
if (!this.queue || !this.owner) {
|
||||||
|
console.error('Queue is not initialized!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.internalDispatch) {
|
||||||
|
if (event.type === 'CLEAR') {
|
||||||
|
this.ignoreFlag = true;
|
||||||
|
}
|
||||||
|
if (event.type === 'ADD_ITEMS') {
|
||||||
|
if (this.ignoreFlag) {
|
||||||
|
this.ignoreFlag = false;
|
||||||
|
const videoList = mapQueueItem((it) => ({
|
||||||
|
videoId: it!.videoId,
|
||||||
|
ownerId: this.owner!.id
|
||||||
|
} satisfies VideoData), event.payload!.items!);
|
||||||
|
const index = this._videoList.length + videoList.length - 1;
|
||||||
|
|
||||||
|
if (videoList.length > 0) {
|
||||||
|
this.broadcast({ // play
|
||||||
|
type: 'ADD_SONGS',
|
||||||
|
payload: {
|
||||||
|
videoList
|
||||||
|
},
|
||||||
|
after: [
|
||||||
|
{
|
||||||
|
type: 'SYNC_PROGRESS',
|
||||||
|
payload: {
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if ((event.payload as {
|
||||||
|
items: unknown[];
|
||||||
|
}).items.length === 1) {
|
||||||
|
this.broadcast({ // add playlist
|
||||||
|
type: 'ADD_SONGS',
|
||||||
|
payload: {
|
||||||
|
// index: (event.payload as any).index,
|
||||||
|
videoList: mapQueueItem((it) => ({
|
||||||
|
videoId: it!.videoId,
|
||||||
|
ownerId: this.owner!.id
|
||||||
|
} satisfies VideoData), event.payload!.items!)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'MOVE_ITEM') {
|
||||||
|
this.broadcast({
|
||||||
|
type: 'MOVE_SONG',
|
||||||
|
payload: {
|
||||||
|
fromIndex: (event.payload as {
|
||||||
|
fromIndex: number;
|
||||||
|
}).fromIndex,
|
||||||
|
toIndex: (event.payload as {
|
||||||
|
toIndex: number;
|
||||||
|
}).toIndex
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.type === 'REMOVE_ITEM') {
|
||||||
|
this.broadcast({
|
||||||
|
type: 'REMOVE_SONG',
|
||||||
|
payload: {
|
||||||
|
index: event.payload as number
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.type === 'SET_INDEX') {
|
||||||
|
this.broadcast({
|
||||||
|
type: 'SYNC_PROGRESS',
|
||||||
|
payload: {
|
||||||
|
index: event.payload as number
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'SET_HEADER') event.payload = getHeaderPayload();
|
||||||
|
if (event.type === 'ADD_STEERING_CHIPS') {
|
||||||
|
event.type = 'CLEAR_STEERING_CHIPS';
|
||||||
|
event.payload = undefined;
|
||||||
|
}
|
||||||
|
if (event.type === 'SET_PLAYER_UI_STATE') {
|
||||||
|
if (event.payload as string === 'INACTIVE' && this.videoList.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.type === 'HAS_SHOWN_AUTOPLAY') return;
|
||||||
|
if (event.type === 'ADD_AUTOMIX_ITEMS') return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeContext = {
|
||||||
|
...this.queue,
|
||||||
|
store: {
|
||||||
|
...this.queue.store,
|
||||||
|
dispatch: this.originalDispatch
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.originalDispatch?.call(fakeContext, event);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sync */
|
||||||
|
initQueue() {
|
||||||
|
if (!this.queue) return;
|
||||||
|
|
||||||
|
this.internalDispatch = true;
|
||||||
|
this.queue.dispatch({
|
||||||
|
type: 'HAS_SHOWN_AUTOPLAY',
|
||||||
|
payload: false
|
||||||
|
});
|
||||||
|
this.queue.dispatch({
|
||||||
|
type: 'SET_HEADER',
|
||||||
|
payload: getHeaderPayload(),
|
||||||
|
});
|
||||||
|
this.queue.dispatch({
|
||||||
|
type: 'CLEAR_STEERING_CHIPS'
|
||||||
|
});
|
||||||
|
this.internalDispatch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncVideo() {
|
||||||
|
const response = await getMusicQueueRenderer(this._videoList.map((it) => it.videoId));
|
||||||
|
if (!response) return false;
|
||||||
|
|
||||||
|
const items = response.queueDatas.map((it) => it.content);
|
||||||
|
|
||||||
|
this.internalDispatch = true;
|
||||||
|
this.queue?.dispatch({
|
||||||
|
type: 'UPDATE_ITEMS',
|
||||||
|
payload: {
|
||||||
|
items: items,
|
||||||
|
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
|
||||||
|
shouldAssignIds: true,
|
||||||
|
currentIndex: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.internalDispatch = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initQueue();
|
||||||
|
this.syncQueueOwner();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncQueueOwner() {
|
||||||
|
const allQueue = document.querySelectorAll('#queue');
|
||||||
|
|
||||||
|
allQueue.forEach((queue) => {
|
||||||
|
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
|
||||||
|
|
||||||
|
list.forEach((item, index: number | undefined) => {
|
||||||
|
if (typeof index !== 'number') return;
|
||||||
|
|
||||||
|
const id = this._videoList[index]?.ownerId;
|
||||||
|
const data = this.getProfile(id);
|
||||||
|
|
||||||
|
const profile = item.querySelector<HTMLImageElement>('.music-together-owner') ?? document.createElement('img');
|
||||||
|
profile.classList.add('music-together-owner');
|
||||||
|
profile.dataset.id = id;
|
||||||
|
profile.dataset.index = index.toString();
|
||||||
|
|
||||||
|
const name = item.querySelector<HTMLElement>('.music-together-name') ?? document.createElement('div');
|
||||||
|
name.classList.add('music-together-name');
|
||||||
|
name.textContent = data?.name ?? t('plugins.music-together.internal.unknown-user');
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
profile.dataset.thumbnail = data.thumbnail ?? '';
|
||||||
|
profile.dataset.name = data.name ?? '';
|
||||||
|
profile.dataset.handleId = data.handleId ?? '';
|
||||||
|
profile.dataset.id = data.id ?? '';
|
||||||
|
|
||||||
|
profile.src = data.thumbnail ?? '';
|
||||||
|
profile.title = data.name ?? '';
|
||||||
|
profile.alt = data.handleId ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!profile.isConnected) item.append(profile);
|
||||||
|
if (!name.isConnected) item.append(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeQueueOwner() {
|
||||||
|
const allQueue = document.querySelectorAll('#queue');
|
||||||
|
|
||||||
|
allQueue.forEach((queue) => {
|
||||||
|
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
|
||||||
|
|
||||||
|
list.forEach((item) => {
|
||||||
|
const profile = item.querySelector<HTMLImageElement>('.music-together-owner');
|
||||||
|
const name = item.querySelector<HTMLElement>('.music-together-name');
|
||||||
|
profile?.remove();
|
||||||
|
name?.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* private */
|
||||||
|
private broadcast(event: ConnectionEventUnion) {
|
||||||
|
this.listeners.forEach((listener) => listener(event));
|
||||||
|
}
|
||||||
|
}
|
||||||