mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 10:11:46 +00:00
Compare commits
403 Commits
v3.2.0
...
feat/webno
| Author | SHA1 | Date | |
|---|---|---|---|
| 22b74113b6 | |||
| 9da3ad2fb7 | |||
| d45d597136 | |||
| 2495d5da99 | |||
| 33aeafd19c | |||
| 374d0ce5e7 | |||
| 371805334b | |||
| 47dbeff0d0 | |||
| 17652b5b77 | |||
| 9608c2a7fc | |||
| 8abe2823d7 | |||
| dbc7f23ab8 | |||
| 357bd935e4 | |||
| f99ca53a6d | |||
| 8700c1a110 | |||
| c5e37b791c | |||
| 307f6387ab | |||
| 652a150a0a | |||
| 2c59badb46 | |||
| 69087bbf1f | |||
| af78f1596a | |||
| fca936a698 | |||
| 54b70f6b3e | |||
| 62f7d440fa | |||
| 752ccbf482 | |||
| a8bc53912d | |||
| ed700c2916 | |||
| 97695444af | |||
| 85e5e1814a | |||
| 88c84a50d0 | |||
| 0004fb3fc8 | |||
| 9cbaf5797b | |||
| 1df75ae82f | |||
| 4d86af5437 | |||
| ba0876fd8b | |||
| a3a3fca694 | |||
| de4396936d | |||
| 164575296f | |||
| 7e8cbfc4c0 | |||
| 679938ccf7 | |||
| 5a6d681bf4 | |||
| 62304b723e | |||
| c89bb4606f | |||
| 14c50e0d57 | |||
| f7e9cf9a29 | |||
| 4bb3f41828 | |||
| e4759ebe25 | |||
| ce33a92f02 | |||
| dd44c07450 | |||
| 15a5b7a820 | |||
| 41b7e095eb | |||
| f34a297fcf | |||
| 9b2c1a320b | |||
| 70ed6f8e6c | |||
| 5a2489f0bf | |||
| a7d035022a | |||
| b879a70b24 | |||
| ec4e9a1d47 | |||
| d9c52c0a7f | |||
| 81ecf18231 | |||
| b0b12a075d | |||
| 54c428083c | |||
| 60228a387a | |||
| 3e04baef00 | |||
| 573bcba1a0 | |||
| ae2fad5db3 | |||
| c8fc12569c | |||
| df8efe3fa4 | |||
| 251131b9b5 | |||
| 35fb61087a | |||
| cbec88ff47 | |||
| f4319616a6 | |||
| 1534b7a67f | |||
| cbfcc9d140 | |||
| 60d10a9222 | |||
| 17e090d5c5 | |||
| fa6b4fa83b | |||
| e4b5244d95 | |||
| 04dc5d6314 | |||
| 33ccb03f90 | |||
| 80011ed3aa | |||
| 3e43cf5959 | |||
| bbd590dde8 | |||
| 0975a951e4 | |||
| 8b78f227a7 | |||
| 5a93a04b61 | |||
| b971eb4191 | |||
| 403e825b8d | |||
| 9164eba88c | |||
| bb83bbac38 | |||
| 651a641b22 | |||
| 441b5fc8dd | |||
| 4fba9445d1 | |||
| 3fb5e01ca5 | |||
| 09b2b0d507 | |||
| a9be35481a | |||
| c871506a69 | |||
| 8e6790d366 | |||
| 46620c5ec9 | |||
| 5f090169da | |||
| 2205150b86 | |||
| 3c4bb8a8fc | |||
| 77d0e71529 | |||
| cf974e2d62 | |||
| ee03db4745 | |||
| efd2061058 | |||
| e7ed20f62f | |||
| 36d4c08a56 | |||
| 2a939e615c | |||
| 9825165286 | |||
| 55c934ac7c | |||
| c7715115ee | |||
| e93b5e8135 | |||
| fd6ba1eda1 | |||
| 34f5411aec | |||
| 2b74ec2ef8 | |||
| 14dd0e8e03 | |||
| b0156261b7 | |||
| 05d520f1c1 | |||
| ccd44c79e8 | |||
| 7be48ab05e | |||
| 2d40b410b5 | |||
| f54df86eec | |||
| 1be476de54 | |||
| 82fa8719a9 | |||
| 7600620c4a | |||
| c5217f3e1e | |||
| 706279852a | |||
| 3a6274504f | |||
| 3aa9398481 | |||
| 7cca435b1d | |||
| 241b5800d1 | |||
| 8abb8acdd3 | |||
| ef8226c091 | |||
| 7484e1bf9a | |||
| 2919fd54b7 | |||
| 9eeb1c986a | |||
| d37cd2418c | |||
| 8bd05f525d | |||
| 47b23b414c | |||
| 6f70d179c7 | |||
| 62a86e9267 | |||
| 6358a2d0b1 | |||
| 273633c2ce | |||
| 8b1209ef73 | |||
| 47505e9748 | |||
| 5178cc6bd8 | |||
| d9a27fff42 | |||
| 9e6560b814 | |||
| afdb19a742 | |||
| 0ae5b668f5 | |||
| 10533e28fa | |||
| 6189e67819 | |||
| f9ad505e40 | |||
| 9b011101ed | |||
| a6ed8bf3aa | |||
| 87acf4cf04 | |||
| b6fe2afd75 | |||
| 6d9bb8eb1c | |||
| 192fd0620f | |||
| 00d0b31980 | |||
| 5edb2131d2 | |||
| 4657aeca45 | |||
| 9f5651a8ba | |||
| 570dcfee29 | |||
| 2c130ce80d | |||
| 8457115105 | |||
| fbc02a494a | |||
| 7e2c254ecf | |||
| 11936a889e | |||
| f33970addd | |||
| 2c02b7193d | |||
| 3f80b598ae | |||
| 477068ed49 | |||
| b4083874ac | |||
| c5d0673b2f | |||
| 9ccc44474b | |||
| 98e341f122 | |||
| b23eba51dd | |||
| 980005a58c | |||
| aba58b1d34 | |||
| bb6a127d22 | |||
| ac6e30a6b6 | |||
| 3f23282eed | |||
| 199a77819c | |||
| 89a53b2854 | |||
| c57bf79b08 | |||
| 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 |
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -26,9 +26,10 @@ body:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Are you using the portable version of the YouTube Music Application?
|
||||
label: Checklists
|
||||
options:
|
||||
- label: I use the portable version of the YouTube Music Application.
|
||||
- label: I can reproduce this issue in the [official version of (WEB) YTM](https://music.youtube.com).
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What operating system are you using?
|
||||
@ -49,7 +50,7 @@ body:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What arch are you using?
|
||||
label: What CPU architecture are you using?
|
||||
options:
|
||||
- x64
|
||||
- ia32
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -15,7 +15,7 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
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:
|
||||
required: true
|
||||
- type: textarea
|
||||
@ -33,6 +33,6 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Add any other context about the problem here.
|
||||
description: Any other context about the problem.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@ -17,4 +17,4 @@ jobs:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v4
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
|
||||
240
README.md
240
README.md
@ -1,7 +1,7 @@
|
||||
# YouTube Music
|
||||
|
||||
<div align="center">
|
||||
|
||||
# YouTube Music
|
||||
|
||||
[](https://github.com/th-ch/youtube-music/releases/)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
||||
@ -14,13 +14,14 @@
|
||||
|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/th-ch/youtube-music/releases/latest">
|
||||
<img src="web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md)
|
||||
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md)
|
||||
|
||||
**Electron wrapper around YouTube Music featuring:**
|
||||
|
||||
@ -28,6 +29,136 @@ 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
|
||||
one click
|
||||
|
||||
## Demo Image
|
||||
|
||||
| Player Screen (album color theme & ambient light) |
|
||||
|:---------------------------------------------------------------------------------------------------------:|
|
||||
||
|
||||
|
||||
## Content
|
||||
|
||||
- [Features](#features)
|
||||
- [Available plugins](#available-plugins)
|
||||
- [Translation](#translation)
|
||||
- [Download](#download)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [MacOS](#macos)
|
||||
- [Windows](#windows)
|
||||
- [How to install without a network connection? (in Windows)](#how-to-install-without-a-network-connection-in-windows)
|
||||
- [Themes](#themes)
|
||||
- [Dev](#dev)
|
||||
- [Build your own plugins](#build-your-own-plugins)
|
||||
- [Creating a plugin](#creating-a-plugin)
|
||||
- [Common use cases](#common-use-cases)
|
||||
- [Build](#build)
|
||||
- [Production Preview](#production-preview)
|
||||
- [Tests](#tests)
|
||||
- [License](#license)
|
||||
- [FAQ](#faq)
|
||||
|
||||
## Features:
|
||||
|
||||
- **Auto confirm when paused** (Always Enabled): disable
|
||||
the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
|
||||
popup that pause music after a certain time
|
||||
|
||||
- And more ...
|
||||
|
||||
## Available plugins:
|
||||
|
||||
- **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
|
||||
|
||||
- **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
|
||||
volume of the softest parts)
|
||||
|
||||
- **Blur Navigation Bar**: makes navigation bar transparent and blurry
|
||||
|
||||
- **Bypass Age Restrictions**: bypass YouTube's age verification
|
||||
|
||||
- **Captions Selector**: Enable captions
|
||||
|
||||
- **Compact Sidebar**: Always set the sidebar in compact mode
|
||||
|
||||
- **Crossfade**: Crossfade between songs
|
||||
|
||||
- **Disable Autoplay**: Makes every song start in "paused" mode
|
||||
|
||||
- **[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)
|
||||
|
||||
- **Downloader**: downloads
|
||||
MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||
|
||||
- **Exponential Volume**: Makes the volume
|
||||
slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to
|
||||
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)
|
||||
|
||||
> (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)
|
||||
|
||||
- **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
|
||||
|
||||
- **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
|
||||
|
||||
- **No Google Login**: Remove Google login buttons and links from the interface
|
||||
|
||||
- **Notifications**: Display a notification when a song starts
|
||||
playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
|
||||
are available on windows)
|
||||
|
||||
- **Picture-in-picture**: allows to switch the app to picture-in-picture mode
|
||||
|
||||
- **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)
|
||||
|
||||
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume
|
||||
steps
|
||||
|
||||
- **Shortcuts (& MPRIS)**: 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)
|
||||
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)
|
||||
for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||
|
||||
- **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
|
||||
parts of music videos where the song isn't playing
|
||||
|
||||
- **Taskbar Media Control**: Control playback from
|
||||
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||
|
||||
- **TouchBar**: Custom TouchBar layout for macOS
|
||||
|
||||
- **Tuna OBS**: Integration with [OBS](https://obsproject.com/)'s
|
||||
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
|
||||
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
|
||||
|
||||
- **Visualizer**: Different music visualizers
|
||||
|
||||
## Translation
|
||||
|
||||
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
||||
@ -44,7 +175,7 @@ latest version.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Install the `youtube-music-bin` package from the AUR. For AUR installation instructions, take a look at
|
||||
Install the [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) package from the AUR. For AUR installation instructions, take a look at
|
||||
this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||
|
||||
### MacOS
|
||||
@ -92,103 +223,6 @@ winget install th-ch.YouTubeMusic
|
||||
- Place them in the **same directory**.
|
||||
- Run the installer.
|
||||
|
||||
## Features:
|
||||
|
||||
- **Auto confirm when paused** (Always Enabled): disable
|
||||
the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
|
||||
popup that pause music after a certain time
|
||||
|
||||
- And more ...
|
||||
|
||||
## Available plugins:
|
||||
|
||||
- **Ad Blocker**: Block all ads and tracking out of the box
|
||||
|
||||
- **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.
|
||||
|
||||
- **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the
|
||||
volume of the softest parts)
|
||||
|
||||
- **Blur Nav Bar**: makes navigation bar transparent and blurry
|
||||
|
||||
- **Bypass age restrictions**: bypass YouTube's age verification
|
||||
|
||||
- **Captions selector**: Enable captions
|
||||
|
||||
- **Compact sidebar**: Always set the sidebar in compact mode
|
||||
|
||||
- **Crossfade**: Crossfade between songs
|
||||
|
||||
- **Disable Autoplay**: Makes every song start in "paused" mode
|
||||
|
||||
- [**Discord**](https://discord.com/): Show your friends what you listen to
|
||||
with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||
|
||||
- **Downloader**: downloads
|
||||
MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||
|
||||
- **Exponential Volume**: Makes the volume
|
||||
slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to
|
||||
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)
|
||||
|
||||
> (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)
|
||||
|
||||
- [**Last.fm**](https://www.last.fm/): Scrobbles support
|
||||
|
||||
- **Lumia Stream**: Adds [Lumia Stream](https://lumiastream.com/) support
|
||||
|
||||
- **Lyrics Genius**: Adds lyrics support for most songs
|
||||
|
||||
- **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
|
||||
|
||||
- **Notifications**: Display a notification when a song starts
|
||||
playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
|
||||
are available on windows)
|
||||
|
||||
- **Picture in picture**: allows to switch the app to picture-in-picture mode
|
||||
|
||||
- **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)
|
||||
|
||||
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume
|
||||
steps
|
||||
|
||||
- **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
|
||||
|
||||
- **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)
|
||||
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)
|
||||
for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||
|
||||
- **Skip-Silences** - Automatically skip silenced sections
|
||||
|
||||
- [**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
|
||||
|
||||
- **Taskbar Media Control**: Control playback from
|
||||
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||
|
||||
- **Touchbar**: Custom TouchBar layout for macOS
|
||||
|
||||
- **Tuna-OBS**: Integration with [OBS](https://obsproject.com/)'s
|
||||
plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
|
||||
|
||||
- **Video Toggle**: Adds
|
||||
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
|
||||
|
||||
- **Visualizer**: Different music visualizers
|
||||
|
||||
## Themes
|
||||
|
||||
You can load CSS files to change the look of the application (Options > Visual Tweaks > Themes).
|
||||
@ -213,7 +247,7 @@ Using plugins, you can:
|
||||
|
||||
### 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
|
||||
```typescript
|
||||
@ -355,7 +389,7 @@ Uses [Playwright](https://playwright.dev/) to test the app.
|
||||
|
||||
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||
|
||||
## Most asked questions
|
||||
## FAQ
|
||||
|
||||
### Why apps menu isn't showing up?
|
||||
|
||||
|
||||
BIN
assets/youtube-music-tray-paused.png
Normal file
BIN
assets/youtube-music-tray-paused.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
255
changelog.md
255
changelog.md
@ -2,8 +2,263 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||
|
||||
#### [v3.3.5](https://github.com/th-ch/youtube-music/compare/v3.3.4...v3.3.5)
|
||||
|
||||
- chore(deps): update dependency node-gyp to v10.1.0 [`#1885`](https://github.com/th-ch/youtube-music/pull/1885)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.4.0 [`#1886`](https://github.com/th-ch/youtube-music/pull/1886)
|
||||
- chore(deps): update dependency vite to v5.2.6 [`#1883`](https://github.com/th-ch/youtube-music/pull/1883)
|
||||
- fix(style): resolve #1887 [`#1887`](https://github.com/th-ch/youtube-music/issues/1887)
|
||||
- chore(i18n): Translated using Weblate (Swedish) [`69087bb`](https://github.com/th-ch/youtube-music/commit/69087bbf1fac1ba58e992146deb1d6f1706b1e3c)
|
||||
- chore(i18n): Translated using Weblate (French) [`af78f15`](https://github.com/th-ch/youtube-music/commit/af78f1596ab8db2fa7069fdb1c4f078099ce4446)
|
||||
- Update changelog for v3.3.4 [`62f7d44`](https://github.com/th-ch/youtube-music/commit/62f7d440fab5bdbe9f49a3a5f8c32e7aaf2f28f6)
|
||||
|
||||
#### [v3.3.4](https://github.com/th-ch/youtube-music/compare/v3.3.3...v3.3.4)
|
||||
|
||||
> 24 March 2024
|
||||
|
||||
- Update changelog for v3.3.3 [`9769544`](https://github.com/th-ch/youtube-music/commit/97695444affbacb71dd73ae7107d4c987e285a37)
|
||||
- fix(style): fix fullscreen style and in-app-menu [`ed700c2`](https://github.com/th-ch/youtube-music/commit/ed700c2916cc7e6ccd2010d0c552364af116eb4f)
|
||||
- fix(style): fix miniplayer style [`a8bc539`](https://github.com/th-ch/youtube-music/commit/a8bc53912d1f4137008ecb2d9d5d9d9eb06ee2a8)
|
||||
|
||||
#### [v3.3.3](https://github.com/th-ch/youtube-music/compare/v3.3.2...v3.3.3)
|
||||
|
||||
> 24 March 2024
|
||||
|
||||
- chore(deps): update dependency electron to v29.1.5 [`#1876`](https://github.com/th-ch/youtube-music/pull/1876)
|
||||
- chore(deps): update dependency typescript to v5.4.3 [`#1877`](https://github.com/th-ch/youtube-music/pull/1877)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.76 [`#1878`](https://github.com/th-ch/youtube-music/pull/1878)
|
||||
- chore(deps): update dependency vite to v5.2.4 [`#1881`](https://github.com/th-ch/youtube-music/pull/1881)
|
||||
- Ambient Plugin cleanup [`#1880`](https://github.com/th-ch/youtube-music/pull/1880)
|
||||
- chore(deps): update dependency vite to v5.2.2 [`#1875`](https://github.com/th-ch/youtube-music/pull/1875)
|
||||
- fix(deps): update dependency solid-js to v1.8.16 [`#1873`](https://github.com/th-ch/youtube-music/pull/1873)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.3.1 [`#1868`](https://github.com/th-ch/youtube-music/pull/1868)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.75 [`#1867`](https://github.com/th-ch/youtube-music/pull/1867)
|
||||
- chore(deps): update pnpm to v8.15.5 [`#1865`](https://github.com/th-ch/youtube-music/pull/1865)
|
||||
- fix: Fix Miniplayer image size [`#1863`](https://github.com/th-ch/youtube-music/pull/1863)
|
||||
- fix(style): fixed image/video alignment when toggle is active [`#1862`](https://github.com/th-ch/youtube-music/pull/1862)
|
||||
- chore: Update README-is.md [`#1858`](https://github.com/th-ch/youtube-music/pull/1858)
|
||||
- chore(deps): update dependency vite-plugin-solid to v2.10.2 [`#1859`](https://github.com/th-ch/youtube-music/pull/1859)
|
||||
- fix: Ambient Mode intialization improvement [`#1857`](https://github.com/th-ch/youtube-music/pull/1857)
|
||||
- chore(deps): bump follow-redirects from 1.15.5 to 1.15.6 [`#1856`](https://github.com/th-ch/youtube-music/pull/1856)
|
||||
- chore(README): Nicer Readme 2.0 [`#1833`](https://github.com/th-ch/youtube-music/pull/1833)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.74 [`#1854`](https://github.com/th-ch/youtube-music/pull/1854)
|
||||
- chore(deps): update dependency esbuild to v0.20.2 [`#1855`](https://github.com/th-ch/youtube-music/pull/1855)
|
||||
- Improve ambient mode [`#1853`](https://github.com/th-ch/youtube-music/pull/1853)
|
||||
- chore(deps): update dependency electron to v29.1.4 [`#1852`](https://github.com/th-ch/youtube-music/pull/1852)
|
||||
- chore(deps): update dependency electron to v29.1.3 [`#1851`](https://github.com/th-ch/youtube-music/pull/1851)
|
||||
- chore(deps): update dependency rollup to v4.13.0 [`#1850`](https://github.com/th-ch/youtube-music/pull/1850)
|
||||
- fix(deps): update dependency electron-store to v8.2.0 [`#1843`](https://github.com/th-ch/youtube-music/pull/1843)
|
||||
- chore(deps): update dependency electron to v29.1.1 [`#1841`](https://github.com/th-ch/youtube-music/pull/1841)
|
||||
- fix(deps): update dependency i18next to v23.10.1 [`#1842`](https://github.com/th-ch/youtube-music/pull/1842)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.2.0 [`#1848`](https://github.com/th-ch/youtube-music/pull/1848)
|
||||
- chore(deps): update dependency vite to v5.1.6 [`#1847`](https://github.com/th-ch/youtube-music/pull/1847)
|
||||
- fix(deps): update dependency async-mutex to v0.5.0 [`#1849`](https://github.com/th-ch/youtube-music/pull/1849)
|
||||
- fix(deps): update dependency ts-morph to v22 [`#1846`](https://github.com/th-ch/youtube-music/pull/1846)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.73 [`#1840`](https://github.com/th-ch/youtube-music/pull/1840)
|
||||
- chore(deps): update dependency rollup to v4.12.1 [`#1837`](https://github.com/th-ch/youtube-music/pull/1837)
|
||||
- chore: Changed a single word (README-is.md) [`#1836`](https://github.com/th-ch/youtube-music/pull/1836)
|
||||
- chore(deps): update dependency typescript to v5.4.2 [`#1838`](https://github.com/th-ch/youtube-music/pull/1838)
|
||||
- chore(deps): update dependency electron-vite to v2.1.0 [`#1823`](https://github.com/th-ch/youtube-music/pull/1823)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.1 [`#1829`](https://github.com/th-ch/youtube-music/pull/1829)
|
||||
- chore(deps): update dependency vite to v5.1.5 [`#1831`](https://github.com/th-ch/youtube-music/pull/1831)
|
||||
- Revert "chore(deps): update dependency electron-builder to v24.13.3" [`#1818`](https://github.com/th-ch/youtube-music/pull/1818)
|
||||
- chore(deps): update dependency electron-builder to v24.13.3 [`#1774`](https://github.com/th-ch/youtube-music/pull/1774)
|
||||
- chore(deps): update playwright monorepo to v1.42.1 [`#1816`](https://github.com/th-ch/youtube-music/pull/1816)
|
||||
- fix: Add scale ratio for tray icons [`#1811`](https://github.com/th-ch/youtube-music/pull/1811)
|
||||
- Icelandic translation of the readme file [`#1806`](https://github.com/th-ch/youtube-music/pull/1806)
|
||||
- chore(deps): update dependency electron to v29.1.0 [`#1808`](https://github.com/th-ch/youtube-music/pull/1808)
|
||||
- chore(deps): update playwright monorepo to v1.42.0 [`#1805`](https://github.com/th-ch/youtube-music/pull/1805)
|
||||
- chore(deps): update dependency eslint to v8.57.0 [`#1793`](https://github.com/th-ch/youtube-music/pull/1793)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.0 [`#1800`](https://github.com/th-ch/youtube-music/pull/1800)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.71 [`#1799`](https://github.com/th-ch/youtube-music/pull/1799)
|
||||
- chore(deps): update pnpm to v8.15.4 [`#1795`](https://github.com/th-ch/youtube-music/pull/1795)
|
||||
- chore(deps): update dependency @types/semver to v7.5.8 [`#1797`](https://github.com/th-ch/youtube-music/pull/1797)
|
||||
- fix: center the pause icon [`#1786`](https://github.com/th-ch/youtube-music/pull/1786)
|
||||
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.16 [`#1788`](https://github.com/th-ch/youtube-music/pull/1788)
|
||||
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.16 [`#1789`](https://github.com/th-ch/youtube-music/pull/1789)
|
||||
- fix(deps): update dependency youtubei.js to v9.1.0 [`#1790`](https://github.com/th-ch/youtube-music/pull/1790)
|
||||
- fix(deps): update dependency i18next to v23.10.0 [`#1785`](https://github.com/th-ch/youtube-music/pull/1785)
|
||||
- chore(deps): update dependency electron to v29 [`#1773`](https://github.com/th-ch/youtube-music/pull/1773)
|
||||
- chore(deps): update dependency vite to v5.1.4 [`#1778`](https://github.com/th-ch/youtube-music/pull/1778)
|
||||
- chore(deps): bump ip from 2.0.0 to 2.0.1 [`#1777`](https://github.com/th-ch/youtube-music/pull/1777)
|
||||
- fix: add support for Wayland [`#1864`](https://github.com/th-ch/youtube-music/issues/1864)
|
||||
- fix(style): fix navigation bar items are not working [`#1381`](https://github.com/th-ch/youtube-music/issues/1381) [`#1396`](https://github.com/th-ch/youtube-music/issues/1396) [`#1649`](https://github.com/th-ch/youtube-music/issues/1649)
|
||||
- fix(ytm-bugs): fixed a `scrollbar-color` bug that affected Chromium 121 and later [`#1737`](https://github.com/th-ch/youtube-music/issues/1737)
|
||||
- chore(i18n): Translated using Weblate (Icelandic) [`82fa871`](https://github.com/th-ch/youtube-music/commit/82fa8719a96abdfaaa8548a0077f4db2164ec09b)
|
||||
- chore(i18n): Translated using Weblate (Romanian) [`c871506`](https://github.com/th-ch/youtube-music/commit/c871506a69180308ab4fc587b6e8a33f193087e8)
|
||||
- chore(i18n): Translated using Weblate (Thai) [`a7d0350`](https://github.com/th-ch/youtube-music/commit/a7d035022a229f0b245694d1fc7a484befe1c269)
|
||||
|
||||
#### [v3.3.2](https://github.com/th-ch/youtube-music/compare/v3.3.1...v3.3.2)
|
||||
|
||||
> 20 February 2024
|
||||
|
||||
- fix: fix bugs in MPRIS, and improve MPRIS [`#1760`](https://github.com/th-ch/youtube-music/pull/1760)
|
||||
- fix(deps): update dependency electron-updater to v6.1.8 [`#1770`](https://github.com/th-ch/youtube-music/pull/1770)
|
||||
- chore(deps): update dependency electron-builder to v24.12.0 [`#1771`](https://github.com/th-ch/youtube-music/pull/1771)
|
||||
- feat(scrobblers): use `BrowserWindow` instead of `shell.openExternal` [`#1758`](https://github.com/th-ch/youtube-music/pull/1758)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.0.2 [`#1763`](https://github.com/th-ch/youtube-music/pull/1763)
|
||||
- chore(deps): update dependency esbuild to v0.20.1 [`#1759`](https://github.com/th-ch/youtube-music/pull/1759)
|
||||
- fix(deps): update dependency i18next to v23.9.0 [`#1754`](https://github.com/th-ch/youtube-music/pull/1754)
|
||||
- fix: fixed an issue that caused infinite loops when using Music Together [`#1752`](https://github.com/th-ch/youtube-music/issues/1752)
|
||||
- chore(deps): rollback dependency electron-builder to v24.9.1 [`8bd05f5`](https://github.com/th-ch/youtube-music/commit/8bd05f525df98671f0a516b159cccab302b7ae99)
|
||||
- chore(deps): update dependency electron-builder to v24.13.1 [`47b23b4`](https://github.com/th-ch/youtube-music/commit/47b23b414c8feb25c4d9a23d6adb7cbf1ac818fb)
|
||||
- chore(i18n): Translated using Weblate (German) [`47505e9`](https://github.com/th-ch/youtube-music/commit/47505e97482f9e953ee451b968d0950585616ffa)
|
||||
|
||||
#### [v3.3.1](https://github.com/th-ch/youtube-music/compare/v3.3.0...v3.3.1)
|
||||
|
||||
> 18 February 2024
|
||||
|
||||
- Update changelog for v3.3.0 [`6d9bb8e`](https://github.com/th-ch/youtube-music/commit/6d9bb8eb1cc2d892a5552ffb1f7c20859aa80f67)
|
||||
- hotfix: in-app-menu position issue [`87acf4c`](https://github.com/th-ch/youtube-music/commit/87acf4cf042ba32a000a4aeaec5c17c93501d333)
|
||||
- release 3.3.1 (HOTFIX) [`a6ed8bf`](https://github.com/th-ch/youtube-music/commit/a6ed8bf3aa20ca8e950e85d88f981ccf9edc7498)
|
||||
|
||||
#### [v3.3.0](https://github.com/th-ch/youtube-music/compare/v3.2.2...v3.3.0)
|
||||
|
||||
> 18 February 2024
|
||||
|
||||
- fix(deps): update dependency i18next to v23.8.3 [`#1751`](https://github.com/th-ch/youtube-music/pull/1751)
|
||||
- import fixed ./constants [`#1748`](https://github.com/th-ch/youtube-music/pull/1748)
|
||||
- chore(deps): update dependency rollup to v4.12.0 [`#1743`](https://github.com/th-ch/youtube-music/pull/1743)
|
||||
- chore(deps): bump undici from 5.28.2 to 5.28.3 [`#1747`](https://github.com/th-ch/youtube-music/pull/1747)
|
||||
- chore(deps): update dependency vite to v5.1.3 [`#1742`](https://github.com/th-ch/youtube-music/pull/1742)
|
||||
- chore(deps): update dependency vite-plugin-solid to v2.10.1 [`#1734`](https://github.com/th-ch/youtube-music/pull/1734)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.70 [`#1740`](https://github.com/th-ch/youtube-music/pull/1740)
|
||||
- chore(deps): update dependency electron to v28.2.3 [`#1736`](https://github.com/th-ch/youtube-music/pull/1736)
|
||||
- chore(deps): update pnpm to v8.15.3 [`#1739`](https://github.com/th-ch/youtube-music/pull/1739)
|
||||
- chore(deps): update dependency rollup to v4.11.0 [`#1738`](https://github.com/th-ch/youtube-music/pull/1738)
|
||||
- fix(deps): update dependency solid-js to v1.8.15 [`#1735`](https://github.com/th-ch/youtube-music/pull/1735)
|
||||
- chore(deps): update dependency vite to v5.1.2 [`#1733`](https://github.com/th-ch/youtube-music/pull/1733)
|
||||
- chore(deps): update dependency vite-plugin-solid to v2.10.0 [`#1732`](https://github.com/th-ch/youtube-music/pull/1732)
|
||||
- chore(deps): update pnpm to v8.15.2 [`#1729`](https://github.com/th-ch/youtube-music/pull/1729)
|
||||
- Update Copyright - 2024 [`#1730`](https://github.com/th-ch/youtube-music/pull/1730)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7 [`#1728`](https://github.com/th-ch/youtube-music/pull/1728)
|
||||
- fix(deps): update dependency @floating-ui/dom to v1.6.3 [`#1727`](https://github.com/th-ch/youtube-music/pull/1727)
|
||||
- chore(deps): update dependency electron to v28.2.2 [`#1717`](https://github.com/th-ch/youtube-music/pull/1717)
|
||||
- chore(deps): update dependency vite to v5.1.1 [`#1718`](https://github.com/th-ch/youtube-music/pull/1718)
|
||||
- chore(deps): update dependency @types/semver to v7.5.7 [`#1724`](https://github.com/th-ch/youtube-music/pull/1724)
|
||||
- fix(deps): update dependency @floating-ui/dom to v1.6.2 [`#1725`](https://github.com/th-ch/youtube-music/pull/1725)
|
||||
- chore(deps): update dependency rollup to v4.10.0 [`#1719`](https://github.com/th-ch/youtube-music/pull/1719)
|
||||
- fix(deps): update dependency solid-js to v1.8.14 [`#1713`](https://github.com/th-ch/youtube-music/pull/1713)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.21.0 [`#1711`](https://github.com/th-ch/youtube-music/pull/1711)
|
||||
- fix(deps): update dependency semver to v7.6.0 [`#1712`](https://github.com/th-ch/youtube-music/pull/1712)
|
||||
- refactor(in-app-menu): refactor `in-app-menu` plugin [`#1710`](https://github.com/th-ch/youtube-music/pull/1710)
|
||||
- chore(deps): update playwright monorepo to v1.41.2 [`#1706`](https://github.com/th-ch/youtube-music/pull/1706)
|
||||
- chore(deps): update dependency electron to v29.0.0-beta.5 [`#1707`](https://github.com/th-ch/youtube-music/pull/1707)
|
||||
- feat(album-color-theme): support album color theme in all pages [`#1685`](https://github.com/th-ch/youtube-music/pull/1685)
|
||||
- fix(deps): update dependency youtubei.js to v9.0.2 [`#1704`](https://github.com/th-ch/youtube-music/pull/1704)
|
||||
- fix(deps): update dependency i18next to v23.8.2 [`#1702`](https://github.com/th-ch/youtube-music/pull/1702)
|
||||
- feat: Support disabling scrobbling for non-music content [`#1665`](https://github.com/th-ch/youtube-music/pull/1665)
|
||||
- fix(deps): update dependency youtubei.js to v9 [`#1682`](https://github.com/th-ch/youtube-music/pull/1682)
|
||||
- chore(deps): update dependency electron to v29.0.0-beta.4 [`#1698`](https://github.com/th-ch/youtube-music/pull/1698)
|
||||
- fix(deps): update dependency i18next to v23.8.1 [`#1694`](https://github.com/th-ch/youtube-music/pull/1694)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.20.0 [`#1700`](https://github.com/th-ch/youtube-music/pull/1700)
|
||||
- chore(deps): update pnpm to v8.15.1 [`#1699`](https://github.com/th-ch/youtube-music/pull/1699)
|
||||
- chore(deps): update dependency esbuild to v0.20.0 [`#1691`](https://github.com/th-ch/youtube-music/pull/1691)
|
||||
- chore(deps): update pnpm to v8.15.0 [`#1692`](https://github.com/th-ch/youtube-music/pull/1692)
|
||||
- fix(deps): update dependency i18next to v23.7.20 [`#1684`](https://github.com/th-ch/youtube-music/pull/1684)
|
||||
- chore(deps): update dependency electron to v29.0.0-beta.3 [`#1683`](https://github.com/th-ch/youtube-music/pull/1683)
|
||||
- chore(deps): update dependency electron to v29.0.0-beta.2 [`#1681`](https://github.com/th-ch/youtube-music/pull/1681)
|
||||
- chore(deps): update dependency rollup to v4.9.6 [`#1663`](https://github.com/th-ch/youtube-music/pull/1663)
|
||||
- chore(deps): update dependency electron to v29.0.0-beta.1 [`#1670`](https://github.com/th-ch/youtube-music/pull/1670)
|
||||
- fix(deps): update dependency i18next to v23.7.19 [`#1680`](https://github.com/th-ch/youtube-music/pull/1680)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.1 [`#1669`](https://github.com/th-ch/youtube-music/pull/1669)
|
||||
- chore(deps): update pnpm to v8.14.3 [`#1668`](https://github.com/th-ch/youtube-music/pull/1668)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.8.3 [`#1672`](https://github.com/th-ch/youtube-music/pull/1672)
|
||||
- chore(deps): update dependency esbuild to v0.19.12 [`#1673`](https://github.com/th-ch/youtube-music/pull/1673)
|
||||
- fix(deps): update dependency @electron/remote to v2.1.2 [`#1676`](https://github.com/th-ch/youtube-music/pull/1676)
|
||||
- chore: Update issue templates [`#1661`](https://github.com/th-ch/youtube-music/pull/1661)
|
||||
- chore(deps): update playwright monorepo to v1.41.1 [`#1660`](https://github.com/th-ch/youtube-music/pull/1660)
|
||||
- fix(deps): update dependency i18next to v23.7.18 [`#1662`](https://github.com/th-ch/youtube-music/pull/1662)
|
||||
- chore(deps): update actions/dependency-review-action action to v4 [`#1654`](https://github.com/th-ch/youtube-music/pull/1654)
|
||||
- chore(deps): update dependency electron to v29.0.0-alpha.11 [`#1656`](https://github.com/th-ch/youtube-music/pull/1656)
|
||||
- chore(deps): update dependency vite to v5.0.12 [security] [`#1659`](https://github.com/th-ch/youtube-music/pull/1659)
|
||||
- fix(deps): update dependency async-mutex to v0.4.1 [`#1653`](https://github.com/th-ch/youtube-music/pull/1653)
|
||||
- chore(deps): update playwright monorepo to v1.41.0 [`#1651`](https://github.com/th-ch/youtube-music/pull/1651)
|
||||
- feat: Better Scrobbler Plugin [`#1640`](https://github.com/th-ch/youtube-music/pull/1640)
|
||||
- chore(deps): update dependency electron to v29.0.0-alpha.10 [`#1645`](https://github.com/th-ch/youtube-music/pull/1645)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.0 [`#1643`](https://github.com/th-ch/youtube-music/pull/1643)
|
||||
- chore(README): Fix plugins names and add plugins in/to Readme (in menu too) [`#1624`](https://github.com/th-ch/youtube-music/pull/1624)
|
||||
- fix(album-actions): Fixed album actions [`#1639`](https://github.com/th-ch/youtube-music/pull/1639)
|
||||
- chore(deps): update playwright monorepo to v1.41.0-beta-1705101589000 [`#1638`](https://github.com/th-ch/youtube-music/pull/1638)
|
||||
- fix(#1543): fix song control doesn't work [`#1637`](https://github.com/th-ch/youtube-music/pull/1637)
|
||||
- chore(deps): update playwright monorepo to v1.41.0-beta-1705092460000 [`#1635`](https://github.com/th-ch/youtube-music/pull/1635)
|
||||
- chore(deps): update dependency rollup to v4.9.5 [`#1629`](https://github.com/th-ch/youtube-music/pull/1629)
|
||||
- chore(deps): update dependency electron to v29.0.0-alpha.9 [`#1627`](https://github.com/th-ch/youtube-music/pull/1627)
|
||||
- chore(deps): update dependency electron to v29.0.0-alpha.8 [`#1608`](https://github.com/th-ch/youtube-music/pull/1608)
|
||||
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.15 [`#1615`](https://github.com/th-ch/youtube-music/pull/1615)
|
||||
- chore(deps): update dependency rollup to v4.9.4 [`#1591`](https://github.com/th-ch/youtube-music/pull/1591)
|
||||
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.15 [`#1616`](https://github.com/th-ch/youtube-music/pull/1616)
|
||||
- chore(deps): update pnpm to v8.14.1 [`#1619`](https://github.com/th-ch/youtube-music/pull/1619)
|
||||
- chore(deps): update dependency eslint-plugin-prettier to v5.1.3 [`#1618`](https://github.com/th-ch/youtube-music/pull/1618)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.1 [`#1612`](https://github.com/th-ch/youtube-music/pull/1612)
|
||||
- fix(deps): update dependency youtubei.js to v8.2.0 [`#1614`](https://github.com/th-ch/youtube-music/pull/1614)
|
||||
- chore(deps): update dependency electron-vite to v2.0.0 [`#1609`](https://github.com/th-ch/youtube-music/pull/1609)
|
||||
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.0 [`#1603`](https://github.com/th-ch/youtube-music/pull/1603)
|
||||
- chore(deps): update dependency electron-vite to v2.0.0-beta.4 [`#1602`](https://github.com/th-ch/youtube-music/pull/1602)
|
||||
- fix: fix upgrade button [`#1199`](https://github.com/th-ch/youtube-music/issues/1199)
|
||||
- fix(mpris): fix mpris invalid position [`#1726`](https://github.com/th-ch/youtube-music/issues/1726)
|
||||
- fix: discord RPC (fix #1664) [`#1664`](https://github.com/th-ch/youtube-music/issues/1664)
|
||||
- fix: remove sign-in button (fix #1199) [`#1199`](https://github.com/th-ch/youtube-music/issues/1199)
|
||||
- Fix #1617 [`#1617`](https://github.com/th-ch/youtube-music/issues/1617)
|
||||
- fix(crossfade): fix #1633 [`#1633`](https://github.com/th-ch/youtube-music/issues/1633)
|
||||
- fix: fix #1621 [`#1621`](https://github.com/th-ch/youtube-music/issues/1621)
|
||||
- fix(tuna-obs): partially fix #1596 [`#1596`](https://github.com/th-ch/youtube-music/issues/1596)
|
||||
- fix(discord): fix hide duration button [`#1644`](https://github.com/th-ch/youtube-music/issues/1644)
|
||||
- fix(in-app-menu): fix invalid `margin-top` [`#1597`](https://github.com/th-ch/youtube-music/issues/1597)
|
||||
- fix(README): fix `plugins` path [`#1598`](https://github.com/th-ch/youtube-music/issues/1598)
|
||||
- chore(i18n): Translated using Weblate (Vietnamese) [`0528637`](https://github.com/th-ch/youtube-music/commit/05286371353e8b4c36a5b9fe9011ae5dfdc7ee82)
|
||||
- chore: update pnpm-lock [`fd8d59b`](https://github.com/th-ch/youtube-music/commit/fd8d59bada56dab4e156d22394fe0c5efec5abc4)
|
||||
- fix(in-app-menu): fix app crash in production [`febc63e`](https://github.com/th-ch/youtube-music/commit/febc63edef375bd82db48b7fb460ec5a601ab872)
|
||||
|
||||
#### [v3.2.2](https://github.com/th-ch/youtube-music/compare/v3.2.1...v3.2.2)
|
||||
|
||||
> 5 January 2024
|
||||
|
||||
- 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)
|
||||
|
||||
@ -478,7 +478,7 @@
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="footer-copyright">© 2021 th-ch</div>
|
||||
<div class="footer-copyright">© 2024 th-ch</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
388
docs/readme/README-is.md
Normal file
388
docs/readme/README-is.md
Normal file
@ -0,0 +1,388 @@
|
||||
<div align="center">
|
||||
|
||||
# YouTube Tónlist
|
||||
|
||||
[](https://github.com/th-ch/youtube-music/releases/)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://aur.archlinux.org/packages/youtube-music-bin)
|
||||
[](https://snyk.io/test/github/th-ch/youtube-music)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/th-ch/youtube-music/releases/latest">
|
||||
<img src="../../web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
**Electron umbúðir utan um YouTube Tónlist sem inniheldur:**
|
||||
|
||||
- Innfæddur útlit og tilfinning, miðar að því að halda upprunalegu viðmótinu
|
||||
- Rammi fyrir sérsniðnar viðbætur: breyttu YouTube Tónlist að þínum þörfum (stíl, efni, eiginleikar), virkjaðu/slökktu á viðbætur í
|
||||
einn smellur
|
||||
|
||||
## Sýnishornsmynd
|
||||
|
||||
| Spilaraskjár (albúmslitaþema & umhverfisljós) |
|
||||
|:---------------------------------------------------------------------------------------------------------:|
|
||||
||
|
||||
|
||||
## Efni
|
||||
|
||||
- [Eiginleikar](#eiginleikar)
|
||||
- [Tiltæk viðbætur](#tiltæk-viðbætur)
|
||||
- [Þýðing](#þýðing)
|
||||
- [Sækja](#sækja)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [MacOS](#macos)
|
||||
- [Windows](#windows)
|
||||
- [Hvernig á að setja upp án nettengingar? (í Windows)](#hvernig-á-að-setja-upp-án-nettengingar-í-windows)
|
||||
- [Þemu](#þemu)
|
||||
- [Þróun](#þróun)
|
||||
- [Búðu til þín eigin viðbætur](#búðu-til-þín-eigin-viðbætur)
|
||||
- [Er að búa til viðbót](#er-að-búa-til-viðbót)
|
||||
- [Algeng notkunartilvik](#algeng-notkunartilvik)
|
||||
- [Byggja](#byggja)
|
||||
- [Framleiðsluforskoðun](#framleiðsluforskoðun)
|
||||
- [Prófanir](#prófanir)
|
||||
- [Leyfi](#leyfi)
|
||||
- [Algengustu spurningar](#algengustu-spurningar)
|
||||
|
||||
## Eiginleikar:
|
||||
|
||||
- **Sjálfvirk staðfesting þegar gert er hlé** (Alltaf virkt): slökkva á
|
||||
["Halda áfram að horfa?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
|
||||
popup sem gerir hlé á tónlist eftir ákveðinn tíma
|
||||
|
||||
- Og meira...
|
||||
|
||||
## Tiltæk viðbætur:
|
||||
|
||||
- **Auglýsingablokkari**: Lokaðu fyrir allar auglýsingar og rakningar úr kassanum
|
||||
|
||||
- **Albúmsaðgerðir**: Bætir Ódíslika, Mislíkt, Líkt, og Ólíkt til að nota þetta á öll lög á spilunarlista eða albúm
|
||||
|
||||
- **Albúmslitaþema**: Beitir kraftmikið þema og sjónrænum áhrifum sem byggjast á litavali albúmsins
|
||||
|
||||
- **Umhverfishamur**: Beitir lýsingaráhrifum með því að varpa mildum litum úr myndbandinu í bakgrunn skjásins
|
||||
|
||||
- **Hljóðþjöppur**: Notaðu þjöppun á hljóð (lækkar hljóðstyrk háværustu hluta merkis og hækkar hljóðstyrk í mýkstu hlutunum)
|
||||
|
||||
- **Þoka Leiðsagnarstika**: Gerir leiðsögustikuna gagnsæja og óskýrt
|
||||
|
||||
- **Farið Framhjá Aldurstakmörkunum**: Framhjá aldursstaðfestingu YouTube
|
||||
|
||||
- **Yfirskriftarval**: Virkja skjátexta
|
||||
|
||||
- **Fyrirferðarlítillhliðarstika**: Stilltu hliðarstikuna alltaf í þétta stillingu
|
||||
|
||||
- **Krossfæra**: Krossfæra á milli lög
|
||||
|
||||
- **Slökkva á Sjálfvirkri Spilun**: Gerir lag að byrja í "hlé" ham
|
||||
|
||||
- **[Discord](https://discord.com/) Rík Nærveru**: Sýndu vinum þínum hvað þú hlustar á
|
||||
með [Rík Nærveru](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||
|
||||
- **Niðurhalari**: Niðurhalum
|
||||
MP3 [beint úr viðmótinu](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||
|
||||
- **Veldibundiðrúmmál**: Gerir hljóðstyrkssleðann [veldisvísis](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/)
|
||||
svo það er auðveldara að velja lægra hljóðstyrk.
|
||||
|
||||
- **Valmynd í Forriti**: [Gefur börum flott, dökkt útlit](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
||||
|
||||
> (sjá [þessa færslu](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) ef þú átt í vandræðum
|
||||
með að fá aðgang að valmyndinni eftir að hafa virkjað þessa viðbót og fela valmyndarvalkostinn)
|
||||
|
||||
- **Scrobbler**: Bætir við scrobbling stuðningi fyrir [Last.fm](https://www.last.fm/) og [ListenBrainz](https://listenbrainz.org/)
|
||||
|
||||
- **Lumia Stream**: Bætir við [Lumia Stream](https://lumiastream.com/) stuðningi
|
||||
|
||||
- **Söngtexti Snilld**: Bætir stuðningi við texta fyrir flest lög
|
||||
|
||||
- **Tónlist Saman**: Deila spilunarlista með öðrum. Þegar gestgjafinn spilar lag munu allir aðrir heyra sama lagið
|
||||
|
||||
- **Leiðsögn**: Næsta/Til baka leiðsagnarörvar beint samþættar í viðmótinu, eins og í uppáhalds vafranum þínum
|
||||
|
||||
- **Engin Google Innskráning**: Fjarlægðu Google innskráningarhnappa og tengla úr viðmótinu
|
||||
|
||||
- **Tilkynningar**: Birta tilkynningu þegar lag byrjar að spila
|
||||
([gagnvirkartilkynningar](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) eru fáanlegar á Windows)
|
||||
|
||||
- **Mynd-í-Mynd**: Gerir kleift að skipta forritinu yfir í mynd-í-mynd stillingu
|
||||
|
||||
- **Spilunarhraði**: Hlustaðu hratt, hlustaðu hægt!
|
||||
[Bætir við sleða sem stjórnar lagahraðanum](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
||||
|
||||
- **Nákvæmshljóðstyrkur**: Stjórnaðu hljóðstyrknum nákvæmlega með músarhjóli/hraðtökkum, með sérsniðnum HUD og sérsniðnum hljóðstyrksþrepum
|
||||
|
||||
- **Flýtileiðir (og MPRIS)**: Leyfir að stilla alþjóðlegarflýtilyklar fyrir spilun (spila/gera hlé/næsta/fyrri) +
|
||||
óvirkja [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
|
||||
með því að hnekkja miðlunarlyklum + virkja Ctrl/CMD + F til að leita + virkja linux mpris stuðning fyrir
|
||||
miðlunarlyklar + [sérsniðnir flýtilyklar](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
|
||||
fyrir [háþróaða notendur](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
|
||||
- **Slepptu Lögum sem Mislíkuðust**: Sleppir mislíkaði lög
|
||||
|
||||
- **Slepptu Þögnum**: Slepptu sjálfkrafa þagnarköflum í lögum
|
||||
|
||||
- [**Styrktarblokk**](https://github.com/ajayyy/SponsorBlock): Sleppur sjálfkrafa hlutum sem ekki eru tónlist, eins og inngangur/lok
|
||||
eða hlutar af tónlistarmyndböndum þar sem lag er ekki að spila
|
||||
|
||||
- **Miðlunarstýringarverkefnastikunnar**: Stjórnaðu spilun frá [Windows verkefnastikunni þinni](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||
|
||||
- **Snertistiku**: Sérsniðið Snertistikuútlit fyrir macOS
|
||||
|
||||
- **Tuna OBS**: Samþætting við [OBS](https://obsproject.com/)
|
||||
viðbótina [Tuna](https://obsproject.com/forum/resources/tuna.843/)
|
||||
|
||||
- **Myndbandgæðisbreyting**: Leyfir að breyta myndbandgæðum með
|
||||
[hnappi](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) á
|
||||
myndbandsyfirlaginu
|
||||
|
||||
- **Myndbandsrofi**: Bætir við [hnappi](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) til
|
||||
að skipta á milli myndbands/lagshams. Getur einnig valfrjálst fjarlægt allan myndbandsflipann
|
||||
|
||||
- **Sjónrænir**: Mismunandi tónlist sjónrænir
|
||||
|
||||
## Þýðing
|
||||
|
||||
Þú getur aðstoðað við þýðingar á [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/youtube-music/">
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="translation status" />
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="translation status 2" />
|
||||
</a>
|
||||
|
||||
## Sækja
|
||||
|
||||
Þú getur skoðað [nýjustu útgáfuna](https://github.com/th-ch/youtube-music/releases/latest) til að finna fljótt
|
||||
nýjustu útgáfuna.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Settu upp [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) pakkann frá AUR. Fyrir AUR uppsetningarleiðbeiningar skaltu skoða
|
||||
þessa [wiki síðu](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||
|
||||
### MacOS
|
||||
|
||||
Þú getur sett upp appið með því að nota Homebrew (sjá [cask skilgreiningu](https://github.com/th-ch/homebrew-youtube-music))
|
||||
|
||||
```bash
|
||||
brew install th-ch/youtube-music/youtube-music
|
||||
```
|
||||
|
||||
Ef þú setur upp forritið handvirkt og færð villu "er skemmd og ekki er hægt að opna það," þegar þú ræsir forritið skaltu keyra eftirfarandi í flugstöðinni:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Þú getur notað [Scoop pakkastjórnun](https://scoop.sh) til að setja upp `youtube-music` pakkann frá
|
||||
[`extras` fötuna](https://github.com/ScoopInstaller/Extras).
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install extras/youtube-music
|
||||
```
|
||||
|
||||
Að öðrum kosti geturðu notað [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), Windows 11s
|
||||
opinber CLI pakkastjóri til að setja upp `th-ch.YouTubeMusic` pakkann.
|
||||
|
||||
*Athugið: Microsoft Defender SmartScreen gæti lokað uppsetningunni þar sem hún er frá „óþekktum útgefanda“. Þetta er einnig
|
||||
satt fyrir handvirka uppsetningu þegar reynt er að keyra executable(.exe) eftir handvirkt niðurhal hér á github (sama
|
||||
skrá).*
|
||||
|
||||
```bash
|
||||
winget install th-ch.YouTubeMusic
|
||||
```
|
||||
|
||||
#### Hvernig á að setja upp án nettengingar? (í Windows)
|
||||
|
||||
- Sæktu `*.nsis.7z` skrána fyrir _arkitektúr tækisins þíns_ á [útgáfusíðu](https://github.com/th-ch/youtube-music/releases/latest).
|
||||
- `x64` fyrir 64-bita Windows
|
||||
- `ia32` fyrir 32-bita Windows
|
||||
- `arm64` fyrir ARM64 Windows
|
||||
- Sæktu uppsetningarforrit á útgáfusíðu. (`*-Setup.exe`)
|
||||
- Settu þær í **sömu möppuna**.
|
||||
- Keyrðu uppsetningarforritið.
|
||||
|
||||
## Þemu
|
||||
|
||||
Þú getur hlaðið CSS skrám til að breyta útliti forritsins (Valkostir > Sjónræn klip > Þemu).
|
||||
|
||||
Sum fyrirframskilgreind þemu eru fáanleg á https://github.com/kerichdev/themes-for-ytmdesktop-player.
|
||||
|
||||
## Þróun
|
||||
|
||||
```bash
|
||||
git clone https://github.com/th-ch/youtube-music
|
||||
cd youtube-music
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Búðu til þín eigin viðbætur
|
||||
|
||||
Með því að nota viðbætur geturðu:
|
||||
|
||||
- vinna með appið - `BrowserWindow` frá electron er sent til viðbótarstjórans
|
||||
- breyttu framhliðinni með því að vinna með HTML/CSS
|
||||
|
||||
### Er að búa til viðbót
|
||||
|
||||
Búðu til möppu í `src/plugins/YOUR-PLUGIN-NAME`:
|
||||
|
||||
- `index.ts`: aðal skránni af viðbótin
|
||||
```typescript
|
||||
import style from './style.css?inline'; // flytja inn stíl sem inline
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // ef gildi er satt, ytmusic show endurræsa gluggann
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // sérsniðnastillingar þinn
|
||||
stylesheets: [style], // sérsniðnastílinn þinn
|
||||
menu: async ({ getConfig, setConfig }) => {
|
||||
// Allar *stillingaraðferðir eru umvafnar Lofor<T>
|
||||
const config = await getConfig();
|
||||
return [
|
||||
{
|
||||
label: 'menu',
|
||||
submenu: [1, 2, 3].map((value) => ({
|
||||
label: `value ${value}`,
|
||||
type: 'radio',
|
||||
checked: config.value === value,
|
||||
click() {
|
||||
setConfig({ value });
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
},
|
||||
backend: {
|
||||
start({ window, ipc }) {
|
||||
window.maximize();
|
||||
|
||||
// þú getur tengst við renderer viðbótina
|
||||
ipc.handle('some-event', () => {
|
||||
return 'hello';
|
||||
});
|
||||
},
|
||||
// það kviknaði þegar stillingum var breytt
|
||||
onConfigChange(newConfig) { /* ... */ },
|
||||
// it fired when plugin disabled
|
||||
stop(context) { /* ... */ },
|
||||
},
|
||||
renderer: {
|
||||
async start(context) {
|
||||
console.log(await context.ipc.invoke('some-event'));
|
||||
},
|
||||
// Aðeins krókur sem er í boði fyrir renderer
|
||||
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
|
||||
// stilltu stillingar viðbótarinnar auðveldlega
|
||||
context.setConfig({ myConfig: api.getVolume() });
|
||||
},
|
||||
onConfigChange(newConfig) { /* ... */ },
|
||||
stop(_context) { /* ... */ },
|
||||
},
|
||||
preload: {
|
||||
async start({ getConfig }) {
|
||||
const config = await getConfig();
|
||||
},
|
||||
onConfigChange(newConfig) {},
|
||||
stop(_context) {},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Algeng notkunartilvik
|
||||
|
||||
- er að sprauta sérsniðnum CSS: búðu til `style.css` skrá í sömu möppu þá:
|
||||
|
||||
```typescript
|
||||
// index.ts
|
||||
import style from './style.css?inline'; // flytja inn stíl sem inline
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // ef gildi er satt, ytmusic show endurræsa gluggann
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // sérsniðnastillingar þinn
|
||||
stylesheets: [style], // sérsniðnastílinn þinn
|
||||
renderer() {} // skilgreina renderer krók
|
||||
});
|
||||
```
|
||||
|
||||
- Ef þú vilt breyta HTML:
|
||||
|
||||
```typescript
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // ef gildi er satt, ytmusic show endurræsa gluggann
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // sérsniðnastillingar þinn
|
||||
renderer() {
|
||||
// Fjarlægðu innskráningarhnappinn
|
||||
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
|
||||
} // skilgreina renderer krók
|
||||
});
|
||||
```
|
||||
|
||||
- samskipti á milli að framan og aftan: hægt að gera með því að nota ipcMain eininguna frá electron. Sjá `index.ts` skrá og
|
||||
dæmi í 'styrktarblokk' viðbótinni.
|
||||
|
||||
## Byggja
|
||||
|
||||
1. Klóna geymsluna
|
||||
2. Fylgdu [þessa handbók](https://pnpm.io/installation) til að setja upp 'pnpm'
|
||||
3. Keyrðu `pnpm install --frozen-lockfile` til að setja upp ósjálfstæði
|
||||
4. Keyrðu `pnpm build:OS`
|
||||
|
||||
- `pnpm dist:win` - Windows
|
||||
- `pnpm dist:linux` - Linux
|
||||
- `pnpm dist:mac` - MacOS
|
||||
|
||||
Byggir appið fyrir macOS, Linux og Windows,
|
||||
með því að nota [electron-builder](https://github.com/electron-userland/electron-builder).
|
||||
|
||||
## Framleiðsluforskoðun
|
||||
|
||||
```bash
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Prófanir
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Notar [Playwright](https://playwright.dev/) til að prófa forritið.
|
||||
|
||||
## Leyfi
|
||||
|
||||
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||
|
||||
## Algengustu Spurningar
|
||||
|
||||
### Hvers vegna forritavalmynd birtist ekki?
|
||||
|
||||
Ef valmöguleikinn „Fela valmynd“ er á - þú getur sýnt valmyndina með <kbd>alt</kbd> lyklinum (eða <kbd>\`</kbd> [bakka]
|
||||
ef þú notar viðbótina fyrir valmynd í forriti)
|
||||
@ -1,7 +1,7 @@
|
||||
# 유튜브 뮤직 (YouTube Music)
|
||||
|
||||
<div align="center">
|
||||
|
||||
# 유튜브 뮤직 (YouTube Music)
|
||||
|
||||
[](https://github.com/th-ch/youtube-music/releases/)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
||||
@ -25,62 +25,26 @@
|
||||
- 원래의 인터페이스를 유지하는 것을 목표로 하는 네이티브 디자인 및 느낌
|
||||
- 맞춤 플러그인을 위한 프레임워크: 스타일, 콘텐츠, 기능 등 필요에 따라 유튜브 뮤직을 변경하고, 클릭 한 번으로 플러그인을 활성화/비활성화할 수 있습니다.
|
||||
|
||||
## 번역
|
||||
## Content
|
||||
|
||||
[Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/)에서 번역을 도울 수 있습니다.
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/youtube-music/">
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="번역 상태" />
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="번역 상태 2" />
|
||||
</a>
|
||||
|
||||
## 다운로드
|
||||
|
||||
[최신 릴리즈](https://github.com/th-ch/youtube-music/releases/latest)를 확인하여 최신 버전을 빠르게 찾을 수 있습니다.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
AUR에서 `youtube-music-bin` 패키지를 설치합니다. AUR 설치 지침은 [이 위키 페이지](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages)를 참조하세요.
|
||||
|
||||
### MacOS
|
||||
|
||||
Homebrew를 사용하여 앱을 설치할 수 있습니다:
|
||||
```bash
|
||||
brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master/youtube-music.rb
|
||||
```
|
||||
|
||||
(앱을 수동으로 설치하고) 앱을 실행할 때 `손상되었기 때문에 열 수 없습니다.`라는 오류가 발생하면 터미널에서 다음을 실행하세요:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
[Scoop 패키지 매니저](https://scoop.sh)를 사용하여 [`extras` 버킷](https://github.com/ScoopInstaller/Extras)에서 `youtube-music` 패키지를 설치할 수 있습니다.
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install extras/youtube-music
|
||||
```
|
||||
|
||||
또는 Windows 11의 공식 CLI 패키지 관리자인 [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)을 사용하여 `th-ch.YouTubeMusic` 패키지를 설치할 수 있습니다.
|
||||
|
||||
*참고: "알 수 없는 게시자"의 파일이기 때문에 Microsoft Defender의 SmartScreen에서 설치를 차단할 수 있습니다. 이는 GitHub에서 동일 파일을 수동으로 다운로드한 후 실행 파일(.exe)을 실행하려고 할 때도 마찬가지로 발생합니다.*
|
||||
|
||||
```bash
|
||||
winget install th-ch.YouTubeMusic
|
||||
```
|
||||
|
||||
#### (Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?
|
||||
|
||||
- [릴리즈 페이지](https://github.com/th-ch/youtube-music/releases/latest)에서 _본인 기기 아키텍처_에 맞는 `*.nsis.7z` 파일을 다운로드하세요.
|
||||
- `x64`는 64비트 Windows 용입니다.
|
||||
- `ia32`는 32비트 Windows 용입니다.
|
||||
- `arm64`는 ARM64 Windows 용입니다.
|
||||
- 릴리즈 페이지에서 설치기를 다운로드하세요. (`*-Setup.exe`)
|
||||
- 두 파일을 **동일한 위치**에 놓아주세요.
|
||||
- 설치기를 실행하세요.
|
||||
- [기능](#기능)
|
||||
- [사용 가능한 플러그인](#사용-가능한-플러그인)
|
||||
- [번역](#번역)
|
||||
- [다운로드](#다운로드)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [MacOS](#macos)
|
||||
- [Windows](#windows)
|
||||
- [(Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?](#windows에서-네트워크에-연결하지-않고-설치하는-방법은-무엇인가요)
|
||||
- [테마](#테마)
|
||||
- [개발](#개발)
|
||||
- [나만의 플러그인 만들기](#나만의-플러그인-만들기)
|
||||
- [플러그인 만들기](#플러그인-만들기)
|
||||
- [일반적인 사용 예](#일반적인-사용-예)
|
||||
- [빌드](#빌드)
|
||||
- [프로덕션 빌드 미리보기](#프로덕션-빌드-미리보기)
|
||||
- [테스트](#테스트)
|
||||
- [라이선스](#라이선스)
|
||||
- [자주 묻는 질문](#자주-묻는-질문)
|
||||
|
||||
## 기능:
|
||||
|
||||
@ -156,6 +120,63 @@ winget install th-ch.YouTubeMusic
|
||||
|
||||
- **비주얼라이저**: 플레이어에 시각화 도구 추가
|
||||
|
||||
## 번역
|
||||
|
||||
[Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/)에서 번역을 도울 수 있습니다.
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/youtube-music/">
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="번역 상태" />
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="번역 상태 2" />
|
||||
</a>
|
||||
|
||||
## 다운로드
|
||||
|
||||
[최신 릴리즈](https://github.com/th-ch/youtube-music/releases/latest)를 확인하여 최신 버전을 빠르게 찾을 수 있습니다.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
AUR에서 [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) 패키지를 설치합니다. AUR 설치 지침은 [이 위키 페이지](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages)를 참조하세요.
|
||||
|
||||
### MacOS
|
||||
|
||||
Homebrew를 사용하여 앱을 설치할 수 있습니다:
|
||||
```bash
|
||||
brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master/youtube-music.rb
|
||||
```
|
||||
|
||||
(앱을 수동으로 설치하고) 앱을 실행할 때 `손상되었기 때문에 열 수 없습니다.`라는 오류가 발생하면 터미널에서 다음을 실행하세요:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
[Scoop 패키지 매니저](https://scoop.sh)를 사용하여 [`extras` 버킷](https://github.com/ScoopInstaller/Extras)에서 `youtube-music` 패키지를 설치할 수 있습니다.
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install extras/youtube-music
|
||||
```
|
||||
|
||||
또는 Windows 11의 공식 CLI 패키지 관리자인 [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)을 사용하여 `th-ch.YouTubeMusic` 패키지를 설치할 수 있습니다.
|
||||
|
||||
*참고: "알 수 없는 게시자"의 파일이기 때문에 Microsoft Defender의 SmartScreen에서 설치를 차단할 수 있습니다. 이는 GitHub에서 동일 파일을 수동으로 다운로드한 후 실행 파일(.exe)을 실행하려고 할 때도 마찬가지로 발생합니다.*
|
||||
|
||||
```bash
|
||||
winget install th-ch.YouTubeMusic
|
||||
```
|
||||
|
||||
#### (Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?
|
||||
|
||||
- [릴리즈 페이지](https://github.com/th-ch/youtube-music/releases/latest)에서 _본인 기기 아키텍처_에 맞는 `*.nsis.7z` 파일을 다운로드하세요.
|
||||
- `x64`는 64비트 Windows 용입니다.
|
||||
- `ia32`는 32비트 Windows 용입니다.
|
||||
- `arm64`는 ARM64 Windows 용입니다.
|
||||
- 릴리즈 페이지에서 설치기를 다운로드하세요. (`*-Setup.exe`)
|
||||
- 두 파일을 **동일한 위치**에 놓아주세요.
|
||||
- 설치기를 실행하세요.
|
||||
|
||||
## 테마
|
||||
|
||||
CSS 파일을 로드하여 애플리케이션의 모양을 변경할 수 있습니다(설정 > 시각적 변경 > 테마).
|
||||
|
||||
@ -11,6 +11,7 @@ import pluginLoader from './vite-plugins/plugin-loader.mjs';
|
||||
|
||||
import type { UserConfig } from 'vite';
|
||||
import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
|
||||
import solidPlugin from 'vite-plugin-solid';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@ -117,6 +118,7 @@ export default defineConfig({
|
||||
'virtual:i18n': i18nImporter(),
|
||||
'virtual:plugins': pluginVirtualModuleGenerator('renderer'),
|
||||
}),
|
||||
solidPlugin(),
|
||||
],
|
||||
root: './src/',
|
||||
build: {
|
||||
|
||||
73
package.json
73
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "youtube-music",
|
||||
"productName": "YouTube Music",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.5",
|
||||
"description": "YouTube Music Desktop App - including custom plugins",
|
||||
"main": "./dist/main/index.js",
|
||||
"license": "MIT",
|
||||
@ -123,28 +123,31 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"esbuild": "0.18.20",
|
||||
"usocket": "1.0.1",
|
||||
"rollup": "4.9.2",
|
||||
"node-gyp": "10.0.1",
|
||||
"xml2js": "0.6.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"@electron/universal": "2.0.1",
|
||||
"@babel/runtime": "7.23.7"
|
||||
"@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": {
|
||||
"@cliqz/adblocker-electron": "1.26.12",
|
||||
"@cliqz/adblocker-electron-preload": "1.26.12",
|
||||
"@cliqz/adblocker-electron": "1.26.16",
|
||||
"@cliqz/adblocker-electron-preload": "1.26.16",
|
||||
"@electron-toolkit/tsconfig": "1.0.1",
|
||||
"@electron/remote": "2.1.1",
|
||||
"@electron/remote": "2.1.2",
|
||||
"@ffmpeg.wasm/core-mt": "0.12.0",
|
||||
"@ffmpeg.wasm/main": "0.12.0",
|
||||
"@floating-ui/dom": "1.6.3",
|
||||
"@foobar404/wave": "2.0.5",
|
||||
"@jellybrick/electron-better-web-request": "1.0.4",
|
||||
"@jellybrick/mpris-service": "2.1.4",
|
||||
"@xhayper/discord-rpc": "1.1.1",
|
||||
"async-mutex": "0.4.0",
|
||||
"@xhayper/discord-rpc": "1.1.2",
|
||||
"async-mutex": "0.5.0",
|
||||
"butterchurn": "3.0.0-beta.4",
|
||||
"butterchurn-presets": "3.0.0-beta.4",
|
||||
"color": "4.2.3",
|
||||
@ -155,61 +158,69 @@
|
||||
"electron-debug": "3.2.0",
|
||||
"electron-is": "3.0.0",
|
||||
"electron-localshortcut": "3.2.1",
|
||||
"electron-store": "8.1.0",
|
||||
"electron-store": "8.2.0",
|
||||
"electron-unhandled": "4.0.1",
|
||||
"electron-updater": "6.1.7",
|
||||
"electron-updater": "6.1.8",
|
||||
"fast-average-color": "9.4.0",
|
||||
"fast-equals": "5.0.1",
|
||||
"filenamify": "6.0.0",
|
||||
"howler": "2.2.4",
|
||||
"html-to-text": "9.0.5",
|
||||
"i18next": "23.7.13",
|
||||
"i18next": "23.10.1",
|
||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||
"keyboardevents-areequal": "0.2.2",
|
||||
"node-html-parser": "6.1.12",
|
||||
"node-id3": "0.2.6",
|
||||
"peerjs": "1.5.2",
|
||||
"semver": "7.5.4",
|
||||
"reconnecting-websocket": "4.4.0",
|
||||
"semver": "7.6.0",
|
||||
"serve": "14.2.1",
|
||||
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
|
||||
"ts-morph": "21.0.1",
|
||||
"solid-floating-ui": "0.3.1",
|
||||
"solid-js": "1.8.16",
|
||||
"solid-styled-components": "0.28.5",
|
||||
"solid-transition-group": "0.2.3",
|
||||
"ts-morph": "22.0.0",
|
||||
"vudio": "2.1.1",
|
||||
"ws": "8.16.0",
|
||||
"x11": "2.3.0",
|
||||
"youtubei.js": "8.1.0"
|
||||
"youtubei.js": "9.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.41.0-alpha-dec-18-2023",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
"@types/color": "3.0.6",
|
||||
"@types/electron-localshortcut": "3.1.3",
|
||||
"@types/howler": "2.2.11",
|
||||
"@types/html-to-text": "9.0.4",
|
||||
"@types/semver": "7.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.16.0",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "7.4.0",
|
||||
"bufferutil": "4.0.8",
|
||||
"builtin-modules": "3.3.0",
|
||||
"cross-env": "7.0.3",
|
||||
"del-cli": "5.1.0",
|
||||
"electron": "28.1.0",
|
||||
"discord-api-types": "0.37.76",
|
||||
"electron": "29.1.6",
|
||||
"electron-builder": "24.9.1",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
"electron-vite": "2.0.0-beta.2",
|
||||
"esbuild": "0.18.20",
|
||||
"eslint": "8.56.0",
|
||||
"electron-vite": "2.1.0",
|
||||
"esbuild": "0.20.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||
"eslint-import-resolver-typescript": "3.6.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-prettier": "5.1.2",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"glob": "10.3.10",
|
||||
"node-gyp": "10.0.1",
|
||||
"playwright": "1.41.0-alpha-dec-18-2023",
|
||||
"rollup": "4.9.2",
|
||||
"typescript": "5.3.3",
|
||||
"node-gyp": "10.1.0",
|
||||
"playwright": "1.42.1",
|
||||
"rollup": "4.13.1",
|
||||
"typescript": "5.4.3",
|
||||
"utf-8-validate": "6.0.3",
|
||||
"vite": "5.0.10",
|
||||
"vite-plugin-inspect": "0.8.1",
|
||||
"vite": "5.2.6",
|
||||
"vite-plugin-inspect": "0.8.3",
|
||||
"vite-plugin-resolve": "2.5.1",
|
||||
"ws": "8.16.0"
|
||||
"vite-plugin-solid": "2.10.2"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"hideCredit": true,
|
||||
@ -217,5 +228,5 @@
|
||||
"unreleased": true,
|
||||
"output": "changelog.md"
|
||||
},
|
||||
"packageManager": "pnpm@8.13.1"
|
||||
"packageManager": "pnpm@8.15.5"
|
||||
}
|
||||
|
||||
17
patches/@xhayper__discord-rpc@1.1.2.patch
Normal file
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
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) {
|
||||
1849
pnpm-lock.yaml
generated
1849
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -32,6 +32,7 @@ export interface DefaultConfig {
|
||||
proxy: string;
|
||||
startingPage: string;
|
||||
overrideUserAgent: boolean;
|
||||
usePodcastParticipantAsArtist: boolean;
|
||||
themes: string[];
|
||||
};
|
||||
plugins: Record<string, unknown>;
|
||||
@ -66,6 +67,7 @@ const defaultConfig: DefaultConfig = {
|
||||
proxy: '',
|
||||
startingPage: '',
|
||||
overrideUserAgent: false,
|
||||
usePodcastParticipantAsArtist: false,
|
||||
themes: [],
|
||||
},
|
||||
'plugins': {},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Store from 'electron-store';
|
||||
import { deepmerge } from 'deepmerge-ts';
|
||||
import { deepmergeCustom } from 'deepmerge-ts';
|
||||
|
||||
import defaultConfig from './defaults';
|
||||
|
||||
@ -8,6 +8,10 @@ import plugins from './plugins';
|
||||
|
||||
import { restart } from '@/providers/app-controls';
|
||||
|
||||
const deepmerge = deepmergeCustom({
|
||||
mergeArrays: false,
|
||||
});
|
||||
|
||||
const set = (key: string, value: unknown) => {
|
||||
store.set(key, value);
|
||||
};
|
||||
|
||||
@ -6,6 +6,56 @@ import defaults from './defaults';
|
||||
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
|
||||
|
||||
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);
|
||||
store.delete('plugins.lastfm');
|
||||
}
|
||||
},
|
||||
'>=3.0.0'(store: Conf<Record<string, unknown>>) {
|
||||
const discordConfig = store.get('plugins.discord') as Record<
|
||||
string,
|
||||
|
||||
178
src/i18n/resources/bg.json
Normal file
178
src/i18n/resources/bg.json
Normal file
@ -0,0 +1,178 @@
|
||||
{
|
||||
"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}}"
|
||||
}
|
||||
},
|
||||
"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": "НОВО"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@
|
||||
"load-failed": "Selhalo načtení \"{{pluginName}}\" pluginu",
|
||||
"loaded": "Plugin \"{{pluginName}}\" načten",
|
||||
"unload-failed": "Selhalo unload \"{{pluginName}}\" pluginu",
|
||||
"unloaded": "Plugin \"{{pluginName}}\" unloaded"
|
||||
"unloaded": "Plugin {{pluginName}} byl odnačten"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Povoleno",
|
||||
"label": "Pluginy"
|
||||
"label": "Pluginy",
|
||||
"new": "NOVÉ"
|
||||
},
|
||||
"view": {
|
||||
"label": "Zobrazení",
|
||||
@ -293,7 +294,7 @@
|
||||
"menu": {
|
||||
"advanced": "Pokročilý"
|
||||
},
|
||||
"name": "Prolínání [beta]",
|
||||
"name": "Prolínání [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -360,6 +361,7 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Konverze: {{percent}}%",
|
||||
"done": "Hotovo: {{filePath}}",
|
||||
"download-info": "Stahování {{artist}} - {{title}} [{{videoId}}",
|
||||
"download-progress": "Stahování: {{percent}}%",
|
||||
@ -401,15 +403,14 @@
|
||||
"name": "Exponenciální hlasitost"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Dává menu panelům fancy, tmavý nebo album-color vzhled"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Přidat scrobbling podporu pro Last.fm",
|
||||
"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]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Přidává lyrics podporu pro většinu písniček",
|
||||
@ -417,6 +418,50 @@
|
||||
"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": {
|
||||
"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"
|
||||
@ -539,7 +584,7 @@
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Ovládejte přehrávání z vašeho hlavního panelu Windows",
|
||||
"description": "Ovládejte přehrávání z vašeho Windows hlavního panelu",
|
||||
"name": "Hlavní panel Media Control"
|
||||
},
|
||||
"touchbar": {
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
"hide-menu-enabled": {
|
||||
"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",
|
||||
"title": "Menü Verstecken Aktiviert"
|
||||
"title": "Menü verstecken aktiviert"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
@ -55,7 +55,7 @@
|
||||
},
|
||||
"detail": "\"{{pluginName}}\"-Erweiterung erfordert einen Neustart, um in Kraft zu treten",
|
||||
"message": "\"{{pluginName}}\" muss neugestartet werden",
|
||||
"title": "Neustart Erforderlich"
|
||||
"title": "Neustart erforderlich"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
@ -75,7 +75,7 @@
|
||||
},
|
||||
"detail": "Eine neue Version ist verfügbar und kann unter {{downloadLink}} heruntergeladen werden",
|
||||
"message": "Eine neue Version ist verfügbar",
|
||||
"title": "Aktualisierung Verfügbar"
|
||||
"title": "Aktualisierung verfügbar"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@ -87,7 +87,7 @@
|
||||
"go-back": "Zurück gehen",
|
||||
"go-forward": "Vorwärts gehen",
|
||||
"quit": "Beenden",
|
||||
"restart": "Anwendung Neustarten"
|
||||
"restart": "Anwendung neustarten"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@ -124,7 +124,7 @@
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "Sprache wird nach Neustart geändert",
|
||||
"title": "Sprache Geändert"
|
||||
"title": "Sprache geändert"
|
||||
},
|
||||
"label": "Sprache",
|
||||
"submenu": {
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Aktiviert",
|
||||
"label": "Erweiterungen"
|
||||
"label": "Erweiterungen",
|
||||
"new": "NEU"
|
||||
},
|
||||
"view": {
|
||||
"label": "Ansicht",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Vorheriges",
|
||||
"quit": "Beenden",
|
||||
"restart": "Anwendung neu starten",
|
||||
"show": "Fenster anzeigen"
|
||||
"show": "Fenster anzeigen",
|
||||
"tooltip": {
|
||||
"default": "YouTube Musik",
|
||||
"with-song-info": "YouTube Musik: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -201,12 +206,24 @@
|
||||
},
|
||||
"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": {
|
||||
"description": "Wendet ein dynamisches Farbthema und visuelle Effekte auf Basis der Farbpalette des Albumcovers an",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Farbmischungsverhältnis",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Thema aus Albumfarbe"
|
||||
},
|
||||
"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": {
|
||||
"blur-amount": {
|
||||
"label": "Unschärfemenge",
|
||||
@ -221,7 +238,7 @@
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "Durchsichtigkeit",
|
||||
"label": "Transparenz",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
@ -266,7 +283,7 @@
|
||||
"description": "Untertitelwähler für YouTube Music-Audio-Lieder",
|
||||
"menu": {
|
||||
"autoload": "Wähle automatisch den zuletzt verwendeten Untertitel",
|
||||
"disable-captions": "Standartmäßig keine Untertitel"
|
||||
"disable-captions": "Standardmäßig keine Untertitel"
|
||||
},
|
||||
"name": "Untertitelwähler",
|
||||
"prompt": {
|
||||
@ -408,10 +425,6 @@
|
||||
},
|
||||
"name": "In-App Menü"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Scrobbling-Unterstützung für Last.fm hinzufügen",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Fügt Unterstützung für Lumia Stream hinzu",
|
||||
"name": "Lumia Stream [Beta]"
|
||||
@ -426,6 +439,52 @@
|
||||
"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": {
|
||||
"description": "Vorwärts/Zurück Navigationspfeile direkt in die Oberfläche integriert - wie in deinem geliebten Browser",
|
||||
"name": "Navigation"
|
||||
@ -518,8 +577,41 @@
|
||||
"description": "Erlaubt die Videoqualität über einen Knopf auf dem Video",
|
||||
"name": "Videoqualitätsänderer"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Scrobbling-Unterstützung aktivieren (z.B. für last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Die Authentifizierung von Last.fm ist fehlgeschlagen.\nBlende das Pop-up bis zum nächsten Neustart aus.",
|
||||
"title": "Authentifizierung fehlgeschlagen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API Einstellungen"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "ListenBrainz-Benutzer-Token eintragen"
|
||||
},
|
||||
"scrobble-other-media": "Andere Medien scrobbeln"
|
||||
},
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API-Schlüssel",
|
||||
"api-secret": "Last.fm API-Kennwort"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "ListenBrainz-Benutzer-Token eintragen:",
|
||||
"title": "ListenBrainz-Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Ermöglicht das Festlegen globaler Hotkeys für die Wiedergabe (Abspielen/Pause/Nächster/Vorheriger) + Deaktivieren des Medien-OSD durch Überschreiben der Medientasten + Aktivieren von Strg/CMD + F zum Suchen + Aktivieren der Linux mpris-Unterstützung für Medientasten + Angepasste Tastenkürzel für fortgeschrittene Benutzer.",
|
||||
"description": "Ermöglicht das Festlegen globaler Hotkeys für die Wiedergabe (Abspielen/Pause/Nächster/Vorheriger) + Deaktivieren des Medien-OSD durch Überschreiben der Medientasten + Aktivieren von Strg/CMD + F zum Suchen + Aktivieren der Linux mpris-Unterstützung für Medientasten + Angepasste Tastenkürzel für fortgeschrittene Benutzer",
|
||||
"menu": {
|
||||
"override-media-keys": "Medientasten überschreiben",
|
||||
"set-keybinds": "Globale Liedsteuerung setzen"
|
||||
|
||||
@ -199,9 +199,6 @@
|
||||
"button": "Download"
|
||||
}
|
||||
},
|
||||
"last-fm": {
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"navigation": {
|
||||
"name": "Navigation"
|
||||
},
|
||||
|
||||
@ -191,7 +191,11 @@
|
||||
"previous": "Previous",
|
||||
"quit": "Exit",
|
||||
"restart": "Restart App",
|
||||
"show": "Show window"
|
||||
"show": "Show window",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -200,14 +204,26 @@
|
||||
"menu": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"blur-amount": {
|
||||
"label": "Blur amount",
|
||||
@ -290,7 +306,7 @@
|
||||
"menu": {
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"name": "Crossfade [beta]",
|
||||
"name": "Crossfade [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -409,13 +425,9 @@
|
||||
},
|
||||
"name": "In-App Menu"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Add scrobbling support for Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Adds Lumia Stream support",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Adds lyrics support for most songs",
|
||||
@ -427,6 +439,52 @@
|
||||
"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": {
|
||||
"description": "Next/Back navigation arrows directly integrated in the interface, like in your favorite browser",
|
||||
"name": "Navigation"
|
||||
@ -435,51 +493,6 @@
|
||||
"description": "Remove Google login buttons and links from the interface",
|
||||
"name": "No Google Login"
|
||||
},
|
||||
"music-together": {
|
||||
"name": "Music Together [Beta]",
|
||||
"description": "Share a playlist with others. When the host plays a song, everyone else will hear the same song",
|
||||
"internal": {
|
||||
"unknown-user": "Unknown User",
|
||||
"track-source": "Track Source",
|
||||
"save": "Save"
|
||||
},
|
||||
"menu": {
|
||||
"disconnect": "Disconnect Music Together",
|
||||
"click-to-copy-id": "Copy Host ID",
|
||||
"close": "Close Music Together",
|
||||
"host": "Music Together Host",
|
||||
"join": "Join Music Together",
|
||||
"connected-users": "Connected Users",
|
||||
"empty-user": "No connected users",
|
||||
"set-permission": "Change Control Permission",
|
||||
"status": {
|
||||
"disconnected": "Disconnected",
|
||||
"host": "Connected as Host",
|
||||
"guest": "Connected as Guest"
|
||||
},
|
||||
"permission": {
|
||||
"host-only": "Host Only",
|
||||
"playlist": "Playlist Control",
|
||||
"all": "All Control"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"enter-host": "Enter Host ID"
|
||||
},
|
||||
"toast": {
|
||||
"add-song-failed": "Failed to add song",
|
||||
"remove-song-failed": "Failed to remove song",
|
||||
"closed": "Music Together closed",
|
||||
"disconnected": "Music Together disconnected",
|
||||
"id-copied": "Host ID copied to clipboard",
|
||||
"host-failed": "Failed to host Music Together",
|
||||
"joined": "Joined Music Together",
|
||||
"user-connected": "{{name}} joined Music Together",
|
||||
"user-disconnected": "{{name}} left Music Together",
|
||||
"join-failed": "Failed to join Music Together",
|
||||
"permission-changed": "Music Together permission changed to \"{{permission}}\""
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Display a notification when a song starts playing (interactive notifications are available on Windows)",
|
||||
"menu": {
|
||||
@ -564,8 +577,41 @@
|
||||
"description": "Allows changing the video quality with a button on the video overlay",
|
||||
"name": "Video Quality Changer"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Add scrobbling support (etc. last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"title": "Authentication Failed",
|
||||
"message": "Failed to authenticate with Last.fm\nHide the popup until the next restart."
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"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": {
|
||||
"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.",
|
||||
"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": {
|
||||
"override-media-keys": "Override Media Keys",
|
||||
"set-keybinds": "Set Global Song Controls"
|
||||
@ -639,10 +685,6 @@
|
||||
"visualizer-type": "Visualizer Type"
|
||||
},
|
||||
"name": "Visualizer"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album.",
|
||||
"name": "Album actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
},
|
||||
"language": {
|
||||
"code": "es",
|
||||
"local-name": "Inglés",
|
||||
"local-name": "Español",
|
||||
"name": "Spanish"
|
||||
},
|
||||
"main": {
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Habilitado",
|
||||
"label": "Plugins"
|
||||
"label": "Plugins",
|
||||
"new": "NUEVO"
|
||||
},
|
||||
"view": {
|
||||
"label": "Ver",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Anterior",
|
||||
"quit": "Salir",
|
||||
"restart": "Reiniciar la aplicación",
|
||||
"show": "Mostrar ventana"
|
||||
"show": "Mostrar ventana",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -199,14 +204,26 @@
|
||||
"menu": {
|
||||
"blocker": "Bloqueador"
|
||||
},
|
||||
"name": "Adblocker"
|
||||
"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 colores",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Color del álbum"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Aplica un efecto de iluminación proyectando colores suaves del vídeo en el fondo de la pantalla.",
|
||||
"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",
|
||||
@ -289,7 +306,7 @@
|
||||
"menu": {
|
||||
"advanced": "Avanzado"
|
||||
},
|
||||
"name": "Crossfade [beta]",
|
||||
"name": "Crossfade [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -408,24 +425,66 @@
|
||||
},
|
||||
"name": "Menú de aplicación"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Añade soporte de scrobbling para Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Agrega soporte para Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"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",
|
||||
"name": "Letras 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"
|
||||
@ -518,8 +577,41 @@
|
||||
"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.)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Error al autenticar con Last.fm\nOcultar la ventana emergente hasta el próximo reinicio.",
|
||||
"title": "Error de autenticación"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"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 el OSD multimedia anulando las teclas multimedia, activar Ctrl/CMD + F para buscar, activar la compatibilidad con MPRIS de Linux para las teclas multimedia y teclas de acceso rápido personalizadas para usuarios avanzados.",
|
||||
"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"
|
||||
|
||||
@ -105,7 +105,7 @@
|
||||
"label": "Définir un proxy",
|
||||
"prompt": {
|
||||
"label": "Entrez l'adresse proxy : (laissez vide pour désactiver)",
|
||||
"placeholder": "Exemple: socks5://127.0.0.1:9999",
|
||||
"placeholder": "Exemple: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Définir un proxy"
|
||||
}
|
||||
},
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Activé",
|
||||
"label": "Extensions"
|
||||
"label": "Extensions",
|
||||
"new": "NOUVELLE"
|
||||
},
|
||||
"view": {
|
||||
"label": "Vue",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Précédent",
|
||||
"quit": "Quitter",
|
||||
"restart": "Redémarrer l'application",
|
||||
"show": "Afficher la fenêtre"
|
||||
"show": "Afficher la fenêtre",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music : {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -201,12 +206,24 @@
|
||||
},
|
||||
"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",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Ratio de mélange des couleurs",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.",
|
||||
"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",
|
||||
@ -289,12 +306,12 @@
|
||||
"menu": {
|
||||
"advanced": "Avancé"
|
||||
},
|
||||
"name": "Fondu enchaîné [bêta]",
|
||||
"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-in-duration": "Durée du début du fondu (ms)",
|
||||
"fade-out-duration": "Durée de sortie du fondu (ms)",
|
||||
"fade-scaling": {
|
||||
"label": "Mise à l'échelle du fondu",
|
||||
"linear": "Linéaire",
|
||||
@ -363,7 +380,7 @@
|
||||
"converting": "Conversion…",
|
||||
"done": "Terminé : {{filePath}}",
|
||||
"download-info": "Téléchargement {{artist}} - {{title}} [{{videoId}}",
|
||||
"download-progress": "Télécharger: {{percent}}%",
|
||||
"download-progress": "Téléchargé : {{percent}}%",
|
||||
"downloading": "Télécharge…",
|
||||
"downloading-counter": "Télécharge {{current}}/{{total}}…",
|
||||
"downloading-playlist": "Téléchargement de la playlist \"{{playlistTitle}}\" – {{playlistSize}} chansons ({{playlistId}})",
|
||||
@ -408,13 +425,9 @@
|
||||
},
|
||||
"name": "Menu intégré à l'application"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Ajouter le support du scrobbling pour Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Ajoute la prise en charge de Lumia Stream",
|
||||
"name": "Lumia Stream [bêta]"
|
||||
"name": "Lumia Stream [Bêta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Ajoute la prise en charge des paroles pour la plupart des chansons",
|
||||
@ -426,6 +439,52 @@
|
||||
"fetched-lyrics": "Paroles récupérées pour Genius"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Partage une playlist avec d'autres personnes. Quand l'hôte joue un son, tout les participants entendront le même son",
|
||||
"dialog": {
|
||||
"enter-host": "Entrer l'identifiant de l'hôte"
|
||||
},
|
||||
"internal": {
|
||||
"save": "Enregistrer",
|
||||
"track-source": "Source de la piste audio",
|
||||
"unknown-user": "Utilisateur inconnu"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Copier l'identifiant de l'hôte",
|
||||
"close": "Fermer Music Together",
|
||||
"connected-users": "Utilisateurs connectés",
|
||||
"disconnect": "Déconnecter Music Together",
|
||||
"empty-user": "Aucun utilisateur connecté",
|
||||
"host": "Hôte du Music Together",
|
||||
"join": "Rejoindre le Music Together",
|
||||
"permission": {
|
||||
"all": "Autorisez les invités à contrôler la musique et le player",
|
||||
"host-only": "Seulement l'hôte peut contrôler les playlists et le lecteur",
|
||||
"playlist": "Autoriser les invités à contrôler les playlists"
|
||||
},
|
||||
"set-permission": "Changer les permissions de contrôle",
|
||||
"status": {
|
||||
"disconnected": "Déconnecté",
|
||||
"guest": "Connecté en tant qu'invité",
|
||||
"host": "Connecté en tant qu'hôte"
|
||||
}
|
||||
},
|
||||
"name": "Music Together [BETA]",
|
||||
"toast": {
|
||||
"add-song-failed": "Echec d'ajout de musique",
|
||||
"closed": "Music Together fermé",
|
||||
"disconnected": "Music Together déconnecté",
|
||||
"host-failed": "Echec de l'hébergement du Music Together",
|
||||
"id-copied": "Identifiant de l'hôte copié dans le presse papier",
|
||||
"id-copy-failed": "Echec de la copie de l'identifiant de l'hôte dans le presse papier",
|
||||
"join-failed": "Echec en rejoignant le Music Together",
|
||||
"joined": "Rejoint le Music Together",
|
||||
"permission-changed": "Permission du Music Together changé à \"{{permission}}\"",
|
||||
"remove-song-failed": "Echec du retrait de la piste",
|
||||
"user-connected": "{{name}} à rejoint le Music Together",
|
||||
"user-disconnected": "{{name}} à quitté le Music Together"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "Flèches de navigation Suivant/Retour directement intégrées dans l'interface, comme dans votre navigateur préféré",
|
||||
"name": "Navigation"
|
||||
@ -518,8 +577,41 @@
|
||||
"description": "Permet de changer la qualité vidéo avec un bouton sur la vidéo",
|
||||
"name": "Changeur de qualité vidéo"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Ajouter le support de scrobbling (ex. last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Erreur lors de l'authetification avec Last.fm\nCachez la popup jusqu'au prochain redémarrage.",
|
||||
"title": "Authentification échouée"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Paramètres API de Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Entrer le token utilisateur de ListenBrainz"
|
||||
},
|
||||
"scrobble-other-media": "Scrobbler d'autres médias"
|
||||
},
|
||||
"name": "Scrobble",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Clé API de Last.fm",
|
||||
"api-secret": "API secret de Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Entrez votre token utilisateur ListenBrainz :",
|
||||
"title": "Token ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Permet de définir des raccourcis clavier globaux pour la lecture (lecture/pause/suivant/précédent) + désactiver l'OSD multimédia en remplaçant les touches multimédias + activer Ctrl/CMD + F pour rechercher + activer la prise en charge Linux MPRIS pour les touches multimédias + raccourcis clavier personnalisés pour les utilisateurs avancés",
|
||||
"description": "Permet de définir des raccourcis clavier globaux pour la lecture (lecture/pause/suivant/précédent) + désactiver l'OSD multimédia en remplaçant les touches multimédias + activer Ctrl/CMD + F pour rechercher + activer la prise en charge Linux MPRIS pour les touches multimédias + raccourcis clavier personnalisés pour les utilisateurs avancés.",
|
||||
"menu": {
|
||||
"override-media-keys": "Remplacer les touches multimédias",
|
||||
"set-keybinds": "Définir les contrôles globaux des morceaux"
|
||||
@ -538,7 +630,8 @@
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Passer les musiques que je n'aime pas"
|
||||
"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",
|
||||
|
||||
1
src/i18n/resources/he.json
Normal file
1
src/i18n/resources/he.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
100
src/i18n/resources/hu.json
Normal file
100
src/i18n/resources/hu.json
Normal file
@ -0,0 +1,100 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Nem sikerült futtatni a plugint {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} a {{ms}}ms időpontban végrehajtott",
|
||||
"initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" plugint",
|
||||
"load-all": "Összes bővítmény betöltése",
|
||||
"load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" plugint",
|
||||
"loaded": "Plugin \"{{pluginName}}\" betöltve",
|
||||
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt letölteni",
|
||||
"unloaded": "A \"{{pluginName}}\" bővítményt nem töltötték be"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "hu",
|
||||
"local-name": "Magyar",
|
||||
"name": "Hungarian"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "Betöltés befejezve. DevTools megnyitva"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n betöltve"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "Fogadott parancs a protokollon keresztül: \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "CSS fájl \"{{cssFile}}\" nem létezik, figyelmen kívül hagyva"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Nem reagál hiba!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Alkalmazás gyorsítótárának törlése"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Az ablak a képernyőn kívül próbált renderelni, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "A menü el van rejtve, a megjelenítéshez használd az 'Alt' billentyűt (vagy az 'Escape' billentyűt, ha az alkalmazáson belüli menüt használod)",
|
||||
"message": "A menü elrejtése engedélyezve",
|
||||
"title": "Menü elrejtése engedélyezve"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Később",
|
||||
"restart-now": "Újraindítás most"
|
||||
},
|
||||
"detail": "A \"{{pluginName}}\" plugin újraindítást igényel a hatálybalépéshez",
|
||||
"message": "\"{{pluginName}}\" újra kell indítani",
|
||||
"title": "Újraindítás szükséges"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Kilépés",
|
||||
"relaunch": "Újraindítás",
|
||||
"wait": "Várj"
|
||||
}
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "Frissítések letiltása",
|
||||
"download": "Letöltés",
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "Az új verzió elérhető, és letölthető a {{downloadLink}}",
|
||||
"message": "Új verzió áll rendelkezésre",
|
||||
"title": "Elérhető frissítés"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "Névjegy",
|
||||
"navigation": {
|
||||
"label": "Navigálás",
|
||||
"submenu": {
|
||||
"copy-current-url": "Jelenlegi URL másolása",
|
||||
"go-back": "Vissza",
|
||||
"go-forward": "Előre",
|
||||
"quit": "Kilépés",
|
||||
"restart": "App újraindítása"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "Beállítások",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "Speciális beállítások"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,5 +17,674 @@
|
||||
"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)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Gagal mengotentikasi Last.fm\nSembunyikan munculan hingga muat ulang selanjutnya.",
|
||||
"title": "Otentikasi Gagal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Pengaturan API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Masukkan token pengguna ListenBrainz"
|
||||
},
|
||||
"scrobble-other-media": "Scrobble media lain"
|
||||
},
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Kunci API Last.fm",
|
||||
"api-secret": "Secret API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Masukkan token pengguna ListenBrainz Anda:",
|
||||
"title": "Token ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Izinkan pengaturan pintasan global untuk pemutar (main/jeda/selanjutnya/sebelumnya) dan mematikan OSD media dengan mengesampingkan tombol media, mengaktifkan Ctrl/CMD + F untuk pencarian, mengaktifkan dukungan MPRIS Linux untuk tombol media, dan tombol pintasan kustom untuk pengguna lanjutan",
|
||||
"menu": {
|
||||
"override-media-keys": "Timpa Tombol Media",
|
||||
"set-keybinds": "Atur Pengendali Lagu Global"
|
||||
},
|
||||
"name": "Pintasan (& MPRIS)",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Selanjutnya",
|
||||
"play-pause": "Main / Jeda",
|
||||
"previous": "Sebelumnya"
|
||||
},
|
||||
"label": "Pilih Pintasan Global untuk Pengendali Lagu:",
|
||||
"title": "Pintasan Global"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Lewati lagu yang tidak disukai",
|
||||
"name": "Lewati Lagu yang Tidak Disukai"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "Otomatis lewati bagian hening dari lagu",
|
||||
"name": "Lewati Keheningan"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "Otomatis Melewati bagian yang bukan musik seperti intro/outro atau bagian dari video musik di mana lagu tidak dimainkan",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Kendalikan pemutaran dari bilah alat Windows",
|
||||
"name": "Pengendali Media di Bilah Alat"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "Tambahkan widget TouchBar untuk pengguna macOS",
|
||||
"name": "TouchBar"
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "Integrasi dengan plugin Tuna OBS",
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "Tambahkan tombol untuk beralih antara mode Lagu/Video. secara opsional juga dapat menghapus keseluruhan tab video",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "Perataan",
|
||||
"submenu": {
|
||||
"left": "Kiri",
|
||||
"middle": "Tengah",
|
||||
"right": "Kanan"
|
||||
}
|
||||
},
|
||||
"force-hide": "Paksa hapus tab video",
|
||||
"mode": {
|
||||
"label": "Mode",
|
||||
"submenu": {
|
||||
"custom": "Peralih kustom",
|
||||
"disabled": "Mati",
|
||||
"native": "Peralih bawaan"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Peralih Video",
|
||||
"templates": {
|
||||
"button": "Lagu"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Tambahkan visualisator ke pemutar",
|
||||
"menu": {
|
||||
"visualizer-type": "Tipe Visualisator"
|
||||
},
|
||||
"name": "Visualisator"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
690
src/i18n/resources/is.json
Normal file
690
src/i18n/resources/is.json
Normal file
@ -0,0 +1,690 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Tókst ekki að framkvæma viðbót {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Viðbótin {{pluginName}}::{{contextName}} var framkvæmd í {{ms}}ms",
|
||||
"initialize-failed": "Tókst ekki að frumstilla viðbót \"{{pluginName}}\"",
|
||||
"load-all": "Er að hlaða öllum viðbótum",
|
||||
"load-failed": "Tókst ekki að hlaða viðbótinni \"{{pluginName}}\"",
|
||||
"loaded": "Viðbót \"{{pluginName}}\" hlaðið",
|
||||
"unload-failed": "Tókst ekki að afhlaða viðbótinni \"{{pluginName}}\"",
|
||||
"unloaded": "Viðbótin „{{pluginName}}“ óhlaðin"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "is",
|
||||
"local-name": "Íslenska",
|
||||
"name": "Icelandic"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "Lokið við hleðslu. DevTools opnuð"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n hlaðið"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "Fengið skipun yfir prótókoll: \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "CSS skrá \"{{cssFile}}\" er ekki til, er að hunsa"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Viðbragðslaust Villa!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Er að hreinsa forritabúfera"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Gluggi reyndi að birta utan skjás, gluggastærð={{windowSize}}, skjástærð={{displaySize}}, stöðu={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Valmyndin er falin, notaðu 'Breytingarlykil' til að sýna hana (eða 'Útfararlykil' ef þú notar valmynd í forriti)",
|
||||
"message": "Fela Valmynd er virkjuð",
|
||||
"title": "Fela Valmynd Virkjuð"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Seinna",
|
||||
"restart-now": "Endurræsa Núna"
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" viðbótin þarfnast endurræsingar til að taka gildi",
|
||||
"message": "\"{{pluginName}}\" þarf að endurræsa",
|
||||
"title": "Endurræsa Krafist"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Hætta",
|
||||
"relaunch": "Endurræsa",
|
||||
"wait": "Bíddu"
|
||||
},
|
||||
"detail": "Við biðjumst velvirðingar á óþægindunum! vinsamlegast veldu hvað á að gera:",
|
||||
"message": "Umsóknin svarar ekki",
|
||||
"title": "Gluggi er svarar ekki"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "Gera Uppfærslur Óvirkar",
|
||||
"download": "Sækja",
|
||||
"ok": "Í lagi"
|
||||
},
|
||||
"detail": "Ný útgáfa er fáanleg og hægt er að hlaða henni niður á {{downloadLink}}",
|
||||
"message": "Ný útgáfa er fáanleg",
|
||||
"title": "Uppfærsla Fáanleg"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "Um",
|
||||
"navigation": {
|
||||
"label": "Leiðsögn",
|
||||
"submenu": {
|
||||
"copy-current-url": "Afritaðu núverandi vefslóð",
|
||||
"go-back": "Farðu til baka",
|
||||
"go-forward": "Farðu áfram",
|
||||
"quit": "Útganga",
|
||||
"restart": "Endurræstu Forritið"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "Valkostir",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "Ítarlegravalkostir",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "Endurstilltu skyndiminni forritsins þegar forritið ræsir",
|
||||
"disable-hardware-acceleration": "Slökktu á vélbúnaðarhröðun",
|
||||
"edit-config-json": "Breyta config.json",
|
||||
"override-user-agent": "Hneka Notandaumboðsmanni",
|
||||
"restart-on-config-changes": "Endurræstu við stillingarbreytingar",
|
||||
"set-proxy": {
|
||||
"label": "Stilla umboð",
|
||||
"prompt": {
|
||||
"label": "Sláðu inn umboðsfang: (skilið eftir autt til að slökkva á)",
|
||||
"placeholder": "Dæmi: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Stilla umboð"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "Breyta DevTools"
|
||||
}
|
||||
},
|
||||
"always-on-top": "Alltaf á toppnum",
|
||||
"auto-update": "Sjálfvirk Uppfærsla",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "Valmyndin verður falin við næstu ræsingu, notaðu [Alt] til að sýna hana (eða bakaðu við [`] ef þú notar valmynd í forriti)",
|
||||
"title": "Fela Valmynd Virkjuð"
|
||||
},
|
||||
"label": "Fela Valmynd"
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "Tungumáli verður breytt eftir endurræsingu",
|
||||
"title": "Tungumáli Breytt"
|
||||
},
|
||||
"label": "Tungumál",
|
||||
"submenu": {
|
||||
"to-help-translate": "Viltu hjálpa til við að þýða? Smellið hér"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "Haltu áfram síðasta lagi þegar forritið byrjar",
|
||||
"single-instance-lock": "Eittdæmilás",
|
||||
"start-at-login": "Byrjaðu á innskráningu",
|
||||
"starting-page": {
|
||||
"label": "Upphafssíða",
|
||||
"unset": "Ósetja"
|
||||
},
|
||||
"tray": {
|
||||
"label": "Bakki",
|
||||
"submenu": {
|
||||
"disabled": "Fötluð",
|
||||
"enabled-and-hide-app": "Bakki virkt, og fela forritsgluggi",
|
||||
"enabled-and-show-app": "Virkjað og sýna forrit",
|
||||
"play-pause-on-click": "Spila/hlé við smell"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Sjónrænaraðlögun",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Sjálfgefinn",
|
||||
"force-show": "Þvingaðu sýna",
|
||||
"hide": "Fela",
|
||||
"label": "Líkartakkar"
|
||||
},
|
||||
"remove-upgrade-button": "Fjarlægja uppgræðartakkan",
|
||||
"theme": {
|
||||
"label": "Þema",
|
||||
"submenu": {
|
||||
"import-css-file": "Flytja inn sérsniðna CSS skrá",
|
||||
"no-theme": "Engin þema"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Virkt",
|
||||
"label": "Viðbætur",
|
||||
"new": "NÝR"
|
||||
},
|
||||
"view": {
|
||||
"label": "Útsýni",
|
||||
"submenu": {
|
||||
"force-reload": "Þvingaðu Endurhleðslu",
|
||||
"reload": "Endurhlaða",
|
||||
"reset-zoom": "Raunveruleg Stærð",
|
||||
"toggle-fullscreen": "Breyta Fullskjá",
|
||||
"zoom-in": "Aðdráttur",
|
||||
"zoom-out": "Aðdráttur út"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "Næst",
|
||||
"play-pause": "Spila/Hlé",
|
||||
"previous": "Fyrri",
|
||||
"quit": "Útganga",
|
||||
"restart": "Endurræstu Forritið",
|
||||
"show": "Sýna glugga",
|
||||
"tooltip": {
|
||||
"default": "YouTube Tónlist",
|
||||
"with-song-info": "YouTube Tónlist: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"adblocker": {
|
||||
"description": "Lokaðu fyrir allar auglýsingar og rakningar úr kassanum",
|
||||
"menu": {
|
||||
"blocker": "Blokkari"
|
||||
},
|
||||
"name": "Auglýsingablokkari"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Bætir Ódíslika, Mislíkt, Líkt, og Ólíkt til að nota þetta á öll lög á spilunarlista eða albúm",
|
||||
"name": "Albúmsaðgerðir"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Beitir kraftmikið þema og sjónrænum áhrifum sem byggjast á litavali albúmsins",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Litablöndunarhlutfall",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Albúmslitaþema"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Beitir lýsingaráhrifum með því að varpa mildum litum úr myndbandinu í bakgrunn skjásins",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Þokuupphæð",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} pixlum"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "Stuðpúði",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "Ógegnsæi",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Gæði",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} pixlum"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "Sæði",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "Slétt umskipti",
|
||||
"submenu": {
|
||||
"during": "Meðan á {{interpolationTime}} s"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Er að nota fullskjár"
|
||||
}
|
||||
},
|
||||
"name": "Umhverfishamur"
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Notaðu þjöppun á hljóð (lækkar hljóðstyrk háværustu hluta merkis og hækkar hljóðstyrk í mýkstu hlutunum)",
|
||||
"name": "Hljóðþjöppu"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Gerir leiðsögustikuna gagnsæja og óskýrt",
|
||||
"name": "Þoka Leiðsagnarstika"
|
||||
},
|
||||
"bypass-age-restrictions": {
|
||||
"description": "Framhjá aldursstaðfestingu YouTube",
|
||||
"name": "Farið Framhjá Aldurstakmörkunum"
|
||||
},
|
||||
"captions-selector": {
|
||||
"description": "Skjátextavali fyrir YouTube Tónlist hljóðrásir",
|
||||
"menu": {
|
||||
"autoload": "Veldu sjálfkrafa síðast notaða myndatexta",
|
||||
"disable-captions": "Engir skjátextar sjálfgefið"
|
||||
},
|
||||
"name": "Yfirskriftarval",
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"label": "Núverandi tungumál skjátexta: {{language}}",
|
||||
"none": "Enginn",
|
||||
"title": "Veldu tungumál fyrir skjátexta"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"title": "Opnaðu skjátextavali"
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
"description": "Stilltu hliðarstikuna alltaf í þétta stillingu",
|
||||
"name": "Fyrirferðarlítillhliðarstika"
|
||||
},
|
||||
"crossfade": {
|
||||
"description": "Krossfæra á milli lög",
|
||||
"menu": {
|
||||
"advanced": "Háþróaður"
|
||||
},
|
||||
"name": "Krossfæra [Prófunarútgáfa]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
"fade-in-duration": "Dvína í lengd (ms)",
|
||||
"fade-out-duration": "Dvína út lengd (ms)",
|
||||
"fade-scaling": {
|
||||
"label": "Fölunarskala",
|
||||
"linear": "Línulegt",
|
||||
"logarithmic": "Logaritmískt"
|
||||
},
|
||||
"seconds-before-end": "Krossfæra N sekúndum fyrir enda"
|
||||
},
|
||||
"title": "Krossfæravalkosti"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable-autoplay": {
|
||||
"description": "Gerir lag að byrja í \"hlé\" ham",
|
||||
"menu": {
|
||||
"apply-once": "Á aðeins við ræsingu"
|
||||
},
|
||||
"name": "Slökkva á sjálfvirkri spilun"
|
||||
},
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "Reyndi að tengja við virka tengingu",
|
||||
"connected": "Tengdur við Discord",
|
||||
"disconnected": "Aftengdur við Discord"
|
||||
},
|
||||
"description": "Sýndu vinum þínum hvað þú hlustar á með Rík Nærvera",
|
||||
"menu": {
|
||||
"auto-reconnect": "Sjálfvirk endurtengja",
|
||||
"clear-activity": "Hreinsa virkni",
|
||||
"clear-activity-after-timeout": "Hreinsa virkni eftir tímamörk",
|
||||
"connected": "Tengt",
|
||||
"disconnected": "Aftengt",
|
||||
"hide-duration-left": "Fela tímalengd til vinstri",
|
||||
"hide-github-button": "Fela GitHub tengilhnapp",
|
||||
"play-on-youtube-music": "Spilaðu á YouTube Tónlist",
|
||||
"set-inactivity-timeout": "Stilltu tímamörk fyrir óvirkni"
|
||||
},
|
||||
"name": "Discord Rík Nærvera",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "Sláðu inn óvirknitíma eftir sekúndur:",
|
||||
"title": "Stilltu tímamörk fyrir óvirkni"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"error": {
|
||||
"buttons": {
|
||||
"ok": "Í lagi"
|
||||
},
|
||||
"message": "Úff! Afsakið, niðurhal mistókst…",
|
||||
"title": "Villa við niðurhal!"
|
||||
},
|
||||
"start-download-playlist": {
|
||||
"buttons": {
|
||||
"ok": "Í lagi"
|
||||
},
|
||||
"detail": "({{playlistSize}} lög)",
|
||||
"message": "Að sækja lagalista {{playlistTitle}}",
|
||||
"title": "Niðurhal byrjað"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Umbreyting: {{percent}}%",
|
||||
"converting": "Er að umbreytir…",
|
||||
"done": "Búið: {{filePath}}",
|
||||
"download-info": "Er að niðurhal {{artist}} - {{title}} [{{videoId}}",
|
||||
"download-progress": "Niðurhal: {{percent}}%",
|
||||
"downloading": "Er að niðurhal…",
|
||||
"downloading-counter": "Er að niðurhal {{current}}/{{total}}…",
|
||||
"downloading-playlist": "Er að niðurhal spilunarlisti \"{{playlistTitle}}\" - {{playlistSize}} lög ({{playlistId}})",
|
||||
"error-while-downloading": "Villa við niðurhal \"{{author}} - {{title}}\": {{error}}",
|
||||
"folder-already-exists": "Mappan {{playlistFolder}} er þegar til",
|
||||
"getting-playlist-info": "Sækir upplýsingar um spilunarlista…",
|
||||
"loading": "Er að hlaða.…",
|
||||
"playlist-has-only-one-song": "Spilunarlista hefur aðeins eitt atriði, það er verið að hlaða því niður beint",
|
||||
"playlist-id-not-found": "Ekkert auðkenni spilunarlista fannst",
|
||||
"playlist-is-empty": "Spilunarlistinn er tómur",
|
||||
"playlist-is-mix-or-private": "Villa við að fá upplýsingar um spilunarlista: Gakktu úr skugga um að þetta sé ekki einkaspilunarlisti eða \"Mixað fyrir þig\"\n\n{{error}}",
|
||||
"preparing-file": "Er að undirbúa skrá…",
|
||||
"saving": "Er að vista…",
|
||||
"trying-to-get-playlist-id": "Er að reyna að fá auðkenni spilunarlista: {{playlistId}}",
|
||||
"video-id-not-found": "Myndband fannst ekki",
|
||||
"writing-id3": "Að skrifa ID3 tög…"
|
||||
}
|
||||
},
|
||||
"description": "Niðurhalar MP3 / upprunahljóði beint úr viðmótinu",
|
||||
"menu": {
|
||||
"choose-download-folder": "Veldu niðurhalsmöppu",
|
||||
"download-playlist": "Sækja spilunarlista",
|
||||
"presets": "Forstillingar",
|
||||
"skip-existing": "Slepptu núverandi skrám"
|
||||
},
|
||||
"name": "Niðurhalari",
|
||||
"renderer": {
|
||||
"can-not-update-progress": "Ekki er hægt að uppfæra framvindu"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Sækja"
|
||||
}
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Gerir hljóðstyrkssleðann veldisvísis svo það er auðveldara að velja lægra hljóðstyrk.",
|
||||
"name": "Veldibundiðrúmmál"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Gefur valmyndastikum glæsilegt, dökkt eða albúmslitsjáðu",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "Fela DOM gluggastýringar"
|
||||
},
|
||||
"name": "Valmynd í forriti"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Bætir við Lumia Stream stuðningi",
|
||||
"name": "Lumia Stream [Prófunarútgáfa]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Bætir stuðningi við texta fyrir flest lög",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Rómaníseraðir Söngtexti"
|
||||
},
|
||||
"name": "Söngtexti Snilld",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "Sótt söngtexti fyrir Snilld"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Deila spilunarlista með öðrum. Þegar gestgjafinn spilar lag munu allir aðrir heyra sama lagið",
|
||||
"dialog": {
|
||||
"enter-host": "Sláðu inn auðkenni gestgjafa"
|
||||
},
|
||||
"internal": {
|
||||
"save": "Vista",
|
||||
"track-source": "Lagsuppspretta",
|
||||
"unknown-user": "Óþekktur notandi"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Afritaðu hýsingarauðkenni",
|
||||
"close": "Lokaðu Tónlist Saman",
|
||||
"connected-users": "Tengdir Notendur",
|
||||
"disconnect": "Aftengdu Tónlist Saman",
|
||||
"empty-user": "Engir tengdir notendur",
|
||||
"host": "Tónlist Saman Gestgjafi",
|
||||
"join": "Vertu með Tónlist Saman",
|
||||
"permission": {
|
||||
"all": "Leyfðu gestum að stjórna spilunarlista og spilara",
|
||||
"host-only": "Aðeins gestgjafi getur stjórnað spilunarlista og spilara",
|
||||
"playlist": "Leyfðu gestum að stjórna spilunarlista"
|
||||
},
|
||||
"set-permission": "Breyta Stjórnunarheimild",
|
||||
"status": {
|
||||
"disconnected": "Aftengt",
|
||||
"guest": "Tengdur sem Gestur",
|
||||
"host": "Tengdur sem Gestgjafi"
|
||||
}
|
||||
},
|
||||
"name": "Tónlist Saman [Prófunarútgáfa]",
|
||||
"toast": {
|
||||
"add-song-failed": "Mistókst að bæta við lagi",
|
||||
"closed": "Tónlist Saman lokað",
|
||||
"disconnected": "Tónlist Saman aftengt",
|
||||
"host-failed": "Mistókst að hýsa Tónlist Saman",
|
||||
"id-copied": "Gestgjafaauðkenni afritað á klippiborð",
|
||||
"id-copy-failed": "Mistókst að afrita Hýsingarauðkenni á klippiborð",
|
||||
"join-failed": "Ekki tókst að taka þátt í Tónlist Saman",
|
||||
"joined": "Tengd Tónlist Saman",
|
||||
"permission-changed": "Tónlist Saman leyfi breytt í \"{{permission}}\"",
|
||||
"remove-song-failed": "Tókst ekki að fjarlægja lag",
|
||||
"user-connected": "{{name}} tengd Tónlist Saman",
|
||||
"user-disconnected": "{{name}} fór frá Tónlist Saman"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "Næsta/Til baka leiðsagnarörvar beint samþættar í viðmótinu, eins og í uppáhalds vafranum þínum",
|
||||
"name": "Leiðsögn"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "Fjarlægðu Google innskráningarhnappa og tengla úr viðmótinu",
|
||||
"name": "Engin Google innskráning"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Birta tilkynningu þegar lag byrjar að spila (gagnvirkartilkynningar eru fáanlegar á Windows)",
|
||||
"menu": {
|
||||
"interactive": "Gagnvirkartilkynningar",
|
||||
"interactive-settings": {
|
||||
"label": "Gagnvirkarstillingar",
|
||||
"submenu": {
|
||||
"hide-button-text": "Fela hnappatexta",
|
||||
"refresh-on-play-pause": "Endurnýjaðu í Spilun/Hlé",
|
||||
"tray-controls": "Opna/loka á bakka smellur"
|
||||
}
|
||||
},
|
||||
"priority": "Tilkynningaforgangur",
|
||||
"toast-style": "Ristað brauð stíl",
|
||||
"unpause-notification": "Sýna tilkynningu þegar ekki er gert hlé"
|
||||
},
|
||||
"name": "Tilkynningar"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"description": "Gerir kleift að skipta forritinu yfir í mynd-í-mynd stillingu",
|
||||
"menu": {
|
||||
"always-on-top": "Alltaf á toppnum",
|
||||
"hotkey": {
|
||||
"label": "Flýtilykil",
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "Flýtilykil"
|
||||
},
|
||||
"label": "Veldu flýtilykil til að skipta mynd-í-mynd",
|
||||
"title": "Mynd-í-mynd Flýtilykil"
|
||||
}
|
||||
},
|
||||
"save-window-position": "Vista gluggastöðu",
|
||||
"save-window-size": "Vista gluggastærð",
|
||||
"use-native-pip": "Notaðu innbyggða PiP í vafra"
|
||||
},
|
||||
"name": "Mynd-í-mynd",
|
||||
"templates": {
|
||||
"button": "Mynd-í-mynd"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"description": "Hlustaðu hratt, hlustaðu hægt! Bætir við sleða sem stjórnar lagahraðanum",
|
||||
"name": "Spilunarhraði",
|
||||
"templates": {
|
||||
"button": "Hraði"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"description": "Stjórnaðu hljóðstyrknum nákvæmlega með músarhjóli/hraðtökkum, með sérsniðnum HUD og sérsniðnum hljóðstyrksþrepum",
|
||||
"menu": {
|
||||
"arrows-shortcuts": "Staðbundnar Örvatakkar Stjórna",
|
||||
"custom-volume-steps": "Stilltu Sérsniðin Hljóðstyrksskref",
|
||||
"global-shortcuts": "Alþjóðlegarflýtilyklar"
|
||||
},
|
||||
"name": "Nákvæmshljóðstyrkur",
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "Minnka Hljóðstyrk",
|
||||
"increase": "Auka Hljóðstyrk"
|
||||
},
|
||||
"label": "Veldu Alþjóðleghljóðstyrklyklabindingar:",
|
||||
"title": "Alþjóðleghljóðstyrklyklabindingar"
|
||||
},
|
||||
"volume-steps": {
|
||||
"label": "Veldu Hljóðstyrksauka/Minnka Skref",
|
||||
"title": "Hljóðstyrksskref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quality-changer": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "Núverandi Gæði: {{quality}}",
|
||||
"message": "Veldu Myndbandsgæði:",
|
||||
"title": "Veldu Myndbandsgæði"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Leyfir að breyta myndbandgæðum með hnappi á myndbandsyfirlaginu",
|
||||
"name": "Myndbandgæðisbreyting"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Bæta við scrobbling stuðningi (osv. last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Mistókst að auðkenna með Last.fm\nFela sprettigluggann þar til næstu endurræsingu.",
|
||||
"title": "Auðkenning Mistókst"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API Stillingar"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Sláðu inn ListenBrainz notandalykilinn"
|
||||
},
|
||||
"scrobble-other-media": "Scrobble aðra fjölmiðla"
|
||||
},
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API lykill",
|
||||
"api-secret": "Last.fm API leyndarmál"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Sláðu inn ListenBrainz notandatáknið þitt:",
|
||||
"title": "ListenBrainz tákn"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Leyfir að stilla alþjóðlegaflýtilykla fyrir spilun (spila/gera hlé/næsta/fyrri) og slökkva á OSD miðla með því að hnekkja miðlunartökkum, kveikja á Ctrl/CMD + F til að leita, kveikja á Linux MPRIS stuðningi fyrir miðlunarlykla og sérsniðna flýtilykla fyrir lengra komna notendur",
|
||||
"menu": {
|
||||
"override-media-keys": "Hneka Fjölmiðlalykla",
|
||||
"set-keybinds": "Stilltu Alþjóðlegslagastýringar"
|
||||
},
|
||||
"name": "Flýtileiðir (og MPRIS)",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Næst",
|
||||
"play-pause": "Spila / Hlé",
|
||||
"previous": "Fyrri"
|
||||
},
|
||||
"label": "Veldu Alþjóðlegslyklabind fyrir Lagastýringu:",
|
||||
"title": "Alþjóðlegslyklabindingar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Sleppir mislíkaði lög",
|
||||
"name": "Slepptu Mislíkaði Lög"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "Slepptu sjálfkrafa þagnarköflum í lögum",
|
||||
"name": "Slepptu Þögnum"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "Sleppur sjálfkrafa hlutum sem ekki eru tónlist, eins og inngangur/lok eða hlutar af tónlistarmyndböndum þar sem lag er ekki að spila",
|
||||
"name": "Styrktarblokk"
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Stjórnaðu spilun frá Windows verkefnastikunni þinni",
|
||||
"name": "Miðlunarstýringarverkefnastikunnar"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "Bætir við Snertistiku græju fyrir macOS notendur",
|
||||
"name": "Snertistiku"
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "Samþætting við OBS viðbót Tuna",
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "Bætir við hnappi til að skipta á milli myndbands/lagshams. Getur einnig valfrjálst fjarlægt allan myndbandsflipann",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "Jöfnun",
|
||||
"submenu": {
|
||||
"left": "Vinstri",
|
||||
"middle": "Miðja",
|
||||
"right": "Rétt"
|
||||
}
|
||||
},
|
||||
"force-hide": "Þvingaðu fjarlægja myndbandsflipann",
|
||||
"mode": {
|
||||
"label": "Hamur",
|
||||
"submenu": {
|
||||
"custom": "Sérsniðinn rofi",
|
||||
"disabled": "Fötluð",
|
||||
"native": "Innfæddsrofi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Myndbandsrofi",
|
||||
"templates": {
|
||||
"button": "Lag"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Bætir sýndarstýringar við spilarann",
|
||||
"menu": {
|
||||
"visualizer-type": "Sýndarstýringartegund"
|
||||
},
|
||||
"name": "Sýndarstýringar"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Attivato",
|
||||
"label": "Plugin"
|
||||
"label": "Plugin",
|
||||
"new": "NUOVO"
|
||||
},
|
||||
"view": {
|
||||
"label": "Visualizzazione",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Precedente",
|
||||
"quit": "Esci",
|
||||
"restart": "Riavvia l'app",
|
||||
"show": "Mostra finestra"
|
||||
"show": "Mostra finestra",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -199,14 +204,26 @@
|
||||
"menu": {
|
||||
"blocker": "Blocco"
|
||||
},
|
||||
"name": "Adblocker"
|
||||
"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",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Percentiuale colore",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Tema abbinato a colore album"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Applica un effetto di illuminazione proiettando i colori delicati del video sullo sfondo dello schermo.",
|
||||
"description": "Applica un effetto di illuminazione proiettando i colori delicati del video sullo sfondo dello schermo",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Intensità sfocatura",
|
||||
@ -408,13 +425,9 @@
|
||||
},
|
||||
"name": "Menu In-App"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Aggiungi supporto per lo scrobbling su Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Aggiungi supporto per Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Aggiunge il supporto dei testi per la maggior parte delle canzoni",
|
||||
@ -426,6 +439,52 @@
|
||||
"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"
|
||||
@ -518,8 +577,41 @@
|
||||
"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.)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Impossibile autenticarsi con Last.fm\nNascondi il popup fino al prossimo riavvio.",
|
||||
"title": "Autenticazione fallita"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Impostazione Last.fm API"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Inserire il token utente per ListenBrainz"
|
||||
},
|
||||
"scrobble-other-media": "Scrobble altri media"
|
||||
},
|
||||
"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.",
|
||||
"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"
|
||||
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "有効",
|
||||
"label": "プラグイン"
|
||||
"label": "プラグイン",
|
||||
"new": "新着"
|
||||
},
|
||||
"view": {
|
||||
"label": "表示",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "前の曲",
|
||||
"quit": "終了",
|
||||
"restart": "アプリを再起動",
|
||||
"show": "ウィンドウを表示"
|
||||
"show": "ウィンドウを表示",
|
||||
"tooltip": {
|
||||
"default": "YouTube ミュージック",
|
||||
"with-song-info": "YouTube ミュージック: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -199,14 +204,26 @@
|
||||
"menu": {
|
||||
"blocker": "ブロッカー"
|
||||
},
|
||||
"name": "Adblocker"
|
||||
"name": "広告ブロッカー"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "「Undislike(嫌いではない)」「Dislike(嫌い)」「Like(好き)」「Unlike(好きではない)」ボタンを追加し、プレイリストやアルバム内のすべての曲にこれらを適用します",
|
||||
"name": "アルバムアクション"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "アルバムカバーの色をベースにして動的テーマと視覚効果を適用します",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "カラー混合比率",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "アルバムカラーベースのテーマ"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "動画の間接照明を画面背景に投射します。",
|
||||
"description": "動画の内容に合った淡い色に画面の背景を変化させるライティング効果を適応します",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "ぼかしの強さ",
|
||||
@ -239,9 +256,9 @@
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "スムーズな切り替えり",
|
||||
"label": "スムーズな切り替え",
|
||||
"submenu": {
|
||||
"during": "{{interpolationTime}}秒間切り替えり"
|
||||
"during": "{{interpolationTime}}秒間切り替え"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
@ -289,7 +306,7 @@
|
||||
"menu": {
|
||||
"advanced": "詳細設定"
|
||||
},
|
||||
"name": "クロスフェード[ベータ]",
|
||||
"name": "クロスフェード [ベータ]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -408,10 +425,6 @@
|
||||
},
|
||||
"name": "アプリ内メニュー"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Last.fmのscrobblingサポートを追加",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Lumia Streamのサポートを追加",
|
||||
"name": "Lumia Stream [ベータ]"
|
||||
@ -426,6 +439,52 @@
|
||||
"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": {
|
||||
"description": "ブラウザの戻る・進むボタンのようにUIからコントロールできるボタン",
|
||||
"name": "ナビゲーション"
|
||||
@ -518,6 +577,39 @@
|
||||
"description": "ビデオオーバーレイのボタンを使用してビデオ品質を変更できるようにします",
|
||||
"name": "ビデオ品質チェンジャー"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "スクロブリング対応を追加します(例:last.fm、Listenbrainzなど)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Last.fm の認証に失敗しました\n次の再起動までポップアップは非表示になります。",
|
||||
"title": "認証に失敗"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API 設定"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "ListenBrainzユーザートークンを入力してください"
|
||||
},
|
||||
"scrobble-other-media": "他のメディアをScrobbleする"
|
||||
},
|
||||
"name": "スクロブラー",
|
||||
"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": {
|
||||
@ -551,7 +643,7 @@
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Windowsタスクバーから再生をコントロール",
|
||||
"name": "Taskbar Media Control"
|
||||
"name": "タスクバーメディアコントロール"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "masOSユーザー向けにTouchBarウィジェットを追加",
|
||||
|
||||
@ -191,7 +191,11 @@
|
||||
"previous": "이전",
|
||||
"quit": "종료",
|
||||
"restart": "앱 재시작",
|
||||
"show": "창 표시"
|
||||
"show": "창 표시",
|
||||
"tooltip": {
|
||||
"default": "유튜브 뮤직",
|
||||
"with-song-info": "유튜브 뮤직: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -203,15 +207,23 @@
|
||||
"name": "광고 차단기"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "좋아요, 싫어요 버튼을 추가하고, 결과를 재생 목록 또는 앨범의 모든 노래에 적용합니다.",
|
||||
"description": "좋아요, 싫어요 버튼을 추가하고, 결과를 재생 목록 또는 앨범의 모든 노래에 적용합니다",
|
||||
"name": "앨범 액션"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "앨범 색상 팔레트를 기반으로 동적 테마 및 시각 효과를 적용합니다",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "색상 혼합 비율",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "앨범 컬러 기반 테마"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "영상의 간접 조명을 화면 배경에 투사합니다.",
|
||||
"description": "영상의 간접 조명을 화면 배경에 투사합니다",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "흐림 효과 강도",
|
||||
@ -413,10 +425,6 @@
|
||||
},
|
||||
"name": "인앱 메뉴"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Last.fm에 대한 스크러블 지원을 추가합니다",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Lumia Stream 지원을 추가합니다",
|
||||
"name": "Lumia Stream [베타]"
|
||||
@ -432,48 +440,49 @@
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"name": "Music Together [베타]",
|
||||
"description": "여러명과 함께 플레이리스트를 공유합니다. 호스트가 음악을 재생하면, 다른 사용자들도 같은 노래를 들을 수 있습니다",
|
||||
"internal": {
|
||||
"unknown-user": "알 수 없는 사용자",
|
||||
"track-source": "재생 중인 트랙 출처",
|
||||
"save": "저장"
|
||||
},
|
||||
"menu": {
|
||||
"disconnect": "Music Together 연결 끊기",
|
||||
"click-to-copy-id": "호스트 아이디 복사",
|
||||
"close": "Music Together 닫기",
|
||||
"host": "Music Together 호스트",
|
||||
"join": "Music Together 참여",
|
||||
"connected-users": "연결된 사용자",
|
||||
"empty-user": "연결된 사용자 없음",
|
||||
"set-permission": "제어 권한 변경",
|
||||
"status": {
|
||||
"disconnected": "연결 끊김",
|
||||
"host": "호스트로 연결됨",
|
||||
"guest": "게스트로 연결됨"
|
||||
},
|
||||
"permission": {
|
||||
"host-only": "호스트만 제어 가능",
|
||||
"playlist": "재생목록 제어 가능",
|
||||
"all": "모두 제어 가능"
|
||||
}
|
||||
},
|
||||
"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": "노래 추가 실패",
|
||||
"remove-song-failed": "노래 제거 실패",
|
||||
"closed": "Music Together가 닫혔습니다",
|
||||
"disconnected": "Music Together 연결이 끊어졌습니다",
|
||||
"id-copied": "호스트 아이디가 클립보드에 복사되었습니다",
|
||||
"host-failed": "Music Together를 열 수 없습니다",
|
||||
"joined": "Music Together에 참여했습니다",
|
||||
"user-connected": "{{name}}님이 Music Together에 참여했습니다",
|
||||
"user-disconnected": "{{name}}님이 Music Together에서 나갔습니다",
|
||||
"id-copied": "호스트 아이디가 클립보드에 복사되었습니다",
|
||||
"id-copy-failed": "호스트 ID를 클립보드에 복사하지 못했습니다",
|
||||
"join-failed": "Music Together에 참여할 수 없습니다",
|
||||
"permission-changed": "Music Together 제어 권한이 \"{{permission}}\" 변경되었습니다"
|
||||
"joined": "Music Together에 참여했습니다",
|
||||
"permission-changed": "Music Together 제어 권한이 \"{{permission}}\"(으)로 변경되었습니다",
|
||||
"remove-song-failed": "노래 제거 실패",
|
||||
"user-connected": "{{name}}님이 Music Together에 참여했습니다",
|
||||
"user-disconnected": "{{name}}님이 Music Together에서 나갔습니다"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
@ -568,8 +577,41 @@
|
||||
"description": "영상 오버레이의 버튼으로 영상 품질을 변경할 수 있습니다",
|
||||
"name": "영상 품질 체인저"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "스크로블링 지원을 추가합니다 (예: last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Last.fm 인증에 실패했습니다\n다음에 다시 시작할 때까지 팝업을 숨깁니다.",
|
||||
"title": "인증 실패"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API 설정"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "ListenBrainz 유저 토큰 입력"
|
||||
},
|
||||
"scrobble-other-media": "다른 미디어 스크로블하기"
|
||||
},
|
||||
"name": "스크로블러",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API 키",
|
||||
"api-secret": "Last.fm API 비밀 키"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "ListenBrainz 유저 토큰을 입력하세요:",
|
||||
"title": "ListenBrainz 토큰"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "재생을 위한 전역 단축키 설정 허용 (재생/일시 정지/다음/이전), 미디어 키를 재정의하여 미디어 OSD 비활성화, Ctrl/CMD + F 검색을 활성화, 미디어키 지원을 위해 리눅스 MPRIS 지원 활성화, 고급 사용자를 위한 사용자 지정 단축키 지원 추가.",
|
||||
"description": "재생을 위한 전역 단축키 설정 허용 (재생/일시 정지/다음/이전), 미디어 키를 재정의하여 미디어 OSD 비활성화, Ctrl/CMD + F 검색을 활성화, 미디어키 지원을 위해 리눅스 MPRIS 지원 활성화, 고급 사용자를 위한 사용자 지정 단축키 지원 추가",
|
||||
"menu": {
|
||||
"override-media-keys": "미디어 키 재정의",
|
||||
"set-keybinds": "전역 노래 제어 설정"
|
||||
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Įjungta",
|
||||
"label": "Įskiepiai"
|
||||
"label": "Įskiepiai",
|
||||
"new": "NAUJIENA"
|
||||
},
|
||||
"view": {
|
||||
"label": "Vaizdas",
|
||||
@ -289,7 +290,7 @@
|
||||
"menu": {
|
||||
"advanced": "Išplėstinė"
|
||||
},
|
||||
"name": "Perliejimas [beta]",
|
||||
"name": "Perliejimas [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -408,13 +409,9 @@
|
||||
},
|
||||
"name": "Programos Meniu"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Pridėkite Last.fm scrobble palaikymą",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Prideda \"Lumia Stream\" palaikymą",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Prideda daugumai dainių žodžių tekstus",
|
||||
|
||||
@ -36,15 +36,15 @@
|
||||
"details": "Svarer ikke\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Tømmer programhurtiglager"
|
||||
"clearing-cache-after-20s": "Tømmer programhurtigbuffer"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Prøvde å tegne vindu utenfor skjermen. Størrelse={{windowSize}}, skjermstørrelse={{displaySize}}, posisjon={{position}}"
|
||||
"tried-to-render-offscreen": "Prøvde å tegne vindu utenfor skjermen, størrelse={{windowSize}}, skjermstørrelse={{displaySize}}, posisjon={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Menyen er skjult. Bruk «Alt» for å vise den, (ller «Esc» for å bruke menyen i programmet).",
|
||||
"detail": "Menyen er skjult, bruk 'Alt' for å vise den (eller 'Escape' for å bruke menyen i programmet)",
|
||||
"message": "Meny skjult",
|
||||
"title": "Meny vist"
|
||||
},
|
||||
@ -85,7 +85,7 @@
|
||||
"submenu": {
|
||||
"copy-current-url": "Kopier nåværende nettadresse",
|
||||
"go-back": "Tilbake",
|
||||
"go-forward": "Forover",
|
||||
"go-forward": "Framover",
|
||||
"quit": "Avslutt",
|
||||
"restart": "Programomstart"
|
||||
}
|
||||
@ -96,7 +96,7 @@
|
||||
"advanced-options": {
|
||||
"label": "Avanserte alternativer",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "Tilbakestill programhurtiglager når programmet startes",
|
||||
"auto-reset-app-cache": "Tilbakestill programhurtigbuffer når programmet startes",
|
||||
"disable-hardware-acceleration": "Skru av maskinvareakselerasjon",
|
||||
"edit-config-json": "Rediger config.json",
|
||||
"override-user-agent": "Overstyr brukeragent",
|
||||
@ -287,7 +287,7 @@
|
||||
"menu": {
|
||||
"advanced": "Avansert"
|
||||
},
|
||||
"name": "Overgang [beta]",
|
||||
"name": "Overgang [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -406,13 +406,9 @@
|
||||
},
|
||||
"name": "Meny i programmet"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Legg til lyttestatistikkstøtte for Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Legger til Lumia Stream-støtte",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Gir sangtekststøtte for de fleste spor",
|
||||
|
||||
51
src/i18n/resources/nl.json
Normal file
51
src/i18n/resources/nl.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Kan plug-in {{pluginName}}::{{contextName}} niet uitvoeren",
|
||||
"executed-at-ms": "Plug-in {{pluginName}}::{{contextName}} uitgevoerd in {{ms}}ms",
|
||||
"initialize-failed": "Kan plug-in \"{{pluginName}}\" niet laden",
|
||||
"load-all": "Alle plug-ins laden",
|
||||
"load-failed": "Kan plug-in \"{{pluginName}}\" niet laden",
|
||||
"loaded": "Plug-in \"{{pluginName}}\" geladen",
|
||||
"unload-failed": "Kan plug-in \"{{pluginName}}\" niet verwijderen",
|
||||
"unloaded": "Plug-in \"{{pluginName}}\" geladen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "nl",
|
||||
"local-name": "Nederlands",
|
||||
"name": "Dutch"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "Klaar met laden, DevTools geopend"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n geladen"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "Ontvangen commando via protocol: \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "CSS bestand \"{{cssFile}}\" niet gevonden"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Niet reagerend door fout:\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "App-cache wissen"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Venster probeerde buiten het scherm te renderen, venstergrootte={{windowSize}}, schermgrootte={{displaySize}}, positie={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Menu is verborgen, gebruik 'Alt' om het te tonen (of 'Escape' als u het In-App Menu gebruikt)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Włączone",
|
||||
"label": "Wtyczki"
|
||||
"label": "Wtyczki",
|
||||
"new": "NOWOŚĆ"
|
||||
},
|
||||
"view": {
|
||||
"label": "Widok",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Poprzedni",
|
||||
"quit": "Wyjdź",
|
||||
"restart": "Uruchom ponownie aplikację",
|
||||
"show": "Pokaż okno"
|
||||
"show": "Pokaż okno",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "{{title}} (autorstwa {{artist}}) - YT Music"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -201,12 +206,24 @@
|
||||
},
|
||||
"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": {
|
||||
"label": "Intensywność koloru",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Motyw kolorów albumu"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Stosuje efekt świetlny, rzucając delikatne kolory z wideo na tło ekranu.",
|
||||
"description": "Stosuje efekt świetlny, rzucając delikatne kolory z wideo na tło ekranu",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Ilość rozmycia",
|
||||
@ -289,7 +306,7 @@
|
||||
"menu": {
|
||||
"advanced": "Zaawansowane"
|
||||
},
|
||||
"name": "Przenikanie [Beta]",
|
||||
"name": "Płynne przejście [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -408,13 +425,9 @@
|
||||
},
|
||||
"name": "Menu w aplikacji"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Dodanie obsługi scrobblingu dla Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Dodaje obsługę Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Dodaje obsługę tekstów dla większości piosenek",
|
||||
@ -426,6 +439,52 @@
|
||||
"fetched-lyrics": "Tekst dostarczony przez Genius"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Pozwala na udostępnianie listy odtwarzania z możliwością słuchania tego samego utworu co host",
|
||||
"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": "Udostępnij tą listę odtwarzania",
|
||||
"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"
|
||||
@ -518,8 +577,41 @@
|
||||
"description": "Umożliwia zmianę jakości wideo za pomocą przycisku na nakładce wideo",
|
||||
"name": "Zmieniacz jakości wideo"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Umożliwia scrobbling utworów do m.in. last.fm lub Listenbrainz",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Podczas autoryzowania z last.fm wystąpił błąd.\nSchowaj pop-up aż do następnego uruchomienia.",
|
||||
"title": "Podczas autoryzowania wystąpił błąd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Ustawienia API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Podaj token użytkownika ListenBrainz"
|
||||
},
|
||||
"scrobble-other-media": "Scrobbluj pozostałe multimedia"
|
||||
},
|
||||
"name": "Scrobblowanie",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "klucz API Last.fm",
|
||||
"api-secret": "Sekretny klucz Last.fm API (\"secret key\")"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Podaj swój token użytkownika ListenBrainz:",
|
||||
"title": "Token ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Umożliwia ustawienie globalnych skrótów klawiszowych do odtwarzania (odtwarzanie/pauza/następny/poprzedni) + wyłączanie OSD multimediów poprzez zastąpienie klawiszy multimediów, włączając kombinację klawiszy Ctrl/CMD + F w celu wyszukiwania, obsługę Linux MPRIS dla klawiszy multimediów oraz niestandardowe skróty klawiszowe dla zaawansowanych użytkowników.",
|
||||
"description": "Umożliwia ustawienie globalnych skrótów klawiszowych do odtwarzania (odtwarzanie/pauza/następny/poprzedni) + wyłączanie OSD multimediów poprzez zastąpienie klawiszy multimediów, włączając kombinację klawiszy Ctrl/CMD + F w celu wyszukiwania, obsługę Linux MPRIS dla klawiszy multimediów oraz niestandardowe skróty klawiszowe dla zaawansowanych użytkowników",
|
||||
"menu": {
|
||||
"override-media-keys": "Zastąp klawisze multimediów",
|
||||
"set-keybinds": "Ustaw globalne sterowanie utworem"
|
||||
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Ativado",
|
||||
"label": "Plugins"
|
||||
"label": "Plugins",
|
||||
"new": "NOVO"
|
||||
},
|
||||
"view": {
|
||||
"label": "Ver",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Anterior",
|
||||
"quit": "Sair",
|
||||
"restart": "Reiniciar aplicativo",
|
||||
"show": "Mostrar janela"
|
||||
"show": "Mostrar janela",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -201,8 +206,20 @@
|
||||
},
|
||||
"name": "Bloqueador de anúncios"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Adiciona os botões Anular Rejeição, Não Gostei, Gostei e Anular o Gosto para ser aplicado a todas as músicas de uma lista de reprodução ou álbum",
|
||||
"name": "Ações no álbum"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Aplica um tema dinâmico e efeitos visuais com base na paleta de cores do álbum",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Rácio de mistura das cores",
|
||||
"submenu": {
|
||||
"percent": "Proporção"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Tema de cores do álbum"
|
||||
},
|
||||
"ambient-mode": {
|
||||
@ -289,7 +306,7 @@
|
||||
"menu": {
|
||||
"advanced": "Avançado"
|
||||
},
|
||||
"name": "Transição entre músicas [beta]",
|
||||
"name": "Transição entre músicas [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -408,13 +425,9 @@
|
||||
},
|
||||
"name": "Menu no aplicativo"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Adiciona suporte de scrobbling para Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Adiciona suporte Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Adiciona suporte a letras para a maioria das músicas",
|
||||
@ -426,6 +439,52 @@
|
||||
"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"
|
||||
@ -518,8 +577,41 @@
|
||||
"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"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Adicionar suporte para scrobbling (Last.fm, ListenBrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Falha ao autenticar com a Last.fm\nOculte o pop-up até a próxima reinicialização.",
|
||||
"title": "Falha na autenticação"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Configurações de API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Insira o token de utilizador ListenBrainz"
|
||||
},
|
||||
"scrobble-other-media": "Scrobble outros mídia"
|
||||
},
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Chave de API Last.fm",
|
||||
"api-secret": "Segredo da API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Insira seu token de usuário do ListenBrainz:",
|
||||
"title": "Token ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Permite definir teclas de atalho globais para reprodução (reproduzir/pausar/próximo/anterior) e desligar o OSD de mídia substituindo as teclas de mídia, ativando Ctrl/CMD + F para pesquisar, ativando o suporte Linux MPRIS para teclas de mídia e teclas de atalho personalizadas para usuários avançados.",
|
||||
"description": "Permite definir teclas de atalho globais para reprodução (reproduzir/pausar/próximo/anterior) e desligar o OSD de mídia substituindo as teclas de mídia, ativando Ctrl/CMD + F para pesquisar, ativando o suporte Linux MPRIS para teclas de mídia e teclas de atalho personalizadas para usuários avançados",
|
||||
"menu": {
|
||||
"override-media-keys": "Substituir teclas de mídia",
|
||||
"set-keybinds": "Definir controles globais de música"
|
||||
|
||||
690
src/i18n/resources/ro.json
Normal file
690
src/i18n/resources/ro.json
Normal file
@ -0,0 +1,690 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Nu s-a reusit executarea plugin-ului {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Plugin-ul {{pluginName}}::{{contextName}} s-a executat in {{ms}} ms",
|
||||
"initialize-failed": "Initializarea plugin-ului \"{{pluginName}}\" a esuat",
|
||||
"load-all": "Se incarca toate plugin-urile",
|
||||
"load-failed": "Esec la incarcarea plugin-ului \"{{pluginName}}\"",
|
||||
"loaded": "Plugin-ul \"{{pluginName}}\" s-a incarcat",
|
||||
"unload-failed": "Esec la oprirea plugin-ului \"{{pluginName}}\"",
|
||||
"unloaded": "Plugin-ul \"{{pluginName}}\" s-a terminat"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "ro",
|
||||
"local-name": "Română",
|
||||
"name": "Romanian"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "S-a terminat incarcarea. Panoul de developer e deschis"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n incarcat"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "Comanda primita prin protocol: \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "Fisierul CSS \"{{cssFile}}\" nu exista, se ignora"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Eroare, procesul nu raspunde\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Se sterge cache-ul aplicatiei"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Fereastra a incercat sa fie randata in afara ecranului, marimeaFerestrei={{windowSize}}, marimeaEcranului={{displaySize}}, pozitia={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Meniul este ascuns, folositi tasta 'Alt' pentru a-l face sa apara (sau tasta 'Esc' daca folositi meniul din aplicatie)",
|
||||
"message": "Ascunderea meniului este activata",
|
||||
"title": "Ascunderea meniului activata"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Mai tarziu",
|
||||
"restart-now": "Reporneste acum"
|
||||
},
|
||||
"detail": "Plugin-ul \"{{pluginName}}\" necesita o repornire pentru a intra in efect",
|
||||
"message": "Pugin-ul \"{{pluginName}}\" trebuie repornit",
|
||||
"title": "Repornire necesara"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Iesi",
|
||||
"relaunch": "Reporneste",
|
||||
"wait": "Asteapta"
|
||||
},
|
||||
"detail": "Ne cerem scuze pentru incovenient! va rugam alegeti ce doriti sa faceti:",
|
||||
"message": "Applicatia nu raspunde",
|
||||
"title": "Fereastra nu raspunde"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "Dezactiveaza actualizarile",
|
||||
"download": "Descarca",
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "O noua versiune este disponibila si poate fi descarcata pe {{downloadLink}}",
|
||||
"message": "O noua versiune este disponibila",
|
||||
"title": "Actualizare disponibila"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "Despre",
|
||||
"navigation": {
|
||||
"label": "Navigatie",
|
||||
"submenu": {
|
||||
"copy-current-url": "Copiaza URL-ul actual",
|
||||
"go-back": "Mergi inapoi",
|
||||
"go-forward": "Mergi inainte",
|
||||
"quit": "Iesi",
|
||||
"restart": "Reporneste aplicatia"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "Setari",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "Setari avansate",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "Reseteaza cache-ul aplicatiei la pornire",
|
||||
"disable-hardware-acceleration": "Dezactiveaza acceleratia hardware",
|
||||
"edit-config-json": "Editeaza config,json",
|
||||
"override-user-agent": "Suprascrie User-Agent-ul",
|
||||
"restart-on-config-changes": "Reporneste la modificarea configuratiei",
|
||||
"set-proxy": {
|
||||
"label": "Seteaza proxy",
|
||||
"prompt": {
|
||||
"label": "Introduceti adresa proxy: (lasati liber pentru a dezactiva)",
|
||||
"placeholder": "Exemplu: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Seteaza proxy"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "Deschide uneltele de dezvoltator"
|
||||
}
|
||||
},
|
||||
"always-on-top": "Mereu deasupra",
|
||||
"auto-update": "Actualizare automata",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "Meniul va fi ascuns la urmatoarea pornire, folositi [Alt] pentru a-l face sa apara (sau ghilimea intoarsa [`] daca folositi meniul applicatiei)",
|
||||
"title": "Ascunderea meniului pornita"
|
||||
},
|
||||
"label": "Ascunde meniul"
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "Limba va fi schimbata dupa repornire",
|
||||
"title": "Limba actualizata"
|
||||
},
|
||||
"label": "Limba",
|
||||
"submenu": {
|
||||
"to-help-translate": "Vrei sa ajuti la traducere? Apasa aici"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "Continua ultimul cantec ascultat cand porneste aplicatia",
|
||||
"single-instance-lock": "Oprirea deschiderii mai multor instante",
|
||||
"start-at-login": "Incepe la autentificare",
|
||||
"starting-page": {
|
||||
"label": "Pagina de pornire",
|
||||
"unset": "Deselectat"
|
||||
},
|
||||
"tray": {
|
||||
"label": "Tray",
|
||||
"submenu": {
|
||||
"disabled": "Dezactivat",
|
||||
"enabled-and-hide-app": "Activeaza si ascunde fereastra aplicatiei",
|
||||
"enabled-and-show-app": "Activeaza si arata fereastra aplicatiei",
|
||||
"play-pause-on-click": "Start/Pauza la click"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Optimizari vizuale",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Default",
|
||||
"force-show": "Forteaza randarea",
|
||||
"hide": "Ascunde",
|
||||
"label": "Butoane de like"
|
||||
},
|
||||
"remove-upgrade-button": "Elimina butonul de upgrade",
|
||||
"theme": {
|
||||
"label": "Tema",
|
||||
"submenu": {
|
||||
"import-css-file": "Importa fisiere CSS proprii",
|
||||
"no-theme": "Fara tema"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Activat",
|
||||
"label": "Plugins",
|
||||
"new": "NOU"
|
||||
},
|
||||
"view": {
|
||||
"label": "Aspect",
|
||||
"submenu": {
|
||||
"force-reload": "Reimprospatare fortata",
|
||||
"reload": "Reimprospateaza",
|
||||
"reset-zoom": "Marimea actuala",
|
||||
"toggle-fullscreen": "Porneste Full Screen",
|
||||
"zoom-in": "Mareste",
|
||||
"zoom-out": "Micsoreaza"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "Urmatorul",
|
||||
"play-pause": "Reda/Pauza",
|
||||
"previous": "Anteriorul",
|
||||
"quit": "Iesi",
|
||||
"restart": "Reporneste aplicatia",
|
||||
"show": "Arata fereastra",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"adblocker": {
|
||||
"description": "Blocheaza toate reclamele si trackers",
|
||||
"menu": {
|
||||
"blocker": "Blocator"
|
||||
},
|
||||
"name": "Blocator de reclame"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Adauga butoane pentru Undislike, Like si Unlike pentru toate piesele dintr-un playlist sau album",
|
||||
"name": "Actiuni pentru album"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Aplica o tema dinamica si efecte vizuale bazate pe paleta de culori a albumului",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Raportul amestecului de culori",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Tema de culori a albumului"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Aplica un efect de iluminare, aplicand culori preluate din video pe fundalul ecranului",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Cantitatea de blur",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} pixeli"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "Buffer",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "Opacitate",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Calitate",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} pixeli"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "Marime",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "Fluenta tranzitiei",
|
||||
"submenu": {
|
||||
"during": "In timpul {{interpolationTime}} s"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Foloseste fullscreen"
|
||||
}
|
||||
},
|
||||
"name": "Mod ambiental"
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Aplica compresie pe audio (scade volumul partilor cele mai sonore si creste volumul partilor mai putin sonore)",
|
||||
"name": "Compresor audio"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Fa bara de navigare semi-transparenta",
|
||||
"name": "Bara de naviagtie semi-transparenta"
|
||||
},
|
||||
"bypass-age-restrictions": {
|
||||
"description": "Treci peste verificarea de varsta a YouTube-ului",
|
||||
"name": "Ignora restrictiile de varsta"
|
||||
},
|
||||
"captions-selector": {
|
||||
"description": "Selector de subtitrari pentru piesele audio de pe YouTube Music",
|
||||
"menu": {
|
||||
"autoload": "Selecteaza automat ultima subtitrare folosita",
|
||||
"disable-captions": "Fara subtitrari by default"
|
||||
},
|
||||
"name": "Selector de subtitrari",
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"label": "Limba curenta a subtitrarilor: {{language}}",
|
||||
"none": "Niciuna",
|
||||
"title": "Alege limba subtitrarilor"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"title": "Deschide selectorul de subtitrari"
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
"description": "Pastreaza bara laterala mereu in modul compact",
|
||||
"name": "Bara laterala compacta"
|
||||
},
|
||||
"crossfade": {
|
||||
"description": "Tranzitioneaza intre cantece",
|
||||
"menu": {
|
||||
"advanced": "Avansat"
|
||||
},
|
||||
"name": "Tranzitie [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
"fade-in-duration": "Durata tranzitie de inceput (ms)",
|
||||
"fade-out-duration": "Durata tranzitie de sfarsit (ms)",
|
||||
"fade-scaling": {
|
||||
"label": "Scala tranzitiei",
|
||||
"linear": "Linear",
|
||||
"logarithmic": "Logaritmic"
|
||||
},
|
||||
"seconds-before-end": "Tranzitie N secunde inainte de final"
|
||||
},
|
||||
"title": "Optiuni de tranzitie"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable-autoplay": {
|
||||
"description": "Fa cantecul sa inceapa in modul \"pauza\"",
|
||||
"menu": {
|
||||
"apply-once": "Se aplica doar la pornirea aplicatiei"
|
||||
},
|
||||
"name": "Dezactiveaza redarea automata"
|
||||
},
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "S-a incercat conectarea cu o conexiune activa",
|
||||
"connected": "Conectat la Discord",
|
||||
"disconnected": "Deconectat de la Discord"
|
||||
},
|
||||
"description": "Arata-le prietenilor ce asculti cu Rich Presence",
|
||||
"menu": {
|
||||
"auto-reconnect": "Reconectare automata",
|
||||
"clear-activity": "Sterge activitatea",
|
||||
"clear-activity-after-timeout": "Sterge activitatea dupa timeout",
|
||||
"connected": "Conectat",
|
||||
"disconnected": "Deconectat",
|
||||
"hide-duration-left": "Ascunde timpul ramas",
|
||||
"hide-github-button": "Ascunde butonul cu link-ul GitHub",
|
||||
"play-on-youtube-music": "Reda pe YouTube Music",
|
||||
"set-inactivity-timeout": "Seteaza intervalul de inactivitate"
|
||||
},
|
||||
"name": "Discord Rich Presence",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "Introduceti perioada de inactivitate dorita in secunde:",
|
||||
"title": "Seteaza timpul de inactivitate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"error": {
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"message": "Argh! Scuze, descarcarea a esuat…",
|
||||
"title": "Eroare la descarcare!"
|
||||
},
|
||||
"start-download-playlist": {
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "({{playlistSize}} cantece)",
|
||||
"message": "Se descarca Playlist-ul {{playlistTitle}}",
|
||||
"title": "Descarcarea a inceput"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Conversie: {{percent}}%",
|
||||
"converting": "Se converteste…",
|
||||
"done": "Descarcat: {{filePath}}",
|
||||
"download-info": "Se descarca {{artist}} -{{title}} [{{videoId}}",
|
||||
"download-progress": "Se descarca: {{percent}}%",
|
||||
"downloading": "Se descarca…",
|
||||
"downloading-counter": "Se descarca {{current}}/{{total}}…",
|
||||
"downloading-playlist": "Se descarca playlist-ul \"{{playlistTitle}}\" - {{playlistSize}} piese ({{playlistId}})",
|
||||
"error-while-downloading": "Eroare la descarcarea piesei \"{{author}} - {{title}}\":{{error}}",
|
||||
"folder-already-exists": "Folderul {{playlistFolder}} exista deja",
|
||||
"getting-playlist-info": "Se aduna informatiile despre playlist…",
|
||||
"loading": "Se incarca…",
|
||||
"playlist-has-only-one-song": "Playlist-ul are doar un element, acesta va fi descarcat direct",
|
||||
"playlist-id-not-found": "Niciun ID al playlist-ului nu a fost gasit",
|
||||
"playlist-is-empty": "Playlist-ul este gol",
|
||||
"playlist-is-mix-or-private": "Eroare la colectarea informatiilor despre playlist: asigurati-va ca nu este privat sau un playlist \"Mixed for you\"\n\n{{error}}",
|
||||
"preparing-file": "Se pregateste fisierul…",
|
||||
"saving": "Se salveaza…",
|
||||
"trying-to-get-playlist-id": "Se incearca obtinerea ID-ului playlist-ului: {{playlistId}}",
|
||||
"video-id-not-found": "Video-ul nu a fost gasit",
|
||||
"writing-id3": "Se scriu tag-urile ID3…"
|
||||
}
|
||||
},
|
||||
"description": "Descarca MP3 / sursa audio direct din interfata",
|
||||
"menu": {
|
||||
"choose-download-folder": "Alege folderul de descarcari",
|
||||
"download-playlist": "Descarca playlist-ul",
|
||||
"presets": "Setari implicite",
|
||||
"skip-existing": "Treci peste fisierele existente"
|
||||
},
|
||||
"name": "Downloader",
|
||||
"renderer": {
|
||||
"can-not-update-progress": "Nu se poate actualiza progresul"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Descarca"
|
||||
}
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Fa slider-ul de volum exponential pentru a fi mai usor de selectat volumuri reduse.",
|
||||
"name": "Volum exponential"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Ofera barelor de meniu un aspect extravagant, intunecat sau de culoarea albumului",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "Ascunde controalele ferestrei DOM"
|
||||
},
|
||||
"name": "Meniul aplicatiei"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Adauga asistenta pentru Lumia Stream",
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Adauga versuri pentru majoritatea cantecelor",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Versuri romantizate"
|
||||
},
|
||||
"name": "Lyrics Genius",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "Versuri preluate de pe Genius"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Impartaseste playlist-ul cu altii. Cand gazda va pune o piesa, toti ceilalti vor auzi acelasi cantec",
|
||||
"dialog": {
|
||||
"enter-host": "Introdu ID-ul host-ului"
|
||||
},
|
||||
"internal": {
|
||||
"save": "Salveaza",
|
||||
"track-source": "Sursa piesei",
|
||||
"unknown-user": "Utilizator necunoscut"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Copiaza ID-ul host-ului",
|
||||
"close": "Inchide Music Together",
|
||||
"connected-users": "Utilizatori conectati",
|
||||
"disconnect": "Deconecteaza Music Together",
|
||||
"empty-user": "Niciun utilizator conectat",
|
||||
"host": "Gazda Music Together",
|
||||
"join": "Alatura-te Music Together",
|
||||
"permission": {
|
||||
"all": "Permite invitatilor sa controleze playlist-ul si player-ul",
|
||||
"host-only": "Doar gazda poate controla playlist-ul si player-ul",
|
||||
"playlist": "Permite invitatilor controlul asupra playlist-ului"
|
||||
},
|
||||
"set-permission": "Schimba controlul permisiunilor",
|
||||
"status": {
|
||||
"disconnected": "Deconectat",
|
||||
"guest": "Conectat ca invitat",
|
||||
"host": "Conectat ca gazda"
|
||||
}
|
||||
},
|
||||
"name": "Music Together [Beta]",
|
||||
"toast": {
|
||||
"add-song-failed": "Adaugarea piesei a esuat",
|
||||
"closed": "Music Together inchis",
|
||||
"disconnected": "Music Together deconectat",
|
||||
"host-failed": "Nu s-a reusit gazduirea Music Together",
|
||||
"id-copied": "ID-ul host-ului a fost copiat in clipboard",
|
||||
"id-copy-failed": "Eroare la copierea ID-ului host-ului in clipboard",
|
||||
"join-failed": "Nu s-a reusit alaturarea la Music Together",
|
||||
"joined": "V-ati alaturat Music Together",
|
||||
"permission-changed": "Permisiunile Music Together s-au schimbat la \"{{permission}}\"",
|
||||
"remove-song-failed": "Eroare la indepartarea cantecului",
|
||||
"user-connected": "{{name}} s-a alaturat la Music Together",
|
||||
"user-disconnected": "{{name}} a parasit Music Together"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "Sagetile pentru Urmatorul/Anteriorul integrate direct in interfata, ca in browser-ul tau preferat",
|
||||
"name": "Navigatie"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "Elimina butonul de autentificare Google si link-urile din interfata",
|
||||
"name": "Nicio autentificare Google"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Afiseaza o notificare cand incepe sa cante o piesa (notificarile interactive sunt disponibile pe Windows)",
|
||||
"menu": {
|
||||
"interactive": "Notificari interactive",
|
||||
"interactive-settings": {
|
||||
"label": "Setari interactive",
|
||||
"submenu": {
|
||||
"hide-button-text": "Ascunde textul butoanelor",
|
||||
"refresh-on-play-pause": "Reimprospateaza la Reda/Pauza",
|
||||
"tray-controls": "Deschide/Inchide la apasarea icnoitei pentru meniul Tray"
|
||||
}
|
||||
},
|
||||
"priority": "Prioritatea notificarilor",
|
||||
"toast-style": "Stilul notificarilor",
|
||||
"unpause-notification": "Arata notificarile la pauza"
|
||||
},
|
||||
"name": "Notificari"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"description": "Permite sa schimbi aplicatie la modul picture-in-picture",
|
||||
"menu": {
|
||||
"always-on-top": "Mereu deasupra",
|
||||
"hotkey": {
|
||||
"label": "Scurtaturi pe tastatura",
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "Scurtaturi din taste"
|
||||
},
|
||||
"label": "Scurtaturi din taste pentru picture-in-picture",
|
||||
"title": "Scurtatura Picture-in-picture"
|
||||
}
|
||||
},
|
||||
"save-window-position": "Salveaza pozitia ferestrei",
|
||||
"save-window-size": "Salveaza marimea ferestrei",
|
||||
"use-native-pip": "Foloseste PiP-ul nativ pentru broswer"
|
||||
},
|
||||
"name": "Picture-in-picture",
|
||||
"templates": {
|
||||
"button": "Picture-in-picture"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"description": "Asculta rapid, asculta lent! Adauga un slider pentru viteza de redare a cantecului",
|
||||
"name": "Viteza de redare",
|
||||
"templates": {
|
||||
"button": "Viteza"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"description": "Controleaza volumul precis folosind rotita mouse-ului/scurtaturi din tastatura, cu un HUD personalizat si incremente de volum personalizate",
|
||||
"menu": {
|
||||
"arrows-shortcuts": "Control cu tastele sageti locale",
|
||||
"custom-volume-steps": "Seteaza incrementele de volum",
|
||||
"global-shortcuts": "Scurtaturi de tastatura globale"
|
||||
},
|
||||
"name": "Volum precis",
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "Redu volumul audio",
|
||||
"increase": "Creste volumul audio"
|
||||
},
|
||||
"label": "Alege combinatiile de taste globale pentru volumul audio:",
|
||||
"title": "Combinatii globale de taste pentru volum"
|
||||
},
|
||||
"volume-steps": {
|
||||
"label": "Alege pasii de increment pentru volum audio",
|
||||
"title": "Incremente de volum"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quality-changer": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "Calitate actuala: {{quality}}",
|
||||
"message": "Alegeti calitatea video:",
|
||||
"title": "Alegeti calitatea video"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Permite schimbarea calitatii video cu un buton prezent peste video",
|
||||
"name": "Modificator de calitate video"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Adauga asistenta pentru scrobbling (etc. last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Autentificarea cu Last.fm a esuat\nAscunde acest pop-up pana la urmatoarea repornire.",
|
||||
"title": "Autentificare Esuata"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Setari pentru API-ul Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Introdu token-ul de utilizator ListenBrainz"
|
||||
},
|
||||
"scrobble-other-media": "Scrobble alte surse media"
|
||||
},
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Cheia API Last.fm",
|
||||
"api-secret": "Secret API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Introdu token-ul tau de utilizator ListenBrainz:",
|
||||
"title": "Token-ul ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Permite setari globale pentru scurtaturi pe tastatura pentru playback (reda/pauza/urmatorul/anteriorul), pentru oprirea media OSD prin suprascriera tastelor media, pentru folosirea combinatiei Ctrl/CMD + F pentru a cauta, pentru asistenta Linux MPRIS pentru taste media si pentru scurtaturi perosnalizate pentru utilizatori avansati",
|
||||
"menu": {
|
||||
"override-media-keys": "Suprascrie tastele media",
|
||||
"set-keybinds": "Seteaza scurtaturile globale pentru cantece"
|
||||
},
|
||||
"name": "Scurtaturi (& MPRIS)",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Urmatorul",
|
||||
"play-pause": "Reda / Pauza",
|
||||
"previous": "Anteriorul"
|
||||
},
|
||||
"label": "Alege combinatia de taste globala pentru controlul cantecelor:",
|
||||
"title": "Scurtaturi pe tastatura globale"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Sari peste cantecele disliked",
|
||||
"name": "Treci peste cantecele disliked"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "Treci automat peste sectiunile de liniste din cantece",
|
||||
"name": "Treci peste liniste"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "Treci automat peste partile non-muzicale precum intro/outro sau parti din video-ul catecului, cand nu se aude cantecul",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Controleaza redarea din Bara de Activitati Windows",
|
||||
"name": "Control media in Bara de Activitate"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "Adauga un widget TouchBar pentru utilizatorii macOS",
|
||||
"name": "TouchBar"
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "Integrare cu plugin-ul OBS Tuna",
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "Adauga un buton ce schimba intre modurile Video/Cantec. se poate optional elimia complet optiunea video",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "Aliniere",
|
||||
"submenu": {
|
||||
"left": "Stanga",
|
||||
"middle": "Mijloc",
|
||||
"right": "Dreapta"
|
||||
}
|
||||
},
|
||||
"force-hide": "Forteaza eliminarea tab-ului video",
|
||||
"mode": {
|
||||
"label": "Mod",
|
||||
"submenu": {
|
||||
"custom": "Comutatoare personalizate",
|
||||
"disabled": "Dezactivat",
|
||||
"native": "Comutatoare native"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Comutator video",
|
||||
"templates": {
|
||||
"button": "Cantec"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Adauga un visualizer la player",
|
||||
"menu": {
|
||||
"visualizer-type": "Tip de visualizer"
|
||||
},
|
||||
"name": "Visualizer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,7 @@
|
||||
"css-file-not-found": "CSS файл \"{{cssFile}}\" не существует, игнорирую"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Приложение не отвечает\n{{error}}"
|
||||
"details": "Приложение не отвечает!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Очищаю кеш приложения"
|
||||
@ -63,7 +63,7 @@
|
||||
"relaunch": "Перезапустить",
|
||||
"wait": "Подождать"
|
||||
},
|
||||
"detail": "Извиняемся за неувязку! пожалуйста выберите что вы хотите сделать:",
|
||||
"detail": "Извините за причиненные неудобства! Пожалуйста, выберите, что делать:",
|
||||
"message": "Приложение не отвечает",
|
||||
"title": "Окно не отвечает"
|
||||
},
|
||||
@ -170,14 +170,15 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Включено",
|
||||
"label": "Плагины"
|
||||
"label": "Плагины",
|
||||
"new": "НОВИНКА"
|
||||
},
|
||||
"view": {
|
||||
"label": "Вид",
|
||||
"submenu": {
|
||||
"force-reload": "Принудительная перезагрузка",
|
||||
"reload": "Перезагрузить",
|
||||
"reset-zoom": "Настоящий размер",
|
||||
"reset-zoom": "Текущий размер",
|
||||
"toggle-fullscreen": "Включить полноэкранный режим",
|
||||
"zoom-in": "Приблизить",
|
||||
"zoom-out": "Отдалить"
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Предыдущий",
|
||||
"quit": "Выйти",
|
||||
"restart": "Перезагрузить приложение",
|
||||
"show": "Показать окно"
|
||||
"show": "Показать окно",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -201,12 +206,24 @@
|
||||
},
|
||||
"name": "Блокировщик рекламы"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Добавляет кнопки Undislike, Dislike и Unlike, чтобы применять их на все композиции в плейлисте или альбоме",
|
||||
"name": "Действия с альбомом"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Применяет динамическую тему и визуальные эффекты на основе цветовой палитры альбома",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Соотношение цветов",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Цветовая тема альбома"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Применяет световой эффект, отбрасывая мягкие цвета из видео на задний фон вашего экрана.",
|
||||
"description": "Применяет световой эффект, отбрасывая мягкие цвета из видео на задний фон вашего экрана",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Степень размытия",
|
||||
@ -394,26 +411,104 @@
|
||||
"can-not-update-progress": "Невозможно обновить прогресс"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Download"
|
||||
"button": "Скачать"
|
||||
}
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Делает слайдер громкости расширенным чтобы было легче выбирать низкие уровни.",
|
||||
"description": "Делает слайдер громкости расширенным чтобы было легче понижать громкость.",
|
||||
"name": "Расширенная громкость"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Придает меню модный вид"
|
||||
"description": "Придает меню цветовую схему альбома",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "Скрыть элементы управления окном DOM"
|
||||
},
|
||||
"name": "Меню в приложении"
|
||||
},
|
||||
"last-fm": {
|
||||
"name": "Last.fm"
|
||||
"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": {
|
||||
"description": "Стрелки навигации \"вперед/назад\" интегрированы в интерфейс, как в вашем любимом браузере",
|
||||
"name": "Навигация"
|
||||
},
|
||||
"no-google-login": {
|
||||
"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": {
|
||||
@ -426,42 +521,152 @@
|
||||
"keybind-options": {
|
||||
"hotkey": "Горячая клавиша"
|
||||
},
|
||||
"label": "Выберите горячую клавишу для переключения режима изображения в изображении"
|
||||
"label": "Выберите горячую клавишу для переключения режима изображения в изображении",
|
||||
"title": "Горячая клавиша для картинки в картинке"
|
||||
}
|
||||
},
|
||||
"save-window-position": "Сохранить положение окна",
|
||||
"save-window-size": "Сохранить размер окна"
|
||||
"save-window-size": "Сохранить размер окна",
|
||||
"use-native-pip": "Использовать нативный PiP браузера"
|
||||
},
|
||||
"name": "Картинка в картинке",
|
||||
"templates": {
|
||||
"button": "Картинка в картинке"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"playback-speed": {
|
||||
"description": "Слушайте быстро, слушайте медленно! Добавляет ползунок, регулирующий скорость композиции",
|
||||
"name": "Скорость воспроизведения",
|
||||
"templates": {
|
||||
"button": "Скорость"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"description": "Точное управление громкостью с помощью колеса мышки/горячих клавиш, пользовательский HUD и настраиваемые ступени громкости",
|
||||
"menu": {
|
||||
"arrows-shortcuts": "Локальное управление со стрелками",
|
||||
"custom-volume-steps": "Настройка пользовательских шагов громкости",
|
||||
"global-shortcuts": "Глобальные горячие клавиши"
|
||||
},
|
||||
"name": "Точная громкость",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"next": "Next"
|
||||
"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)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Не удалось войти с помощью Last.fm\nСкрыть сообщение до следующего запуска",
|
||||
"title": "Ошибка аунтефикации"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "Позволяет задать глобальные горячие клавиши для воспроизведения (play/pause/next/previous) и отключить экранное мультимедийное меню, изменяя мультимедийные клавиши, также включает 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": "Добавляет виджет тачбара для пользователей macOS",
|
||||
"name": "Тачбар"
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "Интеграция с плагином Tuna от OBS",
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "Добавляет кнопку для переключения между режимами видео и песни. Также можно удалить всю вкладку с видео",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "Выравнивание",
|
||||
"submenu": {
|
||||
"middle": "Middle",
|
||||
"right": "Right"
|
||||
"left": "Слева",
|
||||
"middle": "По центру",
|
||||
"right": "Справа"
|
||||
}
|
||||
},
|
||||
"force-hide": "Скрыть обложку",
|
||||
"force-hide": "Принудительное скрыть видео",
|
||||
"mode": {
|
||||
"label": "Mode",
|
||||
"label": "Режим",
|
||||
"submenu": {
|
||||
"custom": "Кастомный переключатель",
|
||||
"disabled": "Отключен",
|
||||
@ -471,7 +676,7 @@
|
||||
},
|
||||
"name": "Переключатель видео",
|
||||
"templates": {
|
||||
"button": "Song"
|
||||
"button": "Песня"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
|
||||
107
src/i18n/resources/sv.json
Normal file
107
src/i18n/resources/sv.json
Normal file
@ -0,0 +1,107 @@
|
||||
{
|
||||
"language": {
|
||||
"code": "sv",
|
||||
"local-name": "Svenska",
|
||||
"name": "Swedish"
|
||||
},
|
||||
"plugins": {
|
||||
"navigation": {
|
||||
"name": "Navigering"
|
||||
},
|
||||
"no-google-login": {
|
||||
"name": "Inget Google Login"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notiser"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"menu": {
|
||||
"hotkey": {
|
||||
"label": "Snabbkommando",
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "Snabbkommando"
|
||||
},
|
||||
"title": "Bild-I-Bild genväg"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Bild-I-Bild",
|
||||
"templates": {
|
||||
"button": "Bild-i-bild"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"name": "Uppspelningshastighet",
|
||||
"templates": {
|
||||
"button": "Hasighet"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "Minska Volym",
|
||||
"increase": "Öka Volym"
|
||||
}
|
||||
},
|
||||
"volume-steps": {
|
||||
"title": "Volymsteg"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quality-changer": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "Nuvarande kvalité: {{quality}}",
|
||||
"message": "Välj Video Kvalité:",
|
||||
"title": "Välj Video Kvalité"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrobbler": {
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API nyckel"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"title": "ListenBrainz token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Nästa",
|
||||
"play-pause": "Spela / Pausa",
|
||||
"previous": "Föregående"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video-toggle": {
|
||||
"menu": {
|
||||
"align": {
|
||||
"submenu": {
|
||||
"left": "Vänster",
|
||||
"middle": "Mitten",
|
||||
"right": "Höger"
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"submenu": {
|
||||
"disabled": "Inaktiverad"
|
||||
}
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"button": "Låt"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,479 @@
|
||||
{
|
||||
"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": "การโหลดเสร็จสิ้น. โหมดนักพัฒนาสามรถใช้งานได้แล้ว"
|
||||
},
|
||||
"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] เพื่อแสดงเมนู (หรือใช้ backtick [`] หากอยู่ในเมนูแอป)",
|
||||
"title": "เปิดใช้งานการซ่อนเมนู"
|
||||
},
|
||||
"label": "ซ่อนเมนู"
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "ภาษาจะเปลี่ยนหลังจากทำการรีสตาร์ท",
|
||||
"title": "ภาษาถูกเปลี่ยนแล้ว"
|
||||
},
|
||||
"label": "ภาษา",
|
||||
"submenu": {
|
||||
"to-help-translate": "ต้องการช่วยแปล? คลิกที่นี่"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "เล่นเพลงล่าสุดต่อ เมื่อแอปเริ่มต้น",
|
||||
"single-instance-lock": "ล็อกการทำงานเพียงรายการเดียว",
|
||||
"start-at-login": "เริ่มต้นที่การเข้าสู่ระบบ",
|
||||
"starting-page": {
|
||||
"label": "หน้าเริ่มต้น",
|
||||
"unset": "ยกเลิกการตั้งค่า"
|
||||
},
|
||||
"tray": {
|
||||
"label": "ถาดรายการ",
|
||||
"submenu": {
|
||||
"disabled": "ปิดการใช้งาน",
|
||||
"enabled-and-hide-app": "เปิดใช้งานและซ่อนแอป",
|
||||
"enabled-and-show-app": "เปิดใช้งานและแสดงแอป",
|
||||
"play-pause-on-click": "เล่น/หยุดเล่น เมื่อคลิก"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "การปรับแต่งหน้าตาแอป",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "ค่าเริ่มต้น",
|
||||
"force-show": "บังคับให้แสดง",
|
||||
"hide": "ซ่อน",
|
||||
"label": "ปุ่มถูกใจ"
|
||||
},
|
||||
"remove-upgrade-button": "ลบปุ่มอัปเกรด",
|
||||
"theme": {
|
||||
"label": "ธีม",
|
||||
"submenu": {
|
||||
"import-css-file": "นำเข้าไฟล์ CSS ที่กำหนดเอง",
|
||||
"no-theme": "ไม่มีธีม"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "เปิดใช้งาน",
|
||||
"label": "ปลั๊กอิน",
|
||||
"new": "ใหม่"
|
||||
},
|
||||
"view": {
|
||||
"label": "มุมมอง",
|
||||
"submenu": {
|
||||
"force-reload": "บังคับโหลดใหม่",
|
||||
"reload": "โหลดใหม่",
|
||||
"reset-zoom": "ขนาดจริง",
|
||||
"toggle-fullscreen": "สลับเต็มหน้าจอ",
|
||||
"zoom-in": "ซูมเข้า",
|
||||
"zoom-out": "ซูมออก"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "ค่อไป",
|
||||
"play-pause": "เล่น/พัก",
|
||||
"previous": "ก่อนหน้า",
|
||||
"quit": "ออก",
|
||||
"restart": "รีสตาร์ทแอป",
|
||||
"show": "แสดงหน้าต่าง",
|
||||
"tooltip": {
|
||||
"default": "ยูทุปมิวสิค",
|
||||
"with-song-info": "ยูทูปมิวสิค: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"adblocker": {
|
||||
"description": "บล็อกโฆษณาและการติดตามทั้งหมดอย่างอัตโนมัติ",
|
||||
"menu": {
|
||||
"blocker": "เครื่องมือบล็อก"
|
||||
},
|
||||
"name": "ตัวบล็อกโฆษณา"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "เพิ่ม ยกเลิกไม่ชอบ, ไม่ชอบ, 'ถูกใจ', และ 'ยกเลิกถูกใจ' เพื่อให้สามารถใช้งานกับเพลงทั้งหมดในรายการเพลงหรืออัลบั้ม",
|
||||
"name": "การกระทำที่เกี่ยวกับอัลบั้ม"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "ใช้ธีมและเอฟเฟกต์ไดนามิก โดยขึ้นอยู่กับสีของอัลบั้ม",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "สัดส่วนการผสมสี",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}เปอร์เซ็น"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "ธีมสีของอัลบั้ม"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "เอฟเฟกต์แสงจะใช้สีอ่อนๆจากวีดิโอมาเป็พื้นหลังสกรีน",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "ระดับความเบลอ",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} พิกเซล"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "บัฟเฟอร์",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "ความโปร่งใส",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "คุณภาพ",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} พิกเซล"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "ขนาด",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "การเปลี่ยนแบบสมูท",
|
||||
"submenu": {
|
||||
"during": "ระหว่าง {{interpolationTime}} วินาที"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "ใช้โหมดเต็มหน้าจอ"
|
||||
}
|
||||
},
|
||||
"name": "โหมดสภาพแวดล้อม"
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "ใช้การบีบอัดเสียง (ลดระดับเสียงของส่วนที่ดังที่สุดของสัญญาณและเพิ่มระดับเสียงของส่วนที่เบาที่สุด)",
|
||||
"name": "เครื่องมือบีบอัดเสียง"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "ทำให้แถบนำทางโปร่งแสงและเบลอ",
|
||||
"name": "เบลอแถบนำทาง"
|
||||
},
|
||||
"bypass-age-restrictions": {
|
||||
"description": "ข้ามการตรวจสอบอายุของยูทูป",
|
||||
"name": "ข้ามข้อจำกัดอายุ"
|
||||
},
|
||||
"captions-selector": {
|
||||
"description": "ตัวเลือกคำบรรยายสำหรับเพลงในYoutube Music",
|
||||
"menu": {
|
||||
"autoload": "เลือกคำบรรยายที่ใช้ครั้งล่าสุดโดยอัตโนมัติ",
|
||||
"disable-captions": "ไม่มีคำบรรยายเป็นค่าเริ่มต้น"
|
||||
},
|
||||
"name": "ตัวเลือกคำบรรยาย",
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"label": "ภาษาคำบรรยายปัจจุบัน: {{language}}",
|
||||
"none": "ไม่มี",
|
||||
"title": "เลือกภาษาคำบรรยาย"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"title": "เปิดตัวเลือกคำบรรยาย"
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
"description": "ตั้งค่าแถบข้างให้อยู่ในโหมดกระชับเสมอ",
|
||||
"name": "แถบข้างแบบกระชับ"
|
||||
},
|
||||
"crossfade": {
|
||||
"description": "การเฟดเพลงระหว่างเพลง",
|
||||
"menu": {
|
||||
"advanced": "ขั้นสูง"
|
||||
},
|
||||
"name": "การเฟดเพลง [เบต้า]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
"fade-in-duration": "ระยะเวลาการเฟดเข้าสู่ (มิลลิวินาที)",
|
||||
"fade-out-duration": "ระยะเวลาการเฟดออก (มิลลิวินาที)",
|
||||
"fade-scaling": {
|
||||
"label": "การปรับขนาดของการเฟด",
|
||||
"linear": "การเปลี่ยนแปลงเชิงเส้น",
|
||||
"logarithmic": "การเปลี่ยนแปลงเชิงลอการิทึม"
|
||||
},
|
||||
"seconds-before-end": "เฟดเพลง N วินาทีก่อนจบ"
|
||||
},
|
||||
"title": "ตัวเลือกเฟดเพลง"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable-autoplay": {
|
||||
"description": "เริ่มเพลงในโหมดหยุด",
|
||||
"menu": {
|
||||
"apply-once": "ใช้เฉพาะเมื่อเริ่มต้น"
|
||||
},
|
||||
"name": "ปิดใช้งานการเล่นอัตโนมัติ"
|
||||
},
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "พยายามเชื่อมต่อกับการเชื่อมต่อที่ทำงานอยู่",
|
||||
"connected": "เชื่อมต่อกับดิสคอร์ดแล้ว",
|
||||
"disconnected": "ตัดการเชื่อมต่อออกจากดิสคอร์ด"
|
||||
},
|
||||
"description": "แสดงให้เพื่อนเห็นว่าคุณกำลังฟังอะไรด้วย Rich Presence",
|
||||
"menu": {
|
||||
"auto-reconnect": "เชื่อมต่อใหม่โดยอัตโนมัติ",
|
||||
"clear-activity": "ล้างกิจกรรม",
|
||||
"clear-activity-after-timeout": "ล้างกิจกรรมหลังจากหมดเวลา",
|
||||
"connected": "เชื่อมต่อแล้ว",
|
||||
"disconnected": "ตัดการเชื่อมต่อ",
|
||||
"hide-duration-left": "ซ่อนระยะเวลาที่เหลือ",
|
||||
"hide-github-button": "ซ่อนปุ่มลิงก์ GitHub",
|
||||
"play-on-youtube-music": "เล่นบนยูทูปมิวสุค",
|
||||
"set-inactivity-timeout": "ตั้งระยะเวลาไม่มีกิจกรรม"
|
||||
},
|
||||
"name": "Discord Rich Presence",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "ป้อนระยะเวลาไม่มีกิจกรรมเป็นวินาที:",
|
||||
"title": "ตั้งระยะเวลาไม่มีกิจกรรม"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
"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": "เพิ่มการรองรับ ลูเมีย สตรีม",
|
||||
"name": "ลูเมีย สตรีม [เบต้า]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "เพิ่มการสนับสนุนเนื้อเพลงสำหรับเพลงหลายๆ เพลง",
|
||||
"menu": {
|
||||
"romanized-lyrics": "เนื้อเพลงโรมันไรซ์"
|
||||
},
|
||||
"name": "เนื้อเพลงแบบอัจฉริยะ",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "ดึงข้อมูลเนื้อเพลงแบบอัจฉริยะแล้ว"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "แบ่งปันเพลย์ลิสต์กับผู้อื่น โดยเมื่อเจ้าภาพเล่นเพลง ทุกคนอื่นๆ จะได้ยินเพลงเดียวกัน",
|
||||
"dialog": {
|
||||
"enter-host": "ป้อนรหัสโฮสต์"
|
||||
},
|
||||
"internal": {
|
||||
"save": "บันทึก",
|
||||
"track-source": "แหล่งข้อมูลเพลง",
|
||||
"unknown-user": "ผู้ใช้ที่ไม่รู้จัก"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "คัดลอกรหัสโฮสต์",
|
||||
"close": "ปิด Music Together",
|
||||
"connected-users": "ผู้ใช้ที่เชื่อมต่อ",
|
||||
"disconnect": "ตัดการเชื่อมต่อ Music Together",
|
||||
"empty-user": "ไม่มีผู้ใช้ที่เชื่อมต่อ",
|
||||
"host": "โฮสต์ Music Together",
|
||||
"join": "เข้าร่วม Music Together",
|
||||
"permission": {
|
||||
"all": "อนุญาตให้แขกควบคุมเพลย์ลิสต์และเครื่องเล่น",
|
||||
"host-only": "เฉพาะโฮสต์เท่านั้นที่สามารถควบคุมเพลย์ลิสต์และเครื่องเล่นได้",
|
||||
"playlist": "อนุญาตให้แขกควบคุมเพลย์ลิสต์"
|
||||
},
|
||||
"set-permission": "เปลี่ยนการอนุญาตในการควบคุม",
|
||||
"status": {
|
||||
"disconnected": "ตัดการเชื่อมต่อแล้ว",
|
||||
"guest": "เชื่อมต่อเป็นแขก",
|
||||
"host": "เชื่อมต่อเป็นโฮสต์"
|
||||
}
|
||||
},
|
||||
"name": "Music Together [เบต้า]",
|
||||
"toast": {
|
||||
"add-song-failed": "เพิ่มเพลงล้มเหลว",
|
||||
"closed": "ปิด Music Together แล้ว"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
"load-all": "Tüm eklentiler yükleniyor",
|
||||
"load-failed": "\"{{pluginName}}\" eklentisi yüklenemedi",
|
||||
"loaded": "\"{{pluginName}}\" eklentisi yüklendi",
|
||||
"unload-failed": "\"{{pluginName}}\" eklentisi kaldırılamadı.",
|
||||
"unloaded": "\"{{pluginName}}\" eklentisi kaldırıldı"
|
||||
"unload-failed": "\"{{pluginName}}\" eklentisi çıkartılamadı",
|
||||
"unloaded": "\"{{pluginName}}\" eklentisi çıkartıldı"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -48,50 +48,119 @@
|
||||
"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"
|
||||
"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",
|
||||
"language": {
|
||||
"label": "Dil"
|
||||
"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": {
|
||||
"enabled-and-hide-app": "Uygulamayı etkinleştirin gizleyin.",
|
||||
"play-pause-on-click": "Tıklaynınca Oynat-Duraklat"
|
||||
"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"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@ -100,10 +169,32 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"label": "Eklentiler"
|
||||
"enabled": "Aktif",
|
||||
"label": "Eklentiler",
|
||||
"new": "YENİ"
|
||||
},
|
||||
"view": {
|
||||
"label": "Görüntü"
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -111,20 +202,33 @@
|
||||
"adblocker": {
|
||||
"description": "Tüm reklamları ve izleyicileri engelle",
|
||||
"menu": {
|
||||
"blocker": "Engelleme Yöntemi"
|
||||
"blocker": "Engelleyici"
|
||||
},
|
||||
"name": "Adblocker"
|
||||
"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.",
|
||||
"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..",
|
||||
"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}} pixels"
|
||||
"pixels": "{{blurAmount}} piksel"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
@ -140,53 +244,82 @@
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Kalite",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} pixels"
|
||||
"pixels": "{{quality}} piksel"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "Boyut",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "Yumuşak Geçiş",
|
||||
"submenu": {
|
||||
"during": "{{interpolationTime}} saniye içinde"
|
||||
"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)"
|
||||
"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"
|
||||
"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"
|
||||
"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": {
|
||||
"none": "Hiçbiri"
|
||||
"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"
|
||||
"description": "Her zaman kompakt kenar çubugu",
|
||||
"name": "Kompakt Kenar Çubuğu"
|
||||
},
|
||||
"crossfade": {
|
||||
"description": "Şarkılar arasında Çapraz Geçiş",
|
||||
"menu": {
|
||||
"advanced": "İleri düzey için"
|
||||
"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ı"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -194,14 +327,33 @@
|
||||
"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": {
|
||||
"description": "Rich Presence ile Discord'da ne dinlediğinizi gösterin.",
|
||||
"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": {
|
||||
@ -211,71 +363,328 @@
|
||||
"buttons": {
|
||||
"ok": "Tamam"
|
||||
},
|
||||
"title": "İndirmede hata meydana geldi!"
|
||||
"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"
|
||||
},
|
||||
"message": "Çalma listesini indir : {{playlistTitle}}",
|
||||
"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}}%",
|
||||
"preparing-file": "Dosya Hazırlanıyor…"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"last-fm": {
|
||||
"name": "Last.fm"
|
||||
"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"
|
||||
},
|
||||
"shortcuts": {
|
||||
"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": {
|
||||
"keybind": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"next": "İler"
|
||||
"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)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Last.fm ile kimlik doğrulaması yapılamadı\nBir sonraki yeniden başlatmaya kadar açılır pencereyi gizle.",
|
||||
"title": "Kimlik Doğrulama Başarısız"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"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"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Увімкнено",
|
||||
"label": "Плагіни"
|
||||
"label": "Плагіни",
|
||||
"new": "НОВЕ"
|
||||
},
|
||||
"view": {
|
||||
"label": "Вид",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "Попередній",
|
||||
"quit": "Вихід",
|
||||
"restart": "Перезапустити програму",
|
||||
"show": "Показати вікно"
|
||||
"show": "Показати вікно",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -201,12 +206,24 @@
|
||||
},
|
||||
"name": "Блокувальник реклами"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Додати андізлайк, дізлайк, лайк та анлайк кнопки щоб застосувати це до всіх пісень в плейлисті або альбомі",
|
||||
"name": "Дії з альбомами"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Застосовує динамічну тему та візуальні ефекти на основі колірної палітри альбому",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Співвідношення змішування кольорів",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Кольорова тема альбому"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Застосовує ефект освітлення, накладаючи ніжні кольори з відео на фон екрана.",
|
||||
"description": "Застосовує ефект освітлення, накладаючи ніжні кольори з відео на фон екрана",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Обсяг розмиття",
|
||||
@ -289,7 +306,7 @@
|
||||
"menu": {
|
||||
"advanced": "Розширене"
|
||||
},
|
||||
"name": "Плавний перехід[бета-версія]",
|
||||
"name": "Плавний перехід[Бета]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -331,7 +348,7 @@
|
||||
"play-on-youtube-music": "Слухати на YouTube Music",
|
||||
"set-inactivity-timeout": "Встановити тайм-аут бездіяльності"
|
||||
},
|
||||
"name": "Discord Rich Presence",
|
||||
"name": "Активність Discord",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "Введіть тайм-аут бездіяльності в секундах:",
|
||||
@ -399,33 +416,81 @@
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Робить регулятор гучності експоненціальним, що полегшує вибір тихих рівнів гучності.",
|
||||
"name": "Експоненціальний обсяг"
|
||||
"name": "Експоненціальна гучність"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Надає меню-барам вишуканого, темного або кольору альбому вигляду",
|
||||
"description": "Надає панелям меню вишуканий темний або кольоровий вигляд, схожий на альбом",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "Сховати елементи керування вікном DOM"
|
||||
},
|
||||
"name": "Меню в програмі"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Додати підтримку прокрутки для Last.fm",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Додано підтримку для Lumia Stream",
|
||||
"name": "Lumia Stream [бета-версія]"
|
||||
"name": "Lumia Stream [Бета]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Додає підтримку текстів для більшості пісень",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Романізована лірика"
|
||||
"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": {
|
||||
@ -435,9 +500,14 @@
|
||||
"interactive-settings": {
|
||||
"label": "Інтерактивні налаштування",
|
||||
"submenu": {
|
||||
"hide-button-text": "Сховати текст кнопки"
|
||||
"hide-button-text": "Сховати текст кнопки",
|
||||
"refresh-on-play-pause": "Оновлення при відтворенні/паузі",
|
||||
"tray-controls": "Відкриття/закриття при натисканні на значок в області повідомлень (tray)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"priority": "Пріоритет повідомлень",
|
||||
"toast-style": "Стиль спливаючих повідомлень",
|
||||
"unpause-notification": "Показувати повідомлення при відновленні відтворення після паузи"
|
||||
},
|
||||
"name": "Сповіщення"
|
||||
},
|
||||
@ -456,9 +526,165 @@
|
||||
}
|
||||
},
|
||||
"save-window-position": "Зберегти положення вікна",
|
||||
"save-window-size": "Зберегти розмір вікна"
|
||||
"save-window-size": "Зберегти розмір вікна",
|
||||
"use-native-pip": "Використовувати вбудований режим \"картинка-у-картинці\" браузера"
|
||||
},
|
||||
"name": "Зображення в зображенні"
|
||||
"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 тощо)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Не вдалося автентифікуватися на Last.fm\nСховати до наступного запуску.",
|
||||
"title": "Не вдалося автентифікуватися"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "Візуалізація"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
690
src/i18n/resources/vi.json
Normal file
690
src/i18n/resources/vi.json
Normal file
@ -0,0 +1,690 @@
|
||||
{
|
||||
"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",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Tỉ lệ trộn màu",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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 thông báo bật lên",
|
||||
"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)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Không thể xác minh với \nẨn thông báo cho đến lần bật ứng dụng tiếp theo.",
|
||||
"title": "Xác minh thất bại"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Cài đặt API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Nhập mã người dùng ListenBrainz"
|
||||
},
|
||||
"scrobble-other-media": "Scrobber nội dung khác"
|
||||
},
|
||||
"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ị"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "已启用",
|
||||
"label": "插件"
|
||||
"label": "插件",
|
||||
"new": "新增"
|
||||
},
|
||||
"view": {
|
||||
"label": "视图",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "上一首",
|
||||
"quit": "退出",
|
||||
"restart": "重启应用",
|
||||
"show": "显示窗口"
|
||||
"show": "显示窗口",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -201,12 +206,24 @@
|
||||
},
|
||||
"name": "广告屏蔽器"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "添加作用于播放列表或专辑中所有歌曲的全局“点赞/取消点赞”与“喜欢/取消喜欢”按钮",
|
||||
"name": "专辑操作"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "根据专辑封面配色动态改变主题与视觉效果",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "颜色混合比例",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "专辑配色主题"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "将视频中的浅配色作为光效投射到背景中,以增加沉浸感。",
|
||||
"description": "将视频中的浅配色作为光效投射到背景中,以增加沉浸感",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "模糊等级",
|
||||
@ -289,7 +306,7 @@
|
||||
"menu": {
|
||||
"advanced": "高级"
|
||||
},
|
||||
"name": "交叉淡化 [beta]",
|
||||
"name": "交叉淡化 [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -408,10 +425,6 @@
|
||||
},
|
||||
"name": "应用内菜单"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "添加 Last.fm 记录支持",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "添加 Lumia Stream 支持",
|
||||
"name": "Lumia Stream [测试]"
|
||||
@ -426,6 +439,52 @@
|
||||
"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": "导航"
|
||||
@ -518,8 +577,41 @@
|
||||
"description": "允许在视频上显示切换画质按钮",
|
||||
"name": "视频画质切换器"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "添加歌曲追踪支持(如 Last.fm 和 Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "与 Last.fm 认证时失败\n弹出窗口将在下次重启前隐藏。",
|
||||
"title": "认证失败"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API 设置"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "输入 ListenBrainz 用户令牌"
|
||||
},
|
||||
"scrobble-other-media": "记录其他媒体文件"
|
||||
},
|
||||
"name": "歌曲记录器",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API 密钥(Key)",
|
||||
"api-secret": "Last.fm API 密文(Secret)"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "输入您的v ListenBrainz 用户令牌:",
|
||||
"title": "ListenBrainz 令牌"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "允许为音频回放(播放/暂停/上一曲/下一曲)设置全局热键,兼具覆盖物理键以禁用 OSD、启用 Ctrl/CMD + F 搜索、为物理键启用 Linux MPRIS 支持及自定义热键等高级功能。",
|
||||
"description": "允许为音频回放操作(播放/暂停/上一曲/下一曲)设置全局热键,兼具覆盖物理键以禁用 OSD、启用 Ctrl/CMD + F 搜索、为物理键启用 Linux MPRIS 支持及自定义热键等高级功能",
|
||||
"menu": {
|
||||
"override-media-keys": "覆盖物理媒体热键",
|
||||
"set-keybinds": "设置全局歌曲控件"
|
||||
|
||||
@ -83,7 +83,7 @@
|
||||
"navigation": {
|
||||
"label": "導覽列",
|
||||
"submenu": {
|
||||
"copy-current-url": "複製目前的網址",
|
||||
"copy-current-url": "複製當前頁面的網址",
|
||||
"go-back": "回到上一頁",
|
||||
"go-forward": "回到下一頁",
|
||||
"quit": "退出",
|
||||
@ -112,7 +112,7 @@
|
||||
"toggle-dev-tools": "切換開發者人員工具"
|
||||
}
|
||||
},
|
||||
"always-on-top": "永遠顯示在最上層",
|
||||
"always-on-top": "最上層顯示",
|
||||
"auto-update": "自動更新",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
@ -128,22 +128,22 @@
|
||||
},
|
||||
"label": "語言",
|
||||
"submenu": {
|
||||
"to-help-translate": "想要協助翻譯?按一下這裡"
|
||||
"to-help-translate": "想協助翻譯?按一下這裡"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "應用啟動時繼續上次播放的歌曲",
|
||||
"single-instance-lock": "單視窗鎖定",
|
||||
"single-instance-lock": "單實例模式",
|
||||
"start-at-login": "開機時啟動",
|
||||
"starting-page": {
|
||||
"label": "啟動頁面",
|
||||
"unset": "不指定"
|
||||
},
|
||||
"tray": {
|
||||
"label": "系統閘圖式",
|
||||
"label": "系統閘",
|
||||
"submenu": {
|
||||
"disabled": "已停用",
|
||||
"enabled-and-hide-app": "啟用並隱藏應用程式",
|
||||
"enabled-and-show-app": "啟用並顯示應用程式",
|
||||
"enabled-and-hide-app": "啟用並最小化應用程式",
|
||||
"enabled-and-show-app": "啟用但持續顯示應用程式",
|
||||
"play-pause-on-click": "點擊時播放/暫停"
|
||||
}
|
||||
},
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "啟用",
|
||||
"label": "外掛功能"
|
||||
"label": "外掛功能",
|
||||
"new": "新的"
|
||||
},
|
||||
"view": {
|
||||
"label": "視窗",
|
||||
@ -190,7 +191,11 @@
|
||||
"previous": "上一首",
|
||||
"quit": "關閉",
|
||||
"restart": "重啟程式",
|
||||
"show": "顯示視窗"
|
||||
"show": "顯示視窗",
|
||||
"tooltip": {
|
||||
"default": "Youtube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -199,14 +204,26 @@
|
||||
"menu": {
|
||||
"blocker": "阻擋方式"
|
||||
},
|
||||
"name": "廣告阻擋"
|
||||
"name": "廣告攔截器"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "新增支援對整個播放清單或專輯\"喜歡/不喜歡\"\"取消喜歡/取消不喜歡\"的按鈕",
|
||||
"name": "進階專輯操作"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "依歌曲色調自動更改應用程式主題",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "顏色混合程度",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "隨歌曲色調變更主題"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "將影片內容的淺色畫面投放至螢幕背景, 讓觀眾在觀賞影片時更有臨場感。",
|
||||
"description": "影片周圍背景根據影片內容改變顏色, 讓觀眾在觀賞影片時更有臨場感",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "模糊等級",
|
||||
@ -408,10 +425,6 @@
|
||||
},
|
||||
"name": "程式內選單列"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "新增對Last.fm的scrobbling支援",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "新增對 Lumia Stream 的支援",
|
||||
"name": "Lumia Stream [Beta]"
|
||||
@ -426,6 +439,52 @@
|
||||
"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": "導覽列"
|
||||
@ -455,7 +514,7 @@
|
||||
"picture-in-picture": {
|
||||
"description": "允許應用程式切換至子母畫面模式",
|
||||
"menu": {
|
||||
"always-on-top": "永遠顯示在最上層",
|
||||
"always-on-top": "最上層顯示",
|
||||
"hotkey": {
|
||||
"label": "快捷鍵",
|
||||
"prompt": {
|
||||
@ -518,8 +577,41 @@
|
||||
"description": "允許在影片內進行畫質更改",
|
||||
"name": "允許變更影片畫質"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "額外新增 scrobbling 支援 (例如:last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Last.fm認證失敗\n將隱藏彈窗直到重啟。",
|
||||
"title": "認證失敗"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API 設定"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "輸入 ListenBrainz 使用者憑證"
|
||||
},
|
||||
"scrobble-other-media": "紀錄其他媒體文件"
|
||||
},
|
||||
"name": "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媒體快捷鍵 + 更多自訂快捷鍵給進階使用者。",
|
||||
"description": "使用全域快捷鍵控制音樂 (播放/暫停/下一首/上一首) + 透過覆寫媒體快捷鍵停用媒體OSD + 允許Ctrl/CMD + F來搜尋 + 支援Linux MPRIS媒體快捷鍵 + 更多自訂快捷鍵給進階使用者",
|
||||
"menu": {
|
||||
"override-media-keys": "覆寫媒體快捷鍵",
|
||||
"set-keybinds": "設定全域歌曲控制"
|
||||
|
||||
49
src/index.ts
49
src/index.ts
@ -53,8 +53,17 @@ import {
|
||||
import { LoggerPrefix } from '@/utils';
|
||||
import { loadI18n, setLanguage, t } from '@/i18n';
|
||||
|
||||
import ErrorHtmlAsset from '@assets/error.html?asset';
|
||||
|
||||
import type { PluginConfig } from '@/types/plugins';
|
||||
|
||||
if (!is.macOS()) {
|
||||
delete allPlugins['touchbar'];
|
||||
}
|
||||
if (!is.windows()) {
|
||||
delete allPlugins['taskbar-mediacontrol'];
|
||||
}
|
||||
|
||||
// Catch errors and log them
|
||||
unhandled({
|
||||
logger: console.error,
|
||||
@ -73,11 +82,15 @@ if (!gotTheLock) {
|
||||
app.exit();
|
||||
}
|
||||
|
||||
// Ozone platform hint: Required for Wayland support
|
||||
app.commandLine.appendSwitch('ozone-platform-hint', 'auto');
|
||||
// SharedArrayBuffer: Required for downloader (@ffmpeg/core-mt)
|
||||
// OverlayScrollbar: Required for overlay scrollbars
|
||||
// UseOzonePlatform: Required for Wayland support
|
||||
// WaylandWindowDecorations: Required for Wayland decorations
|
||||
app.commandLine.appendSwitch(
|
||||
'enable-features',
|
||||
'OverlayScrollbar,SharedArrayBuffer',
|
||||
'OverlayScrollbar,SharedArrayBuffer,UseOzonePlatform,WaylandWindowDecorations',
|
||||
);
|
||||
if (config.get('options.disableHardwareAcceleration')) {
|
||||
if (is.dev()) {
|
||||
@ -114,18 +127,18 @@ function onClosed() {
|
||||
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) => {
|
||||
ipcMain.handle(
|
||||
'get-config',
|
||||
'ytmd:get-config',
|
||||
(_, id: string) =>
|
||||
deepmerge(
|
||||
allPlugins[id].config ?? { enabled: false },
|
||||
config.get(`plugins.${id}`) ?? {},
|
||||
) 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),
|
||||
);
|
||||
|
||||
@ -258,7 +271,7 @@ async function createMainWindow() {
|
||||
const windowPosition: Electron.Point = config.get('window-position');
|
||||
const useInlineMenu = config.plugins.isEnabled('in-app-menu');
|
||||
|
||||
const defaultTitleBarOverlayOptions: Electron.TitleBarOverlayOptions = {
|
||||
const defaultTitleBarOverlayOptions: Electron.TitleBarOverlay = {
|
||||
color: '#00000000',
|
||||
symbolColor: '#ffffff',
|
||||
height: 32,
|
||||
@ -413,6 +426,18 @@ async function createMainWindow() {
|
||||
});
|
||||
}
|
||||
});
|
||||
win.webContents.on('will-redirect', (event) => {
|
||||
const url = new URL(event.url);
|
||||
|
||||
// Workarounds for regions where YTM is restricted
|
||||
if (url.hostname.endsWith('youtube.com') && url.pathname === '/premium') {
|
||||
event.preventDefault();
|
||||
|
||||
win.webContents.loadURL(
|
||||
'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'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
win.webContents.loadURL(urlToLoad);
|
||||
|
||||
@ -486,7 +511,7 @@ app.once('browser-window-created', (_event, win) => {
|
||||
if (errorCode !== -3) {
|
||||
// -3 is a false positive
|
||||
win.webContents.send('log', log);
|
||||
win.webContents.loadFile(path.join(__dirname, 'error.html'));
|
||||
win.webContents.loadFile(ErrorHtmlAsset);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -567,7 +592,7 @@ app.whenReady().then(async () => {
|
||||
);
|
||||
try {
|
||||
// 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 (
|
||||
shortcutDetails.target !== appLocation ||
|
||||
shortcutDetails.appUserModelId !== appID
|
||||
@ -652,7 +677,9 @@ app.whenReady().then(async () => {
|
||||
);
|
||||
}
|
||||
|
||||
handleProtocol(command);
|
||||
const splited = decodeURIComponent(command).split(' ');
|
||||
|
||||
handleProtocol(splited.shift()!, splited);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -687,13 +714,15 @@ app.whenReady().then(async () => {
|
||||
const dialogOptions: Electron.MessageBoxOptions = {
|
||||
type: 'info',
|
||||
buttons: [
|
||||
t('main.dialog.update-available.buttons.download'),
|
||||
t('main.dialog.update-available.buttons.ok'),
|
||||
t('main.dialog.update-available.buttons.download'),
|
||||
t('main.dialog.update-available.buttons.disable'),
|
||||
],
|
||||
title: t('main.dialog.update-available.title'),
|
||||
message: t('main.dialog.update-available.message'),
|
||||
detail: t('main.dialog.update-available.detail', { downloadLink }),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
};
|
||||
|
||||
let dialogPromise: Promise<Electron.MessageBoxReturnValue>;
|
||||
@ -717,7 +746,7 @@ app.whenReady().then(async () => {
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,9 +18,9 @@ const loadedPluginMap: Record<
|
||||
export const createContext = <Config extends PluginConfig>(
|
||||
id: string,
|
||||
): RendererContext<Config> => ({
|
||||
getConfig: async () => window.ipcRenderer.invoke('get-config', id),
|
||||
getConfig: async () => window.ipcRenderer.invoke('ytmd:get-config', id),
|
||||
setConfig: async (newConfig) => {
|
||||
await window.ipcRenderer.invoke('set-config', id, newConfig);
|
||||
await window.ipcRenderer.invoke('ytmd:set-config', id, newConfig);
|
||||
},
|
||||
ipc: {
|
||||
send: (event: string, ...args: unknown[]) => {
|
||||
|
||||
@ -7,149 +7,163 @@ import dislikeHTML from './templates/dislike.html?raw';
|
||||
import likeHTML from './templates/like.html?raw';
|
||||
import unlikeHTML from './templates/unlike.html?raw';
|
||||
|
||||
export default createPlugin({
|
||||
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.0',
|
||||
addedVersion: '3.2.X',
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
renderer: {
|
||||
observer: null as MutationObserver | null,
|
||||
loadObserver: null as MutationObserver | null,
|
||||
changeObserver: null as MutationObserver | null,
|
||||
waiting: false as boolean,
|
||||
waiting: false,
|
||||
start() {
|
||||
//Waits for pagechange
|
||||
// Waits for pagechange
|
||||
this.onPageChange();
|
||||
this.observer = new MutationObserver(() => {
|
||||
this.onPageChange();
|
||||
});
|
||||
this.observer.observe(document.querySelector('#browse-page'), {
|
||||
this.observer.observe(document.querySelector('#browse-page')!, {
|
||||
attributes: false,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
},
|
||||
onPageChange() {
|
||||
async onPageChange() {
|
||||
if (this.waiting) {
|
||||
return;
|
||||
} else {
|
||||
this.waiting = true;
|
||||
}
|
||||
this.waitForElem('#continuations').then((continuations: HTMLElement) => {
|
||||
this.waiting = false;
|
||||
//Gets the for buttons
|
||||
let buttons: Array<HTMLElement> = [
|
||||
ElementFromHtml(undislikeHTML),
|
||||
ElementFromHtml(dislikeHTML),
|
||||
ElementFromHtml(likeHTML),
|
||||
ElementFromHtml(unlikeHTML),
|
||||
];
|
||||
//Finds the playlist
|
||||
const playlist =
|
||||
document.querySelector('ytmusic-shelf-renderer') ??
|
||||
document.querySelector('ytmusic-playlist-shelf-renderer');
|
||||
//Adds an observer for every button so it gets updated when one is clicked
|
||||
this.changeObserver?.disconnect();
|
||||
this.changeObserver = new MutationObserver(() => {
|
||||
this.stop();
|
||||
this.start();
|
||||
const 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,
|
||||
});
|
||||
const allButtons = playlist.querySelectorAll(
|
||||
'yt-button-shape.ytmusic-like-button-renderer',
|
||||
);
|
||||
for (const btn of allButtons)
|
||||
this.changeObserver.observe(btn, {
|
||||
attributes: true,
|
||||
childList: false,
|
||||
subtree: false,
|
||||
});
|
||||
//Determine if button is needed and colors the percentage
|
||||
const listsLength = playlist.querySelectorAll(
|
||||
'#button-shape-dislike > button',
|
||||
).length;
|
||||
if (continuations.children.length == 0 && listsLength > 0) {
|
||||
const counts = [
|
||||
playlist?.querySelectorAll(
|
||||
'#button-shape-dislike[aria-pressed=true] > button',
|
||||
).length,
|
||||
playlist?.querySelectorAll(
|
||||
'#button-shape-dislike[aria-pressed=false] > button',
|
||||
).length,
|
||||
playlist?.querySelectorAll(
|
||||
'#button-shape-like[aria-pressed=false] > button',
|
||||
).length,
|
||||
playlist?.querySelectorAll(
|
||||
'#button-shape-like[aria-pressed=true] > button',
|
||||
).length,
|
||||
];
|
||||
let i = 0;
|
||||
for (const count of counts) {
|
||||
if (count == 0) {
|
||||
buttons.splice(i, 1);
|
||||
i--;
|
||||
} else {
|
||||
buttons[i].children[0].children[0].style.setProperty(
|
||||
'-webkit-mask-size',
|
||||
`100% ${100 - (count / listsLength) * 100}%`,
|
||||
);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
//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);
|
||||
}
|
||||
}
|
||||
const menu = document.querySelector('.detail-page-menu');
|
||||
if (menu && !document.querySelector('.like-menu')) {
|
||||
for (const button of buttons) {
|
||||
menu.appendChild(button);
|
||||
button.addEventListener('click', this.loadFullList);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
loadFullList(event) {
|
||||
event.stopPropagation();
|
||||
const id: string = event.currentTarget.id,
|
||||
loader = document.getElementById('continuations');
|
||||
this.loadObserver = new MutationObserver(() => {
|
||||
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.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');
|
||||
this.loadObserver.observe(loader, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
loader?.style.setProperty('top', '0');
|
||||
loader?.style.setProperty('left', '50%');
|
||||
loader?.style.setProperty('position', 'absolute');
|
||||
}
|
||||
},
|
||||
applyToList(id: string, loader: HTMLElement) {
|
||||
if (loader.children.length != 0) return;
|
||||
this.loadObserver?.disconnect();
|
||||
let playlistbuttons: NodeListOf<Element> | undefined;
|
||||
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(
|
||||
playlistButtons = playlist?.querySelectorAll(
|
||||
'#button-shape-dislike[aria-pressed=true] > button',
|
||||
);
|
||||
break;
|
||||
case 'alldislike':
|
||||
playlistbuttons = playlist?.querySelectorAll(
|
||||
playlistButtons = playlist?.querySelectorAll(
|
||||
'#button-shape-dislike[aria-pressed=false] > button',
|
||||
);
|
||||
break;
|
||||
case 'alllike':
|
||||
playlistbuttons = playlist?.querySelectorAll(
|
||||
playlistButtons = playlist?.querySelectorAll(
|
||||
'#button-shape-like[aria-pressed=false] > button',
|
||||
);
|
||||
break;
|
||||
case 'allunlike':
|
||||
playlistbuttons = playlist?.querySelectorAll(
|
||||
playlistButtons = playlist?.querySelectorAll(
|
||||
'#button-shape-like[aria-pressed=true] > button',
|
||||
);
|
||||
break;
|
||||
@ -167,7 +181,7 @@ export default createPlugin({
|
||||
waitForElem(selector: string) {
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
const elem = document.querySelector(selector);
|
||||
const elem = document.querySelector<HTMLElement>(selector);
|
||||
if (!elem) return;
|
||||
|
||||
clearInterval(interval);
|
||||
|
||||
@ -8,28 +8,73 @@ import { t } from '@/i18n';
|
||||
|
||||
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'),
|
||||
description: () => t('plugins.album-color-theme.description'),
|
||||
restartNeeded: true,
|
||||
restartNeeded: false,
|
||||
config: {
|
||||
enabled: false,
|
||||
ratio: 0.5,
|
||||
},
|
||||
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: {
|
||||
color: null as Color | null,
|
||||
darkColor: null as Color | null,
|
||||
playerPage: null,
|
||||
navBarBackground: null,
|
||||
ytmusicPlayerBar: null,
|
||||
playerBarBackground: null,
|
||||
sidebarBig: null,
|
||||
sidebarSmall: null,
|
||||
ytmusicAppLayout: null,
|
||||
|
||||
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() {
|
||||
async start({ getConfig }) {
|
||||
this.playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||
this.navBarBackground = document.querySelector<HTMLElement>(
|
||||
'#nav-bar-background',
|
||||
@ -44,6 +89,9 @@ export default createPlugin({
|
||||
'#mini-guide-background',
|
||||
);
|
||||
this.ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
|
||||
const config = await getConfig();
|
||||
document.documentElement.style.setProperty(RATIO_KEY, `${~~(config.ratio * 100)}%`);
|
||||
},
|
||||
onPlayerApiReady(playerApi) {
|
||||
const fastAverageColor = new FastAverageColor();
|
||||
@ -82,28 +130,62 @@ export default createPlugin({
|
||||
this.updateColor();
|
||||
});
|
||||
},
|
||||
getColor(key: string, alpha = 1) {
|
||||
return `rgba(var(${key}), ${alpha})`;
|
||||
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 change = (element: HTMLElement | null, color: string) => {
|
||||
if (element) {
|
||||
element.style.backgroundColor = color;
|
||||
}
|
||||
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');
|
||||
});
|
||||
|
||||
change(this.playerPage, this.getColor(DARK_COLOR_KEY));
|
||||
change(this.navBarBackground, this.getColor(COLOR_KEY));
|
||||
change(this.ytmusicPlayerBar, this.getColor(COLOR_KEY));
|
||||
change(this.playerBarBackground, this.getColor(COLOR_KEY));
|
||||
change(this.sidebarBig, this.getColor(COLOR_KEY));
|
||||
|
||||
if (this.ytmusicAppLayout?.hasAttribute('player-page-open')) {
|
||||
change(this.sidebarSmall, this.getColor(DARK_COLOR_KEY));
|
||||
}
|
||||
|
||||
const ytRightClickList = document.querySelector<HTMLElement>('tp-yt-paper-listbox');
|
||||
change(ytRightClickList, this.getColor(COLOR_KEY));
|
||||
document.body.style.setProperty('background', this.getMixedColor('#030303', COLOR_KEY), 'important');
|
||||
document.documentElement.style.setProperty('--ytmusic-background', this.getMixedColor('#030303', DARK_COLOR_KEY), 'important');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -24,19 +24,13 @@ yt-page-navigation-progress {
|
||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
|
||||
}
|
||||
|
||||
#img,
|
||||
#player,
|
||||
.song-media-controls.style-scope.ytmusic-player {
|
||||
border-radius: 2% !important;
|
||||
}
|
||||
|
||||
#items {
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
|
||||
/* fix blur navigation bar */
|
||||
|
||||
ytmusic-app-layout > [slot='player-page'] {
|
||||
ytmusic-app-layout > [slot="player-page"]:not([is-mweb-modernization-enabled]) {
|
||||
padding-top: 90px;
|
||||
margin-top: calc(-90px + var(--menu-bar-height, 0px)) !important;
|
||||
}
|
||||
@ -53,15 +47,37 @@ ytmusic-app-layout > [slot='player-page'] {
|
||||
.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;
|
||||
}
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
import style from './style.css?inline';
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { menu } from './menu';
|
||||
import { AmbientModePluginConfig } from './types';
|
||||
|
||||
export type AmbientModePluginConfig = {
|
||||
enabled: boolean;
|
||||
quality: number;
|
||||
buffer: number;
|
||||
interpolationTime: number;
|
||||
blur: number;
|
||||
size: number;
|
||||
opacity: number;
|
||||
fullscreen: boolean;
|
||||
};
|
||||
const defaultConfig: AmbientModePluginConfig = {
|
||||
enabled: false,
|
||||
quality: 50,
|
||||
@ -30,205 +22,78 @@ export default createPlugin({
|
||||
restartNeeded: false,
|
||||
config: defaultConfig,
|
||||
stylesheets: [style],
|
||||
menu: async ({ getConfig, setConfig }) => {
|
||||
const interpolationTimeList = [0, 500, 1000, 1500, 2000, 3000, 4000, 5000];
|
||||
const qualityList = [10, 25, 50, 100, 200, 500, 1000];
|
||||
const sizeList = [100, 110, 125, 150, 175, 200, 300];
|
||||
const bufferList = [1, 5, 10, 20, 30];
|
||||
const blurAmountList = [0, 5, 10, 25, 50, 100, 150, 200, 500];
|
||||
const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
|
||||
|
||||
const config = await getConfig();
|
||||
|
||||
return [
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.smoothness-transition.label'),
|
||||
submenu: interpolationTimeList.map((interpolationTime) => ({
|
||||
label: t(
|
||||
'plugins.ambient-mode.menu.smoothness-transition.submenu.during',
|
||||
{
|
||||
interpolationTime: interpolationTime / 1000,
|
||||
},
|
||||
),
|
||||
type: 'radio',
|
||||
checked: config.interpolationTime === interpolationTime,
|
||||
click() {
|
||||
setConfig({ interpolationTime });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.quality.label'),
|
||||
submenu: qualityList.map((quality) => ({
|
||||
label: t('plugins.ambient-mode.menu.quality.submenu.pixels', {
|
||||
quality,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.quality === quality,
|
||||
click() {
|
||||
setConfig({ quality });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.size.label'),
|
||||
submenu: sizeList.map((size) => ({
|
||||
label: t('plugins.ambient-mode.menu.size.submenu.percent', { size }),
|
||||
type: 'radio',
|
||||
checked: config.size === size,
|
||||
click() {
|
||||
setConfig({ size });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.buffer.label'),
|
||||
submenu: bufferList.map((buffer) => ({
|
||||
label: t('plugins.ambient-mode.menu.buffer.submenu.buffer', {
|
||||
buffer,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.buffer === buffer,
|
||||
click() {
|
||||
setConfig({ buffer });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.opacity.label'),
|
||||
submenu: opacityList.map((opacity) => ({
|
||||
label: t('plugins.ambient-mode.menu.opacity.submenu.percent', {
|
||||
opacity: opacity * 100,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.opacity === opacity,
|
||||
click() {
|
||||
setConfig({ opacity });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.blur-amount.label'),
|
||||
submenu: blurAmountList.map((blur) => ({
|
||||
label: t('plugins.ambient-mode.menu.blur-amount.submenu.pixels', {
|
||||
blurAmount: blur,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.blur === blur,
|
||||
click() {
|
||||
setConfig({ blur });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.use-fullscreen.label'),
|
||||
type: 'checkbox',
|
||||
checked: config.fullscreen,
|
||||
click(item) {
|
||||
setConfig({ fullscreen: item.checked });
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
menu: menu,
|
||||
|
||||
renderer: {
|
||||
interpolationTime: defaultConfig.interpolationTime,
|
||||
buffer: defaultConfig.buffer,
|
||||
qualityRatio: defaultConfig.quality,
|
||||
sizeRatio: defaultConfig.size / 100,
|
||||
size: defaultConfig.size,
|
||||
blur: defaultConfig.blur,
|
||||
opacity: defaultConfig.opacity,
|
||||
isFullscreen: defaultConfig.fullscreen,
|
||||
|
||||
unregister: null as (() => void) | null,
|
||||
update: null as (() => void) | null,
|
||||
observer: null as MutationObserver | null,
|
||||
interval: null as NodeJS.Timeout | null,
|
||||
lastMediaType: null as "video" | "image" | null,
|
||||
lastVideoSource: null as string | null,
|
||||
lastImageSource: null as string | null,
|
||||
|
||||
async start({ getConfig }) {
|
||||
const config = await getConfig();
|
||||
this.interpolationTime = config.interpolationTime;
|
||||
this.buffer = config.buffer;
|
||||
this.qualityRatio = config.quality;
|
||||
this.size = config.size;
|
||||
this.blur = config.blur;
|
||||
this.opacity = config.opacity;
|
||||
this.isFullscreen = config.fullscreen;
|
||||
|
||||
const songImage = document.querySelector<HTMLImageElement>('#song-image');
|
||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||
const image = songImage?.querySelector<HTMLImageElement>('yt-img-shadow > img');
|
||||
const video = songVideo?.querySelector<HTMLVideoElement>('.html5-video-container > video');
|
||||
const videoWrapper = document.querySelector('#song-video > .player-wrapper');
|
||||
|
||||
start() {
|
||||
const injectBlurImage = () => {
|
||||
const songImage = document.querySelector<HTMLImageElement>(
|
||||
'#song-image',
|
||||
);
|
||||
const image = document.querySelector<HTMLImageElement>(
|
||||
'#song-image yt-img-shadow > img',
|
||||
);
|
||||
if (!songImage || !image) return null;
|
||||
|
||||
if (!songImage) return null;
|
||||
if (!image) return null;
|
||||
this.lastImageSource = image.src;
|
||||
|
||||
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;
|
||||
|
||||
this.update = () => {
|
||||
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('--width', `${this.size}%`);
|
||||
blurImage.style.setProperty('--height', `${this.size}%`);
|
||||
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);
|
||||
this.update();
|
||||
|
||||
/* injecting */
|
||||
songImage.prepend(blurImage);
|
||||
|
||||
/* cleanup */
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
resizeObserver.disconnect();
|
||||
window.removeEventListener('resize', applyImageAttribute);
|
||||
|
||||
if (blurImage.isConnected) blurImage.remove();
|
||||
};
|
||||
};
|
||||
|
||||
const injectBlurVideo = (): (() => void) | null => {
|
||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||
const video = document.querySelector<HTMLVideoElement>(
|
||||
'#song-video .html5-video-container > video',
|
||||
);
|
||||
const wrapper = document.querySelector('#song-video > .player-wrapper');
|
||||
const injectBlurVideo = () => {
|
||||
if (!songVideo || !video || !videoWrapper) return null;
|
||||
|
||||
if (!songVideo) return null;
|
||||
if (!video) return null;
|
||||
if (!wrapper) return null;
|
||||
this.lastVideoSource = video.src;
|
||||
|
||||
const blurCanvas = document.createElement('canvas');
|
||||
blurCanvas.classList.add('html5-blur-canvas');
|
||||
|
||||
const context = blurCanvas.getContext('2d', {
|
||||
willReadFrequently: true,
|
||||
});
|
||||
const context = blurCanvas.getContext('2d', { willReadFrequently: true });
|
||||
|
||||
/* effect */
|
||||
let lastEffectWorkId: number | null = null;
|
||||
@ -242,17 +107,13 @@ export default createPlugin({
|
||||
if (!context) return;
|
||||
|
||||
const width = this.qualityRatio;
|
||||
let height = Math.max(
|
||||
Math.floor((blurCanvas.height / blurCanvas.width) * width),
|
||||
1,
|
||||
);
|
||||
let height = Math.max(Math.floor((blurCanvas.height / blurCanvas.width) * width), 1,);
|
||||
if (!Number.isFinite(height)) height = width;
|
||||
if (!height) return;
|
||||
|
||||
context.globalAlpha = 1;
|
||||
if (lastImageData) {
|
||||
const frameOffset =
|
||||
(1 / this.buffer) * (1000 / this.interpolationTime);
|
||||
const frameOffset = (1 / this.buffer) * (1000 / this.interpolationTime);
|
||||
context.globalAlpha = 1 - (frameOffset * 2); // because of alpha value must be < 1
|
||||
context.putImageData(lastImageData, 0, 0);
|
||||
context.globalAlpha = frameOffset;
|
||||
@ -265,7 +126,7 @@ export default createPlugin({
|
||||
});
|
||||
};
|
||||
|
||||
const applyVideoAttributes = () => {
|
||||
this.update = () => {
|
||||
const rect = video.getBoundingClientRect();
|
||||
|
||||
const newWidth = Math.floor(video.width || rect.width);
|
||||
@ -274,45 +135,21 @@ export default createPlugin({
|
||||
if (newWidth === 0 || newHeight === 0) return;
|
||||
|
||||
blurCanvas.width = this.qualityRatio;
|
||||
blurCanvas.height = Math.floor(
|
||||
(newHeight / newWidth) * this.qualityRatio,
|
||||
);
|
||||
blurCanvas.style.width = `${newWidth * this.sizeRatio}px`;
|
||||
blurCanvas.style.height = `${newHeight * this.sizeRatio}px`;
|
||||
blurCanvas.height = Math.floor((newHeight / newWidth) * this.qualityRatio);
|
||||
|
||||
if (this.isFullscreen) blurCanvas.classList.add('fullscreen');
|
||||
else blurCanvas.classList.remove('fullscreen');
|
||||
|
||||
const leftOffset = (newWidth * (this.sizeRatio - 1)) / 2;
|
||||
const topOffset = (newHeight * (this.sizeRatio - 1)) / 2;
|
||||
blurCanvas.style.setProperty('--left', `${-1 * leftOffset}px`);
|
||||
blurCanvas.style.setProperty('--top', `${-1 * topOffset}px`);
|
||||
blurCanvas.style.setProperty('--width', `${this.size}%`);
|
||||
blurCanvas.style.setProperty('--height', `${this.size}%`);
|
||||
blurCanvas.style.setProperty('--blur', `${this.blur}px`);
|
||||
blurCanvas.style.setProperty('--opacity', `${this.opacity}`);
|
||||
};
|
||||
this.update = applyVideoAttributes;
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'attributes') {
|
||||
applyVideoAttributes();
|
||||
}
|
||||
});
|
||||
});
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
applyVideoAttributes();
|
||||
});
|
||||
this.update();
|
||||
|
||||
/* hooking */
|
||||
let canvasInterval: NodeJS.Timeout | null = null;
|
||||
canvasInterval = setInterval(
|
||||
onSync,
|
||||
Math.max(1, Math.ceil(1000 / this.buffer)),
|
||||
);
|
||||
applyVideoAttributes();
|
||||
observer.observe(songVideo, { attributes: true });
|
||||
resizeObserver.observe(songVideo);
|
||||
window.addEventListener('resize', applyVideoAttributes);
|
||||
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / this.buffer)));
|
||||
|
||||
const onPause = () => {
|
||||
if (canvasInterval) clearInterval(canvasInterval);
|
||||
@ -320,16 +157,13 @@ export default createPlugin({
|
||||
};
|
||||
const onPlay = () => {
|
||||
if (canvasInterval) clearInterval(canvasInterval);
|
||||
canvasInterval = setInterval(
|
||||
onSync,
|
||||
Math.max(1, Math.ceil(1000 / this.buffer)),
|
||||
);
|
||||
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / this.buffer)));
|
||||
};
|
||||
songVideo.addEventListener('pause', onPause);
|
||||
songVideo.addEventListener('play', onPlay);
|
||||
|
||||
/* injecting */
|
||||
wrapper.prepend(blurCanvas);
|
||||
videoWrapper.prepend(blurCanvas);
|
||||
|
||||
/* cleanup */
|
||||
return () => {
|
||||
@ -338,55 +172,63 @@ export default createPlugin({
|
||||
songVideo.removeEventListener('pause', onPause);
|
||||
songVideo.removeEventListener('play', onPlay);
|
||||
|
||||
observer.disconnect();
|
||||
resizeObserver.disconnect();
|
||||
window.removeEventListener('resize', applyVideoAttributes);
|
||||
|
||||
if (blurCanvas.isConnected) blurCanvas.remove();
|
||||
};
|
||||
};
|
||||
|
||||
const isVideoMode = () => {
|
||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||
if (!songVideo) return false;
|
||||
if (!songVideo) {
|
||||
this.lastMediaType = "image";
|
||||
return false;
|
||||
}
|
||||
|
||||
return getComputedStyle(songVideo).display !== 'none';
|
||||
const isVideo = getComputedStyle(songVideo).display !== 'none';
|
||||
this.lastMediaType = isVideo ? "video" : "image";
|
||||
return isVideo;
|
||||
};
|
||||
|
||||
const playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
|
||||
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
|
||||
if (isPageOpen) {
|
||||
this.unregister?.();
|
||||
this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||
const injectBlurElement = (force?: boolean): boolean | void => {
|
||||
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
|
||||
if (isPageOpen) {
|
||||
const isVideo = isVideoMode();
|
||||
if (!force) {
|
||||
if (this.lastMediaType === "video" && this.lastVideoSource === video?.src) return false;
|
||||
if (this.lastMediaType === "image" && this.lastImageSource === image?.src) return false;
|
||||
}
|
||||
this.unregister?.();
|
||||
this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||
} else {
|
||||
this.unregister?.();
|
||||
this.unregister = null;
|
||||
}
|
||||
}
|
||||
|
||||
/* needed for switching between different views (e.g. miniplayer) */
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'attributes') {
|
||||
const isPageOpen =
|
||||
ytmusicAppLayout?.hasAttribute('player-page-open');
|
||||
if (isPageOpen) {
|
||||
this.unregister?.();
|
||||
this.unregister = (isVideoMode() ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||
} else {
|
||||
this.unregister?.();
|
||||
this.unregister = null;
|
||||
}
|
||||
injectBlurElement(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (playerPage) {
|
||||
observer.observe(playerPage, { attributes: true });
|
||||
|
||||
/* fallback ticker for when the observer isn't triggered */
|
||||
this.interval = setInterval(injectBlurElement, 1000);
|
||||
}
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
this.interpolationTime = newConfig.interpolationTime;
|
||||
this.buffer = newConfig.buffer;
|
||||
this.qualityRatio = newConfig.quality;
|
||||
this.sizeRatio = newConfig.size / 100;
|
||||
this.size = newConfig.size;
|
||||
this.blur = newConfig.blur;
|
||||
this.opacity = newConfig.opacity;
|
||||
this.isFullscreen = newConfig.fullscreen;
|
||||
@ -394,9 +236,9 @@ export default createPlugin({
|
||||
this.update?.();
|
||||
},
|
||||
stop() {
|
||||
this.observer?.disconnect();
|
||||
this.update = null;
|
||||
this.unregister?.();
|
||||
if (this.interval) clearInterval(this.interval);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
110
src/plugins/ambient-mode/menu.ts
Normal file
110
src/plugins/ambient-mode/menu.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { t } from "@/i18n";
|
||||
import { MenuContext } from "@/types/contexts";
|
||||
import { MenuItemConstructorOptions } from "electron";
|
||||
import { AmbientModePluginConfig } from "./types";
|
||||
|
||||
export interface menuParameters {
|
||||
getConfig: () => AmbientModePluginConfig | Promise<AmbientModePluginConfig>;
|
||||
setConfig: (conf: Partial<Omit<AmbientModePluginConfig, "enabled">>) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export const menu: (ctx: MenuContext<AmbientModePluginConfig>) => MenuItemConstructorOptions[] | Promise<MenuItemConstructorOptions[]> = async ({ getConfig, setConfig }: menuParameters) => {
|
||||
const interpolationTimeList = [0, 500, 1000, 1500, 2000, 3000, 4000, 5000];
|
||||
const qualityList = [10, 25, 50, 100, 200, 500, 1000];
|
||||
const sizeList = [100, 110, 125, 150, 175, 200, 300];
|
||||
const bufferList = [1, 5, 10, 20, 30];
|
||||
const blurAmountList = [0, 5, 10, 25, 50, 100, 150, 200, 500];
|
||||
const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
|
||||
|
||||
const config = await getConfig();
|
||||
|
||||
return [
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.smoothness-transition.label'),
|
||||
submenu: interpolationTimeList.map((interpolationTime) => ({
|
||||
label: t(
|
||||
'plugins.ambient-mode.menu.smoothness-transition.submenu.during',
|
||||
{
|
||||
interpolationTime: interpolationTime / 1000,
|
||||
},
|
||||
),
|
||||
type: 'radio',
|
||||
checked: config.interpolationTime === interpolationTime,
|
||||
click() {
|
||||
setConfig({ interpolationTime });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.quality.label'),
|
||||
submenu: qualityList.map((quality) => ({
|
||||
label: t('plugins.ambient-mode.menu.quality.submenu.pixels', {
|
||||
quality,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.quality === quality,
|
||||
click() {
|
||||
setConfig({ quality });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.size.label'),
|
||||
submenu: sizeList.map((size) => ({
|
||||
label: t('plugins.ambient-mode.menu.size.submenu.percent', { size }),
|
||||
type: 'radio',
|
||||
checked: config.size === size,
|
||||
click() {
|
||||
setConfig({ size });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.buffer.label'),
|
||||
submenu: bufferList.map((buffer) => ({
|
||||
label: t('plugins.ambient-mode.menu.buffer.submenu.buffer', {
|
||||
buffer,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.buffer === buffer,
|
||||
click() {
|
||||
setConfig({ buffer });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.opacity.label'),
|
||||
submenu: opacityList.map((opacity) => ({
|
||||
label: t('plugins.ambient-mode.menu.opacity.submenu.percent', {
|
||||
opacity: opacity * 100,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.opacity === opacity,
|
||||
click() {
|
||||
setConfig({ opacity });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.blur-amount.label'),
|
||||
submenu: blurAmountList.map((blur) => ({
|
||||
label: t('plugins.ambient-mode.menu.blur-amount.submenu.pixels', {
|
||||
blurAmount: blur,
|
||||
}),
|
||||
type: 'radio',
|
||||
checked: config.blur === blur,
|
||||
click() {
|
||||
setConfig({ blur });
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t('plugins.ambient-mode.menu.use-fullscreen.label'),
|
||||
type: 'checkbox',
|
||||
checked: config.fullscreen,
|
||||
click(item: Electron.MenuItem) {
|
||||
setConfig({ fullscreen: item.checked });
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,40 +1,36 @@
|
||||
#song-video canvas.html5-blur-canvas {
|
||||
#song-video canvas.html5-blur-canvas,
|
||||
#song-image .html5-blur-image {
|
||||
filter: blur(var(--blur, 100px));
|
||||
opacity: var(--opacity, 1);
|
||||
width: var(--width, 100%);
|
||||
height: var(--height, 100%);
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#song-video canvas.html5-blur-canvas:not(.fullscreen) {
|
||||
#song-video canvas.html5-blur-canvas:not(.fullscreen),
|
||||
#song-image .html5-blur-image {
|
||||
position: absolute;
|
||||
|
||||
left: var(--left, 0px);
|
||||
top: var(--top, 0px);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#song-video canvas.html5-blur-canvas.fullscreen {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#song-video .html5-video-container > video {
|
||||
top: 0 !important;
|
||||
#song-video .html5-video-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#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;
|
||||
#player:not([video-mode]):not(.video-mode):not([player-ui-state='MINIPLAYER']):not([is-mweb-modernization-enabled]) {
|
||||
width: 100%;
|
||||
margin: 0 auto !important;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
10
src/plugins/ambient-mode/types.ts
Normal file
10
src/plugins/ambient-mode/types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export type AmbientModePluginConfig = {
|
||||
enabled: boolean;
|
||||
quality: number;
|
||||
buffer: number;
|
||||
interpolationTime: number;
|
||||
blur: number;
|
||||
size: number;
|
||||
opacity: number;
|
||||
fullscreen: boolean;
|
||||
};
|
||||
@ -7,7 +7,7 @@ export default createPlugin({
|
||||
|
||||
renderer() {
|
||||
document.addEventListener(
|
||||
'audioCanPlay',
|
||||
'ytmd:audio-can-play',
|
||||
({ detail: { audioSource, audioContext } }) => {
|
||||
const compressor = audioContext.createDynamicsCompressor();
|
||||
|
||||
|
||||
@ -1,11 +1,24 @@
|
||||
import { createPlugin } from '@/utils';
|
||||
import style from './style.css?inline';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import style from './style.css?inline';
|
||||
|
||||
export default createPlugin({
|
||||
name: () => t('plugins.blur-nav-bar.name'),
|
||||
description: () => t('plugins.blur-nav-bar.description'),
|
||||
restartNeeded: true,
|
||||
stylesheets: [style],
|
||||
renderer() {},
|
||||
restartNeeded: false,
|
||||
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,
|
||||
#header.ytmusic-item-section-renderer,
|
||||
ytmusic-tabs {
|
||||
#header.ytmusic-item-section-renderer {
|
||||
background: rgba(0, 0, 0, 0.3) !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 {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ export default createRenderer<
|
||||
?.unloadModule('captions');
|
||||
document
|
||||
.querySelector('video')
|
||||
?.removeEventListener('srcChanged', this.videoChangeListener);
|
||||
?.removeEventListener('ytmd:src-changed', this.videoChangeListener);
|
||||
this.captionsSettingsButton.removeEventListener(
|
||||
'click',
|
||||
this.captionsButtonClickListener,
|
||||
@ -139,7 +139,7 @@ export default createRenderer<
|
||||
|
||||
document
|
||||
.querySelector('video')
|
||||
?.addEventListener('srcChanged', this.videoChangeListener);
|
||||
?.addEventListener('ytmd:src-changed', this.videoChangeListener);
|
||||
this.captionsSettingsButton.addEventListener(
|
||||
'click',
|
||||
this.captionsButtonClickListener,
|
||||
|
||||
@ -26,8 +26,8 @@ export default createPlugin<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
config: CrossfadePluginConfig | null;
|
||||
ipc: RendererContext<CrossfadePluginConfig>['ipc'] | null;
|
||||
config?: CrossfadePluginConfig;
|
||||
ipc?: RendererContext<CrossfadePluginConfig>['ipc'];
|
||||
},
|
||||
CrossfadePluginConfig
|
||||
>({
|
||||
@ -178,10 +178,8 @@ export default createPlugin<
|
||||
},
|
||||
|
||||
renderer: {
|
||||
config: null,
|
||||
ipc: null,
|
||||
|
||||
start({ ipc }) {
|
||||
async start({ ipc, getConfig }) {
|
||||
this.config = await getConfig();
|
||||
this.ipc = ipc;
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
@ -271,7 +269,7 @@ export default createPlugin<
|
||||
const transitionBeforeEnd = () => {
|
||||
if (
|
||||
video.currentTime >=
|
||||
video.duration - this.config!.secondsBeforeEnd &&
|
||||
video.duration - (this.config?.secondsBeforeEnd ?? 0) &&
|
||||
isReadyToCrossfade()
|
||||
) {
|
||||
video.removeEventListener('timeupdate', transitionBeforeEnd);
|
||||
|
||||
@ -2,14 +2,12 @@ import { app, dialog, ipcMain } from 'electron';
|
||||
import { Client as DiscordClient } from '@xhayper/discord-rpc';
|
||||
import { dev } from 'electron-is';
|
||||
|
||||
import { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
|
||||
|
||||
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
||||
|
||||
import { createBackend, LoggerPrefix } from '@/utils';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { GatewayActivityButton } from 'discord-api-types/v10';
|
||||
import type { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
|
||||
import type { DiscordPluginConfig } from './index';
|
||||
|
||||
// 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 = {
|
||||
details: songInfo.title,
|
||||
state: songInfo.artist,
|
||||
largeImageKey: songInfo.imageSrc ?? '',
|
||||
largeImageText: songInfo.album ?? '',
|
||||
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',
|
||||
},
|
||||
]),
|
||||
],
|
||||
buttons,
|
||||
};
|
||||
|
||||
if (songInfo.isPaused) {
|
||||
@ -244,7 +248,7 @@ export const backend = createBackend<
|
||||
});
|
||||
connect();
|
||||
let lastSent = Date.now();
|
||||
ipcMain.on('timeChanged', (_, t: number) => {
|
||||
ipcMain.on('ytmd:time-changed', (_, t: number) => {
|
||||
const currentTime = Date.now();
|
||||
// if lastSent is more than 5 seconds ago, send the new time
|
||||
if (currentTime - lastSent > 5000) {
|
||||
|
||||
@ -84,7 +84,7 @@ export const onMenu = async ({
|
||||
checked: config.hideDurationLeft,
|
||||
click(item: Electron.MenuItem) {
|
||||
setConfig({
|
||||
hideGitHubButton: item.checked,
|
||||
hideDurationLeft: item.checked,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
createWriteStream,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
writeFileSync,
|
||||
@ -30,9 +29,8 @@ import {
|
||||
|
||||
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
|
||||
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 { cache } from '@/providers/decorators';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
@ -110,7 +108,7 @@ export const onMainLoad = async ({
|
||||
fetch: getNetFetchAsFetch(),
|
||||
});
|
||||
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;
|
||||
});
|
||||
ipc.handle('download-playlist-request', async (url: string) =>
|
||||
@ -297,7 +295,7 @@ async function downloadSongUnsafe(
|
||||
mkdirSync(dir);
|
||||
}
|
||||
|
||||
const fileBuffer = await iterableStreamToTargetFile(
|
||||
let fileBuffer = await iterableStreamToProcessedUint8Array(
|
||||
iterableStream,
|
||||
targetFileExtension,
|
||||
metadata,
|
||||
@ -307,19 +305,16 @@ async function downloadSongUnsafe(
|
||||
increasePlaylistProgress,
|
||||
);
|
||||
|
||||
if (fileBuffer && targetFileExtension === 'mp3') {
|
||||
fileBuffer = await writeID3(
|
||||
Buffer.from(fileBuffer),
|
||||
metadata,
|
||||
sendFeedback,
|
||||
);
|
||||
}
|
||||
|
||||
if (fileBuffer) {
|
||||
if (targetFileExtension !== 'mp3') {
|
||||
createWriteStream(filePath).write(fileBuffer);
|
||||
} else {
|
||||
const buffer = await writeID3(
|
||||
Buffer.from(fileBuffer),
|
||||
metadata,
|
||||
sendFeedback,
|
||||
);
|
||||
if (buffer) {
|
||||
writeFileSync(filePath, buffer);
|
||||
}
|
||||
}
|
||||
writeFileSync(filePath, fileBuffer);
|
||||
}
|
||||
|
||||
sendFeedback(null, -1);
|
||||
@ -330,15 +325,12 @@ async function downloadSongUnsafe(
|
||||
);
|
||||
}
|
||||
|
||||
async function iterableStreamToTargetFile(
|
||||
async function downloadChunks(
|
||||
stream: AsyncGenerator<Uint8Array, void>,
|
||||
extension: string,
|
||||
metadata: CustomSongInfo,
|
||||
presetFfmpegArgs: string[],
|
||||
contentLength: number,
|
||||
sendFeedback: (str: string, value?: number) => void,
|
||||
increasePlaylistProgress: (value: number) => void = () => {},
|
||||
): Promise<Uint8Array | null> {
|
||||
) {
|
||||
const chunks = [];
|
||||
let downloaded = 0;
|
||||
for await (const chunk of stream) {
|
||||
@ -356,65 +348,80 @@ async function iterableStreamToTargetFile(
|
||||
// This is a very rough estimate, trying to make the progress bar look nice
|
||||
increasePlaylistProgress(ratio * 0.15);
|
||||
}
|
||||
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.loading'), 2); // Indefinite progress bar after download
|
||||
|
||||
const buffer = Buffer.concat(chunks);
|
||||
const safeVideoName = randomBytes(32).toString('hex');
|
||||
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
||||
|
||||
try {
|
||||
if (!ffmpeg.isLoaded()) {
|
||||
await ffmpeg.load();
|
||||
}
|
||||
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.preparing-file'));
|
||||
ffmpeg.FS('writeFile', safeVideoName, buffer);
|
||||
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.converting'));
|
||||
|
||||
ffmpeg.setProgress(({ ratio }) => {
|
||||
sendFeedback(
|
||||
t('plugins.downloader.backend.feedback.conversion-progress', {
|
||||
percent: Math.floor(ratio * 100),
|
||||
}),
|
||||
ratio,
|
||||
);
|
||||
increasePlaylistProgress(0.15 + (ratio * 0.85));
|
||||
});
|
||||
|
||||
const safeVideoNameWithExtension = `${safeVideoName}.${extension}`;
|
||||
try {
|
||||
await ffmpeg.run(
|
||||
'-i',
|
||||
safeVideoName,
|
||||
...presetFfmpegArgs,
|
||||
...getFFmpegMetadataArgs(metadata),
|
||||
safeVideoNameWithExtension,
|
||||
);
|
||||
} finally {
|
||||
ffmpeg.FS('unlink', safeVideoName);
|
||||
}
|
||||
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.saving'));
|
||||
|
||||
try {
|
||||
return ffmpeg.FS('readFile', safeVideoNameWithExtension);
|
||||
} finally {
|
||||
ffmpeg.FS('unlink', safeVideoNameWithExtension);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
sendError(error as Error, safeVideoName);
|
||||
} finally {
|
||||
releaseFFmpegMutex();
|
||||
}
|
||||
return null;
|
||||
return chunks;
|
||||
}
|
||||
|
||||
const getCoverBuffer = cache(async (url: string) => {
|
||||
async function iterableStreamToProcessedUint8Array(
|
||||
stream: AsyncGenerator<Uint8Array, void>,
|
||||
extension: string,
|
||||
metadata: CustomSongInfo,
|
||||
presetFfmpegArgs: string[],
|
||||
contentLength: number,
|
||||
sendFeedback: (str: string, value?: number) => void,
|
||||
increasePlaylistProgress: (value: number) => void = () => {},
|
||||
): Promise<Uint8Array | null> {
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.loading'), 2); // Indefinite progress bar after download
|
||||
|
||||
const safeVideoName = randomBytes(32).toString('hex');
|
||||
|
||||
return await ffmpegMutex.runExclusive(async () => {
|
||||
try {
|
||||
if (!ffmpeg.isLoaded()) {
|
||||
await ffmpeg.load();
|
||||
}
|
||||
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.preparing-file'));
|
||||
ffmpeg.FS(
|
||||
'writeFile',
|
||||
safeVideoName,
|
||||
Buffer.concat(
|
||||
await downloadChunks(stream, contentLength, sendFeedback, increasePlaylistProgress),
|
||||
),
|
||||
);
|
||||
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.converting'));
|
||||
|
||||
ffmpeg.setProgress(({ ratio }) => {
|
||||
sendFeedback(
|
||||
t('plugins.downloader.backend.feedback.conversion-progress', {
|
||||
percent: Math.floor(ratio * 100),
|
||||
}),
|
||||
ratio,
|
||||
);
|
||||
increasePlaylistProgress(0.15 + (ratio * 0.85));
|
||||
});
|
||||
|
||||
const safeVideoNameWithExtension = `${safeVideoName}.${extension}`;
|
||||
try {
|
||||
await ffmpeg.run(
|
||||
'-i',
|
||||
safeVideoName,
|
||||
...presetFfmpegArgs,
|
||||
...getFFmpegMetadataArgs(metadata),
|
||||
safeVideoNameWithExtension,
|
||||
);
|
||||
} finally {
|
||||
ffmpeg.FS('unlink', safeVideoName);
|
||||
}
|
||||
|
||||
sendFeedback(t('plugins.downloader.backend.feedback.saving'));
|
||||
|
||||
try {
|
||||
return ffmpeg.FS('readFile', safeVideoNameWithExtension);
|
||||
} finally {
|
||||
ffmpeg.FS('unlink', safeVideoNameWithExtension);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
sendError(error as Error, safeVideoName);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
const getCoverBuffer = async (url: string) => {
|
||||
const nativeImage = cropMaxWidth(await getImage(url));
|
||||
return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null;
|
||||
});
|
||||
};
|
||||
|
||||
async function writeID3(
|
||||
buffer: Buffer,
|
||||
@ -686,6 +693,7 @@ const getMetadata = (info: TrackInfo): CustomSongInfo => ({
|
||||
?.url,
|
||||
views: info.basic_info.view_count!,
|
||||
songDuration: info.basic_info.duration!,
|
||||
mediaType: MediaType.Audio,
|
||||
});
|
||||
|
||||
// This is used to bypass age restrictions
|
||||
|
||||
@ -32,10 +32,22 @@ const menuObserver = new MutationObserver(() => {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuUrl = document.querySelector<HTMLAnchorElement>(
|
||||
// check for video (or music)
|
||||
let menuUrl = document.querySelector<HTMLAnchorElement>(
|
||||
'tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint',
|
||||
)?.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;
|
||||
}
|
||||
|
||||
@ -51,17 +63,32 @@ export const onRendererLoad = ({
|
||||
ipc,
|
||||
}: RendererContext<DownloaderPluginConfig>) => {
|
||||
window.download = () => {
|
||||
let videoUrl = getSongMenu()
|
||||
const songMenu = getSongMenu();
|
||||
let videoUrl = songMenu
|
||||
// Selector of first button which is always "Start Radio"
|
||||
?.querySelector(
|
||||
'ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint',
|
||||
)
|
||||
?.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.startsWith('watch?')) {
|
||||
videoUrl = defaultConfig.url + '/' + videoUrl;
|
||||
}
|
||||
|
||||
if (videoUrl.startsWith('podcast/')) {
|
||||
videoUrl = defaultConfig.url + '/watch?' + videoUrl.replace('podcast/', 'v=');
|
||||
}
|
||||
|
||||
if (videoUrl.includes('?playlist=')) {
|
||||
ipc.invoke('download-playlist-request', videoUrl);
|
||||
return;
|
||||
|
||||
@ -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
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?.toLowerCase().includes('mac')
|
||||
) ||
|
||||
(
|
||||
typeof global !== 'undefined' &&
|
||||
global.process?.platform !== 'darwin'
|
||||
)
|
||||
) && (
|
||||
(
|
||||
typeof window !== 'undefined' &&
|
||||
!window.navigator?.userAgent?.toLowerCase().includes('linux')
|
||||
) ||
|
||||
(
|
||||
typeof global !== 'undefined' &&
|
||||
global.process?.platform !== 'linux'
|
||||
)
|
||||
),
|
||||
hideDOMWindowControls: false,
|
||||
};
|
||||
@ -2,24 +2,15 @@ import titlebarStyle from './titlebar.css?inline';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { onMainLoad } from './main';
|
||||
import { onMenu } from './menu';
|
||||
import { onPlayerApiReady, onRendererLoad } from './renderer';
|
||||
import { onConfigChange, onPlayerApiReady, onRendererLoad } from './renderer';
|
||||
import { t } from '@/i18n';
|
||||
import { defaultInAppMenuConfig } from './constants';
|
||||
|
||||
export interface InAppMenuConfig {
|
||||
enabled: boolean;
|
||||
hideDOMWindowControls: boolean;
|
||||
}
|
||||
export default createPlugin({
|
||||
name: () => t('plugins.in-app-menu.name'),
|
||||
description: () => t('plugins.in-app-menu.description'),
|
||||
restartNeeded: true,
|
||||
config: {
|
||||
enabled:
|
||||
(typeof window !== 'undefined' &&
|
||||
!window.navigator?.userAgent?.includes('mac')) ||
|
||||
(typeof global !== 'undefined' && global.process?.platform !== 'darwin'),
|
||||
hideDOMWindowControls: false,
|
||||
} as InAppMenuConfig,
|
||||
config: defaultInAppMenuConfig,
|
||||
stylesheets: [titlebarStyle],
|
||||
menu: onMenu,
|
||||
|
||||
@ -27,5 +18,6 @@ export default createPlugin({
|
||||
renderer: {
|
||||
start: onRendererLoad,
|
||||
onPlayerApiReady,
|
||||
onConfigChange,
|
||||
},
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import { register } from 'electron-localshortcut';
|
||||
import { BrowserWindow, Menu, MenuItem, ipcMain, nativeImage } from 'electron';
|
||||
|
||||
import type { BackendContext } from '@/types/contexts';
|
||||
import type { InAppMenuConfig } from './index';
|
||||
import type { InAppMenuConfig } from './constants';
|
||||
|
||||
export const onMainLoad = ({
|
||||
window: win,
|
||||
@ -47,7 +47,7 @@ export const onMainLoad = ({
|
||||
return target;
|
||||
};
|
||||
|
||||
ipcMain.handle('menu-event', (event, commandId: number) => {
|
||||
ipcMain.handle('ytmd:menu-event', (event, commandId: number) => {
|
||||
const target = getMenuItemById(commandId);
|
||||
if (target)
|
||||
target.click(
|
||||
|
||||
@ -2,7 +2,7 @@ import is from 'electron-is';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { InAppMenuConfig } from './index';
|
||||
import type { InAppMenuConfig } from './constants';
|
||||
import type { MenuContext } from '@/types/contexts';
|
||||
import type { MenuTemplate } from '@/menu';
|
||||
|
||||
|
||||
@ -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,220 +0,0 @@
|
||||
import Icons from './icons';
|
||||
|
||||
import { ElementFromHtml } from '../../utils/renderer';
|
||||
|
||||
import type { MenuItem } from 'electron';
|
||||
|
||||
interface PanelOptions {
|
||||
placement?: 'bottom' | 'right';
|
||||
order?: number;
|
||||
openOnHover?: boolean;
|
||||
}
|
||||
|
||||
export const createPanel = (
|
||||
parent: HTMLElement,
|
||||
anchor: HTMLElement,
|
||||
items: MenuItem[],
|
||||
options: PanelOptions = { placement: 'bottom', order: 0, openOnHover: false },
|
||||
) => {
|
||||
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);
|
||||
|
||||
if (item.sublabel) {
|
||||
menu.classList.add('badge');
|
||||
const menuBadge = document.createElement('menu-item-badge');
|
||||
menuBadge.append(item.sublabel);
|
||||
menu.append(menuBadge);
|
||||
}
|
||||
if (item.toolTip) {
|
||||
const menuTooltip = document.createElement('menu-item-tooltip');
|
||||
menuTooltip.append(item.toolTip);
|
||||
|
||||
menu.addEventListener('mouseenter', () => {
|
||||
const rect = menu.getBoundingClientRect();
|
||||
menuTooltip.style.setProperty('max-width', `${rect.width - 8}px`);
|
||||
menuTooltip.style.setProperty('--x', `${rect.left}px`);
|
||||
menuTooltip.style.setProperty('--y', `${rect.top + rect.height}px`);
|
||||
menuTooltip.classList.add('show');
|
||||
});
|
||||
menu.addEventListener('mouseleave', () => {
|
||||
menuTooltip.classList.remove('show');
|
||||
});
|
||||
parent.append(menuTooltip);
|
||||
}
|
||||
|
||||
menu.addEventListener('click', async () => {
|
||||
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,
|
||||
openOnHover: true,
|
||||
},
|
||||
);
|
||||
|
||||
childPanels.push(child);
|
||||
childPanels.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');
|
||||
}
|
||||
};
|
||||
|
||||
if (options.openOnHover) {
|
||||
let timeout: number | null = null;
|
||||
anchor.addEventListener('mouseenter', () => {
|
||||
if (timeout) window.clearTimeout(timeout);
|
||||
timeout = window.setTimeout(() => {
|
||||
if (!isOpened()) open();
|
||||
}, 225);
|
||||
});
|
||||
anchor.addEventListener('mouseleave', () => {
|
||||
if (timeout) window.clearTimeout(timeout);
|
||||
let mouseX = 0, mouseY = 0;
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
mouseX = event.clientX;
|
||||
mouseY = event.clientY;
|
||||
};
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
timeout = window.setTimeout(() => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
const now = document.elementFromPoint(mouseX, mouseY);
|
||||
if (now === panel || panel.contains(now)) {
|
||||
const onLeave = () => {
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
timeout = window.setTimeout(() => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
const now = document.elementFromPoint(mouseX, mouseY);
|
||||
if (now === panel || panel.contains(now) || childPanels.some((it) => it.contains(now))) return;
|
||||
|
||||
if (isOpened()) close();
|
||||
panel.removeEventListener('mouseleave', onLeave);
|
||||
}, 225);
|
||||
};
|
||||
panel.addEventListener('mouseleave', onLeave);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOpened()) close();
|
||||
}, 225);
|
||||
});
|
||||
}
|
||||
|
||||
anchor.addEventListener('click', () => {
|
||||
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
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
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
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
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}
|
||||
data-ytmd-sub-panel={true}
|
||||
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
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>
|
||||
);
|
||||
};
|
||||
374
src/plugins/in-app-menu/renderer/TitleBar.tsx
Normal file
374
src/plugins/in-app-menu/renderer/TitleBar.tsx
Normal file
@ -0,0 +1,374 @@
|
||||
import { Menu, MenuItem } from 'electron';
|
||||
import { createEffect, createResource, createSignal, Index, Match, onCleanup, 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,
|
||||
transform 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s,
|
||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
||||
|
||||
&[data-macos="true"] {
|
||||
padding: 4px 4px 4px 74px;
|
||||
}
|
||||
|
||||
ytmusic-app:has(ytmusic-player[player-ui-state=FULLSCREEN]) ~ &:not([data-show="true"]) {
|
||||
transform: translateY(calc(-1 * var(--menu-bar-height, 32px)));
|
||||
}
|
||||
`);
|
||||
|
||||
const separatorStyle = cache(() => css`
|
||||
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 [mouseY, setMouseY] = createSignal(0);
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
const listener = (e: MouseEvent) => {
|
||||
setMouseY(e.clientY);
|
||||
};
|
||||
|
||||
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('nav[data-ytmd-main-panel]') ||
|
||||
e.target.closest('ul[data-ytmd-sub-panel]')
|
||||
)
|
||||
) {
|
||||
setOpenTarget(null);
|
||||
}
|
||||
});
|
||||
|
||||
// tracking mouse position
|
||||
window.addEventListener('mousemove', listener);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (!menu() && data()) {
|
||||
setMenu(data() ?? null);
|
||||
}
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener('mousemove', listener);
|
||||
});
|
||||
|
||||
return (
|
||||
<nav data-ytmd-main-panel={true} class={titleStyle()} data-macos={props.isMacOS} data-show={mouseY() < 32}>
|
||||
<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
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,223 +1,8 @@
|
||||
:root {
|
||||
--titlebar-background-color: #030303;
|
||||
--titlebar-background-color: var(--ytmusic-color-black3);
|
||||
--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 {
|
||||
position: relative;
|
||||
|
||||
-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.badge {
|
||||
grid-template-columns: 32px 1fr auto minmax(32px, auto);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
menu-item-badge {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
margin-left: 8px;
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: #f1f1f1;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
menu-item-tooltip {
|
||||
position: fixed;
|
||||
|
||||
left: var(--x, 0);
|
||||
top: var(--y, 0);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
min-width: 32px;
|
||||
padding: 4px;
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: rgba(25, 25, 25, 0.8);
|
||||
color: #f1f1f1;
|
||||
font-size: 10px;
|
||||
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
|
||||
opacity: 0;
|
||||
scale: 0.9;
|
||||
|
||||
transform-origin: 50% 0;
|
||||
transition: opacity 0.225s ease-out, scale 0.225s ease-out;
|
||||
}
|
||||
menu-item-tooltip.show {
|
||||
opacity: 1;
|
||||
scale: 1.0;
|
||||
}
|
||||
|
||||
/* classes */
|
||||
|
||||
.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 */
|
||||
|
||||
ytmusic-app-layout {
|
||||
@ -233,16 +18,26 @@ ytmusic-app-layout > [slot='nav-bar'],
|
||||
var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px)
|
||||
) !important;
|
||||
}
|
||||
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] #guide-spacer.ytmusic-app,
|
||||
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;
|
||||
}
|
||||
|
||||
@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'] {
|
||||
margin-top: var(--menu-bar-height);
|
||||
height: calc(
|
||||
@ -256,3 +51,13 @@ ytmusic-guide-renderer {
|
||||
100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height)
|
||||
) !important;
|
||||
}
|
||||
|
||||
/* fix mini player behavior */
|
||||
ytmusic-app-layout ytmusic-player-page[is-mweb-modernization-enabled] .side-panel.ytmusic-player-page {
|
||||
transform: translate(0, calc(var(--ytmusic-player-page-inner-height) - var(--ytmusic-player-page-tabs-header-height) - var(--ytmusic-player-page-player-bar-height) - var(--menu-bar-height, 32px) ));
|
||||
}
|
||||
|
||||
/* ytm-bugs: see https://github.com/th-ch/youtube-music/issues/1737 */
|
||||
html {
|
||||
scrollbar-color: unset;
|
||||
}
|
||||
|
||||
@ -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)}`;
|
||||
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.
|
||||
*/
|
||||
let hasAsianChars = false;
|
||||
|
||||
@ -32,7 +32,7 @@ export const onRendererLoad = ({
|
||||
|
||||
let unregister: (() => void) | null = null;
|
||||
|
||||
on('update-song-info', (extractedSongInfo: SongInfo) => {
|
||||
on('ytmd:update-song-info', (extractedSongInfo: SongInfo) => {
|
||||
unregister?.();
|
||||
|
||||
setTimeout(async () => {
|
||||
|
||||
@ -74,7 +74,7 @@ export class Connection {
|
||||
return conn;
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
disconnect() {
|
||||
if (this._mode === 'disconnected') throw new Error('Already disconnected');
|
||||
|
||||
this._mode = 'disconnected';
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
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 { getDefaultProfile, type Permission, type Profile, type VideoData } from './types';
|
||||
import { Queue } from './queue';
|
||||
import { Connection, ConnectionEventUnion } from './connection';
|
||||
import { Connection, type ConnectionEventUnion } from './connection';
|
||||
import { createHostPopup } from './ui/host';
|
||||
import { createGuestPopup } from './ui/guest';
|
||||
import { createSettingPopup } from './ui/setting';
|
||||
@ -17,7 +19,7 @@ import style from './style.css?inline';
|
||||
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||
import { DataConnection } from 'peerjs';
|
||||
import type { AppElement } from '@/types/queue';
|
||||
|
||||
type RawAccountData = {
|
||||
accountName: {
|
||||
@ -34,59 +36,84 @@ type RawAccountData = {
|
||||
};
|
||||
};
|
||||
|
||||
export default createPlugin({
|
||||
export default createPlugin<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
connection?: Connection;
|
||||
ipc?: RendererContext<never>['ipc'];
|
||||
api: AppElement | 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.0',
|
||||
addedVersion: '3.2.X',
|
||||
config: {
|
||||
enabled: false
|
||||
},
|
||||
stylesheets: [style],
|
||||
backend: {
|
||||
async start({ ipc }) {
|
||||
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
|
||||
title,
|
||||
label,
|
||||
type: 'input',
|
||||
...promptOptions()
|
||||
}));
|
||||
}
|
||||
backend({ ipc }) {
|
||||
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
|
||||
title,
|
||||
label,
|
||||
type: 'input',
|
||||
...promptOptions()
|
||||
}));
|
||||
},
|
||||
renderer: {
|
||||
connection: null as Connection | null,
|
||||
ipc: null as RendererContext<never>['ipc'] | null,
|
||||
|
||||
api: null as (HTMLElement & AppAPI) | null,
|
||||
queue: null as Queue | null,
|
||||
playerApi: null as YoutubePlayer | null,
|
||||
showPrompt: (async () => null) as ((title: string, label: string) => Promise<string | null>),
|
||||
|
||||
elements: {} as {
|
||||
setting: HTMLElement;
|
||||
icon: SVGElement;
|
||||
spinner: HTMLElement;
|
||||
},
|
||||
updateNext: false,
|
||||
ignoreChange: false,
|
||||
permission: 'playlist',
|
||||
popups: {} as {
|
||||
host: ReturnType<typeof createHostPopup>;
|
||||
guest: ReturnType<typeof createGuestPopup>;
|
||||
setting: ReturnType<typeof createSettingPopup>;
|
||||
},
|
||||
stateInterval: null as number | null,
|
||||
updateNext: false,
|
||||
ignoreChange: false,
|
||||
rollbackInjector: null as (() => void) | null,
|
||||
|
||||
me: null as Omit<Profile, 'id'> | null,
|
||||
profiles: {} as Record<string, Profile>,
|
||||
permission: 'playlist' as Permission,
|
||||
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: any) => ({
|
||||
videoId: it.videoId,
|
||||
const videoList: VideoData[] = this.queue?.flatItems.map((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.connection!.id
|
||||
} satisfies VideoData)) ?? [];
|
||||
|
||||
@ -123,8 +150,8 @@ export default createPlugin({
|
||||
if (!wait) return false;
|
||||
|
||||
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
||||
const rawItems = this.queue?.flatItems?.map((it: any) => ({
|
||||
videoId: it.videoId,
|
||||
const rawItems = this.queue?.flatItems?.map((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.connection!.id
|
||||
} satisfies VideoData)) ?? [];
|
||||
this.queue?.setOwner({
|
||||
@ -170,7 +197,7 @@ export default createPlugin({
|
||||
case 'REMOVE_SONG': {
|
||||
if (conn && this.permission === 'host-only') return;
|
||||
|
||||
await this.queue?.removeVideo(event.payload.index);
|
||||
this.queue?.removeVideo(event.payload.index);
|
||||
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
||||
break;
|
||||
}
|
||||
@ -295,11 +322,11 @@ export default createPlugin({
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
await this.queue?.removeVideo(event.payload.index);
|
||||
this.queue?.removeVideo(event.payload.index);
|
||||
break;
|
||||
}
|
||||
case 'MOVE_SONG': {
|
||||
await this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
||||
this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
||||
break;
|
||||
}
|
||||
case 'IDENTIFY': {
|
||||
@ -461,7 +488,7 @@ export default createPlugin({
|
||||
this.queue?.removeQueueOwner();
|
||||
if (this.rollbackInjector) {
|
||||
this.rollbackInjector();
|
||||
this.rollbackInjector = null;
|
||||
this.rollbackInjector = undefined;
|
||||
}
|
||||
|
||||
this.profiles = {};
|
||||
@ -530,8 +557,8 @@ export default createPlugin({
|
||||
|
||||
start({ ipc }) {
|
||||
this.ipc = ipc;
|
||||
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label);
|
||||
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
|
||||
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
|
||||
this.api = document.querySelector<AppElement>('ytmusic-app');
|
||||
|
||||
/* setup */
|
||||
document.querySelector('#right-content > ytmusic-settings-button')?.insertAdjacentHTML('beforebegin', settingHTML);
|
||||
@ -571,10 +598,15 @@ export default createPlugin({
|
||||
}
|
||||
|
||||
if (id === 'music-together-copy-id') {
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '');
|
||||
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.dismiss();
|
||||
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') {
|
||||
@ -614,9 +646,14 @@ export default createPlugin({
|
||||
this.hideSpinner();
|
||||
|
||||
if (result) {
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '');
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.showAtAnchor(setting);
|
||||
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'));
|
||||
}
|
||||
@ -642,7 +679,7 @@ export default createPlugin({
|
||||
guest: guestPopup,
|
||||
setting: settingPopup
|
||||
};
|
||||
setting.addEventListener('click', async () => {
|
||||
setting.addEventListener('click', () => {
|
||||
let popup = settingPopup;
|
||||
if (this.connection?.mode === 'host') popup = hostPopup;
|
||||
if (this.connection?.mode === 'guest') popup = guestPopup;
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { SHA1Hash } from './sha1hash';
|
||||
|
||||
export const extractToken = (cookie = document.cookie) => cookie.match(/SAPISID=([^;]+);/)?.[1] ?? cookie.match(/__Secure\-3PAPISID=([^;]+);/)?.[1];
|
||||
export const extractToken = (cookie = document.cookie) => cookie.match(/SAPISID=([^;]+);/)?.[1] ?? cookie.match(/__Secure-3PAPISID=([^;]+);/)?.[1];
|
||||
|
||||
export const getHash = (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') => {
|
||||
const hash = SHA1Hash();
|
||||
hash.update(`${millis} ${papisid} ${origin}`);
|
||||
return hash.digestString().toLowerCase();
|
||||
};
|
||||
export const getHash = async (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') =>
|
||||
(await SHA1Hash(`${millis} ${papisid} ${origin}`)).toLowerCase();
|
||||
|
||||
export const getAuthorizationHeader = (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') => {
|
||||
return `SAPISIDHASH ${millis}_${getHash(papisid, millis, origin)}`;
|
||||
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 = () => {
|
||||
|
||||
@ -1,12 +1,52 @@
|
||||
import { getMusicQueueRenderer } from './song';
|
||||
import { mapQueueItem } from './utils';
|
||||
|
||||
import type { Profile, QueueAPI, VideoData } from '../types';
|
||||
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
||||
import type { Profile, VideoData } from '../types';
|
||||
import type { QueueItem } from '@/types/datahost-get-state';
|
||||
import type { QueueElement } from '@/types/queue';
|
||||
|
||||
const getHeaderPayload = (() => {
|
||||
let payload: unknown = null;
|
||||
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) {
|
||||
@ -58,32 +98,35 @@ const getHeaderPayload = (() => {
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
export type QueueOptions = {
|
||||
videoList?: VideoData[];
|
||||
owner?: Profile;
|
||||
queue?: HTMLElement & QueueAPI;
|
||||
queue?: QueueElement;
|
||||
getProfile: (id: string) => Profile | undefined;
|
||||
}
|
||||
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
||||
|
||||
export class Queue {
|
||||
private queue: (HTMLElement & QueueAPI) | null = null;
|
||||
private originalDispatch: ((obj: {
|
||||
private readonly queue: QueueElement;
|
||||
|
||||
private originalDispatch?: (obj: {
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
}) => void) | null = null;
|
||||
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;
|
||||
|
||||
private owner: Profile | null;
|
||||
private readonly getProfile: (id: string) => Profile | undefined;
|
||||
|
||||
constructor(options: QueueOptions) {
|
||||
this.getProfile = options.getProfile;
|
||||
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue');
|
||||
this.queue = options.queue ?? (document.querySelector<QueueElement>('#queue')!);
|
||||
this.owner = options.owner ?? null;
|
||||
this._videoList = options.videoList ?? [];
|
||||
}
|
||||
@ -96,11 +139,11 @@ export class Queue {
|
||||
}
|
||||
|
||||
get selectedIndex() {
|
||||
return mapQueueItem((it) => it?.selected, this.queue?.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
||||
return mapQueueItem((it) => it?.selected, this.queue.queue.store.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
||||
}
|
||||
|
||||
get rawItems() {
|
||||
return this.queue?.store.getState().queue.items;
|
||||
return this.queue?.queue.store.store.getState().queue.items;
|
||||
}
|
||||
|
||||
get flatItems() {
|
||||
@ -130,8 +173,8 @@ export class Queue {
|
||||
this.queue?.dispatch({
|
||||
type: 'ADD_ITEMS',
|
||||
payload: {
|
||||
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
|
||||
index: index ?? this.queue.store.getState().queue.items.length ?? 0,
|
||||
nextQueueItemId: this.queue.queue.store.store.getState().queue.nextQueueItemId,
|
||||
index: index ?? this.queue.queue.store.store.getState().queue.items.length ?? 0,
|
||||
items,
|
||||
shuffleEnabled: false,
|
||||
shouldAssignIds: true
|
||||
@ -146,7 +189,7 @@ export class Queue {
|
||||
return true;
|
||||
}
|
||||
|
||||
async removeVideo(index: number) {
|
||||
removeVideo(index: number) {
|
||||
this.internalDispatch = true;
|
||||
this._videoList.splice(index, 1);
|
||||
this.queue?.dispatch({
|
||||
@ -210,7 +253,7 @@ export class Queue {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.originalDispatch) this.queue.store.dispatch = this.originalDispatch;
|
||||
if (this.originalDispatch) this.queue.queue.store.store.dispatch = this.originalDispatch;
|
||||
}
|
||||
|
||||
injection() {
|
||||
@ -219,8 +262,8 @@ export class Queue {
|
||||
return;
|
||||
}
|
||||
|
||||
this.originalDispatch = this.queue.store.dispatch;
|
||||
this.queue.store.dispatch = (event) => {
|
||||
this.originalDispatch = this.queue.queue.store.store.dispatch;
|
||||
this.queue.queue.store.store.dispatch = (event) => {
|
||||
if (!this.queue || !this.owner) {
|
||||
console.error('Queue is not initialized!');
|
||||
return;
|
||||
@ -233,10 +276,10 @@ export class Queue {
|
||||
if (event.type === 'ADD_ITEMS') {
|
||||
if (this.ignoreFlag) {
|
||||
this.ignoreFlag = false;
|
||||
const videoList = mapQueueItem((it: any) => ({
|
||||
videoId: it.videoId,
|
||||
const videoList = mapQueueItem((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.owner!.id
|
||||
} satisfies VideoData), (event.payload as any).items);
|
||||
} satisfies VideoData), event.payload!.items!);
|
||||
const index = this._videoList.length + videoList.length - 1;
|
||||
|
||||
if (videoList.length > 0) {
|
||||
@ -255,15 +298,17 @@ export class Queue {
|
||||
]
|
||||
});
|
||||
}
|
||||
} else if ((event.payload as any).items.length === 1) {
|
||||
} 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: any) => ({
|
||||
videoId: it.videoId,
|
||||
videoList: mapQueueItem((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.owner!.id
|
||||
} satisfies VideoData), (event.payload as any).items)
|
||||
} satisfies VideoData), event.payload!.items!)
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -275,8 +320,12 @@ export class Queue {
|
||||
this.broadcast({
|
||||
type: 'MOVE_SONG',
|
||||
payload: {
|
||||
fromIndex: (event.payload as any).fromIndex,
|
||||
toIndex: (event.payload as any).toIndex
|
||||
fromIndex: (event.payload as {
|
||||
fromIndex: number;
|
||||
}).fromIndex,
|
||||
toIndex: (event.payload as {
|
||||
toIndex: number;
|
||||
}).toIndex
|
||||
}
|
||||
});
|
||||
return;
|
||||
@ -306,7 +355,7 @@ export class Queue {
|
||||
event.payload = undefined;
|
||||
}
|
||||
if (event.type === 'SET_PLAYER_UI_STATE') {
|
||||
if (event.payload === 'INACTIVE' && this.videoList.length > 0) {
|
||||
if (event.payload as string === 'INACTIVE' && this.videoList.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -316,17 +365,20 @@ export class Queue {
|
||||
|
||||
const fakeContext = {
|
||||
...this.queue,
|
||||
store: {
|
||||
...this.queue.store,
|
||||
dispatch: this.originalDispatch
|
||||
}
|
||||
queue: {
|
||||
...this.queue.queue,
|
||||
store: {
|
||||
...this.queue.queue.store,
|
||||
dispatch: this.originalDispatch,
|
||||
}
|
||||
},
|
||||
};
|
||||
this.originalDispatch!.call(fakeContext, event);
|
||||
this.originalDispatch?.call(fakeContext, event);
|
||||
};
|
||||
}
|
||||
|
||||
/* sync */
|
||||
async initQueue() {
|
||||
initQueue() {
|
||||
if (!this.queue) return;
|
||||
|
||||
this.internalDispatch = true;
|
||||
@ -355,7 +407,7 @@ export class Queue {
|
||||
type: 'UPDATE_ITEMS',
|
||||
payload: {
|
||||
items: items,
|
||||
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId,
|
||||
nextQueueItemId: this.queue.queue.store.store.getState().queue.nextQueueItemId,
|
||||
shouldAssignIds: true,
|
||||
currentIndex: -1
|
||||
}
|
||||
@ -369,13 +421,13 @@ export class Queue {
|
||||
return true;
|
||||
}
|
||||
|
||||
async syncQueueOwner() {
|
||||
syncQueueOwner() {
|
||||
const allQueue = document.querySelectorAll('#queue');
|
||||
|
||||
allQueue.forEach((queue) => {
|
||||
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
|
||||
|
||||
list.forEach((item, index) => {
|
||||
list.forEach((item, index: number | undefined) => {
|
||||
if (typeof index !== 'number') return;
|
||||
|
||||
const id = this._videoList[index]?.ownerId;
|
||||
|
||||
@ -1,117 +1,7 @@
|
||||
export function SHA1Hash(): {
|
||||
reset: () => void,
|
||||
update: (message: string | number[], length?: number) => void,
|
||||
digest: () => number[],
|
||||
digestString: () => string
|
||||
} {
|
||||
let hash: number[];
|
||||
|
||||
function initialize(): void {
|
||||
hash = [1732584193, 4023233417, 2562383102, 271733878, 3285377520];
|
||||
totalLength = currentLength = 0;
|
||||
}
|
||||
|
||||
function processBlock(block: number[]): void {
|
||||
let words: number[] = [];
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
words[i / 4] = block[i] << 24 | block[i + 1] << 16 | block[i + 2] << 8 | block[i + 3];
|
||||
}
|
||||
|
||||
for (let i = 16; i < 80; i++) {
|
||||
let temp = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
|
||||
words[i] = (temp << 1 | temp >>> 31) & 4294967295;
|
||||
}
|
||||
|
||||
let a = hash[0],
|
||||
b = hash[1],
|
||||
c = hash[2],
|
||||
d = hash[3],
|
||||
e = hash[4];
|
||||
for (let i = 0; i < 80; i++) {
|
||||
let f, k;
|
||||
if (i < 20) {
|
||||
f = d ^ b & (c ^ d);
|
||||
k = 1518500249;
|
||||
} else if (i < 40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 1859775393;
|
||||
} else if (i < 60) {
|
||||
f = b & c | d & (b | c);
|
||||
k = 2400959708;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 3395469782;
|
||||
}
|
||||
let temp = ((a << 5 | a >>> 27) & 4294967295) + f + e + k + words[i] & 4294967295;
|
||||
e = d;
|
||||
d = c;
|
||||
c = (b << 30 | b >>> 2) & 4294967295;
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
hash[0] = hash[0] + a & 4294967295;
|
||||
hash[1] = hash[1] + b & 4294967295;
|
||||
hash[2] = hash[2] + c & 4294967295;
|
||||
hash[3] = hash[3] + d & 4294967295;
|
||||
hash[4] = hash[4] + e & 4294967295;
|
||||
}
|
||||
|
||||
function update(message: string | number[], length?: number): void {
|
||||
if ('string' === typeof message) {
|
||||
message = unescape(encodeURIComponent(message));
|
||||
let bytes: number[] = [];
|
||||
for (let i = 0, len = message.length; i < len; ++i)
|
||||
bytes.push(message.charCodeAt(i));
|
||||
message = bytes;
|
||||
}
|
||||
length || (length = message.length);
|
||||
let i = 0;
|
||||
if (0 == currentLength)
|
||||
for (; i + 64 < length;)
|
||||
processBlock(message.slice(i, i + 64)),
|
||||
i += 64,
|
||||
totalLength += 64;
|
||||
for (; i < length;)
|
||||
if (buffer[currentLength++] = message[i++],
|
||||
totalLength++,
|
||||
64 == currentLength)
|
||||
for (currentLength = 0,
|
||||
processBlock(buffer); i + 64 < length;)
|
||||
processBlock(message.slice(i, i + 64)),
|
||||
i += 64,
|
||||
totalLength += 64;
|
||||
}
|
||||
|
||||
function finalize(): number[] {
|
||||
let result: number[] = []
|
||||
, bits = 8 * totalLength;
|
||||
if (currentLength < 56)
|
||||
update(padding, 56 - currentLength);
|
||||
else
|
||||
update(padding, 64 - (currentLength - 56));
|
||||
for (let i = 63; i >= 56; i--)
|
||||
buffer[i] = bits & 255,
|
||||
bits >>>= 8;
|
||||
processBlock(buffer);
|
||||
for (let i = 0; i < 5; i++)
|
||||
for (let j = 24; j >= 0; j -= 8)
|
||||
result.push(hash[i] >> j & 255);
|
||||
return result;
|
||||
}
|
||||
|
||||
let buffer: number[] = [], padding: number[] = [128], totalLength: number, currentLength: number;
|
||||
for (let i = 1; i < 64; ++i)
|
||||
padding[i] = 0;
|
||||
initialize();
|
||||
return {
|
||||
reset: initialize,
|
||||
update: update,
|
||||
digest: finalize,
|
||||
digestString: function(): string {
|
||||
let hash = finalize(), hex = '';
|
||||
for (let i = 0; i < hash.length; i++)
|
||||
hex += '0123456789ABCDEF'.charAt(Math.floor(hash[i] / 16)) + '0123456789ABCDEF'.charAt(hash[i] % 16);
|
||||
return hex;
|
||||
}
|
||||
};
|
||||
}
|
||||
export const SHA1Hash = async (str: string) => {
|
||||
const enc = new TextEncoder();
|
||||
const hash = await crypto.subtle.digest('SHA-1', enc.encode(str));
|
||||
return Array.from(new Uint8Array(hash))
|
||||
.map((v) => v.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
};
|
||||
|
||||
@ -33,8 +33,8 @@ export const getMusicQueueRenderer = async (videoIds: string[]): Promise<QueueRe
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://music.youtube.com',
|
||||
Authorization: getAuthorizationHeader(token)
|
||||
'Origin': 'https://music.youtube.com',
|
||||
'Authorization': await getAuthorizationHeader(token),
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
export const mapQueueItem = <T>(map: (item: any | null) => T, array: any[]): T[] => array
|
||||
import {
|
||||
ItemPlaylistPanelVideoRenderer,
|
||||
PlaylistPanelVideoWrapperRenderer,
|
||||
QueueItem
|
||||
} from '@/types/datahost-get-state';
|
||||
|
||||
export const mapQueueItem = <T>(map: (item?: ItemPlaylistPanelVideoRenderer) => T, array: QueueItem[]): T[] => array
|
||||
.map((item) => {
|
||||
if ('playlistPanelVideoWrapperRenderer' in item) {
|
||||
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer.primaryRenderer);
|
||||
return item.playlistPanelVideoWrapperRenderer.primaryRenderer[keys[0]];
|
||||
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer!.primaryRenderer) as (keyof PlaylistPanelVideoWrapperRenderer['primaryRenderer'])[];
|
||||
return item.playlistPanelVideoWrapperRenderer!.primaryRenderer[keys[0]];
|
||||
}
|
||||
if ('playlistPanelVideoRenderer' in item) {
|
||||
return item.playlistPanelVideoRenderer;
|
||||
}
|
||||
|
||||
console.error('Music Together: Unknown item', item);
|
||||
return null;
|
||||
return undefined;
|
||||
})
|
||||
.map(map);
|
||||
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<div class="music-together-status">
|
||||
<div class="music-together-status-container">
|
||||
<img class="music-together-profile big">
|
||||
<img class="music-together-profile big" alt="Profile Image">
|
||||
<div class="music-together-status-item">
|
||||
<ytmd-trans key="plugins.music-together.name"></ytmd-trans>
|
||||
<span id="music-together-status-label">
|
||||
<ytmd-trans key="plugins.music-together.menu.status.disconnected"></ytmd-trans>
|
||||
</span>
|
||||
<span id="music-together-permission-label">
|
||||
<marquee id="music-together-permission-label">
|
||||
<ytmd-trans key="plugins.music-together.menu.permission.playlist" style="color: rgba(255, 255, 255, 0.75)"></ytmd-trans>
|
||||
</span>
|
||||
</marquee>
|
||||
</div>
|
||||
</div>
|
||||
<div class="music-together-divider horizontal" style="margin: 16px 0;"></div>
|
||||
|
||||
@ -1,35 +1,3 @@
|
||||
import { YoutubePlayer } from '@/types/youtube-player';
|
||||
type StoreState = any;
|
||||
type Store = {
|
||||
dispatch: (obj: {
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
}) => void;
|
||||
|
||||
getState: () => StoreState;
|
||||
replaceReducer: (param1: unknown) => unknown;
|
||||
subscribe: (callback: () => void) => unknown;
|
||||
};
|
||||
export type QueueAPI = {
|
||||
dispatch(obj: {
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
}): void;
|
||||
getItems(): unknown[];
|
||||
store: Store;
|
||||
continuation?: string;
|
||||
autoPlaying?: boolean;
|
||||
};
|
||||
export type AppAPI = {
|
||||
queue_: QueueAPI;
|
||||
playerApi_: YoutubePlayer;
|
||||
openToast: (message: string) => void;
|
||||
|
||||
// TODO: Add more
|
||||
};
|
||||
|
||||
|
||||
|
||||
export type Profile = {
|
||||
id: string;
|
||||
handleId: string;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||
import statusHTML from '../templates/status.html?raw';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import statusHTML from '../templates/status.html?raw';
|
||||
|
||||
import type { Permission, Profile } from '../types';
|
||||
|
||||
export const createStatus = () => {
|
||||
@ -9,7 +11,7 @@ export const createStatus = () => {
|
||||
|
||||
const profile = element.querySelector<HTMLImageElement>('.music-together-profile')!;
|
||||
const statusLabel = element.querySelector<HTMLSpanElement>('#music-together-status-label')!;
|
||||
const permissionLabel = element.querySelector<HTMLSpanElement>('#music-together-permission-label')!;
|
||||
const permissionLabel = element.querySelector<HTMLMarqueeElement>('#music-together-permission-label')!;
|
||||
|
||||
profile.src = icon?.src ?? '';
|
||||
|
||||
|
||||
@ -50,6 +50,9 @@ export default (
|
||||
toastXml: getXml(songInfo, icon),
|
||||
});
|
||||
|
||||
// To fix the notification not closing
|
||||
setTimeout(() => savedNotification?.close(), 5000);
|
||||
|
||||
savedNotification.on('close', () => {
|
||||
savedNotification = undefined;
|
||||
});
|
||||
@ -253,9 +256,9 @@ export default (
|
||||
songControls = getSongControls(win);
|
||||
|
||||
let currentSeconds = 0;
|
||||
on('ytmd:player-api-loaded', () => send('setupTimeChangedListener'));
|
||||
on('ytmd:player-api-loaded', () => send('ytmd:setup-time-changed-listener'));
|
||||
|
||||
on('timeChanged', (t: number) => {
|
||||
on('ytmd:time-changed', (t: number) => {
|
||||
currentSeconds = t;
|
||||
});
|
||||
|
||||
@ -304,9 +307,9 @@ export default (
|
||||
savedNotification?.close();
|
||||
});
|
||||
|
||||
changeProtocolHandler((cmd) => {
|
||||
changeProtocolHandler((cmd, args) => {
|
||||
if (Object.keys(songControls).includes(cmd)) {
|
||||
songControls[cmd as keyof typeof songControls]();
|
||||
songControls[cmd as keyof typeof songControls](args as never);
|
||||
if (
|
||||
config().refreshOnPlayPause &&
|
||||
(cmd === 'pause' || (cmd === 'play' && !config().unpauseNotification))
|
||||
|
||||
@ -5,7 +5,6 @@ import { app, NativeImage } from 'electron';
|
||||
|
||||
import youtubeMusicIcon from '@assets/youtube-music.png?asset&asarUnpack';
|
||||
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { SongInfo } from '@/providers/song-info';
|
||||
|
||||
import type { NotificationsPluginConfig } from './index';
|
||||
@ -30,7 +29,7 @@ export const urgencyLevels = [
|
||||
{ name: 'High', value: 'critical' } as const,
|
||||
];
|
||||
|
||||
const nativeImageToLogo = cache((nativeImage: NativeImage) => {
|
||||
const nativeImageToLogo = (nativeImage: NativeImage) => {
|
||||
const temporaryImage = nativeImage.resize({ height: 256 });
|
||||
const margin = Math.max(temporaryImage.getSize().width - 256, 0);
|
||||
|
||||
@ -40,7 +39,7 @@ const nativeImageToLogo = cache((nativeImage: NativeImage) => {
|
||||
width: 256,
|
||||
height: 256,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const notificationImage = (
|
||||
songInfo: SongInfo,
|
||||
@ -66,7 +65,7 @@ export const notificationImage = (
|
||||
}
|
||||
};
|
||||
|
||||
export const saveImage = cache((img: NativeImage, savePath: string) => {
|
||||
export const saveImage = (img: NativeImage, savePath: string) => {
|
||||
try {
|
||||
fs.writeFileSync(savePath, img.toPNG());
|
||||
} catch (error: unknown) {
|
||||
@ -76,7 +75,7 @@ export const saveImage = cache((img: NativeImage, savePath: string) => {
|
||||
}
|
||||
|
||||
return savePath;
|
||||
});
|
||||
};
|
||||
|
||||
export const snakeToCamel = (string_: string) =>
|
||||
string_.replaceAll(/([-_][a-z]|^[a-z])/g, (group) =>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user