Repository: pear-devs/pear-desktop
Branch: master
Commit: 83050dee388a
Files: 368
Total size: 2.8 MB
Directory structure:
gitextract_ctpw5b9v/
├── .devcontainer/
│ └── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── feature_request.yml
│ └── workflows/
│ ├── build.yml
│ ├── dependency-review.yml
│ ├── pr-build-artifacts.yml
│ ├── reviewdog.yml
│ ├── winget-cla.yml
│ └── winget-submission.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .vscode/
│ └── launch.json
├── README.md
├── assets/
│ ├── error.html
│ ├── generated/
│ │ └── icons/
│ │ └── mac/
│ │ └── icon.icon/
│ │ └── icon.json
│ └── mdui.css
├── changelog.md
├── electron-builder.yml
├── electron.vite.config.mts
├── eslint.config.mjs
├── license
├── package.json
├── patches/
│ ├── @malept__flatpak-bundler@0.4.0.patch
│ ├── electron-is@3.0.0.patch
│ ├── file-type@16.5.4.patch
│ ├── kuromoji@0.1.2.patch
│ ├── mdui@2.1.4.patch
│ └── vudio@2.1.1.patch
├── renovate.json
├── src/
│ ├── config/
│ │ ├── defaults.ts
│ │ ├── index.ts
│ │ ├── plugins.ts
│ │ └── store.ts
│ ├── custom-electron-prompt.d.ts
│ ├── i18n/
│ │ ├── index.ts
│ │ └── resources/
│ │ ├── @types/
│ │ │ └── index.ts
│ │ ├── ar.json
│ │ ├── az.json
│ │ ├── be.json
│ │ ├── bg.json
│ │ ├── bn.json
│ │ ├── bs.json
│ │ ├── ca.json
│ │ ├── cs.json
│ │ ├── da.json
│ │ ├── de.json
│ │ ├── el.json
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── et.json
│ │ ├── eu.json
│ │ ├── fa.json
│ │ ├── fi.json
│ │ ├── fil.json
│ │ ├── fr.json
│ │ ├── gl.json
│ │ ├── he.json
│ │ ├── hi.json
│ │ ├── hr.json
│ │ ├── hu.json
│ │ ├── id.json
│ │ ├── is.json
│ │ ├── it.json
│ │ ├── ja.json
│ │ ├── ka.json
│ │ ├── kmr.json
│ │ ├── kn.json
│ │ ├── ko.json
│ │ ├── lt.json
│ │ ├── lv.json
│ │ ├── ml.json
│ │ ├── ms.json
│ │ ├── nb.json
│ │ ├── ne.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt-BR.json
│ │ ├── pt.json
│ │ ├── qu.json
│ │ ├── ro.json
│ │ ├── ru.json
│ │ ├── sah.json
│ │ ├── si.json
│ │ ├── sk.json
│ │ ├── sl.json
│ │ ├── sq.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ ├── ta.json
│ │ ├── te.json
│ │ ├── th.json
│ │ ├── tr.json
│ │ ├── uk.json
│ │ ├── ur.json
│ │ ├── vi.json
│ │ ├── zh-CN.json
│ │ └── zh-TW.json
│ ├── index.html
│ ├── index.ts
│ ├── loader/
│ │ ├── main.ts
│ │ ├── menu.ts
│ │ ├── preload.ts
│ │ └── renderer.ts
│ ├── menu.ts
│ ├── music-player.css
│ ├── navigation.d.ts
│ ├── pear-desktop.ts
│ ├── plugins/
│ │ ├── album-actions/
│ │ │ ├── index.tsx
│ │ │ └── templates/
│ │ │ ├── dislike-button.tsx
│ │ │ ├── index.ts
│ │ │ ├── like-button.tsx
│ │ │ ├── undislike-button.tsx
│ │ │ └── unlike-button.tsx
│ │ ├── album-color-theme/
│ │ │ ├── index.ts
│ │ │ └── style.css
│ │ ├── ambient-mode/
│ │ │ ├── index.ts
│ │ │ ├── menu.ts
│ │ │ ├── style.css
│ │ │ └── types.ts
│ │ ├── amuse/
│ │ │ ├── backend.ts
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── api-server/
│ │ │ ├── backend/
│ │ │ │ ├── api-version.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── main.ts
│ │ │ │ ├── routes/
│ │ │ │ │ ├── auth.ts
│ │ │ │ │ ├── control.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── websocket.ts
│ │ │ │ ├── scheme/
│ │ │ │ │ ├── auth.ts
│ │ │ │ │ ├── go-back.ts
│ │ │ │ │ ├── go-forward.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── queue.ts
│ │ │ │ │ ├── search.ts
│ │ │ │ │ ├── seek.ts
│ │ │ │ │ ├── set-fullscreen.ts
│ │ │ │ │ ├── set-volume.ts
│ │ │ │ │ ├── song-info.ts
│ │ │ │ │ └── switch-repeat.ts
│ │ │ │ └── types.ts
│ │ │ ├── config.ts
│ │ │ ├── index.ts
│ │ │ └── menu.ts
│ │ ├── audio-compressor.ts
│ │ ├── auth-proxy-adapter/
│ │ │ ├── backend/
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── config.ts
│ │ │ ├── index.ts
│ │ │ └── menu.ts
│ │ ├── blur-nav-bar/
│ │ │ ├── index.ts
│ │ │ └── style.css
│ │ ├── captions-selector/
│ │ │ ├── back.ts
│ │ │ ├── index.ts
│ │ │ ├── renderer.tsx
│ │ │ └── templates/
│ │ │ └── captions-settings-template.tsx
│ │ ├── clock/
│ │ │ ├── index.tsx
│ │ │ ├── style.css
│ │ │ └── types.ts
│ │ ├── compact-sidebar/
│ │ │ └── index.ts
│ │ ├── crossfade/
│ │ │ ├── fader.ts
│ │ │ └── index.ts
│ │ ├── custom-output-device/
│ │ │ ├── index.ts
│ │ │ └── renderer.ts
│ │ ├── disable-autoplay/
│ │ │ └── index.ts
│ │ ├── discord/
│ │ │ ├── constants.ts
│ │ │ ├── discord-service.ts
│ │ │ ├── index.ts
│ │ │ ├── main.ts
│ │ │ ├── menu.ts
│ │ │ ├── timer-manager.ts
│ │ │ └── utils.ts
│ │ ├── downloader/
│ │ │ ├── index.ts
│ │ │ ├── main/
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ ├── menu.ts
│ │ │ ├── renderer.tsx
│ │ │ ├── style.css
│ │ │ ├── templates/
│ │ │ │ └── download.tsx
│ │ │ └── types.ts
│ │ ├── equalizer/
│ │ │ ├── index.ts
│ │ │ └── presets.ts
│ │ ├── exponential-volume/
│ │ │ └── index.ts
│ │ ├── in-app-menu/
│ │ │ ├── constants.ts
│ │ │ ├── index.ts
│ │ │ ├── main.ts
│ │ │ ├── menu.ts
│ │ │ ├── renderer/
│ │ │ │ ├── IconButton.tsx
│ │ │ │ ├── MenuButton.tsx
│ │ │ │ ├── Panel.tsx
│ │ │ │ ├── PanelItem.tsx
│ │ │ │ ├── TitleBar.tsx
│ │ │ │ └── WindowController.tsx
│ │ │ ├── renderer.tsx
│ │ │ └── titlebar.css
│ │ ├── lumiastream/
│ │ │ └── index.ts
│ │ ├── music-together/
│ │ │ ├── connection.ts
│ │ │ ├── element.ts
│ │ │ ├── index.ts
│ │ │ ├── queue/
│ │ │ │ ├── client.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── queue.ts
│ │ │ │ ├── sha1hash.ts
│ │ │ │ ├── song.ts
│ │ │ │ └── utils.ts
│ │ │ ├── style.css
│ │ │ ├── templates/
│ │ │ │ ├── item.html
│ │ │ │ ├── popup.html
│ │ │ │ ├── setting.html
│ │ │ │ └── status.html
│ │ │ ├── types.ts
│ │ │ └── ui/
│ │ │ ├── guest.ts
│ │ │ ├── host.ts
│ │ │ ├── setting.ts
│ │ │ └── status.ts
│ │ ├── navigation/
│ │ │ └── index.tsx
│ │ ├── notifications/
│ │ │ ├── index.ts
│ │ │ ├── interactive.ts
│ │ │ ├── main.ts
│ │ │ ├── menu.ts
│ │ │ └── utils.ts
│ │ ├── performance-improvement/
│ │ │ ├── index.ts
│ │ │ └── scripts/
│ │ │ ├── cpu-tamer/
│ │ │ │ ├── cpu-tamer-by-animationframe.d.ts
│ │ │ │ ├── cpu-tamer-by-animationframe.js
│ │ │ │ ├── cpu-tamer-by-dom-mutation.d.ts
│ │ │ │ ├── cpu-tamer-by-dom-mutation.js
│ │ │ │ └── index.ts
│ │ │ └── rm3/
│ │ │ ├── index.ts
│ │ │ ├── rm3.d.ts
│ │ │ └── rm3.js
│ │ ├── picture-in-picture/
│ │ │ ├── index.ts
│ │ │ ├── keyboardevent-from-electron-accelerator.d.ts
│ │ │ ├── keyboardevents-areequal.d.ts
│ │ │ ├── main.ts
│ │ │ ├── menu.ts
│ │ │ ├── renderer.tsx
│ │ │ ├── style.css
│ │ │ └── templates/
│ │ │ └── picture-in-picture-button.tsx
│ │ ├── playback-speed/
│ │ │ ├── components/
│ │ │ │ └── slider.tsx
│ │ │ ├── index.ts
│ │ │ └── renderer.tsx
│ │ ├── precise-volume/
│ │ │ ├── index.ts
│ │ │ ├── override.ts
│ │ │ ├── renderer.ts
│ │ │ └── volume-hud.css
│ │ ├── quality-changer/
│ │ │ ├── index.tsx
│ │ │ └── templates/
│ │ │ └── quality-setting-button.tsx
│ │ ├── scrobbler/
│ │ │ ├── index.ts
│ │ │ ├── main.ts
│ │ │ ├── menu.ts
│ │ │ └── services/
│ │ │ ├── base.ts
│ │ │ ├── lastfm.ts
│ │ │ └── listenbrainz.ts
│ │ ├── shortcuts/
│ │ │ ├── index.ts
│ │ │ ├── main.ts
│ │ │ ├── menu.ts
│ │ │ ├── mpris-service.d.ts
│ │ │ └── mpris.ts
│ │ ├── skip-disliked-songs/
│ │ │ └── index.ts
│ │ ├── skip-silences/
│ │ │ ├── index.ts
│ │ │ └── renderer.ts
│ │ ├── sponsorblock/
│ │ │ ├── index.ts
│ │ │ ├── segments.ts
│ │ │ ├── tests/
│ │ │ │ └── segments.test.js
│ │ │ └── types.ts
│ │ ├── synced-lyrics/
│ │ │ ├── backend.ts
│ │ │ ├── index.ts
│ │ │ ├── menu.ts
│ │ │ ├── parsers/
│ │ │ │ ├── lrc.test.ts
│ │ │ │ └── lrc.ts
│ │ │ ├── providers/
│ │ │ │ ├── LRCLib.ts
│ │ │ │ ├── LyricsGenius.ts
│ │ │ │ ├── Megalobiz.ts
│ │ │ │ ├── MusixMatch.ts
│ │ │ │ ├── YTMusic.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── renderer.ts
│ │ │ ├── renderer/
│ │ │ │ ├── components/
│ │ │ │ │ ├── ErrorDisplay.tsx
│ │ │ │ │ ├── LoadingKaomoji.tsx
│ │ │ │ │ ├── LyricsPicker.tsx
│ │ │ │ │ ├── NotFoundKaomoji.tsx
│ │ │ │ │ ├── PlainLyrics.tsx
│ │ │ │ │ ├── SyncedLine.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── renderer.tsx
│ │ │ │ ├── store.ts
│ │ │ │ └── utils.tsx
│ │ │ ├── style.css
│ │ │ └── types.ts
│ │ ├── taskbar-mediacontrol/
│ │ │ └── index.ts
│ │ ├── touchbar/
│ │ │ └── index.ts
│ │ ├── transparent-player/
│ │ │ ├── index.ts
│ │ │ ├── style.css
│ │ │ └── types.ts
│ │ ├── tuna-obs/
│ │ │ └── index.ts
│ │ ├── unobtrusive-player/
│ │ │ ├── index.ts
│ │ │ └── style.css
│ │ ├── utils/
│ │ │ ├── common/
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── main/
│ │ │ │ ├── css.ts
│ │ │ │ ├── fetch.ts
│ │ │ │ ├── fs.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ └── renderer/
│ │ │ ├── check.ts
│ │ │ ├── html.ts
│ │ │ └── index.ts
│ │ ├── video-toggle/
│ │ │ ├── button-switcher.css
│ │ │ ├── force-hide.css
│ │ │ ├── index.tsx
│ │ │ └── templates/
│ │ │ └── video-switch-button.tsx
│ │ └── visualizer/
│ │ ├── butterchurn.d.ts
│ │ ├── empty-player.css
│ │ ├── index.ts
│ │ ├── visualizers/
│ │ │ ├── butterchurn.ts
│ │ │ ├── index.ts
│ │ │ ├── visualizer.ts
│ │ │ ├── vudio.ts
│ │ │ └── wave.ts
│ │ └── vudio.d.ts
│ ├── preload.ts
│ ├── providers/
│ │ ├── app-controls.ts
│ │ ├── decorators.ts
│ │ ├── dom-elements.ts
│ │ ├── extracted-data.ts
│ │ ├── prompt-options.ts
│ │ ├── protocol-handler.ts
│ │ ├── song-controls.ts
│ │ ├── song-info-front.ts
│ │ └── song-info.ts
│ ├── renderer.ts
│ ├── reset.d.ts
│ ├── solit.tsx
│ ├── tray.ts
│ ├── ts-declarations/
│ │ ├── kuroshiro-analyzer-kuromoji.d.ts
│ │ └── kuroshiro.d.ts
│ ├── types/
│ │ ├── contexts.ts
│ │ ├── datahost-get-state.ts
│ │ ├── get-player-response.ts
│ │ ├── icons.ts
│ │ ├── media-icons.ts
│ │ ├── music-player-app-element.ts
│ │ ├── music-player-desktop-internal.ts
│ │ ├── music-player.ts
│ │ ├── player-api-events.ts
│ │ ├── plugins.ts
│ │ ├── queue.ts
│ │ ├── search-box-element.ts
│ │ ├── video-data-changed.ts
│ │ └── video-details.ts
│ ├── utils/
│ │ ├── custom-element.ts
│ │ ├── index.ts
│ │ ├── testing.ts
│ │ ├── trusted-types.ts
│ │ ├── type-utils.ts
│ │ └── wait-for-element.ts
│ ├── virtual-module.d.ts
│ └── yt-web-components.d.ts
├── tests/
│ └── index.test.js
├── tsconfig.json
├── tsconfig.test.json
└── vite-plugins/
├── i18n-importer.mts
├── plugin-importer.mts
└── plugin-loader.mts
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Pear Desktop - Dev Container",
// Keep in sync with `.github/workflows/build.yml`
"image": "mcr.microsoft.com/devcontainers/typescript-node:24",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {},
"postCreateCommand": "pnpm install --frozen-lockfile"
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
*.js text
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Report a Pear Desktop bug
title: "[Bug]: "
labels: "bug :beetle:"
body:
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I use the latest version of Pear Desktop (Application).
required: true
- label: I have searched the [issue tracker](https://github.com/pear-devs/pear-desktop/issues) for a bug report that matches the one I want to file, without success.
required: true
- label: I understand that **pear-devs/pear-desktop has NO affiliation with Google or YouTube**
required: true
- type: input
attributes:
label: Pear Desktop (Application) Version
description: |
What version of the Pear Desktop Application are you using?
Note: Please check if this issue is reproducible with the latest stable release.
placeholder: 2.0.0
validations:
required: true
- type: checkboxes
attributes:
label: Checklists
options:
- label: I use the portable version of the Pear Desktop 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?
options:
- Windows
- macOS
- Ubuntu
- Other Linux
- Other (specify below)
validations:
required: true
- type: input
attributes:
label: Operating System Version
description: What operating system version are you using? On Windows, click the Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a.
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
validations:
required: true
- type: dropdown
attributes:
label: What CPU architecture are you using?
options:
- x64
- ia32
- arm64 (including Apple Silicon)
- Other (specify below)
validations:
required: true
- type: input
attributes:
label: Last Known Working Pear Desktop (Application) version
description: (If applicable) What is the last version of Pear Desktop this worked in?
placeholder: 1.20.0
- type: textarea
attributes:
label: Reproduction steps
description: Provide steps to reproduce the issue.
placeholder: 1. Enable the X plugin.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: A clear description of what actually happens.
validations:
required: true
- type: textarea
attributes:
label: Enabled plugins
description: Provide the list of plugins you enabled.
placeholder: 1. Album Color Theme
validations:
required: true
- type: textarea
attributes:
label: Additional Information
description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Suggest an idea for Pear Desktop
title: "[Feature Request]: "
labels: "enhancement :sparkles:"
body:
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I use the latest version of Pear Desktop (Application).
required: true
- label: I have searched the [issue tracker](https://github.com/pear-devs/pear-desktop/issues) for a feature request that matches the one I want to file, without success.
required: true
- type: textarea
attributes:
label: Problem Description
description: A clear and concise description of the problem you are seeking to solve with this feature request.
validations:
required: true
- type: textarea
attributes:
label: Proposed Solution
description: Describe the solution you'd like in a clear and concise manner.
validations:
required: true
- type: textarea
attributes:
label: Alternatives Considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Additional Information
description: Any other context about the problem.
validations:
required: false
================================================
FILE: .github/workflows/build.yml
================================================
name: Build Pear Desktop
on:
push:
branches: [ master ]
pull_request:
env:
NODE_VERSION: "22.x"
jobs:
build:
if: github.event.pull_request.draft == false
name: Build Pear Desktop
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ macos-26, ubuntu-latest, windows-latest ]
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup NodeJS
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Only vite build without release if it is a fork, or it is a pull-request
- name: Vite Build
if: github.repository == 'pear-devs/pear-desktop' && github.event_name == 'pull_request'
run: |
pnpm build
# Build and release if it's the main repository and is not pull-request
- name: Build and release on Mac
if: startsWith(matrix.os, 'macOS') && (github.repository == 'pear-devs/pear-desktop' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:mac
- name: Build and release on Linux
if: startsWith(matrix.os, 'ubuntu') && (github.repository == 'pear-devs/pear-desktop' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sudo snap install snapcraft --classic
sudo apt update
sudo apt install -y flatpak flatpak-builder
sudo flatpak remote-add --if-not-exists --system flathub https://flathub.org/repo/flathub.flatpakrepo
sudo flatpak install -y flathub org.freedesktop.Platform/x86_64/24.08
sudo flatpak install -y flathub org.freedesktop.Sdk/x86_64/24.08
sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp/x86_64/24.08
pnpm release:linux
- name: Build and release on Windows
if: startsWith(matrix.os, 'windows') && (github.repository == 'pear-devs/pear-desktop' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:win
- name: Test
uses: coactions/setup-xvfb@v1
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
with:
run: pnpm test:debug
release:
runs-on: ubuntu-latest
name: Release Pear Desktop
if: github.repository == 'pear-devs/pear-desktop' && github.ref == 'refs/heads/master'
needs: build
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup NodeJS
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Get version
run: |
echo "VERSION_TAG=v$(node -pe "require('./package.json').version")" >> $GITHUB_ENV
- name: Check if version already exists in tags
run: |
echo "VERSION_HASH=$(git rev-parse -q --verify 'refs/tags/${{ env.VERSION_TAG }}')" >> $GITHUB_ENV
echo "CHANGELOG_ANCHOR=$(echo $VERSION_TAG | sed -e 's/\.//g')" >> $GITHUB_ENV
- name: Fetch draft release
if: ${{ env.VERSION_HASH == '' }}
uses: cardinalby/git-get-release-action@v1
id: get_draft_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
latest: true
draft: true
searchLimit: 1
- name: Publish Release (if it does not exist)
if: ${{ env.VERSION_HASH == '' }}
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GH_TOKEN }}
id: ${{ steps.get_draft_release.outputs.id }}
draft: false
prerelease: false
replacename: true
name: ${{ env.VERSION_TAG }}
replacebody: true
body: |
See [changelog](https://github.com/pear-devs/pear-desktop/blob/master/changelog.md#${{ env.CHANGELOG_ANCHOR }}) for the list of updates and the full diff.
Thanks to all contributors! 🏅
(Note for Windows: `Pear-Desktop-Web-Setup-${{ env.VERSION_TAG }}.exe` is an installer, and `Pear-Desktop-${{ env.VERSION_TAG }}.exe` is a portable version)
- name: Update changelog
if: ${{ env.VERSION_HASH == '' }}
run: |
pnpm changelog
- name: Commit changelog
if: ${{ env.VERSION_HASH == '' }}
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: Update changelog for ${{ env.VERSION_TAG }}
file_pattern: "changelog.md"
commit_user_name: CI
commit_user_email: th-ch@users.noreply.github.com
================================================
FILE: .github/workflows/dependency-review.yml
================================================
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Reqest, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
name: "Dependency Review"
on: [ pull_request ]
permissions:
contents: read
jobs:
dependency-review:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v6
- name: "Dependency Review"
uses: actions/dependency-review-action@v4
================================================
FILE: .github/workflows/pr-build-artifacts.yml
================================================
name: Build PR Artifacts
on:
pull_request:
types: [opened, synchronize, reopened]
env:
NODE_VERSION: "22.x"
jobs:
check-permissions:
if: github.event.pull_request.draft == false
name: Check if user has write access
runs-on: ubuntu-latest
outputs:
has-write-access: ${{ steps.check.outputs.require-result }}
steps:
- name: Check user permission
id: check
uses: actions-cool/check-user-permission@v2
with:
require: write
username: ${{ github.event.pull_request.user.login }}
build:
name: Build ${{ matrix.os }}
needs: check-permissions
if: needs.check-permissions.outputs.has-write-access == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup NodeJS
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build on macOS
if: startsWith(matrix.os, 'macOS')
run: |
pnpm dist:mac
pnpm dist:mac:arm64
- name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo snap install snapcraft --classic
sudo apt update
sudo apt install -y flatpak flatpak-builder
sudo flatpak remote-add --if-not-exists --system flathub https://flathub.org/repo/flathub.flatpakrepo
sudo flatpak install -y flathub org.freedesktop.Platform/x86_64/24.08
sudo flatpak install -y flathub org.freedesktop.Sdk/x86_64/24.08
sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp/x86_64/24.08
- name: Build on Linux
if: startsWith(matrix.os, 'ubuntu')
run: |
pnpm dist:linux
pnpm dist:linux:deb-arm64
pnpm dist:linux:rpm-arm64
- name: Build on Windows
if: startsWith(matrix.os, 'windows')
run: |
pnpm dist:win
- name: Upload artifacts
uses: actions/upload-artifact@v6
with:
name: build-artifacts-${{ matrix.os }}
path: pack/
retention-days: 7
if-no-files-found: error
comment:
name: Comment on PR
needs: [check-permissions, build]
if: always() && needs.check-permissions.outputs.has-write-access == 'true'
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Create comment
uses: actions/github-script@v8
with:
script: |
const runId = context.runId;
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
const buildResult = '${{ needs.build.result }}';
let comment;
if (buildResult === 'success') {
comment = `## 🚀 Build Artifacts Ready!
The builds have completed successfully. You can download the artifacts from the workflow run:
**[📦 Download Artifacts](${runUrl})**
### Available builds:
- **Windows**: \`build-artifacts-windows-latest\`
- **macOS**: \`build-artifacts-macos-latest\`
- **Linux**: \`build-artifacts-ubuntu-latest\`
*Note: Artifacts are available for 7 days.*`;
} else if (buildResult === 'failure') {
comment = `## ❌ Build Failed
Unfortunately, one or more builds failed. Please check the workflow run for details:
**[View Workflow Run](${runUrl})**`;
} else {
comment = `## ⚠️ Build Status: ${buildResult}
The build process completed with status: **${buildResult}**
**[View Workflow Run](${runUrl})**`;
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
================================================
FILE: .github/workflows/reviewdog.yml
================================================
name: reviewdog
on: [pull_request_target]
env:
NODE_VERSION: "22.x"
jobs:
eslint:
if: github.event.pull_request.draft == false
name: runner / eslint
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup NodeJS
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: reviewdog/action-eslint@v1.34.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review # Change reporter.
eslint_flags: './src'
fail_level: error
================================================
FILE: .github/workflows/winget-cla.yml
================================================
name: Submit CLA to Winget PR
on:
workflow_dispatch:
inputs:
pr_url:
description: "Specific PR URL"
required: true
type: string
jobs:
comment:
name: Comment to PR
runs-on: ubuntu-latest
steps:
- name: Submit CLA to Windows Package Manager Community Repository Pull Request
run: gh pr comment $PR_URL --body "@microsoft-github-policy-service agree"
env:
GITHUB_TOKEN: ${{ secrets.WINGET_ACC_TOKEN }}
PR_URL: ${{ inputs.pr_url }}
================================================
FILE: .github/workflows/winget-submission.yml
================================================
name: Submit to Windows Package Manager Community Repository
on:
release:
types: [ released ]
workflow_dispatch:
inputs:
tag_name:
description: "Specific tag name"
required: true
type: string
jobs:
winget:
name: Publish winget package
runs-on: ubuntu-latest
steps:
- name: Set winget version env
env:
TAG_NAME: ${{ inputs.tag_name || github.event.release.tag_name }}
run: echo "WINGET_TAG_NAME=$(echo ${TAG_NAME#v})" >> $GITHUB_ENV
- name: Submit package to Windows Package Manager Community Repository
uses: vedantmgoyal2009/winget-releaser@main
with:
identifier: pear-devs.PearDesktop
installers-regex: '^Pear-Desktop-Web-Setup-[\d\.]+\.exe$'
version: ${{ env.WINGET_TAG_NAME }}
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
token: ${{ secrets.WINGET_ACC_TOKEN }}
fork-user: pear-desktop-winget
================================================
FILE: .gitignore
================================================
node_modules
/dist
/pack
.vscode/settings.json
.idea
.pnp.*
.pnpm-store
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.vite-inspect
.DS_Store
================================================
FILE: .npmrc
================================================
engine-strict=true
scripts-prepend-node-path=true
================================================
FILE: .prettierrc
================================================
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"trailingComma": "all",
"quoteProps": "consistent"
}
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node-terminal",
"name": "Run Script: dev (pear-desktop)",
"request": "launch",
"command": "pnpm run dev",
"cwd": "${workspaceFolder}"
}
]
}
================================================
FILE: README.md
================================================
Special thanks to:
### [Warp, built for coding with multiple AI agents](https://go.warp.dev/pear-desktop)
[Available for macOS, Linux, & Windows](https://go.warp.dev/pear-desktop)
# :pear: Pear Desktop
[](https://github.com/pear-devs/pear-desktop/releases/)
[](https://github.com/pear-devs/pear-desktop/blob/master/license)
[](https://github.com/pear-devs/pear-desktop/blob/master/eslint.config.mjs)
[](https://GitHub.com/pear-devs/pear-desktop/releases/)
[](https://GitHub.com/pear-devs/pear-desktop/releases/)
[](https://snyk.io/test/github/pear-devs/pear-desktop)
- Native look & feel extension
> [!IMPORTANT]
> ⚠️ Disclaimer
>
> **No Affiliation**
>
> This project, and its contributors, are not affiliated with, authorized by, endorsed by, or in any way officially connected with Google LLC, YouTube, or any of their subsidiaries or affiliates. **This is an independent, non-profit, and unofficial extension developed by a team of volunteers with the goal of providing a desktop experience.**
>
> **Trademarks**
>
> The names "Google" and "YouTube Music", as well as related names, marks, emblems, and images, are registered trademarks of their respective owners. Any use of these trademarks is for identification and reference purposes only and does not imply any association with the trademark holder. We have no intention of infringing upon these trademarks or causing harm to the trademark holders.
>
> **Limitation of Liability**
>
> This application (extension) is provided "AS IS", and you use it at your own risk. In no event shall the developers or contributors be liable for any claim, damages, or other liability, including any legal consequences, arising from, out of, or in connection with the software or the use or other dealings in the software. The responsibility for any and all outcomes of using this software rests entirely with the user.
## Content
- [Features](#features)
- [Translation](#translation)
- [Download](#download)
- [Arch Linux](#arch-linux)
- [Solus](#solus)
- [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)
## Translation
You can help with translation on [Hosted Weblate](https://bit.ly/48n5YF7).
## Download
You can check out the [latest release](https://github.com/pear-devs/pear-desktop/releases/latest) to quickly find the
latest version.
### Arch Linux
Install the [`pear-desktop`](https://aur.archlinux.org/packages/pear-desktop) 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).
### [Solus](https://getsol.us/)
```bash
sudo eopkg install pear-desktop
```
### macOS
You can install the app using Homebrew (see the [cask definition](https://github.com/pear-devs/homebrew-pear)):
```bash
brew install pear-devs/pear/pear-desktop
```
If you install the app manually and get an error "is damaged and can’t be opened." when launching the app, run the following in the Terminal:
```bash
/usr/bin/xattr -cr /Applications/Pear\ Desktop.app
```
### Windows
You can use the [Scoop package manager](https://scoop.sh) to install the `pear-desktop` package from
the [`extras` bucket](https://github.com/ScoopInstaller/Extras).
```bash
scoop bucket add extras
scoop install extras/pear-desktop
```
Alternately you can use [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), Windows 11s
official CLI package manager to install the `pear-devs.pear-desktop` package.
*Note: Microsoft Defender SmartScreen might block the installation since it is from an "unknown publisher". This is also
true for the manual installation when trying to run the executable(.exe) after a manual download here on github (same
file).*
```bash
winget install pear-devs.pear-desktop
```
#### How to install without a network connection? (in Windows)
- Download the `*.nsis.7z` file for _your device architecture_ in [release page](https://github.com/pear-devs/pear-desktop/releases/latest).
- `x64` for 64-bit Windows
- `ia32` for 32-bit Windows
- `arm64` for ARM64 Windows
- Download installer in release page. (`*-Setup.exe`)
- Place them in the **same directory**.
- Run the installer.
## Themes
You can load CSS files to change the look of the application (Options > Visual Tweaks > Themes).
Some predefined themes are available in https://github.com/kerichdev/themes-for-ytmdesktop-player.
## Dev
```bash
git clone https://github.com/pear-devs/pear-desktop
cd pear-desktop
pnpm install --frozen-lockfile
pnpm dev
```
Instead of installing pnpm on your system, you can also use [devcontainers](https://containers.dev/). You can use devcontainers either as a development environment in VS Code, or as a way to easily build the project without installing dependencies on your host system.
Note that this has it's own limitations (for example, GUI doesn't work on, at least some, Linux hosts).
## Build your own plugins
Using plugins, you can:
- manipulate the app - the `BrowserWindow` from electron is passed to the plugin handler
- change the front by manipulating the HTML/CSS
### Creating a plugin
Create a folder in `src/plugins/YOUR-PLUGIN-NAME`:
- `index.ts`: the main file of the plugin
```typescript
import style from './style.css?inline'; // import style as inline
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic show restart dialog
config: {
enabled: false,
}, // your custom config
stylesheets: [style], // your custom style,
menu: async ({ getConfig, setConfig }) => {
// All *Config methods are wrapped Promise
const config = await getConfig();
return [
{
label: 'menu',
submenu: [1, 2, 3].map((value) => ({
label: `value ${value}`,
type: 'radio',
checked: config.value === value,
click() {
setConfig({ value });
},
})),
},
];
},
backend: {
start({ window, ipc }) {
window.maximize();
// you can communicate with renderer plugin
ipc.handle('some-event', () => {
return 'hello';
});
},
// it fired when config changed
onConfigChange(newConfig) { /* ... */ },
// it fired when plugin disabled
stop(context) { /* ... */ },
},
renderer: {
async start(context) {
console.log(await context.ipc.invoke('some-event'));
},
// Only renderer available hook
onPlayerApiReady(api, context) {
// set plugin config easily
context.setConfig({ myConfig: api.getVolume() });
},
onConfigChange(newConfig) { /* ... */ },
stop(_context) { /* ... */ },
},
preload: {
async start({ getConfig }) {
const config = await getConfig();
},
onConfigChange(newConfig) {},
stop(_context) {},
},
});
```
### Common use cases
- injecting custom CSS: create a `style.css` file in the same folder then:
```typescript
// index.ts
import style from './style.css?inline'; // import style as inline
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, pear-desktop will show a restart dialog
config: {
enabled: false,
}, // your custom config
stylesheets: [style], // your custom style
renderer() {} // define renderer hook
});
```
- If you want to change the HTML:
```typescript
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic will show the restart dialog
config: {
enabled: false,
}, // your custom config
renderer() {
console.log('hello from renderer');
} // define renderer hook
});
```
- communicating between the front and back: can be done using the ipcMain module from electron. See `index.ts` file and
example in `sponsorblock` plugin.
## Build
1. Clone the repo
2. Follow [this guide](https://pnpm.io/installation) to install `pnpm`
3. Run `pnpm install --frozen-lockfile` to install dependencies
4. Run `pnpm build:OS`
- `pnpm dist:win` - Windows
- `pnpm dist:linux` - Linux (amd64)
- `pnpm dist:linux:deb-arm64` - Linux (arm64 for Debian)
- `pnpm dist:linux:rpm-arm64` - Linux (arm64 for Fedora)
- `pnpm dist:mac` - macOS (amd64)
- `pnpm dist:mac:arm64` - macOS (arm64)
Builds the app for macOS, Linux, and Windows,
using [electron-builder](https://github.com/electron-userland/electron-builder).
### Building in devcontainer
1. Clone the repo;
2. Open the folder in VS Code;
3. Reopen in container when prompted;
4. Run `pnpm build` as above (choosing the desired target);
5. Collect the built files from the `dist` folder.
Since devcontainer uses a mount for the workspace, the built files will be available on the host system as well.
## Production Preview
```bash
pnpm start
```
## Tests
```bash
pnpm test
```
Uses [Playwright](https://playwright.dev/) to test the app.
## License
MIT © [pear-devs](https://github.com/pear-devs/pear-desktop)
## FAQ
### Why apps menu isn't showing up?
If `Hide Menu` option is on - you can show the menu with the alt key (or \` [backtick] if using
the in-app-menu plugin)
================================================
FILE: assets/error.html
================================================
Cannot load Pear Desktop
Cannot load Pear Desktop… Internet disconnected?
Retry
================================================
FILE: assets/generated/icons/mac/icon.icon/icon.json
================================================
{
"fill" : {
"linear-gradient" : [
"display-p3:1.00000,1.00000,1.00000,1.00000",
"srgb:0.84314,0.84314,0.84314,1.00000"
],
"orientation" : {
"start" : {
"x" : 0.5,
"y" : 0
},
"stop" : {
"x" : 0.5,
"y" : 0.7
}
}
},
"groups" : [
{
"blur-material" : null,
"hidden" : false,
"layers" : [
{
"blend-mode-specializations" : [
{
"appearance" : "dark",
"value" : "normal"
}
],
"image-name" : "SVG Image.svg",
"name" : "transparent-icon",
"opacity-specializations" : [
{
"value" : 1
},
{
"appearance" : "dark",
"value" : 1
}
]
}
],
"name" : "group",
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 0.8
}
],
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.5
},
"specular" : true,
"translucency" : {
"enabled" : false,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
================================================
FILE: assets/mdui.css
================================================
:root {
--mdui-breakpoint-xs: 0px;
--mdui-breakpoint-sm: 600px;
--mdui-breakpoint-md: 840px;
--mdui-breakpoint-lg: 1080px;
--mdui-breakpoint-xl: 1440px;
--mdui-breakpoint-xxl: 1920px;
}
:root {
--mdui-color-primary-light: 103, 80, 164;
--mdui-color-primary-container-light: 234, 221, 255;
--mdui-color-on-primary-light: 255, 255, 255;
--mdui-color-on-primary-container-light: 33, 0, 94;
--mdui-color-inverse-primary-light: 208, 188, 255;
--mdui-color-secondary-light: 98, 91, 113;
--mdui-color-secondary-container-light: 232, 222, 248;
--mdui-color-on-secondary-light: 255, 255, 255;
--mdui-color-on-secondary-container-light: 30, 25, 43;
--mdui-color-tertiary-light: 125, 82, 96;
--mdui-color-tertiary-container-light: 255, 216, 228;
--mdui-color-on-tertiary-light: 255, 255, 255;
--mdui-color-on-tertiary-container-light: 55, 11, 30;
--mdui-color-surface-light: 254, 247, 255;
--mdui-color-surface-dim-light: 222, 216, 225;
--mdui-color-surface-bright-light: 254, 247, 255;
--mdui-color-surface-container-lowest-light: 255, 255, 255;
--mdui-color-surface-container-low-light: 247, 242, 250;
--mdui-color-surface-container-light: 243, 237, 247;
--mdui-color-surface-container-high-light: 236, 230, 240;
--mdui-color-surface-container-highest-light: 230, 224, 233;
--mdui-color-surface-variant-light: 231, 224, 236;
--mdui-color-on-surface-light: 28, 27, 31;
--mdui-color-on-surface-variant-light: 73, 69, 78;
--mdui-color-inverse-surface-light: 49, 48, 51;
--mdui-color-inverse-on-surface-light: 244, 239, 244;
--mdui-color-background-light: 254, 247, 255;
--mdui-color-on-background-light: 28, 27, 31;
--mdui-color-error-light: 179, 38, 30;
--mdui-color-error-container-light: 249, 222, 220;
--mdui-color-on-error-light: 255, 255, 255;
--mdui-color-on-error-container-light: 65, 14, 11;
--mdui-color-outline-light: 121, 116, 126;
--mdui-color-outline-variant-light: 196, 199, 197;
--mdui-color-shadow-light: 0, 0, 0;
--mdui-color-surface-tint-color-light: 103, 80, 164;
--mdui-color-scrim-light: 0, 0, 0;
--mdui-color-primary-dark: 208, 188, 255;
--mdui-color-primary-container-dark: 79, 55, 139;
--mdui-color-on-primary-dark: 55, 30, 115;
--mdui-color-on-primary-container-dark: 234, 221, 255;
--mdui-color-inverse-primary-dark: 103, 80, 164;
--mdui-color-secondary-dark: 204, 194, 220;
--mdui-color-secondary-container-dark: 74, 68, 88;
--mdui-color-on-secondary-dark: 51, 45, 65;
--mdui-color-on-secondary-container-dark: 232, 222, 248;
--mdui-color-tertiary-dark: 239, 184, 200;
--mdui-color-tertiary-container-dark: 99, 59, 72;
--mdui-color-on-tertiary-dark: 73, 37, 50;
--mdui-color-on-tertiary-container-dark: 255, 216, 228;
--mdui-color-surface-dark: 20, 18, 24;
--mdui-color-surface-dim-dark: 20, 18, 24;
--mdui-color-surface-bright-dark: 59, 56, 62;
--mdui-color-surface-container-lowest-dark: 15, 13, 19;
--mdui-color-surface-container-low-dark: 29, 27, 32;
--mdui-color-surface-container-dark: 33, 31, 38;
--mdui-color-surface-container-high-dark: 43, 41, 48;
--mdui-color-surface-container-highest-dark: 54, 52, 59;
--mdui-color-surface-variant-dark: 73, 69, 79;
--mdui-color-on-surface-dark: 230, 225, 229;
--mdui-color-on-surface-variant-dark: 202, 196, 208;
--mdui-color-inverse-surface-dark: 230, 225, 229;
--mdui-color-inverse-on-surface-dark: 49, 48, 51;
--mdui-color-background-dark: 20, 18, 24;
--mdui-color-on-background-dark: 230, 225, 229;
--mdui-color-error-dark: 242, 184, 181;
--mdui-color-error-container-dark: 140, 29, 24;
--mdui-color-on-error-dark: 96, 20, 16;
--mdui-color-on-error-container-dark: 249, 222, 220;
--mdui-color-outline-dark: 147, 143, 153;
--mdui-color-outline-variant-dark: 68, 71, 70;
--mdui-color-shadow-dark: 0, 0, 0;
--mdui-color-surface-tint-color-dark: 208, 188, 255;
--mdui-color-scrim-dark: 0, 0, 0;
}
.mdui-theme-dark,
:root {
color-scheme: dark;
--mdui-color-primary: var(--mdui-color-primary-dark);
--mdui-color-primary-container: var(--mdui-color-primary-container-dark);
--mdui-color-on-primary: var(--mdui-color-on-primary-dark);
--mdui-color-on-primary-container: var(
--mdui-color-on-primary-container-dark
);
--mdui-color-inverse-primary: var(--mdui-color-inverse-primary-dark);
--mdui-color-secondary: var(--mdui-color-secondary-dark);
--mdui-color-secondary-container: var(--mdui-color-secondary-container-dark);
--mdui-color-on-secondary: var(--mdui-color-on-secondary-dark);
--mdui-color-on-secondary-container: var(
--mdui-color-on-secondary-container-dark
);
--mdui-color-tertiary: var(--mdui-color-tertiary-dark);
--mdui-color-tertiary-container: var(--mdui-color-tertiary-container-dark);
--mdui-color-on-tertiary: var(--mdui-color-on-tertiary-dark);
--mdui-color-on-tertiary-container: var(
--mdui-color-on-tertiary-container-dark
);
--mdui-color-surface: var(--mdui-color-surface-dark);
--mdui-color-surface-dim: var(--mdui-color-surface-dim-dark);
--mdui-color-surface-bright: var(--mdui-color-surface-bright-dark);
--mdui-color-surface-container-lowest: var(
--mdui-color-surface-container-lowest-dark
);
--mdui-color-surface-container-low: var(
--mdui-color-surface-container-low-dark
);
--mdui-color-surface-container: var(--mdui-color-surface-container-dark);
--mdui-color-surface-container-high: var(
--mdui-color-surface-container-high-dark
);
--mdui-color-surface-container-highest: var(
--mdui-color-surface-container-highest-dark
);
--mdui-color-surface-variant: var(--mdui-color-surface-variant-dark);
--mdui-color-on-surface: var(--mdui-color-on-surface-dark);
--mdui-color-on-surface-variant: var(--mdui-color-on-surface-variant-dark);
--mdui-color-inverse-surface: var(--mdui-color-inverse-surface-dark);
--mdui-color-inverse-on-surface: var(--mdui-color-inverse-on-surface-dark);
--mdui-color-background: var(--mdui-color-background-dark);
--mdui-color-on-background: var(--mdui-color-on-background-dark);
--mdui-color-error: var(--mdui-color-error-dark);
--mdui-color-error-container: var(--mdui-color-error-container-dark);
--mdui-color-on-error: var(--mdui-color-on-error-dark);
--mdui-color-on-error-container: var(--mdui-color-on-error-container-dark);
--mdui-color-outline: var(--mdui-color-outline-dark);
--mdui-color-outline-variant: var(--mdui-color-outline-variant-dark);
--mdui-color-shadow: var(--mdui-color-shadow-dark);
--mdui-color-surface-tint-color: var(--mdui-color-surface-tint-color-dark);
--mdui-color-scrim: var(--mdui-color-scrim-dark);
color: rgb(var(--mdui-color-on-background));
background-color: rgb(var(--mdui-color-background));
}
:root {
--mdui-elevation-level0: none;
--mdui-elevation-level1: 0 0.5px 1.5px 0 rgba(var(--mdui-color-shadow), 19%),
0 0 1px 0 rgba(var(--mdui-color-shadow), 3.9%);
--mdui-elevation-level2: 0 0.85px 3px 0 rgba(var(--mdui-color-shadow), 19%),
0 0.25px 1px 0 rgba(var(--mdui-color-shadow), 3.9%);
--mdui-elevation-level3: 0 1.25px 5px 0 rgba(var(--mdui-color-shadow), 19%),
0 0.3333px 1.5px 0 rgba(var(--mdui-color-shadow), 3.9%);
--mdui-elevation-level4: 0 1.85px 6.25px 0 rgba(var(--mdui-color-shadow), 19%),
0 0.5px 1.75px 0 rgba(var(--mdui-color-shadow), 3.9%);
--mdui-elevation-level5: 0 2.75px 9px 0 rgba(var(--mdui-color-shadow), 19%),
0 0.25px 3px 0 rgba(var(--mdui-color-shadow), 3.9%);
}
:root {
--mdui-motion-easing-linear: cubic-bezier(0, 0, 1, 1);
--mdui-motion-easing-standard: cubic-bezier(0.2, 0, 0, 1);
--mdui-motion-easing-standard-accelerate: cubic-bezier(0.3, 0, 1, 1);
--mdui-motion-easing-standard-decelerate: cubic-bezier(0, 0, 0, 1);
--mdui-motion-easing-emphasized: var(--mdui-motion-easing-standard);
--mdui-motion-easing-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15);
--mdui-motion-easing-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);
--mdui-motion-duration-short1: 50ms;
--mdui-motion-duration-short2: 100ms;
--mdui-motion-duration-short3: 150ms;
--mdui-motion-duration-short4: 200ms;
--mdui-motion-duration-medium1: 250ms;
--mdui-motion-duration-medium2: 300ms;
--mdui-motion-duration-medium3: 350ms;
--mdui-motion-duration-medium4: 400ms;
--mdui-motion-duration-long1: 450ms;
--mdui-motion-duration-long2: 500ms;
--mdui-motion-duration-long3: 550ms;
--mdui-motion-duration-long4: 600ms;
--mdui-motion-duration-extra-long1: 700ms;
--mdui-motion-duration-extra-long2: 800ms;
--mdui-motion-duration-extra-long3: 900ms;
--mdui-motion-duration-extra-long4: 1000ms;
}
.mdui-prose {
line-height: 1.75;
word-wrap: break-word;
}
.mdui-prose :first-child {
margin-top: 0;
}
.mdui-prose :last-child {
margin-bottom: 0;
}
.mdui-prose code,
.mdui-prose kbd,
.mdui-prose pre,
.mdui-prose pre tt,
.mdui-prose samp {
font-family: Consolas, Courier, 'Courier New', monospace;
}
.mdui-prose caption {
text-align: left;
}
.mdui-prose [draggable='true'],
.mdui-prose [draggable] {
cursor: move;
}
.mdui-prose [draggable='false'] {
cursor: inherit;
}
.mdui-prose dl,
.mdui-prose form,
.mdui-prose ol,
.mdui-prose p,
.mdui-prose ul {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
.mdui-prose a {
text-decoration: none;
outline: 0;
color: rgb(var(--mdui-color-primary));
}
.mdui-prose a:focus,
.mdui-prose a:hover {
border-bottom: 0.0625rem solid rgb(var(--mdui-color-primary));
}
.mdui-prose small {
font-size: 0.875em;
}
.mdui-prose strong {
font-weight: 600;
}
.mdui-prose blockquote {
margin: 1.6em 2em;
padding-left: 1em;
border-left: 0.25rem solid rgb(var(--mdui-color-surface-variant));
}
@media only screen and (max-width: 599.98px) {
.mdui-prose blockquote {
margin: 1.6em 0;
}
}
.mdui-prose blockquote footer {
font-size: 86%;
color: rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose mark {
color: inherit;
background-color: rgb(var(--mdui-color-secondary-container));
border-bottom: 0.0625rem solid rgb(var(--mdui-color-secondary));
margin: 0 0.375rem;
padding: 0.125rem;
}
.mdui-prose h1,
.mdui-prose h2,
.mdui-prose h3,
.mdui-prose h4,
.mdui-prose h5,
.mdui-prose h6 {
font-weight: 400;
}
.mdui-prose h1 small,
.mdui-prose h2 small,
.mdui-prose h3 small,
.mdui-prose h4 small,
.mdui-prose h5 small,
.mdui-prose h6 small {
font-weight: inherit;
font-size: 65%;
color: rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose h1 strong,
.mdui-prose h2 strong,
.mdui-prose h3 strong,
.mdui-prose h4 strong,
.mdui-prose h5 strong,
.mdui-prose h6 strong {
font-weight: 600;
}
.mdui-prose h1 {
font-size: 2.5em;
margin-top: 0;
margin-bottom: 1.25em;
line-height: 1.1111;
}
.mdui-prose h2 {
font-size: 1.875em;
margin-top: 2.25em;
margin-bottom: 1.125em;
line-height: 1.3333;
}
.mdui-prose h3 {
font-size: 1.5em;
margin-top: 2em;
margin-bottom: 1em;
line-height: 1.6;
}
.mdui-prose h4 {
font-size: 1.25em;
margin-top: 1.875em;
margin-bottom: 0.875em;
line-height: 1.5;
}
.mdui-prose h2 + *,
.mdui-prose h3 + *,
.mdui-prose h4 + *,
.mdui-prose hr + * {
margin-top: 0;
}
.mdui-prose code,
.mdui-prose kbd {
font-size: 0.875em;
color: rgb(var(--mdui-color-on-surface-container));
background-color: rgba(var(--mdui-color-surface-variant), 0.28);
padding: 0.125rem 0.375rem;
border-radius: var(--mdui-shape-corner-extra-small);
}
.mdui-prose kbd {
font-size: 0.9em;
}
.mdui-prose abbr[title] {
text-decoration: none;
cursor: help;
border-bottom: 0.0625rem dotted rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose ins,
.mdui-prose u {
text-decoration: none;
border-bottom: 0.0625rem solid rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose del {
text-decoration: line-through;
}
.mdui-prose hr {
margin-top: 3em;
margin-bottom: 3em;
border: none;
border-bottom: 0.0625rem solid rgb(var(--mdui-color-surface-variant));
}
.mdui-prose pre {
margin-top: 1.7143em;
margin-bottom: 1.7143em;
}
.mdui-prose pre code {
padding: 0.8571em 1.1429em;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
background-color: rgb(var(--mdui-color-surface-container));
color: rgb(var(--mdui-color-on-surface-container));
border-radius: var(--mdui-shape-corner-extra-small);
}
.mdui-prose ol,
.mdui-prose ul {
padding-left: 1.625em;
}
.mdui-prose ul {
list-style-type: disc;
}
.mdui-prose ol {
list-style-type: decimal;
}
.mdui-prose ol[type='A'] {
list-style-type: upper-alpha;
}
.mdui-prose ol[type='a'] {
list-style-type: lower-alpha;
}
.mdui-prose ol[type='I'] {
list-style-type: upper-roman;
}
.mdui-prose ol[type='i'] {
list-style-type: lower-roman;
}
.mdui-prose ol[type='1'] {
list-style-type: decimal;
}
.mdui-prose li {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.mdui-prose ol > li,
.mdui-prose ul > li {
padding-left: 0.375em;
}
.mdui-prose ol > li > p,
.mdui-prose ul > li > p {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
.mdui-prose ol > li > :first-child,
.mdui-prose ul > li > :first-child {
margin-top: 1.25em;
}
.mdui-prose ol > li > :last-child,
.mdui-prose ul > li > :last-child {
margin-bottom: 1.25em;
}
.mdui-prose ol > li::marker {
font-weight: 400;
color: rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose ul > li::marker {
color: rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose ol ol,
.mdui-prose ol ul,
.mdui-prose ul ol,
.mdui-prose ul ul {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
.mdui-prose fieldset,
.mdui-prose img {
border: none;
}
.mdui-prose figure,
.mdui-prose img,
.mdui-prose video {
margin-top: 2em;
margin-bottom: 2em;
max-width: 100%;
}
.mdui-prose figure > * {
margin-top: 0;
margin-bottom: 0;
}
.mdui-prose figcaption {
font-size: 0.875em;
line-height: 1.4286;
margin-top: 0.8571em;
color: rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose figcaption:empty::before {
z-index: -1;
cursor: text;
content: attr(placeholder);
color: rgb(var(--mdui-color-on-surface-variant));
}
.mdui-prose table {
margin-top: 2em;
margin-bottom: 2em;
border: 0.0625rem solid rgb(var(--mdui-color-surface-variant));
border-radius: var(--mdui-shape-corner-large);
}
.mdui-table {
width: 100%;
overflow-x: auto;
margin-top: 2em;
margin-bottom: 2em;
border: 0.0625rem solid rgb(var(--mdui-color-surface-variant));
border-radius: var(--mdui-shape-corner-large);
}
.mdui-table table {
margin-top: 0;
margin-bottom: 0;
border: none;
border-radius: 0;
}
.mdui-prose table,
.mdui-table table {
width: 100%;
text-align: left;
border-collapse: collapse;
border-spacing: 0;
}
.mdui-prose td,
.mdui-prose th,
.mdui-table td,
.mdui-table th {
border-top: 0.0625rem solid rgb(var(--mdui-color-surface-variant));
}
.mdui-prose td:not(:first-child),
.mdui-prose th:not(:first-child),
.mdui-table td:not(:first-child),
.mdui-table th:not(:first-child) {
border-left: 0.0625rem solid rgb(var(--mdui-color-surface-variant));
}
.mdui-prose td:not(:last-child),
.mdui-prose th:not(:last-child),
.mdui-table td:not(:last-child),
.mdui-table th:not(:last-child) {
border-right: 0.0625rem solid rgb(var(--mdui-color-surface-variant));
}
.mdui-prose tfoot td,
.mdui-prose tfoot th,
.mdui-prose thead td,
.mdui-prose thead th,
.mdui-table tfoot td,
.mdui-table tfoot th,
.mdui-table thead td,
.mdui-table thead th {
position: relative;
vertical-align: middle;
padding: 1.125rem 1rem;
font-weight: var(--mdui-typescale-title-medium-weight);
letter-spacing: var(--mdui-typescale-title-medium-tracking);
line-height: var(--mdui-typescale-title-medium-line-height);
color: rgb(var(--mdui-color-on-surface-variant));
box-shadow: var(--mdui-elevation-level1);
}
.mdui-prose tbody td,
.mdui-prose tbody th,
.mdui-table tbody td,
.mdui-table tbody th {
padding: 0.875rem 1rem;
}
.mdui-prose tbody th,
.mdui-table tbody th {
vertical-align: middle;
font-weight: inherit;
}
.mdui-prose tbody td,
.mdui-table tbody td {
vertical-align: baseline;
}
.mdui-prose tbody:first-child tr:first-child td,
.mdui-prose thead:first-child tr:first-child th,
.mdui-table tbody:first-child tr:first-child td,
.mdui-table thead:first-child tr:first-child th {
border-top: 0;
}
:root {
--mdui-shape-corner-none: 0;
--mdui-shape-corner-extra-small: 0.25rem;
--mdui-shape-corner-small: 0.5rem;
--mdui-shape-corner-medium: 0.75rem;
--mdui-shape-corner-large: 1rem;
--mdui-shape-corner-extra-large: 1.75rem;
--mdui-shape-corner-full: 1000rem;
}
:root {
--mdui-state-layer-hover: 0.08;
--mdui-state-layer-focus: 0.12;
--mdui-state-layer-pressed: 0.12;
--mdui-state-layer-dragged: 0.16;
}
:root {
--mdui-typescale-display-large-weight: 400;
--mdui-typescale-display-medium-weight: 400;
--mdui-typescale-display-small-weight: 400;
--mdui-typescale-display-large-line-height: 4rem;
--mdui-typescale-display-medium-line-height: 3.25rem;
--mdui-typescale-display-small-line-height: 2.75rem;
--mdui-typescale-display-large-size: 3.5625rem;
--mdui-typescale-display-medium-size: 2.8125rem;
--mdui-typescale-display-small-size: 2.25rem;
--mdui-typescale-display-large-tracking: 0rem;
--mdui-typescale-display-medium-tracking: 0rem;
--mdui-typescale-display-small-tracking: 0rem;
--mdui-typescale-headline-large-weight: 400;
--mdui-typescale-headline-medium-weight: 400;
--mdui-typescale-headline-small-weight: 400;
--mdui-typescale-headline-large-line-height: 2.5rem;
--mdui-typescale-headline-medium-line-height: 2.25rem;
--mdui-typescale-headline-small-line-height: 2rem;
--mdui-typescale-headline-large-size: 2rem;
--mdui-typescale-headline-medium-size: 1.75rem;
--mdui-typescale-headline-small-size: 1.5rem;
--mdui-typescale-headline-large-tracking: 0rem;
--mdui-typescale-headline-medium-tracking: 0rem;
--mdui-typescale-headline-small-tracking: 0rem;
--mdui-typescale-title-large-weight: 400;
--mdui-typescale-title-medium-weight: 500;
--mdui-typescale-title-small-weight: 500;
--mdui-typescale-title-large-line-height: 1.75rem;
--mdui-typescale-title-medium-line-height: 1.5rem;
--mdui-typescale-title-small-line-height: 1.25rem;
--mdui-typescale-title-large-size: 1.375rem;
--mdui-typescale-title-medium-size: 1rem;
--mdui-typescale-title-small-size: 0.875rem;
--mdui-typescale-title-large-tracking: 0rem;
--mdui-typescale-title-medium-tracking: 0.009375rem;
--mdui-typescale-title-small-tracking: 0.00625rem;
--mdui-typescale-label-large-weight: 500;
--mdui-typescale-label-medium-weight: 500;
--mdui-typescale-label-small-weight: 500;
--mdui-typescale-label-large-line-height: 1.25rem;
--mdui-typescale-label-medium-line-height: 1rem;
--mdui-typescale-label-small-line-height: 0.375rem;
--mdui-typescale-label-large-size: 0.875rem;
--mdui-typescale-label-medium-size: 0.75rem;
--mdui-typescale-label-small-size: 0.6875rem;
--mdui-typescale-label-large-tracking: 0.00625rem;
--mdui-typescale-label-medium-tracking: 0.03125rem;
--mdui-typescale-label-small-tracking: 0.03125rem;
--mdui-typescale-body-large-weight: 400;
--mdui-typescale-body-medium-weight: 400;
--mdui-typescale-body-small-weight: 400;
--mdui-typescale-body-large-line-height: 1.5rem;
--mdui-typescale-body-medium-line-height: 1.25rem;
--mdui-typescale-body-small-line-height: 1rem;
--mdui-typescale-body-large-size: 1rem;
--mdui-typescale-body-medium-size: 0.875rem;
--mdui-typescale-body-small-size: 0.75rem;
--mdui-typescale-body-large-tracking: 0.009375rem;
--mdui-typescale-body-medium-tracking: 0.015625rem;
--mdui-typescale-body-small-tracking: 0.025rem;
}
.mdui-lock-screen {
overflow: hidden !important;
}
================================================
FILE: changelog.md
================================================
### Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v3.11.0](https://github.com/pear-devs/pear-desktop/compare/v3.10.0...v3.11.0)
- Fixed missing videochange dataupdated event when using shuffle [`#3659`](https://github.com/pear-devs/pear-desktop/pull/3659)
- feat(synced-lyrics): preferred provider (global/per-song) [`#3741`](https://github.com/pear-devs/pear-desktop/pull/3741)
- feat(api-server): send shuffle state over websocket [`#3837`](https://github.com/pear-devs/pear-desktop/pull/3837)
- feat(synced-lyrics): add new "spacer" [`#3742`](https://github.com/pear-devs/pear-desktop/pull/3742)
- feat(downloader): Add context menu button for playlists and albums [`#3768`](https://github.com/pear-devs/pear-desktop/pull/3768)
- feat(transparent-player): new plugin for Acrylic, Mica or Tabbed effects [`#3529`](https://github.com/pear-devs/pear-desktop/pull/3529)
- fix(audio-compressor): real-time behavior and duplicated audio bug [`#3786`](https://github.com/pear-devs/pear-desktop/pull/3786)
- fix: Added Min height and width to the window which doesnt breaks the UI responsiveness [`#3602`](https://github.com/pear-devs/pear-desktop/pull/3602)
- chore(deps): update dependency discord-api-types to v0.38.23 [`#3833`](https://github.com/pear-devs/pear-desktop/pull/3833)
- feat(plugin): Custom output device plugin [`#3789`](https://github.com/pear-devs/pear-desktop/pull/3789)
- chore(deps): update dependency @babel/runtime to v7.28.4 [`#3831`](https://github.com/pear-devs/pear-desktop/pull/3831)
- chore(deps): update eslint monorepo to v9.35.0 [`#3829`](https://github.com/pear-devs/pear-desktop/pull/3829)
- feat(api-server): Add websocket as `/api/v1/ws` route [`#3707`](https://github.com/pear-devs/pear-desktop/pull/3707)
- feat(api-server): Improved api-server volume and like/dislike state [`#3592`](https://github.com/pear-devs/pear-desktop/pull/3592)
- fix(deps): update dependency i18next to v25.5.2 [`#3826`](https://github.com/pear-devs/pear-desktop/pull/3826)
- fix(deps): update dependency virtua to v0.42.2 [`#3827`](https://github.com/pear-devs/pear-desktop/pull/3827)
- fix(exponential-volume): volume desync bug [`#3787`](https://github.com/pear-devs/pear-desktop/pull/3787)
- feat(synced-lyrics): thai romanization [`#3618`](https://github.com/pear-devs/pear-desktop/pull/3618)
- feat(discord): add option to display artist/title in status [`#3692`](https://github.com/pear-devs/pear-desktop/pull/3692)
- chore(deps): update playwright monorepo to v1.55.0 [`#3819`](https://github.com/pear-devs/pear-desktop/pull/3819)
- fix(deps): update dependency i18next to v25.5.1 [`#3820`](https://github.com/pear-devs/pear-desktop/pull/3820)
- fix(deps): update dependency virtua to v0.42.0 [`#3821`](https://github.com/pear-devs/pear-desktop/pull/3821)
- fix(deps): update dependency zod to v4.1.5 [`#3822`](https://github.com/pear-devs/pear-desktop/pull/3822)
- chore(deps): update eslint monorepo to v9.34.0 [`#3818`](https://github.com/pear-devs/pear-desktop/pull/3818)
- chore(deps): update actions/setup-node action to v5 [`#3823`](https://github.com/pear-devs/pear-desktop/pull/3823)
- chore(deps): update dependency electron to v38 [`#3824`](https://github.com/pear-devs/pear-desktop/pull/3824)
- chore(deps): update dependency @stylistic/eslint-plugin to v5.3.1 [`#3817`](https://github.com/pear-devs/pear-desktop/pull/3817)
- fix(deps): update dependency serve to v14.2.5 [`#3816`](https://github.com/pear-devs/pear-desktop/pull/3816)
- feat(discord): add song & artist URLs to rich presence [`#3737`](https://github.com/pear-devs/pear-desktop/pull/3737)
- fix: fix #3621 [`#3774`](https://github.com/pear-devs/pear-desktop/pull/3774)
- feat(refactor): PluginDefinition::platform [`#3665`](https://github.com/pear-devs/pear-desktop/pull/3665)
- chore(docs): update copyright footer year [`#3792`](https://github.com/pear-devs/pear-desktop/pull/3792)
- chore(deps): update dependency vite-plugin-inspect to v11.3.3 [`#3814`](https://github.com/pear-devs/pear-desktop/pull/3814)
- fix(deps): update dependency @floating-ui/dom to v1.7.4 [`#3815`](https://github.com/pear-devs/pear-desktop/pull/3815)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.11.6 [`#3770`](https://github.com/pear-devs/pear-desktop/pull/3770)
- chore(deps): update dependency discord-api-types to v0.38.22 [`#3813`](https://github.com/pear-devs/pear-desktop/pull/3813)
- chore(deps): update dependency @types/semver to v7.7.1 [`#3812`](https://github.com/pear-devs/pear-desktop/pull/3812)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.11.6 [`#3771`](https://github.com/pear-devs/pear-desktop/pull/3771)
- fix(deps): update dependency @hono/node-server to v1.19.1 [`#3759`](https://github.com/pear-devs/pear-desktop/pull/3759)
- chore(deps): update dependency typescript-eslint to v8.42.0 [`#3761`](https://github.com/pear-devs/pear-desktop/pull/3761)
- chore(deps): update dependency node-gyp to v11.4.2 [`#3772`](https://github.com/pear-devs/pear-desktop/pull/3772)
- chore(deps): update actions/checkout action to v5 [`#3757`](https://github.com/pear-devs/pear-desktop/pull/3757)
- chore(deps): update dependency vite to v7.1.5 [`#3760`](https://github.com/pear-devs/pear-desktop/pull/3760)
- fix(deps): update dependency hono to v4.9.6 [security] [`#3807`](https://github.com/pear-devs/pear-desktop/pull/3807)
- chore(deps): update dependency electron to v37.3.1 [security] [`#3806`](https://github.com/pear-devs/pear-desktop/pull/3806)
- chore(deps): bump hono from 4.9.2 to 4.9.6 [`#3805`](https://github.com/pear-devs/pear-desktop/pull/3805)
- chore(deps): update playwright monorepo to v1.54.2 [`#3713`](https://github.com/pear-devs/pear-desktop/pull/3713)
- chore(deps): update dependency vite to v7.1.2 [`#3710`](https://github.com/pear-devs/pear-desktop/pull/3710)
- chore(deps): update dependency @stylistic/eslint-plugin to v5.2.3 [`#3754`](https://github.com/pear-devs/pear-desktop/pull/3754)
- chore(deps): update dependency @babel/runtime to v7.28.3 [`#3753`](https://github.com/pear-devs/pear-desktop/pull/3753)
- chore(deps): update dependency discord-api-types to v0.38.20 [`#3706`](https://github.com/pear-devs/pear-desktop/pull/3706)
- fix(deps): update dependency @floating-ui/dom to v1.7.3 [`#3714`](https://github.com/pear-devs/pear-desktop/pull/3714)
- chore(deps): update dependency vite-plugin-solid to v2.11.8 [`#3711`](https://github.com/pear-devs/pear-desktop/pull/3711)
- chore(deps): update dependency rollup to v4.46.2 [`#3709`](https://github.com/pear-devs/pear-desktop/pull/3709)
- chore(deps): update dependency @electron/universal to v3.0.1 [`#3705`](https://github.com/pear-devs/pear-desktop/pull/3705)
- chore(deps): update dependency electron to v37.3.0 [`#3708`](https://github.com/pear-devs/pear-desktop/pull/3708)
- chore(docs): Grammar mistakes [`#3722`](https://github.com/pear-devs/pear-desktop/pull/3722)
- Fixes the error 500 for /auth/ endpoint [`#3627`](https://github.com/pear-devs/pear-desktop/pull/3627)
- feat: add custom window title option [`#3656`](https://github.com/pear-devs/pear-desktop/pull/3656)
- fix(deps): update dependency zod to v4.0.10 [`#3686`](https://github.com/pear-devs/pear-desktop/pull/3686)
- chore(deps): update dependency @babel/runtime to v7.28.2 [`#3687`](https://github.com/pear-devs/pear-desktop/pull/3687)
- chore(deps): update dependency rollup to v4.46.1 [`#3632`](https://github.com/pear-devs/pear-desktop/pull/3632)
- chore(deps): update dependency electron to v38.0.0-alpha.10 [`#3681`](https://github.com/pear-devs/pear-desktop/pull/3681)
- chore(deps): update dependency eslint-config-prettier to v10.1.8 [`#3676`](https://github.com/pear-devs/pear-desktop/pull/3676)
- chore(deps): update dependency eslint-plugin-prettier to v5.5.3 [`#3678`](https://github.com/pear-devs/pear-desktop/pull/3678)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.11.3 [`#3679`](https://github.com/pear-devs/pear-desktop/pull/3679)
- chore(deps): update dependency discord-api-types to v0.38.17 [`#3620`](https://github.com/pear-devs/pear-desktop/pull/3620)
- chore(deps): update dependency esbuild to v0.25.8 [`#3675`](https://github.com/pear-devs/pear-desktop/pull/3675)
- chore(deps): update dependency @stylistic/eslint-plugin to v5.2.2 [`#3636`](https://github.com/pear-devs/pear-desktop/pull/3636)
- fix(deps): update dependency @hono/node-server to v1.17.1 [`#3625`](https://github.com/pear-devs/pear-desktop/pull/3625)
- fix(deps): update dependency hono to v4.8.7 [`#3567`](https://github.com/pear-devs/pear-desktop/pull/3567)
- chore(deps): update dependency typescript-eslint to v8.38.0 [`#3628`](https://github.com/pear-devs/pear-desktop/pull/3628)
- chore(deps): update dependency electron to v38.0.0-alpha.9 [`#3626`](https://github.com/pear-devs/pear-desktop/pull/3626)
- fix(Skip Disliked Song): updated querySelector [`#3667`](https://github.com/pear-devs/pear-desktop/pull/3667)
- chore(deps): update dependency vite to v7.0.11 [`#3624`](https://github.com/pear-devs/pear-desktop/pull/3624)
- fix(deps): update dependency @hono/zod-validator to v0.7.1 [`#3616`](https://github.com/pear-devs/pear-desktop/pull/3616)
- fix(discord-rpc, scrobbler): Align artist and title with the last.fm's de facto standard [`#3358`](https://github.com/pear-devs/pear-desktop/issues/3358) [`#3641`](https://github.com/pear-devs/pear-desktop/issues/3641)
- fix: fix #3621 (#3774) [`#3621`](https://github.com/pear-devs/pear-desktop/issues/3621)
- fix: fix #3661 [`#3661`](https://github.com/pear-devs/pear-desktop/issues/3661)
- fix: fix #3613, fix #3651 [`#3613`](https://github.com/pear-devs/pear-desktop/issues/3613) [`#3651`](https://github.com/pear-devs/pear-desktop/issues/3651)
- chore: remove unused deps [`2a81a4e`](https://github.com/pear-devs/pear-desktop/commit/2a81a4e887cb5cc3c91a672302db6da3c15544e8)
- chore(i18n): Translated using Weblate (Swedish) [`0a6f244`](https://github.com/pear-devs/pear-desktop/commit/0a6f244035f17724ab1934f3cb2e011adf838e09)
- fix: bump dependencies [`5ba65ea`](https://github.com/pear-devs/pear-desktop/commit/5ba65ea1221236478efa8cb61bca3ffb1bb2d061)
#### [v3.10.0](https://github.com/pear-devs/pear-desktop/compare/v3.9.0...v3.10.0)
> 13 July 2025
- fix(deps): update dependency butterchurn to v3.0.0-beta.5 [`#3610`](https://github.com/pear-devs/pear-desktop/pull/3610)
- chore(deps): update eslint monorepo to v9.31.0 [`#3600`](https://github.com/pear-devs/pear-desktop/pull/3600)
- chore(deps): update dependency rollup to v4.45.0 [`#3568`](https://github.com/pear-devs/pear-desktop/pull/3568)
- feat: code splitting [`#3593`](https://github.com/pear-devs/pear-desktop/pull/3593)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.11.1 [`#3571`](https://github.com/pear-devs/pear-desktop/pull/3571)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.11.1 [`#3570`](https://github.com/pear-devs/pear-desktop/pull/3570)
- chore(deps): update playwright monorepo to v1.54.1 [`#3599`](https://github.com/pear-devs/pear-desktop/pull/3599)
- chore(deps): update playwright monorepo to v1.54.0 [`#3591`](https://github.com/pear-devs/pear-desktop/pull/3591)
- chore(deps): update dependency electron to v37.2.1 [`#3559`](https://github.com/pear-devs/pear-desktop/pull/3559)
- fix(deps): update dependency socks to v2.8.6 [`#3598`](https://github.com/pear-devs/pear-desktop/pull/3598)
- fix(deps): update dependency zod to v4.0.5 [`#3594`](https://github.com/pear-devs/pear-desktop/pull/3594)
- chore(deps): update dependency vite to v7.0.8 [`#3597`](https://github.com/pear-devs/pear-desktop/pull/3597)
- chore(deps): update dependency vite to v7.0.7 [`#3590`](https://github.com/pear-devs/pear-desktop/pull/3590)
- chore(deps): update dependency @electron/universal to v3 [`#3565`](https://github.com/pear-devs/pear-desktop/pull/3565)
- fix(deps): update dependency electron-unhandled to v5 [`#2088`](https://github.com/pear-devs/pear-desktop/pull/2088)
- feat: enable the ESM for main [`#3588`](https://github.com/pear-devs/pear-desktop/pull/3588)
- fix(deps): update dependency zod to v4 [`#3587`](https://github.com/pear-devs/pear-desktop/pull/3587)
- feat: migrate from raw HTML to JSX (TSX / SolidJS) [`#3583`](https://github.com/pear-devs/pear-desktop/pull/3583)
- docs: add Unobtrusive Player in available plugins [`#3582`](https://github.com/pear-devs/pear-desktop/pull/3582)
- fix(deps): update dependency @hono/node-server to v1.15.0 [`#3557`](https://github.com/pear-devs/pear-desktop/pull/3557)
- chore(deps): update dependency vitefu to v1.1.1 [`#3564`](https://github.com/pear-devs/pear-desktop/pull/3564)
- chore(deps): update dependency discord-api-types to v0.38.15 [`#3562`](https://github.com/pear-devs/pear-desktop/pull/3562)
- fix(deps): update dependency es-hangul to v2.3.5 [`#3563`](https://github.com/pear-devs/pear-desktop/pull/3563)
- fix(deps): update dependency zod to v3.25.71 [`#3558`](https://github.com/pear-devs/pear-desktop/pull/3558)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.9.2 [`#3560`](https://github.com/pear-devs/pear-desktop/pull/3560)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.9.2 [`#3561`](https://github.com/pear-devs/pear-desktop/pull/3561)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.9.0 [`#3555`](https://github.com/pear-devs/pear-desktop/pull/3555)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.9.0 [`#3556`](https://github.com/pear-devs/pear-desktop/pull/3556)
- chore(deps): update eslint monorepo to v9.30.1 [`#3552`](https://github.com/pear-devs/pear-desktop/pull/3552)
- Fixed play/pause discord rich presence by fixing lastsonginfo tracking [`#3551`](https://github.com/pear-devs/pear-desktop/pull/3551)
- feat: enable rolldown native plugin [`#3502`](https://github.com/pear-devs/pear-desktop/pull/3502)
- fix(deps): update dependency virtua to v0.41.5 [`#3549`](https://github.com/pear-devs/pear-desktop/pull/3549)
- chore(deps): update dependency typescript-eslint to v8.35.1 [`#3545`](https://github.com/pear-devs/pear-desktop/pull/3545)
- chore(deps): update dependency discord-api-types to v0.38.14 [`#3546`](https://github.com/pear-devs/pear-desktop/pull/3546)
- chore(deps): update playwright monorepo to v1.53.2 [`#3547`](https://github.com/pear-devs/pear-desktop/pull/3547)
- fix(deps): update dependency i18next to v25.3.0 [`#3548`](https://github.com/pear-devs/pear-desktop/pull/3548)
- perf(synced-lyrics): virtual scrolling [`#3162`](https://github.com/pear-devs/pear-desktop/pull/3162)
- chore(deps): update dependency vite to v7 [`#3524`](https://github.com/pear-devs/pear-desktop/pull/3524)
- feat(synced-lyrics): Musixmatch [`#3261`](https://github.com/pear-devs/pear-desktop/pull/3261)
- feat(api-server): add optional params for search [`#3440`](https://github.com/pear-devs/pear-desktop/pull/3440)
- chore(deps): update dependency vite-plugin-inspect to v11.3.0 [`#3543`](https://github.com/pear-devs/pear-desktop/pull/3543)
- chore(deps): update eslint monorepo to v9.30.0 [`#3544`](https://github.com/pear-devs/pear-desktop/pull/3544)
- chore(deps): update dependency rollup to v4.44.1 [`#3537`](https://github.com/pear-devs/pear-desktop/pull/3537)
- chore(deps): update dependency ws to v8.18.3 [`#3538`](https://github.com/pear-devs/pear-desktop/pull/3538)
- fix(deps): update dependency @hono/zod-openapi to v0.19.9 [`#3540`](https://github.com/pear-devs/pear-desktop/pull/3540)
- fix(deps): update dependency @floating-ui/dom to v1.7.2 [`#3539`](https://github.com/pear-devs/pear-desktop/pull/3539)
- fix(deps): update dependency es-hangul to v2.3.4 [`#3541`](https://github.com/pear-devs/pear-desktop/pull/3541)
- fix(deps): update dependency hono to v4.8.3 [`#3542`](https://github.com/pear-devs/pear-desktop/pull/3542)
- fix(style): fix duplicated scrollbar [`#3483`](https://github.com/pear-devs/pear-desktop/pull/3483)
- chore(deps): update dependency typescript-eslint to v8.35.0 [`#3518`](https://github.com/pear-devs/pear-desktop/pull/3518)
- chore(deps): update dependency vite-plugin-solid to v2.11.7 [`#3520`](https://github.com/pear-devs/pear-desktop/pull/3520)
- chore(deps): update dependency discord-api-types to v0.38.13 [`#3517`](https://github.com/pear-devs/pear-desktop/pull/3517)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.4.4 [`#3534`](https://github.com/pear-devs/pear-desktop/pull/3534)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.8.0 [`#3521`](https://github.com/pear-devs/pear-desktop/pull/3521)
- chore(deps): update dependency eslint-plugin-prettier to v5.5.1 [`#3535`](https://github.com/pear-devs/pear-desktop/pull/3535)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.8.0 [`#3522`](https://github.com/pear-devs/pear-desktop/pull/3522)
- chore(deps): update dependency electron to v37 [`#3527`](https://github.com/pear-devs/pear-desktop/pull/3527)
- feat: Add instructional note to Swagger documentation [`#3532`](https://github.com/pear-devs/pear-desktop/pull/3532)
- chore(deps): update playwright monorepo to v1.53.1 [`#3504`](https://github.com/pear-devs/pear-desktop/pull/3504)
- chore(deps): update dependency rollup to v4.44.0 [`#3508`](https://github.com/pear-devs/pear-desktop/pull/3508)
- chore(deps): update dependency eslint-plugin-import to v2.32.0 [`#3513`](https://github.com/pear-devs/pear-desktop/pull/3513)
- fix(deps): update dependency hono to v4.8.2 [`#3509`](https://github.com/pear-devs/pear-desktop/pull/3509)
- chore(deps): update dependency electron to v36.5.0 [`#3503`](https://github.com/pear-devs/pear-desktop/pull/3503)
- chore(deps): update stefanzweifel/git-auto-commit-action action to v6 [`#3500`](https://github.com/pear-devs/pear-desktop/pull/3500)
- chore(deps): update dependency vite to v6.3.21 [`#3492`](https://github.com/pear-devs/pear-desktop/pull/3492)
- fix(deps): update dependency hono to v4.8.0 [`#3499`](https://github.com/pear-devs/pear-desktop/pull/3499)
- chore(deps): update playwright monorepo to v1.53.0 [`#3497`](https://github.com/pear-devs/pear-desktop/pull/3497)
- chore(deps): update eslint monorepo to v9.29.0 [`#3496`](https://github.com/pear-devs/pear-desktop/pull/3496)
- chore(deps): update dependency vite-plugin-inspect to v11.2.0 [`#3495`](https://github.com/pear-devs/pear-desktop/pull/3495)
- fix(deps): update dependency happy-dom to v18 [`#3501`](https://github.com/pear-devs/pear-desktop/pull/3501)
- fix(deps): update dependency electron-store to v10.1.0 [`#3498`](https://github.com/pear-devs/pear-desktop/pull/3498)
- chore(deps): update dependency eslint-plugin-prettier to v5.5.0 [`#3493`](https://github.com/pear-devs/pear-desktop/pull/3493)
- chore(deps): update dependency rollup to v4.43.0 [`#3494`](https://github.com/pear-devs/pear-desktop/pull/3494)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.7.0 [`#3466`](https://github.com/pear-devs/pear-desktop/pull/3466)
- fix(deps): update dependency @hono/swagger-ui to v0.5.2 [`#3465`](https://github.com/pear-devs/pear-desktop/pull/3465)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.7.0 [`#3467`](https://github.com/pear-devs/pear-desktop/pull/3467)
- fix(deps): update dependency youtubei.js to v14 [`#3468`](https://github.com/pear-devs/pear-desktop/pull/3468)
- chore(deps): update dependency discord-api-types to v0.38.12 [`#3490`](https://github.com/pear-devs/pear-desktop/pull/3490)
- chore(deps): update dependency glob to v11.0.3 [`#3491`](https://github.com/pear-devs/pear-desktop/pull/3491)
- chore(deps): update dependency typescript-eslint to v8.34.1 [`#3469`](https://github.com/pear-devs/pear-desktop/pull/3469)
- fix(deps): update dependency socks to v2.8.5 [`#3470`](https://github.com/pear-devs/pear-desktop/pull/3470)
- fix(deps): update dependency zod to v3.25.67 [`#3471`](https://github.com/pear-devs/pear-desktop/pull/3471)
- chore(deps): update dependency @babel/runtime to v7.27.6 [`#3451`](https://github.com/pear-devs/pear-desktop/pull/3451)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.4.3 [`#3453`](https://github.com/pear-devs/pear-desktop/pull/3453)
- fix(deps): update dependency zod to v3.25.56 [`#3454`](https://github.com/pear-devs/pear-desktop/pull/3454)
- fix(deps): update dependency @hono/node-server to v1.14.4 [`#3456`](https://github.com/pear-devs/pear-desktop/pull/3456)
- chore(deps): update dependency rollup to v4.42.0 [`#3457`](https://github.com/pear-devs/pear-desktop/pull/3457)
- fix(deps): update dependency conf to v14 [`#3458`](https://github.com/pear-devs/pear-desktop/pull/3458)
- fix(deps): update dependency peerjs to v1.5.5 [`#3460`](https://github.com/pear-devs/pear-desktop/pull/3460)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v4.4.1 [`#3444`](https://github.com/pear-devs/pear-desktop/pull/3444)
- chore(deps): update dependency discord-api-types to v0.38.11 [`#3445`](https://github.com/pear-devs/pear-desktop/pull/3445)
- chore(deps): update dependency electron to v36.4.0 [`#3447`](https://github.com/pear-devs/pear-desktop/pull/3447)
- fix(deps): update dependency zod to v3.25.51 [`#3446`](https://github.com/pear-devs/pear-desktop/pull/3446)
- fix(deps): update dependency hono to v4.7.11 [`#3435`](https://github.com/pear-devs/pear-desktop/pull/3435)
- fix(deps): update dependency @floating-ui/dom to v1.7.1 [`#3434`](https://github.com/pear-devs/pear-desktop/pull/3434)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.4.2 [`#3432`](https://github.com/pear-devs/pear-desktop/pull/3432)
- chore(deps): update dependency eslint-plugin-prettier to v5.4.1 [`#3433`](https://github.com/pear-devs/pear-desktop/pull/3433)
- fix(deps): update dependency zod to v3.25.50 [`#3443`](https://github.com/pear-devs/pear-desktop/pull/3443)
- fix(deps): update dependency happy-dom to v17.6.3 [`#3438`](https://github.com/pear-devs/pear-desktop/pull/3438)
- fix(deps): update dependency zod to v3.25.49 [`#3436`](https://github.com/pear-devs/pear-desktop/pull/3436)
- chore(deps): update eslint monorepo to v9.28.0 [`#3437`](https://github.com/pear-devs/pear-desktop/pull/3437)
- fix(deps): update dependency @hono/zod-openapi to v0.19.8 [`#3411`](https://github.com/pear-devs/pear-desktop/pull/3411)
- fix: use gtk 3 switch as workaround [`#3366`](https://github.com/pear-devs/pear-desktop/pull/3366)
- chore(deps): update dependency electron to v36.3.2 [`#3431`](https://github.com/pear-devs/pear-desktop/pull/3431)
- fix(deps): update dependency @xhayper/discord-rpc to v1.2.2 [`#3413`](https://github.com/pear-devs/pear-desktop/pull/3413)
- fix(deps): update dependency happy-dom to v17.5.6 [`#3417`](https://github.com/pear-devs/pear-desktop/pull/3417)
- fix(deps): update dependency @hono/zod-validator to v0.7.0 [`#3414`](https://github.com/pear-devs/pear-desktop/pull/3414)
- chore(deps): update dependency discord-api-types to v0.38.10 [`#3430`](https://github.com/pear-devs/pear-desktop/pull/3430)
- chore(deps): update dependency typescript-eslint to v8.33.1 [`#3416`](https://github.com/pear-devs/pear-desktop/pull/3416)
- chore(docs): Improve README-es.md by adjusting the bad Spanish translation [`#3424`](https://github.com/pear-devs/pear-desktop/pull/3424)
- chore(docs): Improve README.md by removing the extra flag for Spanish translation [`#3422`](https://github.com/pear-devs/pear-desktop/pull/3422)
- chore(deps): update dependency @babel/runtime to v7.27.4 [`#3410`](https://github.com/pear-devs/pear-desktop/pull/3410)
- fix(deps): update dependency ts-morph to v26 [`#3409`](https://github.com/pear-devs/pear-desktop/pull/3409)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.6.1 [`#3407`](https://github.com/pear-devs/pear-desktop/pull/3407)
- fix(deps): update dependency zod to v3.25.30 [`#3408`](https://github.com/pear-devs/pear-desktop/pull/3408)
- fix(precise-volume): replace constructor check for volume slider [`#3362`](https://github.com/pear-devs/pear-desktop/pull/3362)
- chore(deps): update dependency vite-plugin-inspect to v11.1.0 [`#3393`](https://github.com/pear-devs/pear-desktop/pull/3393)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.4.1 [`#3406`](https://github.com/pear-devs/pear-desktop/pull/3406)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v4.4.0 [`#3391`](https://github.com/pear-devs/pear-desktop/pull/3391)
- fix(deps): update dependency i18next to v25.2.1 [`#3405`](https://github.com/pear-devs/pear-desktop/pull/3405)
- chore(deps): update dependency esbuild to v0.25.5 [`#3403`](https://github.com/pear-devs/pear-desktop/pull/3403)
- fix(deps): update dependency @hono/node-server to v1.14.3 [`#3404`](https://github.com/pear-devs/pear-desktop/pull/3404)
- chore(deps): update dependency rollup to v4.41.1 [`#3392`](https://github.com/pear-devs/pear-desktop/pull/3392)
- chore(deps): update eslint monorepo to v9.27.0 [`#3394`](https://github.com/pear-devs/pear-desktop/pull/3394)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.6.1 [`#3395`](https://github.com/pear-devs/pear-desktop/pull/3395)
- fix(deps): update dependency hono to v4.7.10 [`#3390`](https://github.com/pear-devs/pear-desktop/pull/3390)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.3.5 [`#3388`](https://github.com/pear-devs/pear-desktop/pull/3388)
- fix(deps): update dependency @hono/node-server to v1.14.2 [`#3389`](https://github.com/pear-devs/pear-desktop/pull/3389)
- chore(deps): update dependency electron to v36.3.1 [`#3372`](https://github.com/pear-devs/pear-desktop/pull/3372)
- chore(deps): update dependency typescript-eslint to v8.32.1 [`#3364`](https://github.com/pear-devs/pear-desktop/pull/3364)
- fix(deps): update dependency semver to v7.7.2 [`#3365`](https://github.com/pear-devs/pear-desktop/pull/3365)
- fix(album-actions): update playlist and button selectors [`#3367`](https://github.com/pear-devs/pear-desktop/pull/3367)
- fix(deps): update dependency i18next to v25.2.0 [`#3370`](https://github.com/pear-devs/pear-desktop/pull/3370)
- chore(deps): update dependency discord-api-types to v0.38.8 [`#3361`](https://github.com/pear-devs/pear-desktop/pull/3361)
- fix(deps): update dependency solid-js to v1.9.7 [`#3375`](https://github.com/pear-devs/pear-desktop/pull/3375)
- chore(deps): update dependency electron to v36 [`#3307`](https://github.com/pear-devs/pear-desktop/pull/3307)
- fix(deps): update dependency @floating-ui/dom to v1.7.0 [`#3357`](https://github.com/pear-devs/pear-desktop/pull/3357)
- chore(deps): update eslint monorepo to v9.26.0 [`#3356`](https://github.com/pear-devs/pear-desktop/pull/3356)
- chore(deps): update dependency eslint-plugin-prettier to v5.4.0 [`#3355`](https://github.com/pear-devs/pear-desktop/pull/3355)
- fix(deps): update dependency zod to v3.24.4 [`#3354`](https://github.com/pear-devs/pear-desktop/pull/3354)
- fix(deps): update dependency solid-js to v1.9.6 [`#3353`](https://github.com/pear-devs/pear-desktop/pull/3353)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.5.2 [`#3350`](https://github.com/pear-devs/pear-desktop/pull/3350)
- fix(deps): update dependency happy-dom to v17.4.7 [`#3352`](https://github.com/pear-devs/pear-desktop/pull/3352)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.5.2 [`#3349`](https://github.com/pear-devs/pear-desktop/pull/3349)
- chore(deps): update dependency vite to v6.3.5 [`#3346`](https://github.com/pear-devs/pear-desktop/pull/3346)
- chore(deps): update dependency ws to v8.18.2 [`#3347`](https://github.com/pear-devs/pear-desktop/pull/3347)
- feat(plugin): support authenticated proxy [`#3175`](https://github.com/pear-devs/pear-desktop/pull/3175)
- chore(deps): update dependency esbuild to v0.25.4 [`#3344`](https://github.com/pear-devs/pear-desktop/pull/3344)
- chore(deps): update dependency eslint-config-prettier to v10.1.5 [`#3345`](https://github.com/pear-devs/pear-desktop/pull/3345)
- fix(deps): update dependency hono to v4.7.9 [`#3302`](https://github.com/pear-devs/pear-desktop/pull/3302)
- chore(deps): update dependency electron to v34.5.5 [`#3343`](https://github.com/pear-devs/pear-desktop/pull/3343)
- docs: Add Ukrainian translation [`#3338`](https://github.com/pear-devs/pear-desktop/pull/3338)
- chore(deps): update dependency discord-api-types to v0.38.4 [`#3342`](https://github.com/pear-devs/pear-desktop/pull/3342)
- chore(deps): update dependency rollup to v4.40.2 [`#3301`](https://github.com/pear-devs/pear-desktop/pull/3301)
- chore(deps): update dependency @electron/universal to v2.0.3 [`#3341`](https://github.com/pear-devs/pear-desktop/pull/3341)
- fix(deps): update dependency @hono/zod-validator to v0.5.0 [`#3295`](https://github.com/pear-devs/pear-desktop/pull/3295)
- chore(deps): update dependency @babel/runtime to v7.27.1 [`#3340`](https://github.com/pear-devs/pear-desktop/pull/3340)
- fix(deps): update dependency i18next to v25.1.2 [`#3305`](https://github.com/pear-devs/pear-desktop/pull/3305)
- fix(album-actions): use playlist shelf for playlist detection [`#3306`](https://github.com/pear-devs/pear-desktop/pull/3306)
- chore(deps): update dependency typescript-eslint to v8.32.0 [`#3304`](https://github.com/pear-devs/pear-desktop/pull/3304)
- chore(deps): update dependency vite to v6.3.4 [security] [`#3313`](https://github.com/pear-devs/pear-desktop/pull/3313)
- fix(deps): update dependency @hono/zod-openapi to v0.19.6 [`#3294`](https://github.com/pear-devs/pear-desktop/pull/3294)
- fix(deps): update dependency es-hangul to v2.3.3 [`#3293`](https://github.com/pear-devs/pear-desktop/pull/3293)
- fix(api-server): fix #3572 [`#3572`](https://github.com/pear-devs/pear-desktop/issues/3572)
- fix(music-player): fix #3296, and macOS traffic lights [`#3296`](https://github.com/pear-devs/pear-desktop/issues/3296)
- fix: kuromoji zlib and apply rolldown-vite [`f047dd2`](https://github.com/pear-devs/pear-desktop/commit/f047dd2d2df189b55b60188392c451aad65b7a1b)
- fix: apply fix from eslint [`1da83ff`](https://github.com/pear-devs/pear-desktop/commit/1da83ff27c2ccfcdf48c0b0b125033b1a1c194d2)
- feat: refactor [`51b3f53`](https://github.com/pear-devs/pear-desktop/commit/51b3f535695c0f19cd252049985578d11a853ccb)
#### [v3.9.0](https://github.com/pear-devs/pear-desktop/compare/v3.8.1...v3.9.0)
> 27 April 2025
- feat(tuna-obs): added alternativeTitle and tags to tuna [`#3288`](https://github.com/pear-devs/pear-desktop/pull/3288)
- fix: rollback electron version to v34 (for fix #3216) [`#3216`](https://github.com/pear-devs/pear-desktop/issues/3216)
- fix(synced-lyrics): fix #3157 [`#3157`](https://github.com/pear-devs/pear-desktop/issues/3157)
- feat(performance-improvement): added "performance improvement" plugin [`1c76415`](https://github.com/pear-devs/pear-desktop/commit/1c764158461da414cea8bf34c3b514f1f98d7adf)
- chore(i18n): Translated using Weblate (Bosnian) [`a3d620b`](https://github.com/pear-devs/pear-desktop/commit/a3d620ba52b7fa8ae623b9d42b0652a22eaf65e7)
- Update changelog for v3.8.1 [`58cf1a5`](https://github.com/pear-devs/pear-desktop/commit/58cf1a543d98419c1a944e57407c0ea53b8168d8)
#### [v3.8.1](https://github.com/pear-devs/pear-desktop/compare/v3.8.0...v3.8.1)
> 25 April 2025
- chore(deps): update dependency glob to v11.0.2 [`#3283`](https://github.com/pear-devs/pear-desktop/pull/3283)
- fix(deps): update dependency i18next to v25.0.1 [`#3284`](https://github.com/pear-devs/pear-desktop/pull/3284)
- chore(deps): update dependency typescript-eslint to v8.31.0 [`#3286`](https://github.com/pear-devs/pear-desktop/pull/3286)
- chore(deps): update dependency discord-api-types to v0.38.1 [`#3285`](https://github.com/pear-devs/pear-desktop/pull/3285)
- fix(deps): update dependency youtubei.js to v13.4.0 [`#3287`](https://github.com/pear-devs/pear-desktop/pull/3287)
- fix(deps): update dependency zod to v3.24.3 [`#3250`](https://github.com/pear-devs/pear-desktop/pull/3250)
- chore(deps): update dependency vite to v6.3.3 [`#3251`](https://github.com/pear-devs/pear-desktop/pull/3251)
- fix(deps): update dependency @hono/zod-openapi to v0.19.5 [`#3258`](https://github.com/pear-devs/pear-desktop/pull/3258)
- chore(deps): update dependency vite-plugin-inspect to v11.0.1 [`#3259`](https://github.com/pear-devs/pear-desktop/pull/3259)
- chore(deps): update dependency esbuild to v0.25.3 [`#3282`](https://github.com/pear-devs/pear-desktop/pull/3282)
- chore(deps): update eslint monorepo to v9.25.1 [`#3260`](https://github.com/pear-devs/pear-desktop/pull/3260)
- chore(deps): update dependency electron to v35.2.1 [`#3281`](https://github.com/pear-devs/pear-desktop/pull/3281)
- chore(deps): update playwright monorepo to v1.52.0 [`#3256`](https://github.com/pear-devs/pear-desktop/pull/3256)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.3.4 [`#3273`](https://github.com/pear-devs/pear-desktop/pull/3273)
- fix: fix cuted "j" and glow in lyrics [`#3277`](https://github.com/pear-devs/pear-desktop/pull/3277)
- chore(deps): update dependency electron to v35.2.0 [`#3263`](https://github.com/pear-devs/pear-desktop/pull/3263)
- fix(unobtrusive-player): handle shuffle play [`#3247`](https://github.com/pear-devs/pear-desktop/pull/3247)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.5.1 [`#3238`](https://github.com/pear-devs/pear-desktop/pull/3238)
- chore(deps): update dependency vite to v6.3.0 [`#3248`](https://github.com/pear-devs/pear-desktop/pull/3248)
- chore(deps): update dependency typescript-eslint to v8.30.1 [`#3234`](https://github.com/pear-devs/pear-desktop/pull/3234)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.5.1 [`#3239`](https://github.com/pear-devs/pear-desktop/pull/3239)
- fix(deps): update dependency i18next to v25 [`#3243`](https://github.com/pear-devs/pear-desktop/pull/3243)
- fix(deps): update dependency hono to v4.7.7 [`#3245`](https://github.com/pear-devs/pear-desktop/pull/3245)
- chore(deps): update dependency vite to v6.2.6 [`#3189`](https://github.com/pear-devs/pear-desktop/pull/3189)
- feat(Synced-Lyrics): Also search for lyrics with the original title language [`#3206`](https://github.com/pear-devs/pear-desktop/pull/3206)
- chore(deps): update dependency eslint-config-prettier to v10.1.2 [`#3219`](https://github.com/pear-devs/pear-desktop/pull/3219)
- chore(deps): update dependency discord-api-types to v0.37.120 [`#3221`](https://github.com/pear-devs/pear-desktop/pull/3221)
- fix(deps): update dependency @hono/node-server to v1.14.1 [`#3223`](https://github.com/pear-devs/pear-desktop/pull/3223)
- chore(deps): update dependency vite to v6.2.6 [security] [`#3224`](https://github.com/pear-devs/pear-desktop/pull/3224)
- chore(deps): update dependency rollup to v4.40.0 [`#3227`](https://github.com/pear-devs/pear-desktop/pull/3227)
- fix(mpris): keep MPRIS volume in sync with the actual volume [`#3226`](https://github.com/pear-devs/pear-desktop/pull/3226)
- fix(deps): update dependency @hono/zod-openapi to v0.19.4 [`#3215`](https://github.com/pear-devs/pear-desktop/pull/3215)
- chore(deps): update dependency electron to v35.1.5 [`#3218`](https://github.com/pear-devs/pear-desktop/pull/3218)
- fix(deps): update dependency hono to v4.7.6 [`#3217`](https://github.com/pear-devs/pear-desktop/pull/3217)
- docs: add Portuguese README translation and update language shortcuts [`#3192`](https://github.com/pear-devs/pear-desktop/pull/3192)
- fix: custom Video/Audio switcher in Plugins menu [`#3174`](https://github.com/pear-devs/pear-desktop/pull/3174)
- chore(deps): update dependency typescript-eslint to v8.29.1 [`#3214`](https://github.com/pear-devs/pear-desktop/pull/3214)
- chore(deps): update eslint monorepo to v9.24.0 [`#3195`](https://github.com/pear-devs/pear-desktop/pull/3195)
- chore(deps): update dependency typescript to v5.8.3 [`#3196`](https://github.com/pear-devs/pear-desktop/pull/3196)
- chore(deps): update dependency vite to v6.2.5 [security] [`#3194`](https://github.com/pear-devs/pear-desktop/pull/3194)
- fix(deps): update dependency node-id3 to v0.2.9 [`#3191`](https://github.com/pear-devs/pear-desktop/pull/3191)
- chore(deps): update dependency electron to v35.1.4 [`#3184`](https://github.com/pear-devs/pear-desktop/pull/3184)
- chore(deps): update dependency eslint-plugin-prettier to v5.2.6 [`#3182`](https://github.com/pear-devs/pear-desktop/pull/3182)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.3.2 [`#3208`](https://github.com/pear-devs/pear-desktop/pull/3208)
- docs: add Japanese README [`#3180`](https://github.com/pear-devs/pear-desktop/pull/3180)
- chore(deps): update dependency node-gyp to v11.2.0 [`#3177`](https://github.com/pear-devs/pear-desktop/pull/3177)
- chore(deps): update dependency rollup to v4.39.0 [`#3179`](https://github.com/pear-devs/pear-desktop/pull/3179)
- chore(deps): update dependency typescript-eslint to v8.29.0 [`#3169`](https://github.com/pear-devs/pear-desktop/pull/3169)
- fix(downloader): allow downloads for signed out users [`#3145`](https://github.com/pear-devs/pear-desktop/pull/3145)
- fix(README): Fixed typos in some hyperlinks [`#3158`](https://github.com/pear-devs/pear-desktop/pull/3158)
- chore(deps): update dependency vite to v6.2.4 [`#3124`](https://github.com/pear-devs/pear-desktop/pull/3124)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.3.1 [`#3151`](https://github.com/pear-devs/pear-desktop/pull/3151)
- chore(deps): update dependency rollup to v4.38.0 [`#3154`](https://github.com/pear-devs/pear-desktop/pull/3154)
- chore(deps): update dependency esbuild to v0.25.2 [`#3160`](https://github.com/pear-devs/pear-desktop/pull/3160)
- chore(deps): update dependency electron to v35.1.2 [`#3147`](https://github.com/pear-devs/pear-desktop/pull/3147)
- chore(deps): update dependency electron to v35.1.1 [`#3143`](https://github.com/pear-devs/pear-desktop/pull/3143)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.2.5 [`#3144`](https://github.com/pear-devs/pear-desktop/pull/3144)
- chore(deps): update dependency @types/semver to v7.7.0 [`#3141`](https://github.com/pear-devs/pear-desktop/pull/3141)
- fix(deps): update dependency electron-updater to v6.6.2 [`#3142`](https://github.com/pear-devs/pear-desktop/pull/3142)
- chore(i18n): Translated using Weblate (Greek) [`8bb4f44`](https://github.com/pear-devs/pear-desktop/commit/8bb4f4426f6a82b1b5c13a385a6e2b94c25f88a2)
- chore(i18n): Translated using Weblate (Bulgarian) [`89fe072`](https://github.com/pear-devs/pear-desktop/commit/89fe072c0e719026212bb506bb66baf37b31ceb4)
- chore(i18n): Translated using Weblate (Greek) [`5a7daaf`](https://github.com/pear-devs/pear-desktop/commit/5a7daaf2f6d1211c4b9461facf4de1962714bacf)
#### [v3.8.0](https://github.com/pear-devs/pear-desktop/compare/v3.7.5...v3.8.0)
> 26 March 2025
- chore(deps): update dependency typescript-eslint to v8.28.0 [`#3128`](https://github.com/pear-devs/pear-desktop/pull/3128)
- chore(deps): update dependency eslint-plugin-prettier to v5.2.5 [`#3123`](https://github.com/pear-devs/pear-desktop/pull/3123)
- fix(deps): update dependency @hono/node-server to v1.14.0 [`#3131`](https://github.com/pear-devs/pear-desktop/pull/3131)
- chore(deps): update dependency electron to v35.1.0 [`#3136`](https://github.com/pear-devs/pear-desktop/pull/3136)
- fix(deps): update dependency es-hangul to v2.3.2 [`#3138`](https://github.com/pear-devs/pear-desktop/pull/3138)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.2.4 [`#3135`](https://github.com/pear-devs/pear-desktop/pull/3135)
- chore(deps): update eslint monorepo to v9.23.0 [`#3130`](https://github.com/pear-devs/pear-desktop/pull/3130)
- chore(deps): update dependency electron-vite to v3.1.0 [`#3137`](https://github.com/pear-devs/pear-desktop/pull/3137)
- chore(deps): update dependency @babel/runtime to v7.27.0 [`#3127`](https://github.com/pear-devs/pear-desktop/pull/3127)
- feat(synced-lyrics): romanization [`#2790`](https://github.com/pear-devs/pear-desktop/pull/2790)
- feat(plugin): add unobtrusive player plugin [`#3104`](https://github.com/pear-devs/pear-desktop/pull/3104)
- chore(deps): update dependency vite to v6.2.3 [security] [`#3134`](https://github.com/pear-devs/pear-desktop/pull/3134)
- fix(deps): update dependency youtubei.js to v13.3.0 [`#3133`](https://github.com/pear-devs/pear-desktop/pull/3133)
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.12 [`#3122`](https://github.com/pear-devs/pear-desktop/pull/3122)
- chore(deps): update dependency eslint-import-resolver-typescript to v4.2.2 [`#3106`](https://github.com/pear-devs/pear-desktop/pull/3106)
- chore(deps): update dependency electron-builder to v26.0.12 [`#3121`](https://github.com/pear-devs/pear-desktop/pull/3121)
- fix: Discord Rich Presence Fix [`#3074`](https://github.com/pear-devs/pear-desktop/pull/3074)
- fix(deps): update dependency @xhayper/discord-rpc to v1.2.1 [`#3107`](https://github.com/pear-devs/pear-desktop/pull/3107)
- chore(deps): update dependency typescript-eslint to v8.27.0 [`#3111`](https://github.com/pear-devs/pear-desktop/pull/3111)
- chore(deps): update dependency electron to v35.0.3 [`#3112`](https://github.com/pear-devs/pear-desktop/pull/3112)
- fix(deps): update dependency hono to v4.7.5 [`#3113`](https://github.com/pear-devs/pear-desktop/pull/3113)
- fix(deps): update dependency fast-average-color to v9.5.0 [`#3114`](https://github.com/pear-devs/pear-desktop/pull/3114)
- chore(deps): update dependency rollup to v4.37.0 [`#3103`](https://github.com/pear-devs/pear-desktop/pull/3103)
- chore(deps): update playwright monorepo to v1.51.1 [`#3105`](https://github.com/pear-devs/pear-desktop/pull/3105)
- chore(deps): update dependency eslint-import-resolver-typescript to v4 [`#3102`](https://github.com/pear-devs/pear-desktop/pull/3102)
- chore(deps): update dependency electron to v35.0.2 [`#3099`](https://github.com/pear-devs/pear-desktop/pull/3099)
- fix(deps): update dependency i18next to v24.2.3 [`#3100`](https://github.com/pear-devs/pear-desktop/pull/3100)
- chore(deps): update dependency electron-builder to v26.0.11 [`#3095`](https://github.com/pear-devs/pear-desktop/pull/3095)
- chore(deps): update dependency @babel/runtime to v7.26.10 [security] [`#3094`](https://github.com/pear-devs/pear-desktop/pull/3094)
- chore(deps): update dependency eslint-config-prettier to v10.1.1 [`#3069`](https://github.com/pear-devs/pear-desktop/pull/3069)
- chore(deps): update playwright monorepo to v1.51.0 [`#3065`](https://github.com/pear-devs/pear-desktop/pull/3065)
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.11 [`#3096`](https://github.com/pear-devs/pear-desktop/pull/3096)
- chore(deps): update dependency esbuild to v0.25.1 [`#3097`](https://github.com/pear-devs/pear-desktop/pull/3097)
- chore(deps): update dependency typescript-eslint to v8.26.1 [`#3098`](https://github.com/pear-devs/pear-desktop/pull/3098)
- chore(deps): update eslint monorepo to v9.22.0 [`#3070`](https://github.com/pear-devs/pear-desktop/pull/3070)
- chore(deps): update dependency rollup to v4.35.0 [`#3071`](https://github.com/pear-devs/pear-desktop/pull/3071)
- chore(deps): update dependency electron to v35.0.1 [`#3075`](https://github.com/pear-devs/pear-desktop/pull/3075)
- fix(deps): update dependency happy-dom to v17.4.4 [`#3068`](https://github.com/pear-devs/pear-desktop/pull/3068)
- chore(deps): update dependency vite to v6.2.2 [`#3067`](https://github.com/pear-devs/pear-desktop/pull/3067)
- fix: Update winget-releaser version to main [`#3091`](https://github.com/pear-devs/pear-desktop/pull/3091)
- feat: Allow scrobbling using alternative song titles [`#3093`](https://github.com/pear-devs/pear-desktop/pull/3093)
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.10 [`#3054`](https://github.com/pear-devs/pear-desktop/pull/3054)
- chore(deps): update dependency typescript-eslint to v8.26.0 [`#3056`](https://github.com/pear-devs/pear-desktop/pull/3056)
- fix(deps): update dependency hono to v4.7.4 [`#3062`](https://github.com/pear-devs/pear-desktop/pull/3062)
- chore(deps): update dependency electron-builder to v26.0.10 [`#3053`](https://github.com/pear-devs/pear-desktop/pull/3053)
- chore(deps): update dependency electron to v35 [`#3058`](https://github.com/pear-devs/pear-desktop/pull/3058)
- fix(deps): update dependency happy-dom to v17.2.2 [`#3060`](https://github.com/pear-devs/pear-desktop/pull/3060)
- fix(deps): update dependency happy-dom to v17.1.13 [`#3057`](https://github.com/pear-devs/pear-desktop/pull/3057)
- chore(deps): update dependency typescript to v5.8.2 [`#3040`](https://github.com/pear-devs/pear-desktop/pull/3040)
- chore(deps): update dependency rollup to v4.34.9 [`#3043`](https://github.com/pear-devs/pear-desktop/pull/3043)
- fix(deps): update dependency @hono/swagger-ui to v0.5.1 [`#3045`](https://github.com/pear-devs/pear-desktop/pull/3045)
- fix: added French link in general README file [`#3047`](https://github.com/pear-devs/pear-desktop/pull/3047)
- fix(deps): update dependency @hono/zod-openapi to v0.19.2 [`#3046`](https://github.com/pear-devs/pear-desktop/pull/3046)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v4.2.0 [`#3050`](https://github.com/pear-devs/pear-desktop/pull/3050)
- fix(deps): update dependency bgutils-js to v3.2.0 [`#3049`](https://github.com/pear-devs/pear-desktop/pull/3049)
- chore(i18n): Translated using Weblate (Tamil) [`a3601ce`](https://github.com/pear-devs/pear-desktop/commit/a3601cece6a1d291f9887e1a5049fb3c6ad09eb1)
- chore(i18n): Translated using Weblate (Arabic) [`06aaba0`](https://github.com/pear-devs/pear-desktop/commit/06aaba0c7fe9173051701c626d44a347b1bd7574)
- chore(i18n): Translated using Weblate (Spanish) [`dbf8b1c`](https://github.com/pear-devs/pear-desktop/commit/dbf8b1c5c53d88f676c12b7ffca0e23b3efaa541)
#### [v3.7.5](https://github.com/pear-devs/pear-desktop/compare/v3.7.4...v3.7.5)
> 1 March 2025
- chore(deps): update dependency builtin-modules to v5 [`#3038`](https://github.com/pear-devs/pear-desktop/pull/3038)
- chore(deps): update dependency typescript-eslint to v8.25.0 [`#2953`](https://github.com/pear-devs/pear-desktop/pull/2953)
- fix(deps): update dependency happy-dom to v17.1.8 [`#3001`](https://github.com/pear-devs/pear-desktop/pull/3001)
- chore(deps): update dependency eslint-config-prettier to v10.0.2 [`#3035`](https://github.com/pear-devs/pear-desktop/pull/3035)
- chore(deps): update dependency @electron/universal to v2.0.2 [`#3034`](https://github.com/pear-devs/pear-desktop/pull/3034)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v4 [`#2950`](https://github.com/pear-devs/pear-desktop/pull/2950)
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.9 [`#2930`](https://github.com/pear-devs/pear-desktop/pull/2930)
- fix(no-google-login): Remove Library icon removal code [`#3010`](https://github.com/pear-devs/pear-desktop/pull/3010)
- fix: Updated tray pause icon for consistency [`#3025`](https://github.com/pear-devs/pear-desktop/pull/3025)
- chore(deps): update dependency eslint-import-resolver-typescript to v3.8.3 [`#2992`](https://github.com/pear-devs/pear-desktop/pull/2992)
- fix(deps): update dependency hono to v4.7.2 [`#2999`](https://github.com/pear-devs/pear-desktop/pull/2999)
- fix: Filter for only `MusicResponsiveListItem` in playlist items [`#3022`](https://github.com/pear-devs/pear-desktop/pull/3022)
- fix(deps): update dependency youtubei.js to v13.1.0 [`#3015`](https://github.com/pear-devs/pear-desktop/pull/3015)
- fix: Match engines.pnpm with the pnpm version used to create the lock files [`#2995`](https://github.com/pear-devs/pear-desktop/pull/2995)
- chore(deps): update dependency electron-builder to v26.0.9 [`457e1bb`](https://github.com/pear-devs/pear-desktop/commit/457e1bb48e2bcc742680d22b7d34ffdbe7f33eae)
- chore(deps): update deps [`c8bb1f3`](https://github.com/pear-devs/pear-desktop/commit/c8bb1f386d7053d755c38ca2cac8708af7af1fb9)
- chore(i18n): Translated using Weblate (Thai) [`c9b7901`](https://github.com/pear-devs/pear-desktop/commit/c9b790168130d0cfc9b2ff23cdcb56ab082a4b66)
#### [v3.7.4](https://github.com/pear-devs/pear-desktop/compare/v3.7.3...v3.7.4)
> 18 February 2025
- chore(deps): update dependency rollup to v4.34.8 [`#2982`](https://github.com/pear-devs/pear-desktop/pull/2982)
- fix(plugin-loader): update context filtering logic for backend mode [`#2990`](https://github.com/pear-devs/pear-desktop/pull/2990)
- Update changelog for v3.7.3 [`86c77d1`](https://github.com/pear-devs/pear-desktop/commit/86c77d141f2bec62a4a578fff96d51ed388c05a5)
- HOTFIX: Bump version to 3.7.4 [`0d462ac`](https://github.com/pear-devs/pear-desktop/commit/0d462ac3a26caee23854014cbf5e4b91e2d385e2)
#### [v3.7.3](https://github.com/pear-devs/pear-desktop/compare/v3.7.2...v3.7.3)
> 17 February 2025
- fix(downloader): use the upgrade button to check for premium status [`#2987`](https://github.com/pear-devs/pear-desktop/pull/2987)
- chore(deps): update dependency electron-vite to v3 [`#2986`](https://github.com/pear-devs/pear-desktop/pull/2986)
- chore(deps): update dependency @babel/runtime to v7.26.9 [`#2980`](https://github.com/pear-devs/pear-desktop/pull/2980)
- fix(vite): set server.cors.origin [`#2981`](https://github.com/pear-devs/pear-desktop/pull/2981)
- chore(deps-dev): bump esbuild from 0.24.2 to 0.25.0 [`#2973`](https://github.com/pear-devs/pear-desktop/pull/2973)
- fix(deps): update dependency solid-transition-group to v0.3.0 [`#2949`](https://github.com/pear-devs/pear-desktop/pull/2949)
- fix: remove disable-gpu-memory-buffer-video-frames flag [`#2963`](https://github.com/pear-devs/pear-desktop/pull/2963)
- fix(deps): update dependency semver to v7.7.0 [`#2948`](https://github.com/pear-devs/pear-desktop/pull/2948)
- chore(deps): update playwright monorepo to v1.50.1 [`#2943`](https://github.com/pear-devs/pear-desktop/pull/2943)
- fix(deps): update dependency @hono/node-server to v1.13.8 [`#2944`](https://github.com/pear-devs/pear-desktop/pull/2944)
- fix(deps): update dependency electron-store to v10.0.1 [`#2945`](https://github.com/pear-devs/pear-desktop/pull/2945)
- chore(deps): update dependency rollup to v4.34.1 [`#2946`](https://github.com/pear-devs/pear-desktop/pull/2946)
- chore(deps): update dependency typescript-eslint to v8.22.0 [`#2947`](https://github.com/pear-devs/pear-desktop/pull/2947)
- fix(synced-lyrics): Fix reverse direction of synced lyrics for persian or other rtl languages [`#2940`](https://github.com/pear-devs/pear-desktop/pull/2940)
- chore(deps): update dependency electron to v34.0.2 [`#2942`](https://github.com/pear-devs/pear-desktop/pull/2942)
- chore(deps): update dependency discord-api-types to v0.37.119 [`#2941`](https://github.com/pear-devs/pear-desktop/pull/2941)
- fix(deps): update dependency hono to v4.6.20 [`#2932`](https://github.com/pear-devs/pear-desktop/pull/2932)
- chore(deps): update eslint monorepo to v9.19.0 [`#2935`](https://github.com/pear-devs/pear-desktop/pull/2935)
- fix(deps): update dependency bgutils-js to v3.1.3 [`#2934`](https://github.com/pear-devs/pear-desktop/pull/2934)
- fix(deps): update dependency i18next to v24.2.2 [`#2933`](https://github.com/pear-devs/pear-desktop/pull/2933)
- fix(deps): update dependency happy-dom to v16.8.1 [`#2936`](https://github.com/pear-devs/pear-desktop/pull/2936)
- chore(deps): update dependency @babel/runtime to v7.26.7 [`#2924`](https://github.com/pear-devs/pear-desktop/pull/2924)
- chore(config): migrate renovate config [`#2925`](https://github.com/pear-devs/pear-desktop/pull/2925)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.5.0 [`#2923`](https://github.com/pear-devs/pear-desktop/pull/2923)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.5.0 [`#2922`](https://github.com/pear-devs/pear-desktop/pull/2922)
- chore(deps): update playwright monorepo to v1.50.0 [`#2921`](https://github.com/pear-devs/pear-desktop/pull/2921)
- chore(deps): update dependency vite-plugin-inspect to v10.1.0 [`#2920`](https://github.com/pear-devs/pear-desktop/pull/2920)
- chore(deps): update dependency rollup to v4.32.0 [`#2919`](https://github.com/pear-devs/pear-desktop/pull/2919)
- fix(deps): update dependency hono to v4.6.18 [`#2918`](https://github.com/pear-devs/pear-desktop/pull/2918)
- fix(deps): update dependency deepmerge-ts to v7.1.4 [`#2917`](https://github.com/pear-devs/pear-desktop/pull/2917)
- chore(deps): update dependency vite to v6.0.11 [`#2894`](https://github.com/pear-devs/pear-desktop/pull/2894)
- chore(deps): update dependency electron to v34.0.1 [`#2916`](https://github.com/pear-devs/pear-desktop/pull/2916)
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.0-alpha.10 [`#2899`](https://github.com/pear-devs/pear-desktop/pull/2899)
- chore(deps): update dependency electron-builder to v26.0.0-alpha.10 [`#2898`](https://github.com/pear-devs/pear-desktop/pull/2898)
- chore(deps): update dependency typescript-eslint to v8.21.0 [`#2901`](https://github.com/pear-devs/pear-desktop/pull/2901)
- chore(deps): update dependency discord-api-types to v0.37.117 [`#2895`](https://github.com/pear-devs/pear-desktop/pull/2895)
- fix(deps): update dependency youtubei.js to v13 [`#2904`](https://github.com/pear-devs/pear-desktop/pull/2904)
- chore(deps): update dependency vite to v6.0.9 [security] [`#2907`](https://github.com/pear-devs/pear-desktop/pull/2907)
- fix(deps): update dependency happy-dom to v16.7.2 [`#2902`](https://github.com/pear-devs/pear-desktop/pull/2902)
- fix(discord-plugin): handle album name padding if length < 2 [`#2903`](https://github.com/pear-devs/pear-desktop/pull/2903)
- feat(navigation): added nav icon padding [`#2905`](https://github.com/pear-devs/pear-desktop/pull/2905)
- chore(deps): update dependency rollup to v4.31.0 [`#2891`](https://github.com/pear-devs/pear-desktop/pull/2891)
- chore(deps): update dependency eslint-plugin-prettier to v5.2.3 [`#2889`](https://github.com/pear-devs/pear-desktop/pull/2889)
- chore(deps): update dependency vite-plugin-inspect to v10.0.7 [`#2882`](https://github.com/pear-devs/pear-desktop/pull/2882)
- fix(deps): update dependency hono to v4.6.17 [`#2883`](https://github.com/pear-devs/pear-desktop/pull/2883)
- fix: bump deps [`e9184e5`](https://github.com/pear-devs/pear-desktop/commit/e9184e5d60c2495473a7c3226ce9748ba89fceb3)
- fix(deps): fix pnpm [`040db75`](https://github.com/pear-devs/pear-desktop/commit/040db7539ccd1ae40f2632fdf38168cdaa26f112)
- chore(i18n): Translated using Weblate (Persian) [`9d18587`](https://github.com/pear-devs/pear-desktop/commit/9d185872dba5b56dabc691e56eafb13dc192b9cd)
#### [v3.7.2](https://github.com/pear-devs/pear-desktop/compare/v3.7.1...v3.7.2)
> 18 January 2025
- feat(api-server): add endpoint to get shuffle state [`#2792`](https://github.com/pear-devs/pear-desktop/pull/2792)
- chore(deps): update dependency discord-api-types to v0.37.116 [`#2877`](https://github.com/pear-devs/pear-desktop/pull/2877)
- chore(deps): update dependency eslint-plugin-prettier to v5.2.2 [`#2875`](https://github.com/pear-devs/pear-desktop/pull/2875)
- chore(deps): update eslint monorepo to v9.18.0 [`#2858`](https://github.com/pear-devs/pear-desktop/pull/2858)
- chore(deps): update dependency glob to v11.0.1 [`#2857`](https://github.com/pear-devs/pear-desktop/pull/2857)
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.0-alpha.9 [`#2874`](https://github.com/pear-devs/pear-desktop/pull/2874)
- chore(deps): update dependency electron to v34 [`#2867`](https://github.com/pear-devs/pear-desktop/pull/2867)
- chore(deps): update dependency eslint-config-prettier to v10 [`#2866`](https://github.com/pear-devs/pear-desktop/pull/2866)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.13.0 [`#2864`](https://github.com/pear-devs/pear-desktop/pull/2864)
- chore(deps): update dependency typescript-eslint to v8.20.0 [`#2865`](https://github.com/pear-devs/pear-desktop/pull/2865)
- chore(deps): update dependency electron-builder to v26.0.0-alpha.9 [`#2869`](https://github.com/pear-devs/pear-desktop/pull/2869)
- fix: fix build.linux.desktop.entry [`#2859`](https://github.com/pear-devs/pear-desktop/pull/2859)
- feat(api-server): add endpoint to get volume state [`#2813`](https://github.com/pear-devs/pear-desktop/pull/2813)
- chore(deps): update dependency vite-plugin-inspect to v10 [`#2856`](https://github.com/pear-devs/pear-desktop/pull/2856)
- chore(deps): update dependency typescript to v5.7.3 [`#2855`](https://github.com/pear-devs/pear-desktop/pull/2855)
- fix(deps): update dependency @floating-ui/dom to v1.6.13 [`#2846`](https://github.com/pear-devs/pear-desktop/pull/2846)
- chore(deps): bump nanoid from 3.3.7 to 3.3.8 [`#2854`](https://github.com/pear-devs/pear-desktop/pull/2854)
- chore(deps): update dependency electron to v33.3.1 [`#2841`](https://github.com/pear-devs/pear-desktop/pull/2841)
- fix(deps): update dependency i18next to v24.2.1 [`#2840`](https://github.com/pear-devs/pear-desktop/pull/2840)
- chore(deps): update dependency typescript-eslint to v8.19.1 [`#2836`](https://github.com/pear-devs/pear-desktop/pull/2836)
- chore(deps): update dependency rollup to v4.30.1 [`#2833`](https://github.com/pear-devs/pear-desktop/pull/2833)
- fix(deps): update dependency solid-js to v1.9.4 [`#2849`](https://github.com/pear-devs/pear-desktop/pull/2849)
- fix(deps): update dependency fast-equals to v5.2.2 [`#2842`](https://github.com/pear-devs/pear-desktop/pull/2842)
- chore: Update README.md [`#2845`](https://github.com/pear-devs/pear-desktop/pull/2845)
- chore: Fixing the Content section in the README-ru.md file. [`#2847`](https://github.com/pear-devs/pear-desktop/pull/2847)
- chore: Create music-player-hu.svg [`#2844`](https://github.com/pear-devs/pear-desktop/pull/2844)
- chore: Create Transalated README-hu.md [`#2843`](https://github.com/pear-devs/pear-desktop/pull/2843)
- chore(deps): update dependency vite to v6.0.7 [`#2819`](https://github.com/pear-devs/pear-desktop/pull/2819)
- chore(deps): update dependency discord-api-types to v0.37.115 [`#2818`](https://github.com/pear-devs/pear-desktop/pull/2818)
- fix(deps): update dependency hono to v4.6.16 [`#2829`](https://github.com/pear-devs/pear-desktop/pull/2829)
- chore(deps): update dependency rollup to v4.29.2 [`#2832`](https://github.com/pear-devs/pear-desktop/pull/2832)
- fix(deps): update dependency fast-equals to v5.2.0 [`#2822`](https://github.com/pear-devs/pear-desktop/pull/2822)
- feat(api-server): add `insertPosition` for `addSongToQueue` [`#2808`](https://github.com/pear-devs/pear-desktop/pull/2808)
- chore(deps): update dependency typescript-eslint to v8.19.0 [`#2812`](https://github.com/pear-devs/pear-desktop/pull/2812)
- fix(deps): update dependency ts-morph to v25 [`#2810`](https://github.com/pear-devs/pear-desktop/pull/2810)
- fix(renderer): update event handler from onVolumeTap to onVolumeClick [`#2791`](https://github.com/pear-devs/pear-desktop/pull/2791)
- fix(deps): update dependency hono to v4.6.15 [`#2796`](https://github.com/pear-devs/pear-desktop/pull/2796)
- chore(deps): update dependency bufferutil to v4.0.9 [`#2787`](https://github.com/pear-devs/pear-desktop/pull/2787)
- feat: Refactor Menu Navigation and Update Media Control Icons [`#2783`](https://github.com/pear-devs/pear-desktop/pull/2783)
- fix(synced-lyrics): Revert font-size behavior for non-fancy modes [`#2788`](https://github.com/pear-devs/pear-desktop/pull/2788)
- fix(downloader): apply poToken [`#2863`](https://github.com/pear-devs/pear-desktop/issues/2863) [`#2780`](https://github.com/pear-devs/pear-desktop/issues/2780)
- chore(deps): update dependency electron-builder to v26 [`67fc0a4`](https://github.com/pear-devs/pear-desktop/commit/67fc0a415cae231a11f2846aadf01edb04f5c677)
- fix: fix lock file [`3339f99`](https://github.com/pear-devs/pear-desktop/commit/3339f997e3c2d4d2c32b3aee95c65d561f123fcb)
- chore(i18n): Translated using Weblate (Romanian) [`845dac3`](https://github.com/pear-devs/pear-desktop/commit/845dac3c0393dadea8efdd03ba1f41b1b36e6191)
#### [v3.7.1](https://github.com/pear-devs/pear-desktop/compare/v3.7.0...v3.7.1)
> 27 December 2024
- fix(deps): update dependency node-html-parser to v7 [`#2776`](https://github.com/pear-devs/pear-desktop/pull/2776)
- chore(deps): update dependency vite to v6.0.6 [`#2774`](https://github.com/pear-devs/pear-desktop/pull/2774)
- feat(api-server): Add queue api [`#2767`](https://github.com/pear-devs/pear-desktop/pull/2767)
- fix(downloader): fix #2234 [`#2234`](https://github.com/pear-devs/pear-desktop/issues/2234)
- fix(downloader): fix #2769 [`#2769`](https://github.com/pear-devs/pear-desktop/issues/2769)
- fix: fix #2645, fix #2741 [`#2645`](https://github.com/pear-devs/pear-desktop/issues/2645) [`#2741`](https://github.com/pear-devs/pear-desktop/issues/2741)
- Update changelog for v3.7.0 [`1cc1530`](https://github.com/pear-devs/pear-desktop/commit/1cc153084d5f354ea928fcde50207f8f6b14ea4c)
- fix: use networkManager.fetch instead of fetch [`80471b0`](https://github.com/pear-devs/pear-desktop/commit/80471b0ca4b3d3efc9e3db87d434290c9ebc79f6)
- chore(i18n): Translated using Weblate (Hindi) [`6d1237c`](https://github.com/pear-devs/pear-desktop/commit/6d1237c2a2ad2408a738e00992ae5ed8a1e900da)
#### [v3.7.0](https://github.com/pear-devs/pear-desktop/compare/v3.6.2...v3.7.0)
> 25 December 2024
- feat(amuse): song query api (add amuse plugin) [`#2723`](https://github.com/pear-devs/pear-desktop/pull/2723)
- feat(api-server): add absolute seek endpoint [`#2748`](https://github.com/pear-devs/pear-desktop/pull/2748)
- feat(api-server): Add repeat mode and seek time API [`#2630`](https://github.com/pear-devs/pear-desktop/pull/2630)
- feat(synced-lyrics): Better-Lyrics Styling for Synced-Lyrics [`#2554`](https://github.com/pear-devs/pear-desktop/pull/2554)
- feat(synced-lyrics): multiple lyric sources [`#2383`](https://github.com/pear-devs/pear-desktop/pull/2383)
- chore(deps): update dependency typescript-eslint to v8.18.2 [`#2763`](https://github.com/pear-devs/pear-desktop/pull/2763)
- chore(deps): update dependency discord-api-types to v0.37.114 [`#2761`](https://github.com/pear-devs/pear-desktop/pull/2761)
- chore(deps): update dependency discord-api-types to v0.37.113 [`#2759`](https://github.com/pear-devs/pear-desktop/pull/2759)
- fix: Set correct window class for X11 and Wayland [`#2758`](https://github.com/pear-devs/pear-desktop/pull/2758)
- feat: Specify flatpak runtime [`#2755`](https://github.com/pear-devs/pear-desktop/pull/2755)
- chore(deps): update dependency rollup to v4.29.1 [`#2749`](https://github.com/pear-devs/pear-desktop/pull/2749)
- chore(deps): update dependency esbuild to v0.24.2 [`#2742`](https://github.com/pear-devs/pear-desktop/pull/2742)
- fix: Add Flatpak permissions needed for MPRIS and tray icon [`#2754`](https://github.com/pear-devs/pear-desktop/pull/2754)
- chore(deps): update dependency vite-plugin-inspect to v0.10.6 [`#2756`](https://github.com/pear-devs/pear-desktop/pull/2756)
- chore(deps): update dependency vite to v6.0.5 [`#2745`](https://github.com/pear-devs/pear-desktop/pull/2745)
- fix(deps): update dependency i18next to v24.2.0 [`#2744`](https://github.com/pear-devs/pear-desktop/pull/2744)
- chore(deps): update dependency vite-plugin-inspect to v0.10.4 [`#2743`](https://github.com/pear-devs/pear-desktop/pull/2743)
- chore(deps): update dependency discord-api-types to v0.37.112 [`#2740`](https://github.com/pear-devs/pear-desktop/pull/2740)
- fix(discord): Fix Album Art failing on Discord RPC [`#2666`](https://github.com/pear-devs/pear-desktop/pull/2666)
- feat: Add equalizer plugin with presets (e.g. bass booster) [`#2575`](https://github.com/pear-devs/pear-desktop/pull/2575)
- chore(deps): update dependency vite to v6.0.4 [`#2738`](https://github.com/pear-devs/pear-desktop/pull/2738)
- fix: Fixed #1796 [`#2736`](https://github.com/pear-devs/pear-desktop/pull/2736)
- chore(deps): update dependency electron-devtools-installer to v4 [`#2734`](https://github.com/pear-devs/pear-desktop/pull/2734)
- Revert "chore(deps): update dependency electron-builder to v25" [`#2732`](https://github.com/pear-devs/pear-desktop/pull/2732)
- chore(deps): update dependency electron-builder to v25 [`#2490`](https://github.com/pear-devs/pear-desktop/pull/2490)
- fix(deps): update dependency i18next to v24.1.2 [`#2727`](https://github.com/pear-devs/pear-desktop/pull/2727)
- chore(deps): update dependency electron-devtools-installer to v3.2.1 [`#2731`](https://github.com/pear-devs/pear-desktop/pull/2731)
- chore(deps): update dependency typescript-eslint to v8.18.1 [`#2724`](https://github.com/pear-devs/pear-desktop/pull/2724)
- fix: tab misalignment [`#2713`](https://github.com/pear-devs/pear-desktop/pull/2713)
- fix(deps): update dependency @hono/zod-validator to v0.4.2 [`#2709`](https://github.com/pear-devs/pear-desktop/pull/2709)
- chore(deps): update eslint monorepo to v9.17.0 [`#2712`](https://github.com/pear-devs/pear-desktop/pull/2712)
- fix(deps): update dependency hono to v4.6.14 [`#2716`](https://github.com/pear-devs/pear-desktop/pull/2716)
- fix: discord rich presence connection status [`#2714`](https://github.com/pear-devs/pear-desktop/pull/2714)
- fix: Laggy scrolling behaviour in large playlists [`#2708`](https://github.com/pear-devs/pear-desktop/pull/2708)
- fix(deps): update dependency youtubei.js to v12.2.0 [`#2705`](https://github.com/pear-devs/pear-desktop/pull/2705)
- fix(deps): update dependency i18next to v24.1.0 [`#2698`](https://github.com/pear-devs/pear-desktop/pull/2698)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.12.1 [`#2697`](https://github.com/pear-devs/pear-desktop/pull/2697)
- fix(deps): update dependency zod to v3.24.1 [`#2694`](https://github.com/pear-devs/pear-desktop/pull/2694)
- fix(deps): update dependency youtubei.js to v12.1.0 [`#2695`](https://github.com/pear-devs/pear-desktop/pull/2695)
- chore(deps): update dependency discord-api-types to v0.37.111 [`#2690`](https://github.com/pear-devs/pear-desktop/pull/2690)
- chore(deps): update dependency typescript-eslint to v8.18.0 [`#2692`](https://github.com/pear-devs/pear-desktop/pull/2692)
- chore(deps): update playwright monorepo to v1.49.1 [`#2693`](https://github.com/pear-devs/pear-desktop/pull/2693)
- fix(deps): update dependency hono to v4.6.13 [`#2682`](https://github.com/pear-devs/pear-desktop/pull/2682)
- chore(deps): update dependency rollup to v4.28.1 [`#2683`](https://github.com/pear-devs/pear-desktop/pull/2683)
- fix(deps): update dependency conf to v13.1.0 [`#2686`](https://github.com/pear-devs/pear-desktop/pull/2686)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.12.0 [`#2689`](https://github.com/pear-devs/pear-desktop/pull/2689)
- fix(deps): update dependency youtubei.js to v12 [`#2681`](https://github.com/pear-devs/pear-desktop/pull/2681)
- chore(deps): update dependency vite to v6.0.3 [`#2680`](https://github.com/pear-devs/pear-desktop/pull/2680)
- fix(album-actions): Fixed #2312 [`#2676`](https://github.com/pear-devs/pear-desktop/pull/2676)
- chore(deps): update dependency eslint-import-resolver-typescript to v3.7.0 [`#2672`](https://github.com/pear-devs/pear-desktop/pull/2672)
- chore(deps): update dependency node-gyp to v11 [`#2678`](https://github.com/pear-devs/pear-desktop/pull/2678)
- fix(deps): update dependency i18next to v24.0.5 [`#2669`](https://github.com/pear-devs/pear-desktop/pull/2669)
- fix(deps): update dependency i18next to v24.0.4 [`#2668`](https://github.com/pear-devs/pear-desktop/pull/2668)
- chore(deps): update dependency vite to v6.0.2 [`#2662`](https://github.com/pear-devs/pear-desktop/pull/2662)
- chore(deps): update dependency node-gyp to v10.3.1 [`#2665`](https://github.com/pear-devs/pear-desktop/pull/2665)
- chore(deps): update dependency typescript-eslint to v8.17.0 [`#2664`](https://github.com/pear-devs/pear-desktop/pull/2664)
- chore(deps): update dependency vite-plugin-inspect to v0.10.3 [`#2667`](https://github.com/pear-devs/pear-desktop/pull/2667)
- chore(deps): update dependency rollup to v4.28.0 [`#2661`](https://github.com/pear-devs/pear-desktop/pull/2661)
- chore(deps): update dependency discord-api-types to v0.37.110 [`#2653`](https://github.com/pear-devs/pear-desktop/pull/2653)
- fix(deps): update dependency @hono/zod-openapi to v0.18.3 [`#2654`](https://github.com/pear-devs/pear-desktop/pull/2654)
- chore(deps): update eslint monorepo to v9.16.0 [`#2656`](https://github.com/pear-devs/pear-desktop/pull/2656)
- chore(deps): update dependency vite-plugin-inspect to v0.10.2 [`#2657`](https://github.com/pear-devs/pear-desktop/pull/2657)
- fix(music-player.css): Fixed #2514 [`#2659`](https://github.com/pear-devs/pear-desktop/pull/2659)
- fix: Fixed Skip Disliked Song not working [`#2651`](https://github.com/pear-devs/pear-desktop/pull/2651)
- fix(deps): update dependency @hono/zod-openapi to v0.18.2 [`#2650`](https://github.com/pear-devs/pear-desktop/pull/2650)
- chore(deps): update dependency vite-plugin-inspect to v0.10.1 [`#2652`](https://github.com/pear-devs/pear-desktop/pull/2652)
- chore(deps): update dependency electron to v33.2.1 [`#2649`](https://github.com/pear-devs/pear-desktop/pull/2649)
- chore(deps): update dependency vite-plugin-inspect to v0.10.0 [`#2646`](https://github.com/pear-devs/pear-desktop/pull/2646)
- chore(deps): update dependency vite to v6 [`#2644`](https://github.com/pear-devs/pear-desktop/pull/2644)
- fix(deps): update dependency @hono/swagger-ui to v0.5.0 [`#2643`](https://github.com/pear-devs/pear-desktop/pull/2643)
- chore(deps): update dependency discord-api-types to v0.37.109 [`#2642`](https://github.com/pear-devs/pear-desktop/pull/2642)
- chore(deps): update dependency vite-plugin-solid to v2.11.0 [`#2641`](https://github.com/pear-devs/pear-desktop/pull/2641)
- fix(deps): update dependency hono to v4.6.12 [`#2636`](https://github.com/pear-devs/pear-desktop/pull/2636)
- fix(deps): update dependency i18next to v24.0.2 [`#2637`](https://github.com/pear-devs/pear-desktop/pull/2637)
- chore(deps): update dependency discord-api-types to v0.37.108 [`#2638`](https://github.com/pear-devs/pear-desktop/pull/2638)
- chore(deps): update dependency typescript-eslint to v8.16.0 [`#2639`](https://github.com/pear-devs/pear-desktop/pull/2639)
- chore(deps): update dependency rollup to v4.27.4 [`#2632`](https://github.com/pear-devs/pear-desktop/pull/2632)
- fix(deps): update dependency i18next to v24 [`#2633`](https://github.com/pear-devs/pear-desktop/pull/2633)
- chore(deps): update dependency typescript to v5.7.2 [`#2629`](https://github.com/pear-devs/pear-desktop/pull/2629)
- chore(deps): update dependency discord-api-types to v0.37.107 [`#2627`](https://github.com/pear-devs/pear-desktop/pull/2627)
- fix(deps): update dependency @hono/zod-openapi to v0.18.0 [`#2626`](https://github.com/pear-devs/pear-desktop/pull/2626)
- fix(deps): update dependency i18next to v23.16.8 [`#2625`](https://github.com/pear-devs/pear-desktop/pull/2625)
- chore(deps): update dependency vite-plugin-inspect to v0.8.8 [`#2623`](https://github.com/pear-devs/pear-desktop/pull/2623)
- fix(deps): update dependency hono to v4.6.11 [`#2624`](https://github.com/pear-devs/pear-desktop/pull/2624)
- chore(deps): update playwright monorepo to v1.49.0 [`#2617`](https://github.com/pear-devs/pear-desktop/pull/2617)
- chore(deps): update dependency rollup to v4.27.3 [`#2610`](https://github.com/pear-devs/pear-desktop/pull/2610)
- chore(deps): update dependency typescript-eslint to v8.15.0 [`#2611`](https://github.com/pear-devs/pear-desktop/pull/2611)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.11.0 [`#2618`](https://github.com/pear-devs/pear-desktop/pull/2618)
- chore(deps): update dependency discord-api-types to v0.37.105 [`#2603`](https://github.com/pear-devs/pear-desktop/pull/2603)
- chore(deps): update dependency rollup to v4.27.2 [`#2604`](https://github.com/pear-devs/pear-desktop/pull/2604)
- chore(deps): update eslint monorepo to v9.15.0 [`#2607`](https://github.com/pear-devs/pear-desktop/pull/2607)
- fix(deps): update dependency @hono/zod-openapi to v0.17.1 [`#2608`](https://github.com/pear-devs/pear-desktop/pull/2608)
- fix(ambient-mode): fix ambient-mode overlapping other elements [`#2609`](https://github.com/pear-devs/pear-desktop/pull/2609)
- fix: Allow media playback control (MPRIS) for flatpak [`#2606`](https://github.com/pear-devs/pear-desktop/pull/2606)
- fix(deps): update dependency @hono/node-server to v1.13.7 [`#2598`](https://github.com/pear-devs/pear-desktop/pull/2598)
- chore(deps): update dependency rollup to v4.26.0 [`#2600`](https://github.com/pear-devs/pear-desktop/pull/2600)
- fix(deps): update dependency hono to v4.6.10 [`#2601`](https://github.com/pear-devs/pear-desktop/pull/2601)
- fix(deps): update dependency @hono/node-server to v1.13.6 [`#2594`](https://github.com/pear-devs/pear-desktop/pull/2594)
- chore(deps): update dependency vite to v5.4.11 [`#2595`](https://github.com/pear-devs/pear-desktop/pull/2595)
- chore(deps): update dependency typescript-eslint to v8.14.0 [`#2596`](https://github.com/pear-devs/pear-desktop/pull/2596)
- chore(deps): update dependency electron to v33.2.0 [`#2591`](https://github.com/pear-devs/pear-desktop/pull/2591)
- fix(deps): update dependency @hono/zod-openapi to v0.17.0 [`#2592`](https://github.com/pear-devs/pear-desktop/pull/2592)
- fix(deps): update dependency i18next to v23.16.5 [`#2589`](https://github.com/pear-devs/pear-desktop/pull/2589)
- fix(deps): update dependency @hono/node-server to v1.13.5 [`#2578`](https://github.com/pear-devs/pear-desktop/pull/2578)
- fix(deps): update dependency hono to v4.6.9 [`#2579`](https://github.com/pear-devs/pear-desktop/pull/2579)
- chore(deps): update dependency discord-api-types to v0.37.104 [`#2588`](https://github.com/pear-devs/pear-desktop/pull/2588)
- chore(deps): update dependency typescript-eslint to v8.13.0 [`#2581`](https://github.com/pear-devs/pear-desktop/pull/2581)
- chore(deps): update dependency rollup to v4.25.0 [`#2580`](https://github.com/pear-devs/pear-desktop/pull/2580)
- chore(docs): Update screenshot [`#2587`](https://github.com/pear-devs/pear-desktop/pull/2587)
- chore(docs): Specify full path to xattr for macOS, fixes #2583 [`#2586`](https://github.com/pear-devs/pear-desktop/pull/2586)
- fix: callback for time-changed event [`#2577`](https://github.com/pear-devs/pear-desktop/pull/2577)
- chore(deps): update eslint monorepo to v9.14.0 [`#2573`](https://github.com/pear-devs/pear-desktop/pull/2573)
- chore(deps): update dependency utf-8-validate to v6.0.5 [`#2572`](https://github.com/pear-devs/pear-desktop/pull/2572)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.10.1 [`#2571`](https://github.com/pear-devs/pear-desktop/pull/2571)
- fix(deps): update dependency @hono/node-server to v1.13.4 [`#2570`](https://github.com/pear-devs/pear-desktop/pull/2570)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.10.0 [`#2569`](https://github.com/pear-devs/pear-desktop/pull/2569)
- fix(deps): update dependency @floating-ui/dom to v1.6.12 [`#2568`](https://github.com/pear-devs/pear-desktop/pull/2568)
- chore(deps): update dependency rollup to v4.24.3 [`#2565`](https://github.com/pear-devs/pear-desktop/pull/2565)
- fix(deps): update dependency hono to v4.6.8 [`#2564`](https://github.com/pear-devs/pear-desktop/pull/2564)
- chore(deps): update dependency typescript-eslint to v8.12.2 [`#2563`](https://github.com/pear-devs/pear-desktop/pull/2563)
- chore(deps): update dependency typescript-eslint to v8.12.0 [`#2561`](https://github.com/pear-devs/pear-desktop/pull/2561)
- fix(deps): update dependency youtubei.js to v11 [`#2562`](https://github.com/pear-devs/pear-desktop/pull/2562)
- chore(deps): update dependency rollup to v4.24.2 [`#2559`](https://github.com/pear-devs/pear-desktop/pull/2559)
- fix(deps): update dependency @hono/node-server to v1.13.3 [`#2560`](https://github.com/pear-devs/pear-desktop/pull/2560)
- fix(deps): update dependency i18next to v23.16.4 [`#2550`](https://github.com/pear-devs/pear-desktop/pull/2550)
- chore(deps): update playwright monorepo to v1.48.2 [`#2551`](https://github.com/pear-devs/pear-desktop/pull/2551)
- fix(deps): update dependency hono to v4.6.7 [`#2552`](https://github.com/pear-devs/pear-desktop/pull/2552)
- chore(deps): update dependency @babel/runtime to v7.26.0 [`#2548`](https://github.com/pear-devs/pear-desktop/pull/2548)
- chore(deps): update dependency @types/color to v4 [`#2547`](https://github.com/pear-devs/pear-desktop/pull/2547)
- fix(deps): update dependency i18next to v23.16.3 [`#2545`](https://github.com/pear-devs/pear-desktop/pull/2545)
- fix(deps): update dependency solid-js to v1.9.3 [`#2541`](https://github.com/pear-devs/pear-desktop/pull/2541)
- chore(deps): update dependency vite to v5.4.10 [`#2542`](https://github.com/pear-devs/pear-desktop/pull/2542)
- chore(deps): update dependency electron to v33.0.2 [`#2537`](https://github.com/pear-devs/pear-desktop/pull/2537)
- chore(deps): update dependency @babel/runtime to v7.25.9 [`#2538`](https://github.com/pear-devs/pear-desktop/pull/2538)
- chore(deps): update dependency discord-api-types to v0.37.103 [`#2532`](https://github.com/pear-devs/pear-desktop/pull/2532)
- chore(deps): update dependency typescript-eslint to v8.11.0 [`#2534`](https://github.com/pear-devs/pear-desktop/pull/2534)
- fix(deps): update dependency hono to v4.6.6 [`#2536`](https://github.com/pear-devs/pear-desktop/pull/2536)
- fix(tuna-obs): Added song url to tuna-obs plugin [`#2524`](https://github.com/pear-devs/pear-desktop/pull/2524)
- fix(deps): update dependency i18next to v23.16.2 [`#2530`](https://github.com/pear-devs/pear-desktop/pull/2530)
- fix(deps): update dependency i18next to v23.16.1 [`#2529`](https://github.com/pear-devs/pear-desktop/pull/2529)
- chore(deps): update eslint monorepo to v9.13.0 [`#2528`](https://github.com/pear-devs/pear-desktop/pull/2528)
- chore(deps): update dependency typescript-eslint to v8.10.0 [`#2527`](https://github.com/pear-devs/pear-desktop/pull/2527)
- chore(deps): update playwright monorepo to v1.48.1 [`#2516`](https://github.com/pear-devs/pear-desktop/pull/2516)
- chore(deps): update dependency electron to v33.0.1 [`#2523`](https://github.com/pear-devs/pear-desktop/pull/2523)
- fix: disable gpu memory buffer video frames [`#2519`](https://github.com/pear-devs/pear-desktop/pull/2519)
- fix: use HEAD instead of GET in songInfo.imageSrc validation step [`#2766`](https://github.com/pear-devs/pear-desktop/issues/2766)
- fix: Fixed #1796 (#2736) [`#1796`](https://github.com/pear-devs/pear-desktop/issues/1796)
- fix(album-actions): Fixed #2312 (#2676) [`#2312`](https://github.com/pear-devs/pear-desktop/issues/2312) [`#2312`](https://github.com/pear-devs/pear-desktop/issues/2312)
- fix(music-player.css): Fixed #2514 (#2659) [`#2514`](https://github.com/pear-devs/pear-desktop/issues/2514)
- chore(docs): Specify full path to xattr for macOS, fixes #2583 (#2586) [`#2583`](https://github.com/pear-devs/pear-desktop/issues/2583)
- fix: fix pnpm-lock.yaml [`3208bf4`](https://github.com/pear-devs/pear-desktop/commit/3208bf4a6d47d824875b06bd031299694482f02d)
- Revert "feat: use swc and lightningcss" [`3b50cbc`](https://github.com/pear-devs/pear-desktop/commit/3b50cbcb6e3163115d52f05075af5d6f25b80660)
- feat: use swc and lightningcss [`ae3a289`](https://github.com/pear-devs/pear-desktop/commit/ae3a28900576ea388666747bc4794577e1d57e23)
#### [v3.6.2](https://github.com/pear-devs/pear-desktop/compare/v3.6.1...v3.6.2)
> 16 October 2024
- fix(deps): update dependency serve to v14.2.4 [`#2515`](https://github.com/pear-devs/pear-desktop/pull/2515)
- fix(deps): update dependency hono to v4.6.5 [`#2509`](https://github.com/pear-devs/pear-desktop/pull/2509)
- chore(deps): update dependency vite to v5.4.9 [`#2500`](https://github.com/pear-devs/pear-desktop/pull/2500)
- fix(api-server): properly implement next api call [`#2505`](https://github.com/pear-devs/pear-desktop/pull/2505)
- chore(deps): update dependency electron to v33 [`#2507`](https://github.com/pear-devs/pear-desktop/pull/2507)
- chore(deps): update dependency typescript-eslint to v8.9.0 [`#2503`](https://github.com/pear-devs/pear-desktop/pull/2503)
- chore(deps): update dependency discord-api-types to v0.37.102 [`#2501`](https://github.com/pear-devs/pear-desktop/pull/2501)
- fix: trustedTypes issue [`#2339`](https://github.com/pear-devs/pear-desktop/issues/2339)
- chore(i18n): Translated using Weblate (Icelandic) [`5f79b7e`](https://github.com/pear-devs/pear-desktop/commit/5f79b7e788c47b0a27a4967c9f3a9e20b483cd75)
- chore(i18n): Translated using Weblate (Chinese (Traditional Han script)) [`12d6939`](https://github.com/pear-devs/pear-desktop/commit/12d693921e26a5c54015673a404e005d1a7175a4)
- chore(i18n): Translated using Weblate (Ukrainian) [`836cedb`](https://github.com/pear-devs/pear-desktop/commit/836cedb0f317b74bf2fc3ec2d1aa865719f46ec0)
#### [v3.6.1](https://github.com/pear-devs/pear-desktop/compare/v3.6.0...v3.6.1)
> 14 October 2024
- fix(api-server): Various fixes and improvements [`#2496`](https://github.com/pear-devs/pear-desktop/pull/2496)
- fix(deps): update dependency electron-debug to v4.1.0 [`#2499`](https://github.com/pear-devs/pear-desktop/pull/2499)
- fix(renderer): fix force like buttons display logic [`#2493`](https://github.com/pear-devs/pear-desktop/pull/2493)
- fix(deps): update dependency i18next to v23.16.0 [`#2492`](https://github.com/pear-devs/pear-desktop/pull/2492)
- fix(downloader): fix #2371 [`#2371`](https://github.com/pear-devs/pear-desktop/issues/2371)
- fix(ytm-bugs): incorrect video ratio [`#2459`](https://github.com/pear-devs/pear-desktop/issues/2459)
- fix(api-server): fix init/authentication error [`#2497`](https://github.com/pear-devs/pear-desktop/issues/2497)
- fix: RSS feed CORS issue [`#1620`](https://github.com/pear-devs/pear-desktop/issues/1620)
- chore(flatpak-builder): Add more details when failing [`d3acb49`](https://github.com/pear-devs/pear-desktop/commit/d3acb4945a8dcde6598c53d8207bbf16eda8c739)
- chore(i18n): Translated using Weblate (Filipino) [`e428708`](https://github.com/pear-devs/pear-desktop/commit/e4287085a11f30d141148ab0432cc684819fd0d0)
- Bump version to 3.6.1 [`b668730`](https://github.com/pear-devs/pear-desktop/commit/b6687307dfe7ef765517019093c8db3c2ad14417)
#### [v3.6.0](https://github.com/pear-devs/pear-desktop/compare/v3.5.3...v3.6.0)
> 13 October 2024
- feat(api-server): remote control api [`#1909`](https://github.com/pear-devs/pear-desktop/pull/1909)
- chore(deps): update playwright monorepo to v1.48.0 [`#2489`](https://github.com/pear-devs/pear-desktop/pull/2489)
- fix(`synced-lyrics`): Fix 2 issues [`#2441`](https://github.com/pear-devs/pear-desktop/pull/2441)
- chore(deps): update dependency typescript to v5.6.3 [`#2486`](https://github.com/pear-devs/pear-desktop/pull/2486)
- chore(deps): update dependency electron to v32.2.0 [`#2487`](https://github.com/pear-devs/pear-desktop/pull/2487)
- chore(deps): update dependency del-cli to v6 [`#2475`](https://github.com/pear-devs/pear-desktop/pull/2475)
- chore(deps): update dependency typescript-eslint to v8.8.1 [`#2477`](https://github.com/pear-devs/pear-desktop/pull/2477)
- fix(deps): update dependency solid-js to v1.9.2 [`#2480`](https://github.com/pear-devs/pear-desktop/pull/2480)
- Revert "chore(deps): update dependency electron-builder to v25" [`#2488`](https://github.com/pear-devs/pear-desktop/pull/2488)
- chore(deps): update dependency electron-builder to v25 [`#2406`](https://github.com/pear-devs/pear-desktop/pull/2406)
- fix(deps): update dependency deepmerge-ts to v7.1.3 [`#2481`](https://github.com/pear-devs/pear-desktop/pull/2481)
- fix(deps): update dependency ts-morph to v24 [`#2474`](https://github.com/pear-devs/pear-desktop/pull/2474)
- fix(deps): update dependency i18next to v23.15.2 [`#2471`](https://github.com/pear-devs/pear-desktop/pull/2471)
- chore(deps): update eslint monorepo to v9.12.0 [`#2470`](https://github.com/pear-devs/pear-desktop/pull/2470)
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.9.0 [`#2469`](https://github.com/pear-devs/pear-desktop/pull/2469)
- chore(deps): bump micromatch from 4.0.5 to 4.0.8 [`#2465`](https://github.com/pear-devs/pear-desktop/pull/2465)
- chore(deps): bump braces from 3.0.2 to 3.0.3 [`#2466`](https://github.com/pear-devs/pear-desktop/pull/2466)
- fix(deps): update dependency electron-updater to v6.3.9 [`#2468`](https://github.com/pear-devs/pear-desktop/pull/2468)
- fix(deps): update dependency deepmerge-ts to v7.1.1 [`#2467`](https://github.com/pear-devs/pear-desktop/pull/2467)
- chore(deps): update dependency typescript-eslint to v8.8.0 [`#2457`](https://github.com/pear-devs/pear-desktop/pull/2457)
- chore(deps): update dependency @babel/runtime to v7.25.7 [`#2462`](https://github.com/pear-devs/pear-desktop/pull/2462)
- chore(deps): update dependency rollup to v4.24.0 [`#2458`](https://github.com/pear-devs/pear-desktop/pull/2458)
- chore(deps): update dependency eslint-plugin-import to v2.31.0 [`#2464`](https://github.com/pear-devs/pear-desktop/pull/2464)
- chore(deps): update dependency rollup to v4.22.5 [`#2448`](https://github.com/pear-devs/pear-desktop/pull/2448)
- chore(deps): update dependency typescript-eslint to v8.7.0 [`#2450`](https://github.com/pear-devs/pear-desktop/pull/2450)
- fix(deps): update dependency solid-js to v1.9.1 [`#2451`](https://github.com/pear-devs/pear-desktop/pull/2451)
- chore(deps): update dependency vite to v5.4.8 [`#2449`](https://github.com/pear-devs/pear-desktop/pull/2449)
- chore(deps): update dependency discord-api-types to v0.37.101 [`#2440`](https://github.com/pear-devs/pear-desktop/pull/2440)
- chore(deps): update dependency esbuild to v0.24.0 [`#2439`](https://github.com/pear-devs/pear-desktop/pull/2439)
- chore(deps): update eslint monorepo to v9.11.1 [`#2442`](https://github.com/pear-devs/pear-desktop/pull/2442)
- chore(deps): update dependency @types/howler to v2.2.12 [`#2443`](https://github.com/pear-devs/pear-desktop/pull/2443)
- chore(deps): update dependency vite to v5.4.7 [`#2434`](https://github.com/pear-devs/pear-desktop/pull/2434)
- chore(deps): update playwright monorepo to v1.47.2 [`#2436`](https://github.com/pear-devs/pear-desktop/pull/2436)
- chore(deps): update eslint monorepo to v9.11.0 [`#2437`](https://github.com/pear-devs/pear-desktop/pull/2437)
- fix(deps): update dependency youtubei.js to v10.5.0 [`#2431`](https://github.com/pear-devs/pear-desktop/pull/2431)
- chore(deps): update dependency rollup to v4.22.4 [`#2430`](https://github.com/pear-devs/pear-desktop/pull/2430)
- chore(deps): update dependency electron to v32.1.2 [`#2433`](https://github.com/pear-devs/pear-desktop/pull/2433)
- feat: ESLint Flat Config (v9 support #2229) [`#2426`](https://github.com/pear-devs/pear-desktop/pull/2426)
- fix(taskbar-mediacontrol): fix icon color [`#2485`](https://github.com/pear-devs/pear-desktop/issues/2485)
- chore(eslint): apply eslint-plugin-prettier [`#2438`](https://github.com/pear-devs/pear-desktop/issues/2438)
- fix: apply fix from eslint [`cb1381b`](https://github.com/pear-devs/pear-desktop/commit/cb1381bbb394e2bbb404f44817ef96411dabc8a9)
- chore(i18n): Translated using Weblate (Portuguese (Brazil)) [`bcff26c`](https://github.com/pear-devs/pear-desktop/commit/bcff26c85b18258806f3960309776bc860c3a54e)
- chore(i18n): Translated using Weblate (Persian) [`ead448e`](https://github.com/pear-devs/pear-desktop/commit/ead448ed98095339557903eb0f84c4a6d0f32058)
#### [v3.5.3](https://github.com/pear-devs/pear-desktop/compare/v3.5.2...v3.5.3)
> 17 September 2024
- fix: fix `trustedHTML` issue [`#2339`](https://github.com/pear-devs/pear-desktop/issues/2339)
- chore(deps): update dependency rollup to v4.21.3 [`6edc84a`](https://github.com/pear-devs/pear-desktop/commit/6edc84a8bd6c7e009041117ba0d2004783eb3a47)
- chore(deps): update typescript-eslint monorepo to v8.6.0 [`d4c8a43`](https://github.com/pear-devs/pear-desktop/commit/d4c8a4320d733f7bddc4dcd1de93644790e71d66)
- chore(deps): update dependency eslint to v8.57.1 [`02b7a39`](https://github.com/pear-devs/pear-desktop/commit/02b7a39753528cfd8c0d107d6d2ec6ef78c5afe7)
#### [v3.5.2](https://github.com/pear-devs/pear-desktop/compare/v3.5.1...v3.5.2)
> 7 September 2024
- chore(deps): update typescript-eslint monorepo to v8.4.0 [`#2401`](https://github.com/pear-devs/pear-desktop/pull/2401)
- chore(deps): update dependency @total-typescript/ts-reset to v0.6.1 [`#2396`](https://github.com/pear-devs/pear-desktop/pull/2396)
- chore(deps): update dependency electron to v31.5.0 [`#2397`](https://github.com/pear-devs/pear-desktop/pull/2397)
- chore(deps): update dependency eslint-import-resolver-typescript to v3.6.3 [`#2376`](https://github.com/pear-devs/pear-desktop/pull/2376)
- chore(deps): update dependency discord-api-types to v0.37.100 [`#2394`](https://github.com/pear-devs/pear-desktop/pull/2394)
- fix(deps): update dependency electron-updater to v6.3.4 [`#2395`](https://github.com/pear-devs/pear-desktop/pull/2395)
- chore(deps): update dependency @babel/runtime to v7.25.6 [`#2388`](https://github.com/pear-devs/pear-desktop/pull/2388)
- chore(deps): update dependency vite-plugin-inspect to v0.8.7 [`#2389`](https://github.com/pear-devs/pear-desktop/pull/2389)
- chore(deps): update dependency discord-api-types to v0.37.99 [`#2374`](https://github.com/pear-devs/pear-desktop/pull/2374)
- chore(deps): update dependency vite to v5.4.3 [`#2377`](https://github.com/pear-devs/pear-desktop/pull/2377)
- fix: incorrect regex when splitting artistName [`#2378`](https://github.com/pear-devs/pear-desktop/pull/2378)
- chore(deps): update dependency @babel/runtime to v7.25.4 [`#2373`](https://github.com/pear-devs/pear-desktop/pull/2373)
- synced-lyrics: make the lyrics search more reliable [`#2343`](https://github.com/pear-devs/pear-desktop/pull/2343)
- fix(deps): update dependency solid-js to v1.8.22 [`#2354`](https://github.com/pear-devs/pear-desktop/pull/2354)
- chore(deps): update typescript-eslint monorepo to v8.3.0 [`#2350`](https://github.com/pear-devs/pear-desktop/pull/2350)
- fix(deps): update dependency electron-debug to v4.0.1 [`#2349`](https://github.com/pear-devs/pear-desktop/pull/2349)
- chore(deps): update dependency electron to v31.4.0 [`#2356`](https://github.com/pear-devs/pear-desktop/pull/2356)
- fix: hide native-controls on linux when in-app-menu is used [`#2366`](https://github.com/pear-devs/pear-desktop/pull/2366)
- fix: detect the upgrade btn using the icon [`#2364`](https://github.com/pear-devs/pear-desktop/pull/2364)
- fix: exclude build-id files from rpm [`#2361`](https://github.com/pear-devs/pear-desktop/pull/2361)
- fix(deps): update dependency i18next to v23.12.3 [`#2352`](https://github.com/pear-devs/pear-desktop/pull/2352)
- fix(deps): update dependency @floating-ui/dom to v1.6.10 [`#2340`](https://github.com/pear-devs/pear-desktop/pull/2340)
- fix(deps): update dependency electron-updater to v6.3.3 [`#2347`](https://github.com/pear-devs/pear-desktop/pull/2347)
- fix(deps): update dependency solid-js to v1.8.20 [`#2345`](https://github.com/pear-devs/pear-desktop/pull/2345)
- chore(deps): update dependency vite to v5.4.0 [`#2342`](https://github.com/pear-devs/pear-desktop/pull/2342)
- chore(deps): update typescript-eslint monorepo to v8.0.1 [`#2335`](https://github.com/pear-devs/pear-desktop/pull/2335)
- fix(deps): update dependency @floating-ui/dom to v1.6.9 [`#2337`](https://github.com/pear-devs/pear-desktop/pull/2337)
- chore(deps): update playwright monorepo to v1.46.0 [`#2336`](https://github.com/pear-devs/pear-desktop/pull/2336)
- chore(README): Translation README to Russian and adding Synced Lyrics to main README [`#2338`](https://github.com/pear-devs/pear-desktop/pull/2338)
- chore(deps): update dependency rollup to v4.20.0 [`#2326`](https://github.com/pear-devs/pear-desktop/pull/2326)
- fix(synced-lyric): fix timestamp [`#2323`](https://github.com/pear-devs/pear-desktop/issues/2323) [`#2379`](https://github.com/pear-devs/pear-desktop/issues/2379)
- Revert "fix(MPRIS): Prevents player to start with invalid MPRIS interface (#1996)" [`#2225`](https://github.com/pear-devs/pear-desktop/issues/2225)
- fix(adblocker/inplayer): fix Response.prototype.json [`#2310`](https://github.com/pear-devs/pear-desktop/issues/2310)
- chore(deps): update dependency eslint-plugin-import to v2.30.0 [`f48e46d`](https://github.com/pear-devs/pear-desktop/commit/f48e46d29cf09c76c5172fd56d2d0f705616e4e3)
- Revert "chore(deps): update dependency electron-builder to v25" [`089eff3`](https://github.com/pear-devs/pear-desktop/commit/089eff3152903c8b55ad3e5571b944062a647e27)
- chore(deps): update dependency electron-builder to v25 [`fe4c89c`](https://github.com/pear-devs/pear-desktop/commit/fe4c89c349bb9f4f54d95c2018943095ccfdab0c)
#### [v3.5.1](https://github.com/pear-devs/pear-desktop/compare/v3.5.0...v3.5.1)
> 1 August 2024
- fix(deps): update dependency youtubei.js to v10.3.0 [`#2306`](https://github.com/pear-devs/pear-desktop/pull/2306)
- fix: Window gets stuck offscreen in some instances [`#2303`](https://github.com/pear-devs/pear-desktop/pull/2303)
- fix: Incorrect window size on multi-monitor scaled displays [`#2302`](https://github.com/pear-devs/pear-desktop/pull/2302)
- chore(deps): update dependency rollup to v4.19.2 [`#2304`](https://github.com/pear-devs/pear-desktop/pull/2304)
- chore(deps): update typescript-eslint monorepo to v8 (major) [`#2297`](https://github.com/pear-devs/pear-desktop/pull/2297)
- fix(ambient-mode): fix ambient-mode not working for videos after restart [`#2294`](https://github.com/pear-devs/pear-desktop/pull/2294)
- fix(deps): update dependency @xhayper/discord-rpc to v1.2.0 [`#2291`](https://github.com/pear-devs/pear-desktop/pull/2291)
- fix(synced-lyrics): fix lyric load [`#2295`](https://github.com/pear-devs/pear-desktop/issues/2295)
- fix(ambient-mode): fix ambient-mode not working for videos after restart (#2294) [`#1641`](https://github.com/pear-devs/pear-desktop/issues/1641)
- fix(synced-lyrics): fix i18n [`8750b54`](https://github.com/pear-devs/pear-desktop/commit/8750b54f766c735ff039c6be454427f17d4737e2)
- ts-fix: disambiguate ElectronStore typings [`8775735`](https://github.com/pear-devs/pear-desktop/commit/877573532c1b68af861a3fdc44d093f3097d36ab)
- chore(i18n): Translated using Weblate (Hungarian) [`3537dc1`](https://github.com/pear-devs/pear-desktop/commit/3537dc19eecce7f7deb2478942f70d3c7b72148d)
#### [v3.5.0](https://github.com/pear-devs/pear-desktop/compare/v3.4.1...v3.5.0)
> 31 July 2024
- plugin: Synced Lyrics [`#2207`](https://github.com/pear-devs/pear-desktop/pull/2207)
- chore(deps): update dependency electron to v31.3.1 [`#2290`](https://github.com/pear-devs/pear-desktop/pull/2290)
- chore(deps): update typescript-eslint monorepo to v7.18.0 [`#2292`](https://github.com/pear-devs/pear-desktop/pull/2292)
- fix(deps): update dependency youtubei.js to v10.2.0 [`#2285`](https://github.com/pear-devs/pear-desktop/pull/2285)
- chore(deps): update dependency electron to v31.3.0 [`#2282`](https://github.com/pear-devs/pear-desktop/pull/2282)
- chore(deps): update typescript-eslint monorepo to v7.17.0 [`#2283`](https://github.com/pear-devs/pear-desktop/pull/2283)
- fix(deps): update dependency solid-js to v1.8.19 [`#2280`](https://github.com/pear-devs/pear-desktop/pull/2280)
- fix(deps): update dependency @xhayper/discord-rpc to v1.1.4 [`#2279`](https://github.com/pear-devs/pear-desktop/pull/2279)
- chore(deps): update dependency @babel/runtime to v7.25.0 [`#2281`](https://github.com/pear-devs/pear-desktop/pull/2281)
- fix(deps): update dependency @floating-ui/dom to v1.6.8 [`#2278`](https://github.com/pear-devs/pear-desktop/pull/2278)
- Fix: Incorrect window size on scaled displays [`#2258`](https://github.com/pear-devs/pear-desktop/pull/2258)
- chore(deps): update dependency vite-plugin-resolve to v2.5.2 [`#2276`](https://github.com/pear-devs/pear-desktop/pull/2276)
- chore(deps): update playwright monorepo to v1.45.3 [`#2277`](https://github.com/pear-devs/pear-desktop/pull/2277)
- fix(deps): update dependency deepmerge-ts to v7.1.0 [`#2263`](https://github.com/pear-devs/pear-desktop/pull/2263)
- chore(deps): update dependency typescript to v5.5.4 [`#2274`](https://github.com/pear-devs/pear-desktop/pull/2274)
- chore(deps): update dependency vite to v5.3.5 [`#2275`](https://github.com/pear-devs/pear-desktop/pull/2275)
- fix(deps): update dependency i18next to v23.12.2 [`#2260`](https://github.com/pear-devs/pear-desktop/pull/2260)
- chore(deps): update dependency discord-api-types to v0.37.93 [`#2273`](https://github.com/pear-devs/pear-desktop/pull/2273)
- chore(deps): update dependency rollup to v4.19.1 [`#2261`](https://github.com/pear-devs/pear-desktop/pull/2261)
- fix(deps): update dependency custom-electron-prompt to v1.5.8 [`#2262`](https://github.com/pear-devs/pear-desktop/pull/2262)
- feat(adblocker): add new option AdSpeedup [`#2235`](https://github.com/pear-devs/pear-desktop/pull/2235)
- fix: disable multi-plane format for software video [`#2254`](https://github.com/pear-devs/pear-desktop/pull/2254)
- chore(deps): update dependency eslint-plugin-prettier to v5.2.1 [`#2253`](https://github.com/pear-devs/pear-desktop/pull/2253)
- chore(deps): update dependency vite to v5.3.4 [`#2243`](https://github.com/pear-devs/pear-desktop/pull/2243)
- chore(deps): update typescript-eslint monorepo to v7.16.1 [`#2239`](https://github.com/pear-devs/pear-desktop/pull/2239)
- chore(deps): update playwright monorepo to v1.45.2 [`#2244`](https://github.com/pear-devs/pear-desktop/pull/2244)
- chore(deps): update dependency vite-plugin-inspect to v0.8.5 [`#2252`](https://github.com/pear-devs/pear-desktop/pull/2252)
- fix(deps): update dependency semver to v7.6.3 [`#2250`](https://github.com/pear-devs/pear-desktop/pull/2250)
- chore(deps): update dependency electron to v31.2.1 [`#2241`](https://github.com/pear-devs/pear-desktop/pull/2241)
- chore(i18n): Translated using Weblate (Catalan) [`4a8440c`](https://github.com/pear-devs/pear-desktop/commit/4a8440c281c341977ab3687982cec8cbc5af6cf7)
- Update changelog for v3.4.1 [`18e0b1b`](https://github.com/pear-devs/pear-desktop/commit/18e0b1b86341b13f1cbc713bfbd7b5d7a45ee392)
- fix(synced-lyrics): fix type error [`9357a15`](https://github.com/pear-devs/pear-desktop/commit/9357a15116a8526d22ba6142c0a02f31688743f2)
#### [v3.4.1](https://github.com/pear-devs/pear-desktop/compare/v3.4.0...v3.4.1)
> 15 July 2024
- fix(mpris): fix mpris position [`#2225`](https://github.com/pear-devs/pear-desktop/issues/2225)
- fix(deb): fix depends [`#1983`](https://github.com/pear-devs/pear-desktop/issues/1983)
- fix: fix touchbar icon [`#2183`](https://github.com/pear-devs/pear-desktop/issues/2183)
- fix: fix "Starting page" [`#1822`](https://github.com/pear-devs/pear-desktop/issues/1822)
- fix: fix album actions [`#2202`](https://github.com/pear-devs/pear-desktop/issues/2202)
- fix: fix playback slider [`#2045`](https://github.com/pear-devs/pear-desktop/issues/2045)
- chore(i18n): Translated using Weblate (Spanish) [`91bee48`](https://github.com/pear-devs/pear-desktop/commit/91bee4880ed2c6fdd887814a2620877d89bea311)
- Bump version to 3.4.1 [`02e2fb6`](https://github.com/pear-devs/pear-desktop/commit/02e2fb6a83844f439f760e72cdcb935b86000df2)
#### [v3.4.0](https://github.com/pear-devs/pear-desktop/compare/v3.3.12...v3.4.0)
> 14 July 2024
- fix(deps): update dependency i18next to v23.12.1 [`#2230`](https://github.com/pear-devs/pear-desktop/pull/2230)
- feat(downloader): New option to download on finish [`#1964`](https://github.com/pear-devs/pear-desktop/pull/1964)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.42 [`#2228`](https://github.com/pear-devs/pear-desktop/pull/2228)
- chore(deps): update dependency eslint to v9.7.0 [`#2226`](https://github.com/pear-devs/pear-desktop/pull/2226)
- chore(deps): update dependency @babel/runtime to v7.24.8 [`#2221`](https://github.com/pear-devs/pear-desktop/pull/2221)
- chore(deps): update dependency node-gyp to v10.2.0 [`#2216`](https://github.com/pear-devs/pear-desktop/pull/2216)
- chore(deps): update dependency ws to v8.18.0 [`#2217`](https://github.com/pear-devs/pear-desktop/pull/2217)
- chore(deps): update dependency glob to v11 [`#2219`](https://github.com/pear-devs/pear-desktop/pull/2219)
- chore(deps): update dependency esbuild to v0.23.0 [`#2215`](https://github.com/pear-devs/pear-desktop/pull/2215)
- chore(deps): update dependency electron to v31.2.0 [`#2214`](https://github.com/pear-devs/pear-desktop/pull/2214)
- fix(deps): update dependency youtubei.js to v10.1.0 [`#2218`](https://github.com/pear-devs/pear-desktop/pull/2218)
- chore(deps): update playwright monorepo to v1.45.1 [`#2212`](https://github.com/pear-devs/pear-desktop/pull/2212)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.41 [`#2213`](https://github.com/pear-devs/pear-desktop/pull/2213)
- chore(deps): update dependency rollup to v4.18.1 [`#2210`](https://github.com/pear-devs/pear-desktop/pull/2210)
- chore(deps): update dependency eslint to v9.6.0 [`#2192`](https://github.com/pear-devs/pear-desktop/pull/2192)
- chore(deps): update dependency vite to v5.3.3 [`#2211`](https://github.com/pear-devs/pear-desktop/pull/2211)
- chore(deps): update dependency glob to v10.4.5 [`#2205`](https://github.com/pear-devs/pear-desktop/pull/2205)
- chore(deps): update dependency discord-api-types to v0.37.92 [`#2204`](https://github.com/pear-devs/pear-desktop/pull/2204)
- fix(deps): update dependency solid-js to v1.8.18 [`#2189`](https://github.com/pear-devs/pear-desktop/pull/2189)
- chore(deps): update dependency typescript to v5.5.3 [`#2206`](https://github.com/pear-devs/pear-desktop/pull/2206)
- chore(deps): update dependency electron to v31.1.0 [`#2190`](https://github.com/pear-devs/pear-desktop/pull/2190)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.40 [`#2193`](https://github.com/pear-devs/pear-desktop/pull/2193)
- fix(deps): update dependency @floating-ui/dom to v1.6.7 [`#2196`](https://github.com/pear-devs/pear-desktop/pull/2196)
- chore(deps): update dependency vite to v5.3.2 [`#2188`](https://github.com/pear-devs/pear-desktop/pull/2188)
- chore(deps): update dependency discord-api-types to v0.37.91 [`#2187`](https://github.com/pear-devs/pear-desktop/pull/2187)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.34 [`#2184`](https://github.com/pear-devs/pear-desktop/pull/2184)
- fix(deps): update dependency @floating-ui/dom to v1.6.6 [`#2182`](https://github.com/pear-devs/pear-desktop/pull/2182)
- chore(deps): update playwright monorepo to v1.45.0 [`#2181`](https://github.com/pear-devs/pear-desktop/pull/2181)
- fix(deps): update dependency ts-morph to v23 [`#2180`](https://github.com/pear-devs/pear-desktop/pull/2180)
- chore(deps): update dependency electron-vite to v2.3.0 [`#2178`](https://github.com/pear-devs/pear-desktop/pull/2178)
- fix(deps): update dependency conf to v13.0.1 [`#2175`](https://github.com/pear-devs/pear-desktop/pull/2175)
- chore(deps): update dependency glob to v10.4.2 [`#2168`](https://github.com/pear-devs/pear-desktop/pull/2168)
- chore(deps): update dependency discord-api-types to v0.37.90 [`#2167`](https://github.com/pear-devs/pear-desktop/pull/2167)
- chore(deps): update dependency typescript to v5.5.2 [`#2173`](https://github.com/pear-devs/pear-desktop/pull/2173)
- chore(deps): update dependency electron to v31.0.2 [`#2170`](https://github.com/pear-devs/pear-desktop/pull/2170)
- chore(deps): update dependency ws to v8.17.1 [`#2164`](https://github.com/pear-devs/pear-desktop/pull/2164)
- chore(deps): update dependency eslint to v9.5.0 [`#2162`](https://github.com/pear-devs/pear-desktop/pull/2162)
- fix(deps): update dependency youtubei.js to v10 [`#2136`](https://github.com/pear-devs/pear-desktop/pull/2136)
- chore(deps): update dependency discord-api-types to v0.37.89 [`#2153`](https://github.com/pear-devs/pear-desktop/pull/2153)
- chore(deps): update dependency vite to v5.3.1 [`#2154`](https://github.com/pear-devs/pear-desktop/pull/2154)
- fix(deps): update dependency electron-store to v10 [`#2157`](https://github.com/pear-devs/pear-desktop/pull/2157)
- fix(deps): update dependency conf to v13 [`#2156`](https://github.com/pear-devs/pear-desktop/pull/2156)
- chore(deps): update dependency electron to v31.0.1 [`#2148`](https://github.com/pear-devs/pear-desktop/pull/2148)
- chore(deps): update dependency discord-api-types to v0.37.88 [`#2138`](https://github.com/pear-devs/pear-desktop/pull/2138)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.30 [`#2139`](https://github.com/pear-devs/pear-desktop/pull/2139)
- chore(deps): update dependency electron to v31 [`#2141`](https://github.com/pear-devs/pear-desktop/pull/2141)
- chore(deps): update dependency esbuild to v0.21.5 [`#2135`](https://github.com/pear-devs/pear-desktop/pull/2135)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.29 [`#2132`](https://github.com/pear-devs/pear-desktop/pull/2132)
- fix: rollback eslint version to v8 [`45931a2`](https://github.com/pear-devs/pear-desktop/commit/45931a25b08ab8a406f9e102486585311fd14bf9)
- chore(i18n): Translated using Weblate (Filipino) [`8a20566`](https://github.com/pear-devs/pear-desktop/commit/8a20566e0f2736f72d46282188ada69df1d7076a)
- chore(i18n): Translated using Weblate (Slovenian) [`40f0b9b`](https://github.com/pear-devs/pear-desktop/commit/40f0b9b852dcd9146e1c1e6c741b5baaf55ac079)
#### [v3.3.12](https://github.com/pear-devs/pear-desktop/compare/v3.3.11...v3.3.12)
> 8 June 2024
- hotfix: Revert "chore(deps): update dependencies `@cliqz/adblocker-electron`, `@cliqz/adblocker-electron-preload`" [`3c4abc1`](https://github.com/pear-devs/pear-desktop/commit/3c4abc14187e51f7e47c1ae71b3513f6d8c9912a)
- Update changelog for v3.3.11 [`de22444`](https://github.com/pear-devs/pear-desktop/commit/de224444c2a6d9030aa22a3b263ceacbc4b41914)
- Bump version to 3.3.12 [`89ed7d2`](https://github.com/pear-devs/pear-desktop/commit/89ed7d2345001fea59514944f4c1d56d2b7bd888)
#### [v3.3.11](https://github.com/pear-devs/pear-desktop/compare/v3.3.10...v3.3.11)
> 8 June 2024
- Revert "fix(deps): update dependency @cliqz/adblocker-electron to v1.27.10" [`#2129`](https://github.com/pear-devs/pear-desktop/pull/2129)
- chore(deps): update dependency vite to v5.2.13 [`#2127`](https://github.com/pear-devs/pear-desktop/pull/2127)
- chore(deps): update dependency electron to v30.1.0 [`#2126`](https://github.com/pear-devs/pear-desktop/pull/2126)
- fix(deps): update dependency deepmerge-ts to v7.0.3 [`#2125`](https://github.com/pear-devs/pear-desktop/pull/2125)
- chore(deps): update dependency @babel/runtime to v7.24.7 [`#2124`](https://github.com/pear-devs/pear-desktop/pull/2124)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.28 [`#2121`](https://github.com/pear-devs/pear-desktop/pull/2121)
- fix(deps): update dependency electron-updater to v6.2.1 [`#2120`](https://github.com/pear-devs/pear-desktop/pull/2120)
- chore(deps): update dependency discord-api-types to v0.37.87 [`#2119`](https://github.com/pear-devs/pear-desktop/pull/2119)
- fix(deps): update dependency deepmerge-ts to v7.0.2 [`#2118`](https://github.com/pear-devs/pear-desktop/pull/2118)
- chore(deps): update typescript-eslint monorepo to v8.0.0-alpha.25 [`#2114`](https://github.com/pear-devs/pear-desktop/pull/2114)
- fix(menu): fix menubar items doesn't rendered [`#2113`](https://github.com/pear-devs/pear-desktop/issues/2113)
- chore(i18n): Translated using Weblate (Nepali) [`4ae9a28`](https://github.com/pear-devs/pear-desktop/commit/4ae9a2820e9d453635094956264dd8b42c4997f7)
- chore(i18n): Translated using Weblate (Nepali) [`7e8d311`](https://github.com/pear-devs/pear-desktop/commit/7e8d31172ceb175ba07f307d248fc1246265a4c0)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.10 [`d97aa1a`](https://github.com/pear-devs/pear-desktop/commit/d97aa1a8a003f15eea63c8cb2dabd0f215e885f1)
#### [v3.3.10](https://github.com/pear-devs/pear-desktop/compare/v3.3.9...v3.3.10)
> 2 June 2024
- fix(adblocker): fix blank screen [`#2103`](https://github.com/pear-devs/pear-desktop/issues/2103) [`#2105`](https://github.com/pear-devs/pear-desktop/issues/2105)
- chore(i18n): Translated using Weblate (Hungarian) [`25958a7`](https://github.com/pear-devs/pear-desktop/commit/25958a7bb1fea20e59676e7821f3dd8819602b68)
- fix(deps): bump deps [`4fa9762`](https://github.com/pear-devs/pear-desktop/commit/4fa9762a506544ce453894ce2df13033225e6c7d)
- fix(deps): bump `@typescript-eslint/eslint-plugin` version to 8.0.0-alpha.24 [`1e5bea8`](https://github.com/pear-devs/pear-desktop/commit/1e5bea85b31da5de868d9eff8758e5d2d888c2c8)
#### [v3.3.9](https://github.com/pear-devs/pear-desktop/compare/v3.3.8...v3.3.9)
> 1 June 2024
- chore(deps): update dependency eslint to v9.4.0 [`#2106`](https://github.com/pear-devs/pear-desktop/pull/2106)
- fix(adblocker): fix In-Player adblocker [`#1817`](https://github.com/pear-devs/pear-desktop/issues/1817)
- feat(adblocker): improve In-Player adblocker [`5b9e947`](https://github.com/pear-devs/pear-desktop/commit/5b9e947b8feebb57d9a2122ae7b7ab2ff7c37c06)
- chore(i18n): Translated using Weblate (French) [`9e809b0`](https://github.com/pear-devs/pear-desktop/commit/9e809b002d10f6ec0202a7d56d3d0b73f8093012)
- chore(i18n): Translated using Weblate (Malay) [`79151cb`](https://github.com/pear-devs/pear-desktop/commit/79151cb3aa6c087b8d8bb500322f505797b822bd)
#### [v3.3.8](https://github.com/pear-devs/pear-desktop/compare/v3.3.7...v3.3.8)
> 1 June 2024
- fix(adblocker): fix blank screen [`#1942`](https://github.com/pear-devs/pear-desktop/issues/1942) [`#2100`](https://github.com/pear-devs/pear-desktop/issues/2100) [`#2103`](https://github.com/pear-devs/pear-desktop/issues/2103)
- Update changelog for v3.3.7 [`b572623`](https://github.com/pear-devs/pear-desktop/commit/b572623442fc8b45b593dc0c91623fbf814115b4)
- Bump version to 3.3.8 [`5d99a85`](https://github.com/pear-devs/pear-desktop/commit/5d99a854e2f29bdb6682beeffa4e6b9b8be0f60f)
#### [v3.3.7](https://github.com/pear-devs/pear-desktop/compare/v3.3.6...v3.3.7)
> 1 June 2024
- chore(deps): update dependency electron to v30.0.9 [`#2098`](https://github.com/pear-devs/pear-desktop/pull/2098)
- Revert "fix(deps): update dependency @cliqz/adblocker-electron to v1.27.6" [`#2101`](https://github.com/pear-devs/pear-desktop/pull/2101)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.6 [`#2096`](https://github.com/pear-devs/pear-desktop/pull/2096)
- chore(deps): update dependency discord-api-types to v0.37.86 [`#2092`](https://github.com/pear-devs/pear-desktop/pull/2092)
- chore(deps): update dependency vite to v5.2.12 [`#2094`](https://github.com/pear-devs/pear-desktop/pull/2094)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.11.0 [`#2093`](https://github.com/pear-devs/pear-desktop/pull/2093)
- chore(docs): Added README-es.md and linked to README.md [`#2090`](https://github.com/pear-devs/pear-desktop/pull/2090)
- fix(deps): update dependency deepmerge-ts to v7 [`#2085`](https://github.com/pear-devs/pear-desktop/pull/2085)
- chore(deps): update dependency builtin-modules to v4 [`#2084`](https://github.com/pear-devs/pear-desktop/pull/2084)
- fix(deps): update dependency electron-debug to v4 [`#2086`](https://github.com/pear-devs/pear-desktop/pull/2086)
- fix(deps): update dependency electron-store to v9 [`#2087`](https://github.com/pear-devs/pear-desktop/pull/2087)
- fix(deps): update dependency conf to v12 [`#1463`](https://github.com/pear-devs/pear-desktop/pull/1463)
- fix(deps): update dependency youtubei.js to v9.4.0 [`#2083`](https://github.com/pear-devs/pear-desktop/pull/2083)
- chore(deps): update playwright monorepo to v1.44.1 [`#2082`](https://github.com/pear-devs/pear-desktop/pull/2082)
- chore(deps): update dependency ws to v8.17.0 [`#2081`](https://github.com/pear-devs/pear-desktop/pull/2081)
- chore(deps): update dependency glob to v10.4.1 [`#2080`](https://github.com/pear-devs/pear-desktop/pull/2080)
- chore(deps): update dependency eslint to v9.3.0 [`#2079`](https://github.com/pear-devs/pear-desktop/pull/2079)
- fix(deps): update dependency peerjs to v1.5.4 [`#2075`](https://github.com/pear-devs/pear-desktop/pull/2075)
- chore(deps): update dependency esbuild to v0.21.4 [`#2078`](https://github.com/pear-devs/pear-desktop/pull/2078)
- fix(deps): update dependency semver to v7.6.2 [`#2076`](https://github.com/pear-devs/pear-desktop/pull/2076)
- chore(deps): update dependency electron-vite to v2.2.0 [`#2077`](https://github.com/pear-devs/pear-desktop/pull/2077)
- fix(deps): update dependency i18next to v23.11.5 [`#2074`](https://github.com/pear-devs/pear-desktop/pull/2074)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.3 [`#2071`](https://github.com/pear-devs/pear-desktop/pull/2071)
- chore(deps): update dependency vite to v5.2.11 [`#2070`](https://github.com/pear-devs/pear-desktop/pull/2070)
- fix(deps): update dependency @floating-ui/dom to v1.6.5 [`#2073`](https://github.com/pear-devs/pear-desktop/pull/2073)
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.27.3 [`#2072`](https://github.com/pear-devs/pear-desktop/pull/2072)
- chore(deps): update pnpm to v9 [`#1980`](https://github.com/pear-devs/pear-desktop/pull/1980)
- chore(deps): update dependency electron to v30.0.8 [`#2068`](https://github.com/pear-devs/pear-desktop/pull/2068)
- chore(deps-dev): bump ejs from 3.1.9 to 3.1.10 [`#2023`](https://github.com/pear-devs/pear-desktop/pull/2023)
- chore(deps): update dependency utf-8-validate to v6.0.4 [`#2069`](https://github.com/pear-devs/pear-desktop/pull/2069)
- fix(MPRIS): Prevents player to start with invalid MPRIS interface [`#1996`](https://github.com/pear-devs/pear-desktop/pull/1996)
- fix(deps): update dependency solid-js to v1.8.17 [`#2002`](https://github.com/pear-devs/pear-desktop/pull/2002)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.10.0 [`#2000`](https://github.com/pear-devs/pear-desktop/pull/2000)
- chore(deps): update dependency discord-api-types to v0.37.85 [`#1998`](https://github.com/pear-devs/pear-desktop/pull/1998)
- fix(deps): update dependency serve to v14.2.3 [`#1997`](https://github.com/pear-devs/pear-desktop/pull/1997)
- chore(deps): update dependency rollup to v4.18.0 [`#1990`](https://github.com/pear-devs/pear-desktop/pull/1990)
- feat: Enable arm64 for deb and rpm [`#2033`](https://github.com/pear-devs/pear-desktop/pull/2033)
- chore (README-is.md): Replace viðbót with tengiforrit [`#2004`](https://github.com/pear-devs/pear-desktop/pull/2004)
- chore(docs): readme file translated to french [`#2049`](https://github.com/pear-devs/pear-desktop/pull/2049)
- chore(deps): update dependency @babel/runtime to v7.24.6 [`#2039`](https://github.com/pear-devs/pear-desktop/pull/2039)
- Fix substract `margin-top` in fullscreen mode [`#2015`](https://github.com/pear-devs/pear-desktop/pull/2015)
- chore(deps): update pnpm to v8.15.7 [`#1970`](https://github.com/pear-devs/pear-desktop/pull/1970)
- fix(renderer): fix macos traffic lights gap [`#2035`](https://github.com/pear-devs/pear-desktop/issues/2035)
- Fix substract `margin-top` in fullscreen mode [`#2013`](https://github.com/pear-devs/pear-desktop/issues/2013)
- chore(i18n): Translated using Weblate (Hungarian) [`f3de171`](https://github.com/pear-devs/pear-desktop/commit/f3de17112af787437362f31b5c4e2d4149ba1436)
- feat(menu): add theme list in menu [`933b4cc`](https://github.com/pear-devs/pear-desktop/commit/933b4cc8f062b3442afd4516a40eb2938db98fc6)
- chore(i18n): Translated using Weblate (Filipino) [`91392c0`](https://github.com/pear-devs/pear-desktop/commit/91392c0c7efaf3b33da4be4aaa7946af7108d676)
#### [v3.3.6](https://github.com/pear-devs/pear-desktop/compare/v3.3.5...v3.3.6)
> 13 April 2024
- fix: add AdGuard as blocklist sources [`#1966`](https://github.com/pear-devs/pear-desktop/pull/1966)
- chore(deps): update dependency rollup to v4.14.2 [`#1968`](https://github.com/pear-devs/pear-desktop/pull/1968)
- fix(deps): update dependency youtubei.js to v9.3.0 [`#1967`](https://github.com/pear-devs/pear-desktop/pull/1967)
- chore(deps): update playwright monorepo to v1.43.1 [`#1969`](https://github.com/pear-devs/pear-desktop/pull/1969)
- chore(deps): update dependency electron to v29.3.0 [`#1961`](https://github.com/pear-devs/pear-desktop/pull/1961)
- fix(mpris): use global regex to replace minus in the video ID [`#1963`](https://github.com/pear-devs/pear-desktop/pull/1963)
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.27.1 [`#1954`](https://github.com/pear-devs/pear-desktop/pull/1954)
- chore(deps): update dependency typescript to v5.4.5 [`#1958`](https://github.com/pear-devs/pear-desktop/pull/1958)
- fix(deps): update dependency youtubei.js to v9.2.1 [`#1957`](https://github.com/pear-devs/pear-desktop/pull/1957)
- fix(deps): update dependency i18next to v23.11.1 [`#1956`](https://github.com/pear-devs/pear-desktop/pull/1956)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.1 [`#1953`](https://github.com/pear-devs/pear-desktop/pull/1953)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.6.0 [`#1947`](https://github.com/pear-devs/pear-desktop/pull/1947)
- fix(deps): update dependency i18next to v23.11.0 [`#1946`](https://github.com/pear-devs/pear-desktop/pull/1946)
- chore(deps): update dependency node-gyp to v10.1.0 [`#1941`](https://github.com/pear-devs/pear-desktop/pull/1941)
- chore(deps): update dependency eslint to v9 [`#1940`](https://github.com/pear-devs/pear-desktop/pull/1940)
- chore(deps): update dependency rollup to v4.14.1 [`#1944`](https://github.com/pear-devs/pear-desktop/pull/1944)
- chore(deps): update dependency node-gyp to v10.1.0 [`#1937`](https://github.com/pear-devs/pear-desktop/pull/1937)
- chore(deps): update dependency typescript to v5.4.4 [`#1936`](https://github.com/pear-devs/pear-desktop/pull/1936)
- chore(deps): update playwright monorepo to v1.43.0 [`#1938`](https://github.com/pear-devs/pear-desktop/pull/1938)
- chore(deps): bump undici from 5.28.3 to 5.28.4 [`#1935`](https://github.com/pear-devs/pear-desktop/pull/1935)
- chore(deps): update dependency vite to v5.2.8 [`#1930`](https://github.com/pear-devs/pear-desktop/pull/1930)
- chore(deps): update dependency discord-api-types to v0.37.79 [`#1933`](https://github.com/pear-devs/pear-desktop/pull/1933)
- chore(deps): update dependency node-gyp to v10.1.0 [`#1910`](https://github.com/pear-devs/pear-desktop/pull/1910)
- chore(deps): update dependency node-gyp to v10.1.0 [`#1908`](https://github.com/pear-devs/pear-desktop/pull/1908)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.27.0 [`#1906`](https://github.com/pear-devs/pear-desktop/pull/1906)
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.27.0 [`#1907`](https://github.com/pear-devs/pear-desktop/pull/1907)
- chore(deps): update dependency rollup to v4.13.2 [`#1901`](https://github.com/pear-devs/pear-desktop/pull/1901)
- chore(deps): update dependency glob to v10.3.12 [`#1900`](https://github.com/pear-devs/pear-desktop/pull/1900)
- chore(deps): update dependency vite to v5.2.7 [`#1905`](https://github.com/pear-devs/pear-desktop/pull/1905)
- fix(deps): update dependency node-html-parser to v6.1.13 [`#1903`](https://github.com/pear-devs/pear-desktop/pull/1903)
- chore(deps): update dependency discord-api-types to v0.37.77 [`#1899`](https://github.com/pear-devs/pear-desktop/pull/1899)
- chore(deps): update dependency electron to v29.1.6 [`#1898`](https://github.com/pear-devs/pear-desktop/pull/1898)
- Improve video title filters [`#1667`](https://github.com/pear-devs/pear-desktop/pull/1667)
- chore(deps): update dependency rollup to v4.13.1 [`#1896`](https://github.com/pear-devs/pear-desktop/pull/1896)
- chore(deps): update dependency node-gyp to v10.1.0 [`#1890`](https://github.com/pear-devs/pear-desktop/pull/1890)
- chore(deps): update dependency node-gyp to v10.1.0 [`#1889`](https://github.com/pear-devs/pear-desktop/pull/1889)
- fix: fix `switch-repeat` [`#1810`](https://github.com/pear-devs/pear-desktop/issues/1810)
- i18n Translation to Dutch/nl [`0dbf029`](https://github.com/pear-devs/pear-desktop/commit/0dbf0295b805f9883522ee00983b338060fbddbe)
- fix: rollback electron-builder version to 24.9.4 [`4a57cc5`](https://github.com/pear-devs/pear-desktop/commit/4a57cc5ee9ab2ad6835cff75b8b3aead75d9e564)
- chore: update electron-builder to 25.0.0-alpha.6 [`aef03ab`](https://github.com/pear-devs/pear-desktop/commit/aef03ab9fd440fe19c41e315cffab27e976c723d)
#### [v3.3.5](https://github.com/pear-devs/pear-desktop/compare/v3.3.4...v3.3.5)
> 26 March 2024
- chore(deps): update dependency node-gyp to v10.1.0 [`#1885`](https://github.com/pear-devs/pear-desktop/pull/1885)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.4.0 [`#1886`](https://github.com/pear-devs/pear-desktop/pull/1886)
- chore(deps): update dependency vite to v5.2.6 [`#1883`](https://github.com/pear-devs/pear-desktop/pull/1883)
- fix(style): resolve #1887 [`#1887`](https://github.com/pear-devs/pear-desktop/issues/1887)
- chore(i18n): Translated using Weblate (Swedish) [`69087bb`](https://github.com/pear-devs/pear-desktop/commit/69087bbf1fac1ba58e992146deb1d6f1706b1e3c)
- chore(i18n): Translated using Weblate (French) [`af78f15`](https://github.com/pear-devs/pear-desktop/commit/af78f1596ab8db2fa7069fdb1c4f078099ce4446)
- Update changelog for v3.3.4 [`62f7d44`](https://github.com/pear-devs/pear-desktop/commit/62f7d440fab5bdbe9f49a3a5f8c32e7aaf2f28f6)
#### [v3.3.4](https://github.com/pear-devs/pear-desktop/compare/v3.3.3...v3.3.4)
> 24 March 2024
- Update changelog for v3.3.3 [`9769544`](https://github.com/pear-devs/pear-desktop/commit/97695444affbacb71dd73ae7107d4c987e285a37)
- fix(style): fix fullscreen style and in-app-menu [`ed700c2`](https://github.com/pear-devs/pear-desktop/commit/ed700c2916cc7e6ccd2010d0c552364af116eb4f)
- fix(style): fix miniplayer style [`a8bc539`](https://github.com/pear-devs/pear-desktop/commit/a8bc53912d1f4137008ecb2d9d5d9d9eb06ee2a8)
#### [v3.3.3](https://github.com/pear-devs/pear-desktop/compare/v3.3.2...v3.3.3)
> 24 March 2024
- chore(deps): update dependency electron to v29.1.5 [`#1876`](https://github.com/pear-devs/pear-desktop/pull/1876)
- chore(deps): update dependency typescript to v5.4.3 [`#1877`](https://github.com/pear-devs/pear-desktop/pull/1877)
- chore(deps): update dependency discord-api-types to v0.37.76 [`#1878`](https://github.com/pear-devs/pear-desktop/pull/1878)
- chore(deps): update dependency vite to v5.2.4 [`#1881`](https://github.com/pear-devs/pear-desktop/pull/1881)
- Ambient Plugin cleanup [`#1880`](https://github.com/pear-devs/pear-desktop/pull/1880)
- chore(deps): update dependency vite to v5.2.2 [`#1875`](https://github.com/pear-devs/pear-desktop/pull/1875)
- fix(deps): update dependency solid-js to v1.8.16 [`#1873`](https://github.com/pear-devs/pear-desktop/pull/1873)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.3.1 [`#1868`](https://github.com/pear-devs/pear-desktop/pull/1868)
- chore(deps): update dependency discord-api-types to v0.37.75 [`#1867`](https://github.com/pear-devs/pear-desktop/pull/1867)
- chore(deps): update pnpm to v8.15.5 [`#1865`](https://github.com/pear-devs/pear-desktop/pull/1865)
- fix: Fix Miniplayer image size [`#1863`](https://github.com/pear-devs/pear-desktop/pull/1863)
- fix(style): fixed image/video alignment when toggle is active [`#1862`](https://github.com/pear-devs/pear-desktop/pull/1862)
- chore: Update README-is.md [`#1858`](https://github.com/pear-devs/pear-desktop/pull/1858)
- chore(deps): update dependency vite-plugin-solid to v2.10.2 [`#1859`](https://github.com/pear-devs/pear-desktop/pull/1859)
- fix: Ambient Mode intialization improvement [`#1857`](https://github.com/pear-devs/pear-desktop/pull/1857)
- chore(deps): bump follow-redirects from 1.15.5 to 1.15.6 [`#1856`](https://github.com/pear-devs/pear-desktop/pull/1856)
- chore(README): Nicer Readme 2.0 [`#1833`](https://github.com/pear-devs/pear-desktop/pull/1833)
- chore(deps): update dependency discord-api-types to v0.37.74 [`#1854`](https://github.com/pear-devs/pear-desktop/pull/1854)
- chore(deps): update dependency esbuild to v0.20.2 [`#1855`](https://github.com/pear-devs/pear-desktop/pull/1855)
- Improve ambient mode [`#1853`](https://github.com/pear-devs/pear-desktop/pull/1853)
- chore(deps): update dependency electron to v29.1.4 [`#1852`](https://github.com/pear-devs/pear-desktop/pull/1852)
- chore(deps): update dependency electron to v29.1.3 [`#1851`](https://github.com/pear-devs/pear-desktop/pull/1851)
- chore(deps): update dependency rollup to v4.13.0 [`#1850`](https://github.com/pear-devs/pear-desktop/pull/1850)
- fix(deps): update dependency electron-store to v8.2.0 [`#1843`](https://github.com/pear-devs/pear-desktop/pull/1843)
- chore(deps): update dependency electron to v29.1.1 [`#1841`](https://github.com/pear-devs/pear-desktop/pull/1841)
- fix(deps): update dependency i18next to v23.10.1 [`#1842`](https://github.com/pear-devs/pear-desktop/pull/1842)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.2.0 [`#1848`](https://github.com/pear-devs/pear-desktop/pull/1848)
- chore(deps): update dependency vite to v5.1.6 [`#1847`](https://github.com/pear-devs/pear-desktop/pull/1847)
- fix(deps): update dependency async-mutex to v0.5.0 [`#1849`](https://github.com/pear-devs/pear-desktop/pull/1849)
- fix(deps): update dependency ts-morph to v22 [`#1846`](https://github.com/pear-devs/pear-desktop/pull/1846)
- chore(deps): update dependency discord-api-types to v0.37.73 [`#1840`](https://github.com/pear-devs/pear-desktop/pull/1840)
- chore(deps): update dependency rollup to v4.12.1 [`#1837`](https://github.com/pear-devs/pear-desktop/pull/1837)
- chore: Changed a single word (README-is.md) [`#1836`](https://github.com/pear-devs/pear-desktop/pull/1836)
- chore(deps): update dependency typescript to v5.4.2 [`#1838`](https://github.com/pear-devs/pear-desktop/pull/1838)
- chore(deps): update dependency electron-vite to v2.1.0 [`#1823`](https://github.com/pear-devs/pear-desktop/pull/1823)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.1 [`#1829`](https://github.com/pear-devs/pear-desktop/pull/1829)
- chore(deps): update dependency vite to v5.1.5 [`#1831`](https://github.com/pear-devs/pear-desktop/pull/1831)
- Revert "chore(deps): update dependency electron-builder to v24.13.3" [`#1818`](https://github.com/pear-devs/pear-desktop/pull/1818)
- chore(deps): update dependency electron-builder to v24.13.3 [`#1774`](https://github.com/pear-devs/pear-desktop/pull/1774)
- chore(deps): update playwright monorepo to v1.42.1 [`#1816`](https://github.com/pear-devs/pear-desktop/pull/1816)
- fix: Add scale ratio for tray icons [`#1811`](https://github.com/pear-devs/pear-desktop/pull/1811)
- Icelandic translation of the readme file [`#1806`](https://github.com/pear-devs/pear-desktop/pull/1806)
- chore(deps): update dependency electron to v29.1.0 [`#1808`](https://github.com/pear-devs/pear-desktop/pull/1808)
- chore(deps): update playwright monorepo to v1.42.0 [`#1805`](https://github.com/pear-devs/pear-desktop/pull/1805)
- chore(deps): update dependency eslint to v8.57.0 [`#1793`](https://github.com/pear-devs/pear-desktop/pull/1793)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.0 [`#1800`](https://github.com/pear-devs/pear-desktop/pull/1800)
- chore(deps): update dependency discord-api-types to v0.37.71 [`#1799`](https://github.com/pear-devs/pear-desktop/pull/1799)
- chore(deps): update pnpm to v8.15.4 [`#1795`](https://github.com/pear-devs/pear-desktop/pull/1795)
- chore(deps): update dependency @types/semver to v7.5.8 [`#1797`](https://github.com/pear-devs/pear-desktop/pull/1797)
- fix: center the pause icon [`#1786`](https://github.com/pear-devs/pear-desktop/pull/1786)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.16 [`#1788`](https://github.com/pear-devs/pear-desktop/pull/1788)
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.16 [`#1789`](https://github.com/pear-devs/pear-desktop/pull/1789)
- fix(deps): update dependency youtubei.js to v9.1.0 [`#1790`](https://github.com/pear-devs/pear-desktop/pull/1790)
- fix(deps): update dependency i18next to v23.10.0 [`#1785`](https://github.com/pear-devs/pear-desktop/pull/1785)
- chore(deps): update dependency electron to v29 [`#1773`](https://github.com/pear-devs/pear-desktop/pull/1773)
- chore(deps): update dependency vite to v5.1.4 [`#1778`](https://github.com/pear-devs/pear-desktop/pull/1778)
- chore(deps): bump ip from 2.0.0 to 2.0.1 [`#1777`](https://github.com/pear-devs/pear-desktop/pull/1777)
- fix: add support for Wayland [`#1864`](https://github.com/pear-devs/pear-desktop/issues/1864)
- fix(style): fix navigation bar items are not working [`#1381`](https://github.com/pear-devs/pear-desktop/issues/1381) [`#1396`](https://github.com/pear-devs/pear-desktop/issues/1396) [`#1649`](https://github.com/pear-devs/pear-desktop/issues/1649)
- fix(ytm-bugs): fixed a `scrollbar-color` bug that affected Chromium 121 and later [`#1737`](https://github.com/pear-devs/pear-desktop/issues/1737)
- chore(i18n): Translated using Weblate (Icelandic) [`82fa871`](https://github.com/pear-devs/pear-desktop/commit/82fa8719a96abdfaaa8548a0077f4db2164ec09b)
- chore(i18n): Translated using Weblate (Romanian) [`c871506`](https://github.com/pear-devs/pear-desktop/commit/c871506a69180308ab4fc587b6e8a33f193087e8)
- chore(i18n): Translated using Weblate (Thai) [`a7d0350`](https://github.com/pear-devs/pear-desktop/commit/a7d035022a229f0b245694d1fc7a484befe1c269)
#### [v3.3.2](https://github.com/pear-devs/pear-desktop/compare/v3.3.1...v3.3.2)
> 20 February 2024
- fix: fix bugs in MPRIS, and improve MPRIS [`#1760`](https://github.com/pear-devs/pear-desktop/pull/1760)
- fix(deps): update dependency electron-updater to v6.1.8 [`#1770`](https://github.com/pear-devs/pear-desktop/pull/1770)
- chore(deps): update dependency electron-builder to v24.12.0 [`#1771`](https://github.com/pear-devs/pear-desktop/pull/1771)
- feat(scrobblers): use `BrowserWindow` instead of `shell.openExternal` [`#1758`](https://github.com/pear-devs/pear-desktop/pull/1758)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.0.2 [`#1763`](https://github.com/pear-devs/pear-desktop/pull/1763)
- chore(deps): update dependency esbuild to v0.20.1 [`#1759`](https://github.com/pear-devs/pear-desktop/pull/1759)
- fix(deps): update dependency i18next to v23.9.0 [`#1754`](https://github.com/pear-devs/pear-desktop/pull/1754)
- fix: fixed an issue that caused infinite loops when using Music Together [`#1752`](https://github.com/pear-devs/pear-desktop/issues/1752)
- chore(deps): rollback dependency electron-builder to v24.9.1 [`8bd05f5`](https://github.com/pear-devs/pear-desktop/commit/8bd05f525df98671f0a516b159cccab302b7ae99)
- chore(deps): update dependency electron-builder to v24.13.1 [`47b23b4`](https://github.com/pear-devs/pear-desktop/commit/47b23b414c8feb25c4d9a23d6adb7cbf1ac818fb)
- chore(i18n): Translated using Weblate (German) [`47505e9`](https://github.com/pear-devs/pear-desktop/commit/47505e97482f9e953ee451b968d0950585616ffa)
#### [v3.3.1](https://github.com/pear-devs/pear-desktop/compare/v3.3.0...v3.3.1)
> 18 February 2024
- Update changelog for v3.3.0 [`6d9bb8e`](https://github.com/pear-devs/pear-desktop/commit/6d9bb8eb1cc2d892a5552ffb1f7c20859aa80f67)
- hotfix: in-app-menu position issue [`87acf4c`](https://github.com/pear-devs/pear-desktop/commit/87acf4cf042ba32a000a4aeaec5c17c93501d333)
- release 3.3.1 (HOTFIX) [`a6ed8bf`](https://github.com/pear-devs/pear-desktop/commit/a6ed8bf3aa20ca8e950e85d88f981ccf9edc7498)
#### [v3.3.0](https://github.com/pear-devs/pear-desktop/compare/v3.2.2...v3.3.0)
> 18 February 2024
- fix(deps): update dependency i18next to v23.8.3 [`#1751`](https://github.com/pear-devs/pear-desktop/pull/1751)
- import fixed ./constants [`#1748`](https://github.com/pear-devs/pear-desktop/pull/1748)
- chore(deps): update dependency rollup to v4.12.0 [`#1743`](https://github.com/pear-devs/pear-desktop/pull/1743)
- chore(deps): bump undici from 5.28.2 to 5.28.3 [`#1747`](https://github.com/pear-devs/pear-desktop/pull/1747)
- chore(deps): update dependency vite to v5.1.3 [`#1742`](https://github.com/pear-devs/pear-desktop/pull/1742)
- chore(deps): update dependency vite-plugin-solid to v2.10.1 [`#1734`](https://github.com/pear-devs/pear-desktop/pull/1734)
- chore(deps): update dependency discord-api-types to v0.37.70 [`#1740`](https://github.com/pear-devs/pear-desktop/pull/1740)
- chore(deps): update dependency electron to v28.2.3 [`#1736`](https://github.com/pear-devs/pear-desktop/pull/1736)
- chore(deps): update pnpm to v8.15.3 [`#1739`](https://github.com/pear-devs/pear-desktop/pull/1739)
- chore(deps): update dependency rollup to v4.11.0 [`#1738`](https://github.com/pear-devs/pear-desktop/pull/1738)
- fix(deps): update dependency solid-js to v1.8.15 [`#1735`](https://github.com/pear-devs/pear-desktop/pull/1735)
- chore(deps): update dependency vite to v5.1.2 [`#1733`](https://github.com/pear-devs/pear-desktop/pull/1733)
- chore(deps): update dependency vite-plugin-solid to v2.10.0 [`#1732`](https://github.com/pear-devs/pear-desktop/pull/1732)
- chore(deps): update pnpm to v8.15.2 [`#1729`](https://github.com/pear-devs/pear-desktop/pull/1729)
- Update Copyright - 2024 [`#1730`](https://github.com/pear-devs/pear-desktop/pull/1730)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7 [`#1728`](https://github.com/pear-devs/pear-desktop/pull/1728)
- fix(deps): update dependency @floating-ui/dom to v1.6.3 [`#1727`](https://github.com/pear-devs/pear-desktop/pull/1727)
- chore(deps): update dependency electron to v28.2.2 [`#1717`](https://github.com/pear-devs/pear-desktop/pull/1717)
- chore(deps): update dependency vite to v5.1.1 [`#1718`](https://github.com/pear-devs/pear-desktop/pull/1718)
- chore(deps): update dependency @types/semver to v7.5.7 [`#1724`](https://github.com/pear-devs/pear-desktop/pull/1724)
- fix(deps): update dependency @floating-ui/dom to v1.6.2 [`#1725`](https://github.com/pear-devs/pear-desktop/pull/1725)
- chore(deps): update dependency rollup to v4.10.0 [`#1719`](https://github.com/pear-devs/pear-desktop/pull/1719)
- fix(deps): update dependency solid-js to v1.8.14 [`#1713`](https://github.com/pear-devs/pear-desktop/pull/1713)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.21.0 [`#1711`](https://github.com/pear-devs/pear-desktop/pull/1711)
- fix(deps): update dependency semver to v7.6.0 [`#1712`](https://github.com/pear-devs/pear-desktop/pull/1712)
- refactor(in-app-menu): refactor `in-app-menu` plugin [`#1710`](https://github.com/pear-devs/pear-desktop/pull/1710)
- chore(deps): update playwright monorepo to v1.41.2 [`#1706`](https://github.com/pear-devs/pear-desktop/pull/1706)
- chore(deps): update dependency electron to v29.0.0-beta.5 [`#1707`](https://github.com/pear-devs/pear-desktop/pull/1707)
- feat(album-color-theme): support album color theme in all pages [`#1685`](https://github.com/pear-devs/pear-desktop/pull/1685)
- fix(deps): update dependency youtubei.js to v9.0.2 [`#1704`](https://github.com/pear-devs/pear-desktop/pull/1704)
- fix(deps): update dependency i18next to v23.8.2 [`#1702`](https://github.com/pear-devs/pear-desktop/pull/1702)
- feat: Support disabling scrobbling for non-music content [`#1665`](https://github.com/pear-devs/pear-desktop/pull/1665)
- fix(deps): update dependency youtubei.js to v9 [`#1682`](https://github.com/pear-devs/pear-desktop/pull/1682)
- chore(deps): update dependency electron to v29.0.0-beta.4 [`#1698`](https://github.com/pear-devs/pear-desktop/pull/1698)
- fix(deps): update dependency i18next to v23.8.1 [`#1694`](https://github.com/pear-devs/pear-desktop/pull/1694)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.20.0 [`#1700`](https://github.com/pear-devs/pear-desktop/pull/1700)
- chore(deps): update pnpm to v8.15.1 [`#1699`](https://github.com/pear-devs/pear-desktop/pull/1699)
- chore(deps): update dependency esbuild to v0.20.0 [`#1691`](https://github.com/pear-devs/pear-desktop/pull/1691)
- chore(deps): update pnpm to v8.15.0 [`#1692`](https://github.com/pear-devs/pear-desktop/pull/1692)
- fix(deps): update dependency i18next to v23.7.20 [`#1684`](https://github.com/pear-devs/pear-desktop/pull/1684)
- chore(deps): update dependency electron to v29.0.0-beta.3 [`#1683`](https://github.com/pear-devs/pear-desktop/pull/1683)
- chore(deps): update dependency electron to v29.0.0-beta.2 [`#1681`](https://github.com/pear-devs/pear-desktop/pull/1681)
- chore(deps): update dependency rollup to v4.9.6 [`#1663`](https://github.com/pear-devs/pear-desktop/pull/1663)
- chore(deps): update dependency electron to v29.0.0-beta.1 [`#1670`](https://github.com/pear-devs/pear-desktop/pull/1670)
- fix(deps): update dependency i18next to v23.7.19 [`#1680`](https://github.com/pear-devs/pear-desktop/pull/1680)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.1 [`#1669`](https://github.com/pear-devs/pear-desktop/pull/1669)
- chore(deps): update pnpm to v8.14.3 [`#1668`](https://github.com/pear-devs/pear-desktop/pull/1668)
- chore(deps): update dependency vite-plugin-inspect to v0.8.3 [`#1672`](https://github.com/pear-devs/pear-desktop/pull/1672)
- chore(deps): update dependency esbuild to v0.19.12 [`#1673`](https://github.com/pear-devs/pear-desktop/pull/1673)
- fix(deps): update dependency @electron/remote to v2.1.2 [`#1676`](https://github.com/pear-devs/pear-desktop/pull/1676)
- chore: Update issue templates [`#1661`](https://github.com/pear-devs/pear-desktop/pull/1661)
- chore(deps): update playwright monorepo to v1.41.1 [`#1660`](https://github.com/pear-devs/pear-desktop/pull/1660)
- fix(deps): update dependency i18next to v23.7.18 [`#1662`](https://github.com/pear-devs/pear-desktop/pull/1662)
- chore(deps): update actions/dependency-review-action action to v4 [`#1654`](https://github.com/pear-devs/pear-desktop/pull/1654)
- chore(deps): update dependency electron to v29.0.0-alpha.11 [`#1656`](https://github.com/pear-devs/pear-desktop/pull/1656)
- chore(deps): update dependency vite to v5.0.12 [security] [`#1659`](https://github.com/pear-devs/pear-desktop/pull/1659)
- fix(deps): update dependency async-mutex to v0.4.1 [`#1653`](https://github.com/pear-devs/pear-desktop/pull/1653)
- chore(deps): update playwright monorepo to v1.41.0 [`#1651`](https://github.com/pear-devs/pear-desktop/pull/1651)
- feat: Better Scrobbler Plugin [`#1640`](https://github.com/pear-devs/pear-desktop/pull/1640)
- chore(deps): update dependency electron to v29.0.0-alpha.10 [`#1645`](https://github.com/pear-devs/pear-desktop/pull/1645)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.0 [`#1643`](https://github.com/pear-devs/pear-desktop/pull/1643)
- chore(README): Fix plugins names and add plugins in/to Readme (in menu too) [`#1624`](https://github.com/pear-devs/pear-desktop/pull/1624)
- fix(album-actions): Fixed album actions [`#1639`](https://github.com/pear-devs/pear-desktop/pull/1639)
- chore(deps): update playwright monorepo to v1.41.0-beta-1705101589000 [`#1638`](https://github.com/pear-devs/pear-desktop/pull/1638)
- fix(#1543): fix song control doesn't work [`#1637`](https://github.com/pear-devs/pear-desktop/pull/1637)
- chore(deps): update playwright monorepo to v1.41.0-beta-1705092460000 [`#1635`](https://github.com/pear-devs/pear-desktop/pull/1635)
- chore(deps): update dependency rollup to v4.9.5 [`#1629`](https://github.com/pear-devs/pear-desktop/pull/1629)
- chore(deps): update dependency electron to v29.0.0-alpha.9 [`#1627`](https://github.com/pear-devs/pear-desktop/pull/1627)
- chore(deps): update dependency electron to v29.0.0-alpha.8 [`#1608`](https://github.com/pear-devs/pear-desktop/pull/1608)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.15 [`#1615`](https://github.com/pear-devs/pear-desktop/pull/1615)
- chore(deps): update dependency rollup to v4.9.4 [`#1591`](https://github.com/pear-devs/pear-desktop/pull/1591)
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.15 [`#1616`](https://github.com/pear-devs/pear-desktop/pull/1616)
- chore(deps): update pnpm to v8.14.1 [`#1619`](https://github.com/pear-devs/pear-desktop/pull/1619)
- chore(deps): update dependency eslint-plugin-prettier to v5.1.3 [`#1618`](https://github.com/pear-devs/pear-desktop/pull/1618)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.1 [`#1612`](https://github.com/pear-devs/pear-desktop/pull/1612)
- fix(deps): update dependency youtubei.js to v8.2.0 [`#1614`](https://github.com/pear-devs/pear-desktop/pull/1614)
- chore(deps): update dependency electron-vite to v2.0.0 [`#1609`](https://github.com/pear-devs/pear-desktop/pull/1609)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.0 [`#1603`](https://github.com/pear-devs/pear-desktop/pull/1603)
- chore(deps): update dependency electron-vite to v2.0.0-beta.4 [`#1602`](https://github.com/pear-devs/pear-desktop/pull/1602)
- fix: fix upgrade button [`#1199`](https://github.com/pear-devs/pear-desktop/issues/1199)
- fix(mpris): fix mpris invalid position [`#1726`](https://github.com/pear-devs/pear-desktop/issues/1726)
- fix: discord RPC (fix #1664) [`#1664`](https://github.com/pear-devs/pear-desktop/issues/1664)
- fix: remove sign-in button (fix #1199) [`#1199`](https://github.com/pear-devs/pear-desktop/issues/1199)
- Fix #1617 [`#1617`](https://github.com/pear-devs/pear-desktop/issues/1617)
- fix(crossfade): fix #1633 [`#1633`](https://github.com/pear-devs/pear-desktop/issues/1633)
- fix: fix #1621 [`#1621`](https://github.com/pear-devs/pear-desktop/issues/1621)
- fix(tuna-obs): partially fix #1596 [`#1596`](https://github.com/pear-devs/pear-desktop/issues/1596)
- fix(discord): fix hide duration button [`#1644`](https://github.com/pear-devs/pear-desktop/issues/1644)
- fix(in-app-menu): fix invalid `margin-top` [`#1597`](https://github.com/pear-devs/pear-desktop/issues/1597)
- fix(README): fix `plugins` path [`#1598`](https://github.com/pear-devs/pear-desktop/issues/1598)
- chore(i18n): Translated using Weblate (Vietnamese) [`0528637`](https://github.com/pear-devs/pear-desktop/commit/05286371353e8b4c36a5b9fe9011ae5dfdc7ee82)
- chore: update pnpm-lock [`fd8d59b`](https://github.com/pear-devs/pear-desktop/commit/fd8d59bada56dab4e156d22394fe0c5efec5abc4)
- fix(in-app-menu): fix app crash in production [`febc63e`](https://github.com/pear-devs/pear-desktop/commit/febc63edef375bd82db48b7fb460ec5a601ab872)
#### [v3.2.2](https://github.com/pear-devs/pear-desktop/compare/v3.2.1...v3.2.2)
> 5 January 2024
- feat(tray): Add song info and paused icon [`#1592`](https://github.com/pear-devs/pear-desktop/pull/1592)
- fix(skip-silences): fix audio distorted [`#1141`](https://github.com/pear-devs/pear-desktop/issues/1141)
- chore(deps): update dependency rollup to v4.9.3 [`0c3c380`](https://github.com/pear-devs/pear-desktop/commit/0c3c3805918adf2a185a7f1dc67ea3af8135863d)
- chore(i18n): Translated using Weblate (Turkish) [`64ea1fd`](https://github.com/pear-devs/pear-desktop/commit/64ea1fdb58fdf2766ae3284ac1a51bfac8894b36)
- fix(music-together): typing [`895386f`](https://github.com/pear-devs/pear-desktop/commit/895386f6f8c649f77ea15c88f6fb7ecc5b775554)
#### [v3.2.1](https://github.com/pear-devs/pear-desktop/compare/v3.2.0...v3.2.1)
> 1 January 2024
- fix: fix #1574 [`#1574`](https://github.com/pear-devs/pear-desktop/issues/1574)
- fix: fix #1575 [`#1575`](https://github.com/pear-devs/pear-desktop/issues/1575)
- chore(i18n): Translated using Weblate [`f5aa179`](https://github.com/pear-devs/pear-desktop/commit/f5aa179cd639eb4b8f70f1264b5b459ebcc16695)
- chore(i18n): Translated using Weblate (English) [`e409165`](https://github.com/pear-devs/pear-desktop/commit/e409165e1bed85f3d1aea3a565e7b9e462b1e05b)
- chore(i18n): Translated using Weblate (Czech) [`0ca4e34`](https://github.com/pear-devs/pear-desktop/commit/0ca4e34efd86e877314e5a245f266065b4cf0013)
#### [v3.2.0](https://github.com/pear-devs/pear-desktop/compare/v3.1.1...v3.2.0)
> 1 January 2024
- feat(album-color-theme): improve `Album Color Theme` style [`#1571`](https://github.com/pear-devs/pear-desktop/pull/1571)
- feat(menu): add more detail in Menu [`#1570`](https://github.com/pear-devs/pear-desktop/pull/1570)
- feat(music-together): Add new plugin `Music Together` [`#1562`](https://github.com/pear-devs/pear-desktop/pull/1562)
- chore(deps): update dependency rollup to v4.9.2 [`#1567`](https://github.com/pear-devs/pear-desktop/pull/1567)
- fix(deps): update dependency i18next to v23.7.13 [`#1569`](https://github.com/pear-devs/pear-desktop/pull/1569)
- feat: Add new plugin `Album actions` [`#1515`](https://github.com/pear-devs/pear-desktop/pull/1515)
- fix(deps): update dependency i18next to v23.7.12 [`#1564`](https://github.com/pear-devs/pear-desktop/pull/1564)
- fix: Only apply scale factor on Windows [`#1565`](https://github.com/pear-devs/pear-desktop/pull/1565)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.16.0 [`#1556`](https://github.com/pear-devs/pear-desktop/pull/1556)
- chore(deps): update pnpm to v8.13.1 [`#1557`](https://github.com/pear-devs/pear-desktop/pull/1557)
- chore(deps): update dependency ws to v8.16.0 [`#1559`](https://github.com/pear-devs/pear-desktop/pull/1559)
- fix(deps): update dependency youtubei.js to v8.1.0 [`#1560`](https://github.com/pear-devs/pear-desktop/pull/1560)
- fix(deps): update dependency node-html-parser to v6.1.12 [`#1554`](https://github.com/pear-devs/pear-desktop/pull/1554)
- Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2" [`#1552`](https://github.com/pear-devs/pear-desktop/pull/1552)
- feat(ambient-mode): support ambient mode on `Song section` [`#1555`](https://github.com/pear-devs/pear-desktop/issues/1555)
- fix: fixed an issue with the download button disappearing [`#1551`](https://github.com/pear-devs/pear-desktop/issues/1551)
- fix: fix `homebrew cask` [`#1514`](https://github.com/pear-devs/pear-desktop/issues/1514)
- fix: pnpm build error [`13ef856`](https://github.com/pear-devs/pear-desktop/commit/13ef8560ff43353030537403be7da82542ba535e)
- chore(i18n): Translated using Weblate (Czech) [`0dc9c6a`](https://github.com/pear-devs/pear-desktop/commit/0dc9c6a1a90bce6505614617b827e816cbaaf875)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.15.0 [`c5bcd89`](https://github.com/pear-devs/pear-desktop/commit/c5bcd89f164b51d7380486a8ae35edd0caeea842)
#### [v3.1.1](https://github.com/pear-devs/pear-desktop/compare/v3.1.0...v3.1.1)
> 18 December 2023
- fix: fix renderer plugin load timing [`#1522`](https://github.com/pear-devs/pear-desktop/issues/1522)
- chore(i18n): Translated using Weblate (Lithuanian) [`fc1a7cd`](https://github.com/pear-devs/pear-desktop/commit/fc1a7cda62b6e33e5f5d57a5a6e0adef6a32bf9a)
- chore(i18n): Translated using Weblate (Chinese (Simplified)) [`eba7026`](https://github.com/pear-devs/pear-desktop/commit/eba7026b89bbfdd3ac07cf728a66ba9bdd274ec0)
- chore(deps): update dependency rollup to v4.8.0 [`a601d0b`](https://github.com/pear-devs/pear-desktop/commit/a601d0b3d2dee0fabad79a18e1a7dd0ca84ccf01)
#### [v3.1.0](https://github.com/pear-devs/pear-desktop/compare/v3.0.2...v3.1.0)
> 11 December 2023
- chore(deps): update dependency electron to v28 [`#1498`](https://github.com/pear-devs/pear-desktop/pull/1498)
- Enable/Disable Navigation without restart [`#1507`](https://github.com/pear-devs/pear-desktop/pull/1507)
- Turkish(tr)_lang_file [`#1513`](https://github.com/pear-devs/pear-desktop/pull/1513)
- Skip Disliked Songs [`#1505`](https://github.com/pear-devs/pear-desktop/pull/1505)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.13.2 [`#1452`](https://github.com/pear-devs/pear-desktop/pull/1452)
- fix: Homebrew latest release url parsing [`#1496`](https://github.com/pear-devs/pear-desktop/pull/1496)
- fix: in-player adblocker inject timing issue [`#1478`](https://github.com/pear-devs/pear-desktop/issues/1478)
- fix(package.json): fix RPM version `libuuid` issue [`#1508`](https://github.com/pear-devs/pear-desktop/issues/1508)
- Translated using Weblate (Polish) [`7b78ba6`](https://github.com/pear-devs/pear-desktop/commit/7b78ba67613f14be65a45751efeb06431b405a91)
- Translated using Weblate (French) [`ebc0879`](https://github.com/pear-devs/pear-desktop/commit/ebc087963b23265ff00528c8305d51597abf587a)
- Translated using Weblate (Chinese (Traditional)) [`020bdc0`](https://github.com/pear-devs/pear-desktop/commit/020bdc0811ea45ad6c2853c62a05ae6695c5c4f9)
#### [v3.0.2](https://github.com/pear-devs/pear-desktop/compare/v3.0.1...v3.0.2)
> 3 December 2023
- fix(adblocker): fix In-Player adblocker [`#1478`](https://github.com/pear-devs/pear-desktop/issues/1478)
- fix(menu): crash on linux [`#1477`](https://github.com/pear-devs/pear-desktop/issues/1477)
- fix: update pnpm-lock.yaml [`9e2c6b1`](https://github.com/pear-devs/pear-desktop/commit/9e2c6b1afa33b5708853c8328946e68ec45b09c3)
- Translated using Weblate (Chinese (Traditional)) [`125b69f`](https://github.com/pear-devs/pear-desktop/commit/125b69fd75a05c3eb893886119e2d9f2332b3e56)
- Translated using Weblate (French) [`15c4551`](https://github.com/pear-devs/pear-desktop/commit/15c455105b5100a8ee2bd0a4631548d3d455f047)
#### [v3.0.1](https://github.com/pear-devs/pear-desktop/compare/v3.0.0...v3.0.1)
> 2 December 2023
- hotfix(adblocker): fix #1475 [`#1475`](https://github.com/pear-devs/pear-desktop/issues/1475)
- Translated using Weblate (French) [`7f02afc`](https://github.com/pear-devs/pear-desktop/commit/7f02afc5a6839adfe8437d4e2cc8dee13a93b311)
- Update changelog for v3.0.0 [`d8c8bd1`](https://github.com/pear-devs/pear-desktop/commit/d8c8bd17ecfbdf96ebd29eb4c5748c07876ee242)
- Translated using Weblate (German) [`0660f0b`](https://github.com/pear-devs/pear-desktop/commit/0660f0b7ce6895ef5800f48ade1da2d7f8e0c1f7)
### [v3.0.0](https://github.com/pear-devs/pear-desktop/compare/v2.2.0...v3.0.0)
> 2 December 2023
- Add text to Translation section [`#1470`](https://github.com/pear-devs/pear-desktop/pull/1470)
- fix(deps): update dependency youtubei.js to v8 [`#1473`](https://github.com/pear-devs/pear-desktop/pull/1473)
- chore(deps): update dependency electron to v27.1.3 [`#1471`](https://github.com/pear-devs/pear-desktop/pull/1471)
- fix(deps): update dependency @xhayper/discord-rpc to v1.1.1 [`#1472`](https://github.com/pear-devs/pear-desktop/pull/1472)
- feat: add support i18n [`#1468`](https://github.com/pear-devs/pear-desktop/pull/1468)
- chore(deps): update dependency electron to v27.1.2 [`#1441`](https://github.com/pear-devs/pear-desktop/pull/1441)
- Nicer Readme [`#1439`](https://github.com/pear-devs/pear-desktop/pull/1439)
- Windows Zoom, ScaleFactor [`#1402`](https://github.com/pear-devs/pear-desktop/pull/1402)
- chore(deps): bump axios from 1.5.1 to 1.6.1 [`#1400`](https://github.com/pear-devs/pear-desktop/pull/1400)
- Updated mac icon to better reflect the Mac styling [`#1395`](https://github.com/pear-devs/pear-desktop/pull/1395)
- feat: rename plugins to clarify context [`#1392`](https://github.com/pear-devs/pear-desktop/pull/1392)
- feat: refactor plugin utils [`#1391`](https://github.com/pear-devs/pear-desktop/pull/1391)
- feat: plugin auto-importer with `vite-plugin-resolve` [`#1385`](https://github.com/pear-devs/pear-desktop/pull/1385)
- feat: migrate from `rollup` to `electron-vite` [`#1364`](https://github.com/pear-devs/pear-desktop/pull/1364)
- feat: enable `context-isolation` [`#1361`](https://github.com/pear-devs/pear-desktop/pull/1361)
- fix: add workaround for `podcast` type video [`#1362`](https://github.com/pear-devs/pear-desktop/pull/1362)
- fix: fix broken menu-layout [`#1360`](https://github.com/pear-devs/pear-desktop/pull/1360)
- Add Homebrew cask install option for MacOS. [`#1357`](https://github.com/pear-devs/pear-desktop/pull/1357)
- feat: changed Zoom shortcuts to standard [`#1458`](https://github.com/pear-devs/pear-desktop/issues/1458)
- fix(in-app-menu): fix #1436 [`#1436`](https://github.com/pear-devs/pear-desktop/issues/1436)
- fix(discord): update application client-id [`#1431`](https://github.com/pear-devs/pear-desktop/issues/1431)
- chore(deps): update dependency electron to v27.0.4 [`#1324`](https://github.com/pear-devs/pear-desktop/issues/1324)
- fix(in-app-menu): panel should close with the window when it is closed [`#1389`](https://github.com/pear-devs/pear-desktop/issues/1389)
- fix: change titleBarOverlay height based on zoomFactor [`#1375`](https://github.com/pear-devs/pear-desktop/issues/1375)
- fix: fixed an issue if "Always on top" is enabled, the dialog is displayed below the window [`#1379`](https://github.com/pear-devs/pear-desktop/issues/1379)
- fix: fix winget version (fix #1363) [`#1363`](https://github.com/pear-devs/pear-desktop/issues/1363)
- feat: run prettier [`a3104fd`](https://github.com/pear-devs/pear-desktop/commit/a3104fda4b0d58b076d0c737111636a66e468acc)
- Translated using Weblate (Korean) [`b4b7ad8`](https://github.com/pear-devs/pear-desktop/commit/b4b7ad824b8c489ae483eba139b46e5b200231fc)
- Translated using Weblate (English) [`d2eabaa`](https://github.com/pear-devs/pear-desktop/commit/d2eabaa4bbccd89eae529eae52cec035e8e2620c)
#### [v2.2.0](https://github.com/pear-devs/pear-desktop/compare/v2.1.3...v2.2.0)
> 27 October 2023
- feat(ambient-mode): add config for `ambient-mode` plugin [`#1349`](https://github.com/pear-devs/pear-desktop/pull/1349)
- bump deps [`4248d20`](https://github.com/pear-devs/pear-desktop/commit/4248d20e8ef926ce7b1d07eb83743755a341d9f6)
- Update changelog for v2.1.3 [`dc73561`](https://github.com/pear-devs/pear-desktop/commit/dc73561c8a8acfc8ba91aff2dc78e4267869f2fd)
- Bump version to 2.2.0 [`6288d0b`](https://github.com/pear-devs/pear-desktop/commit/6288d0b171a65ea015922cdf3af6c7bd9a1f269b)
#### [v2.1.3](https://github.com/pear-devs/pear-desktop/compare/v2.1.2...v2.1.3)
> 23 October 2023
- fix: fixed bugs in downloader [`#1342`](https://github.com/pear-devs/pear-desktop/pull/1342)
- feat(discord): rename `Listen Along` to `Play on YTM` [`#1341`](https://github.com/pear-devs/pear-desktop/issues/1341)
- chore(deps): bump deps [`4333891`](https://github.com/pear-devs/pear-desktop/commit/4333891ccabe42aedf756fd48618be715db13262)
- Update changelog for v2.1.2 [`fa4c69d`](https://github.com/pear-devs/pear-desktop/commit/fa4c69d228d4e06a7858e2b22fcdfa075a8ca766)
- fix(store): fix listenAlong statement [`bceaa05`](https://github.com/pear-devs/pear-desktop/commit/bceaa05197d47a4a4bbd22e767d1e4d6ec277514)
#### [v2.1.2](https://github.com/pear-devs/pear-desktop/compare/v2.1.1...v2.1.2)
> 19 October 2023
- feat(in-app-menu): add an option to hide the window controls [`#1335`](https://github.com/pear-devs/pear-desktop/pull/1335)
- fix: fixed an issue where the album name was missing [`#1334`](https://github.com/pear-devs/pear-desktop/pull/1334)
- chore(deps): update dependency electron to v27.0.1 [`#1331`](https://github.com/pear-devs/pear-desktop/pull/1331)
- fix: fixed an issue where only the first 100 songs in a playlist were downloaded [`#1329`](https://github.com/pear-devs/pear-desktop/pull/1329)
- Updated readme plugins list [`#1326`](https://github.com/pear-devs/pear-desktop/pull/1326)
- QOL: Move source code under the src directory. [`#1318`](https://github.com/pear-devs/pear-desktop/pull/1318)
- feat: migrate from `npm` to `pnpm` [`#1316`](https://github.com/pear-devs/pear-desktop/pull/1316)
- fix: fix unresponsive (fix #1325) [`#1325`](https://github.com/pear-devs/pear-desktop/issues/1325)
- fix(blocker): remove the `app.isPackaged` check (fix #1315) [`#1315`](https://github.com/pear-devs/pear-desktop/issues/1315)
- fix(discord): `Discord RPC fails if a song's title is only one character` (fix #1314) [`#1314`](https://github.com/pear-devs/pear-desktop/issues/1314)
- chore(deps): Bump @rollup/plugin-commonjs, pnpm version, Remove ytpl [`9705f84`](https://github.com/pear-devs/pear-desktop/commit/9705f8489d7bf262bfd8b15ab84c2d3485f10eae)
- chore(deps): Bump rollup, @xhayper/discord-rpc version [`00a3e8d`](https://github.com/pear-devs/pear-desktop/commit/00a3e8d35ec335e1913be19f30ae09dbe0b7acdd)
- chore(deps): update dependency rollup to v4.1.4 [`6774d54`](https://github.com/pear-devs/pear-desktop/commit/6774d54f5eca432edc2e11743d9d1b1c2fda9ac8)
#### [v2.1.1](https://github.com/pear-devs/pear-desktop/compare/v2.1.0...v2.1.1)
> 14 October 2023
- hotfix(downloader): can't get an album title (fix #1313) [`#1313`](https://github.com/pear-devs/pear-desktop/issues/1313)
- Update changelog for v2.1.0 [`92cab89`](https://github.com/pear-devs/pear-desktop/commit/92cab89d17175741e60e65ea61633e23ebdc1f45)
- Bump version to 2.1.1 [`3bb5bc2`](https://github.com/pear-devs/pear-desktop/commit/3bb5bc2ca1856f4e222ee1e01e865f1ab804fdba)
- Add "about" menu to show app version [`21c45fa`](https://github.com/pear-devs/pear-desktop/commit/21c45faf2043cf72a7c14d5cf6c8d848d0448528)
#### [v2.1.0](https://github.com/pear-devs/pear-desktop/compare/v2.0.4...v2.1.0)
> 14 October 2023
- feat(downloader): Added support for audio format auto-detection [`#1310`](https://github.com/pear-devs/pear-desktop/pull/1310)
- feat(in-app-menu): enable in-app-menu by default (in Windows) [`#1311`](https://github.com/pear-devs/pear-desktop/pull/1311)
- fix: winget publish [`#1307`](https://github.com/pear-devs/pear-desktop/pull/1307)
- hotfix(downloader): fix invalid query selector (fix #1308) [`#1308`](https://github.com/pear-devs/pear-desktop/issues/1308)
- chore(deps): bump dependencies [`3c6b3ae`](https://github.com/pear-devs/pear-desktop/commit/3c6b3aeff0aae32adb2f2ad9c091b0a9701d3c24)
- chore(actions): create winget-cla.yml [`37181a7`](https://github.com/pear-devs/pear-desktop/commit/37181a7b5e2aa5bed6a36298eac3a66aac2762b8)
- Update changelog for v2.0.4 [`e9398ad`](https://github.com/pear-devs/pear-desktop/commit/e9398adac34a8abb11801e32999a915a8be0ece6)
#### [v2.0.4](https://github.com/pear-devs/pear-desktop/compare/v2.0.3...v2.0.4)
> 12 October 2023
- hotfix(adblocker): fix `ipcRenderer.sendSync() with ...` [`#1301`](https://github.com/pear-devs/pear-desktop/pull/1301)
- fix(downloader): Korean filename is broken on non-macOS devices [`#1297`](https://github.com/pear-devs/pear-desktop/pull/1297)
- chore(deps): bump deps [`b6894dc`](https://github.com/pear-devs/pear-desktop/commit/b6894dca2974c63fa2945d3a4995665d11eb2a78)
- fix: bump dependencies [`7aa970c`](https://github.com/pear-devs/pear-desktop/commit/7aa970cebc8e1407ff6937b402ba303e14c73efd)
- fix(downloader): private playlist download [`1d5b299`](https://github.com/pear-devs/pear-desktop/commit/1d5b2997bd0c72c1c007c57b145509e4a8f77fef)
#### [v2.0.3](https://github.com/pear-devs/pear-desktop/compare/v2.0.2...v2.0.3)
> 10 October 2023
- feat(discord): add `Hide GitHub link Button` [`#1293`](https://github.com/pear-devs/pear-desktop/pull/1293)
- feat(deps): bundle `youtubei.js` (temporary solution) [`#1292`](https://github.com/pear-devs/pear-desktop/pull/1292)
- fix(mpris): fixed an issue where MPRIS information was incorrect [`#1291`](https://github.com/pear-devs/pear-desktop/pull/1291)
- fix(discord): fixed an issue where `timeChanged` was not being applied to Discord activities [`#1290`](https://github.com/pear-devs/pear-desktop/pull/1290)
- Fix: typo in README [`#1286`](https://github.com/pear-devs/pear-desktop/pull/1286)
- fix: chore(deps): update dependency @jellybrick/mpris-service to 2.1.4 (fix #971) [`#971`](https://github.com/pear-devs/pear-desktop/issues/971)
- chore(deps): Bump `@cliqz/adblocker-electron` to 1.26.8 (fix #1269) [`#1269`](https://github.com/pear-devs/pear-desktop/issues/1269)
- fix: missing icons taskbar-mediacontrol [`fbf4b3b`](https://github.com/pear-devs/pear-desktop/commit/fbf4b3b8b5e39c61975e67efc990c45f62de76d8)
- remove: migration scripts [`52ba2dc`](https://github.com/pear-devs/pear-desktop/commit/52ba2dc9ffd8e235251d1279686f55e33b3fa3bb)
- feat: add migration script [`926b9fb`](https://github.com/pear-devs/pear-desktop/commit/926b9fb5e6db69b69935ec5d7be9a76a84e54ceb)
#### [v2.0.2](https://github.com/pear-devs/pear-desktop/compare/v2.0.1...v2.0.2)
> 8 October 2023
- fix: discord-rpc [`#1278`](https://github.com/pear-devs/pear-desktop/pull/1278)
- Bump version to 2.0.2 [`b5dbfaf`](https://github.com/pear-devs/pear-desktop/commit/b5dbfaf68691a546d72f5c1818fd3a44802eb0fa)
- Merge pull request #1272 from th-ch/feat/resolves-1265 [`6b7fd5b`](https://github.com/pear-devs/pear-desktop/commit/6b7fd5ba630888de08004105179c059c6d93e028)
- Merge pull request #1279 from th-ch/fix/1274 [`73a049a`](https://github.com/pear-devs/pear-desktop/commit/73a049a7bc5161f0d53c252cf510f1e2a6f6eeb3)
#### [v2.0.1](https://github.com/pear-devs/pear-desktop/compare/v2.0.0...v2.0.1)
> 8 October 2023
- Update changelog for v2.0.0 [`2d69dfd`](https://github.com/pear-devs/pear-desktop/commit/2d69dfd333c3223ecc7de13a0abc98fd99aa3a2b)
- hotfix: hotfix for #1267 [`c002263`](https://github.com/pear-devs/pear-desktop/commit/c002263c3bdd51890b8ffb431283afb60405d8fe)
- Bump version to 2.0.1 [`a1f025e`](https://github.com/pear-devs/pear-desktop/commit/a1f025e23c599fe5eb63b32ea38ee81200d232d6)
### [v2.0.0](https://github.com/pear-devs/pear-desktop/compare/v1.20.0...v2.0.0)
> 7 October 2023
- Bump version to 2.0.0 [`#1257`](https://github.com/pear-devs/pear-desktop/pull/1257)
- feat(GitHub): add issue template [`#1264`](https://github.com/pear-devs/pear-desktop/pull/1264)
- feat: I guess it's TypeScript [`#1235`](https://github.com/pear-devs/pear-desktop/pull/1235)
- chore(deps): update dependency rollup to v4 [`#44`](https://github.com/pear-devs/pear-desktop/pull/44)
- feat: apply rollup 🚀 [`#20`](https://github.com/pear-devs/pear-desktop/pull/20)
- fix: Fixes the video-toggle being displayed at the wrong position on fullscreen [`#1218`](https://github.com/pear-devs/pear-desktop/pull/1218)
- Change Winget Releaser job to `ubuntu-latest` [`#1225`](https://github.com/pear-devs/pear-desktop/pull/1225)
- Fixes the video-toggle being displayed at the wrong position on fullscreen [`#1218`](https://github.com/pear-devs/pear-desktop/pull/1218)
- Fix Remove upgrade button [`#1206`](https://github.com/pear-devs/pear-desktop/pull/1206)
- Fixed Age Restriction Bypass [`#1221`](https://github.com/pear-devs/pear-desktop/pull/1221)
- fix(tuna): handle `playPaused` [`#1`](https://github.com/pear-devs/pear-desktop/pull/1)
- Add plugin to always use the compact sidebar [`#1190`](https://github.com/pear-devs/pear-desktop/pull/1190)
- Hide login elements [`#1189`](https://github.com/pear-devs/pear-desktop/pull/1189)
- Fix navigation arrows [`#1191`](https://github.com/pear-devs/pear-desktop/pull/1191)
- MacOS better copy paste in readme.md [`#1156`](https://github.com/pear-devs/pear-desktop/pull/1156)
- feat(build-windows): Add support for IA32 (resolves #1110) [`#1110`](https://github.com/pear-devs/pear-desktop/issues/1110)
- fix: fix the downloader to work in a proxy environment (resolve #46) [`#46`](https://github.com/pear-devs/pear-desktop/issues/46)
- fix: fix #34 [`#34`](https://github.com/pear-devs/pear-desktop/issues/34)
- fix: fix #32 [`#32`](https://github.com/pear-devs/pear-desktop/issues/32)
- fix: fix #29 [`#29`](https://github.com/pear-devs/pear-desktop/issues/29)
- fix: fix #30 [`#30`](https://github.com/pear-devs/pear-desktop/issues/30)
- fix: fix #29 [`#29`](https://github.com/pear-devs/pear-desktop/issues/29)
- fix: fix #30 [`#30`](https://github.com/pear-devs/pear-desktop/issues/30)
- hotfix: fix #28 [`#28`](https://github.com/pear-devs/pear-desktop/issues/28)
- fix: resolve #12 [`#12`](https://github.com/pear-devs/pear-desktop/issues/12)
- fix(precise-volume): fix slider ui does not sync [`#15`](https://github.com/pear-devs/pear-desktop/issues/15)
- fix(video-toggle): fix video config not load config [`#16`](https://github.com/pear-devs/pear-desktop/issues/16)
- refactor(in-app-menu): refactor in-app-menu plugin [`#13`](https://github.com/pear-devs/pear-desktop/issues/13)
- feat(disable-autoplay): add `apply once`, resolve #9 [`#9`](https://github.com/pear-devs/pear-desktop/issues/9)
- fix: fix #4 [`#4`](https://github.com/pear-devs/pear-desktop/issues/4)
- fix: fix #7 [`#7`](https://github.com/pear-devs/pear-desktop/issues/7)
- fix: fix #1187 [`#1187`](https://github.com/pear-devs/pear-desktop/issues/1187)
- fix: resolves #978 [`#978`](https://github.com/pear-devs/pear-desktop/issues/978)
- fix: resolves #958 [`#958`](https://github.com/pear-devs/pear-desktop/issues/958)
- Merge pull request #1259 from organization/feat/fork-to-main [`457a8b5`](https://github.com/pear-devs/pear-desktop/commit/457a8b5018695d82b043cb7fa7264fbcf43f996c)
- fix: remove `xo`, migration to `eslint` [`c722896`](https://github.com/pear-devs/pear-desktop/commit/c722896a73cfbca3bbbab67bfcdfa639474e9030)
- fix: rollback changelog [`9048da2`](https://github.com/pear-devs/pear-desktop/commit/9048da22f98b9091ab606464a6cbdaad8bc185ae)
#### [v1.20.0](https://github.com/pear-devs/pear-desktop/compare/v1.19.0...v1.20.0)
> 18 May 2023
- Bump version to 1.20.0 [`#1117`](https://github.com/pear-devs/pear-desktop/pull/1117)
- Multiple implementations for the Adblocker plugin [`#1134`](https://github.com/pear-devs/pear-desktop/pull/1134)
- add xesam:url mpris from songInfo.url [`#1138`](https://github.com/pear-devs/pear-desktop/pull/1138)
- revert adblocker bump [`#1124`](https://github.com/pear-devs/pear-desktop/pull/1124)
- fix security issues in dependencies [`#1116`](https://github.com/pear-devs/pear-desktop/pull/1116)
- commit assets/generated [`#1118`](https://github.com/pear-devs/pear-desktop/pull/1118)
- remove `electron.remote` dependency [`#1113`](https://github.com/pear-devs/pear-desktop/pull/1113)
- .gitattributes set `eol=lf` on *all* files [`#1115`](https://github.com/pear-devs/pear-desktop/pull/1115)
- [crossfade] add `[beta]` tag to warn of possible bugs [`#1096`](https://github.com/pear-devs/pear-desktop/pull/1096)
- [crossfade] add menu options [`#1065`](https://github.com/pear-devs/pear-desktop/pull/1065)
- [captions-selector] add `autoload` option [`#1079`](https://github.com/pear-devs/pear-desktop/pull/1079)
- [downloader] Cleanup metadata [`#1091`](https://github.com/pear-devs/pear-desktop/pull/1091)
- fix protocol handler on unix [`#1099`](https://github.com/pear-devs/pear-desktop/pull/1099)
- fix merge conflict mistake in #1032 [`#1090`](https://github.com/pear-devs/pear-desktop/pull/1090)
- Create providers/decorators.js [`#1068`](https://github.com/pear-devs/pear-desktop/pull/1068)
- [adblocker] fix ads showing on program start [`#1100`](https://github.com/pear-devs/pear-desktop/pull/1100)
- Allow downloading age restricted videos [`#1086`](https://github.com/pear-devs/pear-desktop/pull/1086)
- add starting page option [`#1073`](https://github.com/pear-devs/pear-desktop/pull/1073)
- [downloader] plugin overhaul [`#1054`](https://github.com/pear-devs/pear-desktop/pull/1054)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.25.2 to 1.26.0 [`#1070`](https://github.com/pear-devs/pear-desktop/pull/1070)
- [in-app-menu] fix css style of the library of uploaded songs [`#1072`](https://github.com/pear-devs/pear-desktop/pull/1072)
- add option to hide the like buttons [`#1077`](https://github.com/pear-devs/pear-desktop/pull/1077)
- Nitpick: Fix name casing in tray icon tooltip [`#1081`](https://github.com/pear-devs/pear-desktop/pull/1081)
- [lyrics-genius] Improved reliability of east asian language detection #1080 [`#1082`](https://github.com/pear-devs/pear-desktop/pull/1082)
- Add dynamic synced plugin config provider [`#1064`](https://github.com/pear-devs/pear-desktop/pull/1064)
- [captions-selector] fix button showing when there aren't any captions available [`#1063`](https://github.com/pear-devs/pear-desktop/pull/1063)
- [in-app-menu] fix items hidden by navbar in library [`#1067`](https://github.com/pear-devs/pear-desktop/pull/1067)
- Fix Music Player logo is draggable [`#1061`](https://github.com/pear-devs/pear-desktop/pull/1061)
- fix build action failing on forks, and run it on pull requests [`#1069`](https://github.com/pear-devs/pear-desktop/pull/1069)
- try to fix songInfo time&album [`#1032`](https://github.com/pear-devs/pear-desktop/pull/1032)
- [lyrics] Romanization toggle for Genius plugin [`#1039`](https://github.com/pear-devs/pear-desktop/pull/1039)
- [Snyk] Upgrade html-to-text from 9.0.3 to 9.0.4 [`#1056`](https://github.com/pear-devs/pear-desktop/pull/1056)
- [in-app-menu] add toggle menu icon [`#988`](https://github.com/pear-devs/pear-desktop/pull/988)
- Fix playback speed slider not showing and PiP button showing when it shouldn't [`#1048`](https://github.com/pear-devs/pear-desktop/pull/1048)
- [lyrics-genius] Fix lyrics not showing up or showing up when they shouldn't [`#1052`](https://github.com/pear-devs/pear-desktop/pull/1052)
- [in-app-menu] disable nav-bar drag when menu is open [`#1055`](https://github.com/pear-devs/pear-desktop/pull/1055)
- [Notifications] [Windows] Native interactive notifications [`#946`](https://github.com/pear-devs/pear-desktop/pull/946)
- automate winget releases [`#1049`](https://github.com/pear-devs/pear-desktop/pull/1049)
- build win target on ARM [`#1029`](https://github.com/pear-devs/pear-desktop/pull/1029)
- feat: auto reconnect rpc and CSP fix [`#961`](https://github.com/pear-devs/pear-desktop/pull/961)
- [in-app-menu] make navbar draggable [`#989`](https://github.com/pear-devs/pear-desktop/pull/989)
- Add option `useNativePiP` in PiP plugin to use native PiP [`#1013`](https://github.com/pear-devs/pear-desktop/pull/1013)
- [PiP] fix hotkey activating when typing in the search box [`#1025`](https://github.com/pear-devs/pear-desktop/pull/1025)
- [PiP] Remove titlebar when in-app-menu is enabled [`#1024`](https://github.com/pear-devs/pear-desktop/pull/1024)
- [Shortcuts] MPRIS fixes, Repeat Language bug fix [`#1005`](https://github.com/pear-devs/pear-desktop/pull/1005)
- Build without release in forks [`#1023`](https://github.com/pear-devs/pear-desktop/pull/1023)
- [in-app-menu] fix navbar position [`#997`](https://github.com/pear-devs/pear-desktop/pull/997)
- Migrate to yarn v3 [`#1022`](https://github.com/pear-devs/pear-desktop/pull/1022)
- [precise-volume] fix arrows shortcuts active in search box [`#1002`](https://github.com/pear-devs/pear-desktop/pull/1002)
- [new plugin] Add first version for crossfade plugin [`#1012`](https://github.com/pear-devs/pear-desktop/pull/1012)
- Fix bypass-age-restriction lib import [`#984`](https://github.com/pear-devs/pear-desktop/pull/984)
- Add menu entry to copy current URL [`#977`](https://github.com/pear-devs/pear-desktop/pull/977)
- Remove deprecated code [`#979`](https://github.com/pear-devs/pear-desktop/pull/979)
- Update dev dependencies [`#976`](https://github.com/pear-devs/pear-desktop/pull/976)
- Update electron and various dependencies [`#974`](https://github.com/pear-devs/pear-desktop/pull/974)
- Add CI job for dependency review [`#973`](https://github.com/pear-devs/pear-desktop/pull/973)
- Improve captions plugin [`#972`](https://github.com/pear-devs/pear-desktop/pull/972)
- fix malformed json in tuna-obs [`#817`](https://github.com/pear-devs/pear-desktop/pull/817)
- Add Captions selector [`#866`](https://github.com/pear-devs/pear-desktop/pull/866)
- fix SnoreToast implementation [`#941`](https://github.com/pear-devs/pear-desktop/pull/941)
- Bump json5 from 1.0.1 to 1.0.2 [`#942`](https://github.com/pear-devs/pear-desktop/pull/942)
- [Snyk] Upgrade custom-electron-titlebar from 4.1.3 to 4.1.5 [`#969`](https://github.com/pear-devs/pear-desktop/pull/969)
- Fixed video-toggle aligning running before #main-panel exists [`#956`](https://github.com/pear-devs/pear-desktop/pull/956)
- [New plugin] Music visualizers [`#953`](https://github.com/pear-devs/pear-desktop/pull/953)
- fix PiP buttons not showing up [`#964`](https://github.com/pear-devs/pear-desktop/pull/964)
- Use same audio context/source everywhere [`#951`](https://github.com/pear-devs/pear-desktop/pull/951)
- revert adblocker bump [`#1105`](https://github.com/pear-devs/pear-desktop/issues/1105)
- Allow downloading age restricted videos [`#1084`](https://github.com/pear-devs/pear-desktop/issues/1084)
- add option to hide the like buttons [`#1075`](https://github.com/pear-devs/pear-desktop/issues/1075)
- add starting page option [`#1071`](https://github.com/pear-devs/pear-desktop/issues/1071)
- add slight delay to lyrics genius [`#1041`](https://github.com/pear-devs/pear-desktop/issues/1041)
- fix unescaped url params [`#1050`](https://github.com/pear-devs/pear-desktop/issues/1050)
- fix playback speed selector [`#1045`](https://github.com/pear-devs/pear-desktop/issues/1045)
- fix PiP button [`#959`](https://github.com/pear-devs/pear-desktop/issues/959)
- fix security issues in deps [`9cde19d`](https://github.com/pear-devs/pear-desktop/commit/9cde19d906081fe1851f90fa44581b2b74c328e3)
- rome lint [`325026e`](https://github.com/pear-devs/pear-desktop/commit/325026e3eae3daed33a6d66d1ef9f898d6805b28)
- lint [`b652a01`](https://github.com/pear-devs/pear-desktop/commit/b652a011a5a08978db6660aeca6908c47a7cf07a)
#### [v1.19.0](https://github.com/pear-devs/pear-desktop/compare/v1.18.0...v1.19.0)
> 31 December 2022
- Automatic release by CI when version is updated [`#936`](https://github.com/pear-devs/pear-desktop/pull/936)
- Center toggle of video-toggle [`#894`](https://github.com/pear-devs/pear-desktop/pull/894)
- Load plugins as soon as the window is created [`#890`](https://github.com/pear-devs/pear-desktop/pull/890)
- Bump qs from 6.5.2 to 6.5.3 [`#913`](https://github.com/pear-devs/pear-desktop/pull/913)
- [Snyk] Upgrade custom-electron-titlebar from 4.1.1 to 4.1.2 [`#900`](https://github.com/pear-devs/pear-desktop/pull/900)
- Add option in skip-silences plugin to only skip at the beginning [`#931`](https://github.com/pear-devs/pear-desktop/pull/931)
- Replace rimraf by del-cli [`#932`](https://github.com/pear-devs/pear-desktop/pull/932)
- docs: Added winget install instructions [`#873`](https://github.com/pear-devs/pear-desktop/pull/873)
- [Snyk] Upgrade async-mutex from 0.3.2 to 0.4.0 [`#855`](https://github.com/pear-devs/pear-desktop/pull/855)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.25.0 to 1.25.1 [`#856`](https://github.com/pear-devs/pear-desktop/pull/856)
- [Snyk] Upgrade custom-electron-titlebar from 4.1.0 to 4.1.1 [`#865`](https://github.com/pear-devs/pear-desktop/pull/865)
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.11.5 to 0.11.6 [`#876`](https://github.com/pear-devs/pear-desktop/pull/876)
- Discord Plugin RPC Fix [`#888`](https://github.com/pear-devs/pear-desktop/pull/888)
- Bump FFMpeg [`#854`](https://github.com/pear-devs/pear-desktop/pull/854)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.8 to 1.23.9 [`#823`](https://github.com/pear-devs/pear-desktop/pull/823)
- [Snyk] Upgrade electron-store from 8.0.2 to 8.1.0 [`#801`](https://github.com/pear-devs/pear-desktop/pull/801)
- proposal: Adding an option to hide duration before the song ends [`#802`](https://github.com/pear-devs/pear-desktop/pull/802)
- [Snyk] Security upgrade node-fetch from 2.6.7 to 3.2.10 [`#790`](https://github.com/pear-devs/pear-desktop/pull/790)
- Update README.md with a new theme repo [`#807`](https://github.com/pear-devs/pear-desktop/pull/807)
- Fix likes on touchbar (they were inverted) [`#822`](https://github.com/pear-devs/pear-desktop/pull/822)
- Add Scoop install directions for Windows 🪟 [`#839`](https://github.com/pear-devs/pear-desktop/pull/839)
- Bump version and change release type when publishing a new version [`31ab27c`](https://github.com/pear-devs/pear-desktop/commit/31ab27c39ff6319116a6514d952eed1f02dd45fd)
- Lock node-fetch to v2 for commonJS [`c9f610f`](https://github.com/pear-devs/pear-desktop/commit/c9f610f7fcfcce1317338364045ab0e1bf4249a4)
- fix: upgrade @cliqz/adblocker-electron from 1.25.0 to 1.25.1 [`762ef4e`](https://github.com/pear-devs/pear-desktop/commit/762ef4eede29b53aae912b3b50a1ca53f6765c53)
#### [v1.18.0](https://github.com/pear-devs/pear-desktop/compare/v1.17.0...v1.18.0)
> 5 September 2022
- Bump ytdl-core (bug fix) [`#816`](https://github.com/pear-devs/pear-desktop/pull/816)
- Bump electron and fix tests in CI [`#813`](https://github.com/pear-devs/pear-desktop/pull/813)
- Allow user to pass custom CSS file [`#800`](https://github.com/pear-devs/pear-desktop/pull/800)
- [Snyk] Upgrade html-to-text from 8.2.0 to 8.2.1 [`#799`](https://github.com/pear-devs/pear-desktop/pull/799)
- [Snyk] Upgrade electron-store from 8.0.1 to 8.0.2 [`#772`](https://github.com/pear-devs/pear-desktop/pull/772)
- Bump jpeg-js from 0.4.3 to 0.4.4 [`#756`](https://github.com/pear-devs/pear-desktop/pull/756)
- Support MPRIS loop and volume change [`#749`](https://github.com/pear-devs/pear-desktop/pull/749)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.7 to 1.23.8 [`#742`](https://github.com/pear-devs/pear-desktop/pull/742)
- Use ; instead of space for play/pause. [`#745`](https://github.com/pear-devs/pear-desktop/pull/745)
- Update readme.md [`#750`](https://github.com/pear-devs/pear-desktop/pull/750)
- fix lyrics font size [`#753`](https://github.com/pear-devs/pear-desktop/pull/753)
- fix top gap between nav-bar and browse-page [`#734`](https://github.com/pear-devs/pear-desktop/pull/734)
- migrate from remote to ipc + fix restart in portable app [`#605`](https://github.com/pear-devs/pear-desktop/pull/605)
- [Snyk] Upgrade custom-electron-prompt from 1.4.2 to 1.5.0 [`#717`](https://github.com/pear-devs/pear-desktop/pull/717)
- Picture in Picture v2 [`#685`](https://github.com/pear-devs/pear-desktop/pull/685)
- Add MPRIS volume control [`#776`](https://github.com/pear-devs/pear-desktop/issues/776)
- Remove jest [`bb6115f`](https://github.com/pear-devs/pear-desktop/commit/bb6115fec1a18a416edb365a442eb0b0ee330768)
- migrate from remote to ipc [`5bd9768`](https://github.com/pear-devs/pear-desktop/commit/5bd97685b9e07c656e0b57a9e02819afc70af1b1)
- v3 [`d23bfe9`](https://github.com/pear-devs/pear-desktop/commit/d23bfe936840b947ca101fd304464f65d36e88cc)
#### [v1.17.0](https://github.com/pear-devs/pear-desktop/compare/v1.16.0...v1.17.0)
> 16 May 2022
- Bump ejs from 3.1.6 to 3.1.7 [`#712`](https://github.com/pear-devs/pear-desktop/pull/712)
- fix injectCSS `did-finish-load` listener overload [`#693`](https://github.com/pear-devs/pear-desktop/pull/693)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.6 to 1.23.7 [`#689`](https://github.com/pear-devs/pear-desktop/pull/689)
- [Snyk] Upgrade custom-electron-prompt from 1.4.1 to 1.4.2 [`#686`](https://github.com/pear-devs/pear-desktop/pull/686)
- [Snyk] Upgrade @electron/remote from 2.0.7 to 2.0.8 [`#684`](https://github.com/pear-devs/pear-desktop/pull/684)
- Improve plugin submenu ux [`#699`](https://github.com/pear-devs/pear-desktop/pull/699)
- update build action [`#702`](https://github.com/pear-devs/pear-desktop/pull/702)
- add different modes to video-toggle plugin [`#700`](https://github.com/pear-devs/pear-desktop/pull/700)
- lint [`#701`](https://github.com/pear-devs/pear-desktop/pull/701)
- [ImgBot] Optimize images [`#703`](https://github.com/pear-devs/pear-desktop/pull/703)
- add album to lastfm if available [`#695`](https://github.com/pear-devs/pear-desktop/pull/695)
- [in-app-menu] add hide icon option [`#680`](https://github.com/pear-devs/pear-desktop/pull/680)
- Add plugin to bypass age restrictions [`#682`](https://github.com/pear-devs/pear-desktop/pull/682)
- Add "Picture in picture" plugin [`#674`](https://github.com/pear-devs/pear-desktop/pull/674)
- Set lyrics metadata from Genius [`#679`](https://github.com/pear-devs/pear-desktop/pull/679)
- MacOS: bring back the app in dock when using tray + app hidden [`#677`](https://github.com/pear-devs/pear-desktop/pull/677)
- [Snyk] Upgrade @electron/remote from 2.0.4 to 2.0.5 [`#644`](https://github.com/pear-devs/pear-desktop/pull/644)
- [Snyk] Upgrade ytpl from 2.2.3 to 2.3.0 [`#660`](https://github.com/pear-devs/pear-desktop/pull/660)
- [Snyk] Upgrade ytdl-core from 4.10.1 to 4.11.0 [`#659`](https://github.com/pear-devs/pear-desktop/pull/659)
- Bump plist from 3.0.2 to 3.0.5 [`#678`](https://github.com/pear-devs/pear-desktop/pull/678)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.4 to 1.23.5 [`#624`](https://github.com/pear-devs/pear-desktop/pull/624)
- [Precise-Volume] fix volumeHud position in miniplayer [`#645`](https://github.com/pear-devs/pear-desktop/pull/645)
- add always-on-top option [`#655`](https://github.com/pear-devs/pear-desktop/pull/655)
- [precise-volume] fix expand-volume-slider not updating its value [`#670`](https://github.com/pear-devs/pear-desktop/pull/670)
- Fix lyrics genius missing parts [`#671`](https://github.com/pear-devs/pear-desktop/pull/671)
- feat: option to force show like buttons [`#673`](https://github.com/pear-devs/pear-desktop/pull/673)
- fix custom titlebar in prompt options [`#619`](https://github.com/pear-devs/pear-desktop/pull/619)
- Process lyrics HTML in Genius util [`d0532d6`](https://github.com/pear-devs/pear-desktop/commit/d0532d691e56f955ef0b41f5fe2efe6295dddf9e)
- Create first version of picture in picture plugin [`d2265b5`](https://github.com/pear-devs/pear-desktop/commit/d2265b59d78143cf51fe4dc3d5dee9da66873cc1)
- Bump electron-builder to fix Mac build script [`ae8365f`](https://github.com/pear-devs/pear-desktop/commit/ae8365f721eafda6c502d02eee86d098f2b9e2a1)
#### [v1.16.0](https://github.com/pear-devs/pear-desktop/compare/v1.15.0...v1.16.0)
> 20 February 2022
- update in-app-menu [`#596`](https://github.com/pear-devs/pear-desktop/pull/596)
- Fix clientID [`#602`](https://github.com/pear-devs/pear-desktop/pull/602)
- Add snoretoast custom compile script [`#600`](https://github.com/pear-devs/pear-desktop/pull/600)
- fix interactive notifications icon + exclude platform specific plugins from build [`#591`](https://github.com/pear-devs/pear-desktop/pull/591)
- Add album title to largeImage and change paused icon [`#587`](https://github.com/pear-devs/pear-desktop/pull/587)
- make useragent override optional [`#595`](https://github.com/pear-devs/pear-desktop/pull/595)
- get album name from DOM [`#588`](https://github.com/pear-devs/pear-desktop/pull/588)
- fix various lyrics issues [`#584`](https://github.com/pear-devs/pear-desktop/pull/584)
- discord set inactivity timeout prompt [`#580`](https://github.com/pear-devs/pear-desktop/pull/580)
- add single instance lock option [`#578`](https://github.com/pear-devs/pear-desktop/pull/578)
- fix "restart app on config change" option [`#561`](https://github.com/pear-devs/pear-desktop/pull/561)
- fix window position save spam [`#562`](https://github.com/pear-devs/pear-desktop/pull/562)
- load adblocker sooner [`#583`](https://github.com/pear-devs/pear-desktop/pull/583)
- add description of new plugins to readme [`#585`](https://github.com/pear-devs/pear-desktop/pull/585)
- Use `center` alignment for lyrics text [`#573`](https://github.com/pear-devs/pear-desktop/pull/573)
- fix precise-volume hud positioning [`#567`](https://github.com/pear-devs/pear-desktop/pull/567)
- update electron and dependencies [`#565`](https://github.com/pear-devs/pear-desktop/pull/565)
- filenamify playlist folder name [`#557`](https://github.com/pear-devs/pear-desktop/pull/557)
- [Snyk] Security upgrade node-fetch from 2.6.6 to 2.6.7 (3.1.1 incompatible) [`#554`](https://github.com/pear-devs/pear-desktop/pull/554)
- fix app starting offscreen [`#548`](https://github.com/pear-devs/pear-desktop/pull/548)
- Release Mac arm64 [`#566`](https://github.com/pear-devs/pear-desktop/pull/566)
- Build command for Apple (m1) silicon macs [`#553`](https://github.com/pear-devs/pear-desktop/pull/553)
- [Snyk] Upgrade custom-electron-titlebar from 3.2.9 to 3.2.10 [`#545`](https://github.com/pear-devs/pear-desktop/pull/545)
- Fix duplicate media session on linux [`#551`](https://github.com/pear-devs/pear-desktop/pull/551)
- show a badge remaining items when downloading a playlist [`#550`](https://github.com/pear-devs/pear-desktop/pull/550)
- allow downloading playlists from popup menu [`#549`](https://github.com/pear-devs/pear-desktop/pull/549)
- xesam:artist should be a list [`#539`](https://github.com/pear-devs/pear-desktop/pull/539)
- fix notifications showing thumbnail of last song [`#537`](https://github.com/pear-devs/pear-desktop/pull/537)
- Fix https://github.com/pear-devs/pear-desktop/pull/578#issuecomment-1035517531 [`#578`](https://github.com/pear-devs/pear-desktop/pull/578)
- Add automatic changelog [`1d9bfe8`](https://github.com/pear-devs/pear-desktop/commit/1d9bfe8ac8869cde648164979986964baa52c2f9)
- update electron to v17.0.0 [`fef7115`](https://github.com/pear-devs/pear-desktop/commit/fef711549fa9862f8ea23301edde747c5802e352)
- update dependencies [`8be07bc`](https://github.com/pear-devs/pear-desktop/commit/8be07bcb7ad8b727d97c36aa0760aed4e2fc481f)
#### [v1.15.0](https://github.com/pear-devs/pear-desktop/compare/v1.14.0...v1.15.0)
> 30 December 2021
- Switch from spectron to playwright to fix tests [`#531`](https://github.com/pear-devs/pear-desktop/pull/531)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.1 [`#529`](https://github.com/pear-devs/pear-desktop/pull/529)
- fix precise-volume options sync [`#525`](https://github.com/pear-devs/pear-desktop/pull/525)
- Add album art/thumbnail to discord activity [`#524`](https://github.com/pear-devs/pear-desktop/pull/524)
- fix skip-silences plugin [`#521`](https://github.com/pear-devs/pear-desktop/pull/521)
- [Snyk] Upgrade electron-updater from 4.6.2 to 4.6.3 [`#520`](https://github.com/pear-devs/pear-desktop/pull/520)
- update electron & remote & user agents [`#515`](https://github.com/pear-devs/pear-desktop/pull/515)
- fixes mpris bug in snap [`#513`](https://github.com/pear-devs/pear-desktop/pull/513)
- Add "Skip silences" plugin [`#519`](https://github.com/pear-devs/pear-desktop/pull/519)
- Aligned lyric design [`#510`](https://github.com/pear-devs/pear-desktop/pull/510)
- Fix mpris bugs - follows #480 [`#509`](https://github.com/pear-devs/pear-desktop/pull/509)
- Various small fixes (discord, video-toggle, precise-volume, playback-speed, shortcuts, lyrics) [`#476`](https://github.com/pear-devs/pear-desktop/pull/476)
- Mpris + obs-tuna fixes [`#480`](https://github.com/pear-devs/pear-desktop/pull/480)
- [Snyk] Upgrade node-fetch from 2.6.5 to 2.6.6 [`#498`](https://github.com/pear-devs/pear-desktop/pull/498)
- fix interaction between blur navbar & in-app-menu [`#491`](https://github.com/pear-devs/pear-desktop/pull/491)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.0 [`#475`](https://github.com/pear-devs/pear-desktop/pull/475)
- New Plugin: Exponential Volume [`#488`](https://github.com/pear-devs/pear-desktop/pull/488)
- [Snyk] Upgrade electron-updater from 4.6.0 to 4.6.1 [`#474`](https://github.com/pear-devs/pear-desktop/pull/474)
- Fix loadeddata/metadata video events rarely not firing (+other small fixes) [`#477`](https://github.com/pear-devs/pear-desktop/pull/477)
- fix #490 [`#490`](https://github.com/pear-devs/pear-desktop/issues/490)
- fix #472 [`#472`](https://github.com/pear-devs/pear-desktop/issues/472)
- fix mpris [`ccfe743`](https://github.com/pear-devs/pear-desktop/commit/ccfe7434bf708ee58156c2952234a049706edfc2)
- lint [`4362101`](https://github.com/pear-devs/pear-desktop/commit/4362101c0a2ebb7f0536f615cecba8a55ac96702)
- rework songInfo pause listener [`6726e26`](https://github.com/pear-devs/pear-desktop/commit/6726e2600b3ca3a8d68e3e1b95b50da211fa354d)
#### [v1.14.0](https://github.com/pear-devs/pear-desktop/compare/v1.13.0...v1.14.0)
> 7 November 2021
- [Snyk] Upgrade custom-electron-prompt from 1.1.0 to 1.2.0 [`#467`](https://github.com/pear-devs/pear-desktop/pull/467)
- Video Toggle Plugin [`#448`](https://github.com/pear-devs/pear-desktop/pull/448)
- fix playback speed plugin [`#462`](https://github.com/pear-devs/pear-desktop/pull/462)
- Fix sponsorblock skipping when not needed [`#465`](https://github.com/pear-devs/pear-desktop/pull/465)
- Sponsorblock fix + use new apiLoaded event [`#463`](https://github.com/pear-devs/pear-desktop/pull/463)
- use apiLoaded event in audio-compressor plugin [`#458`](https://github.com/pear-devs/pear-desktop/pull/458)
- alert on initial hide-menu enabled [`#456`](https://github.com/pear-devs/pear-desktop/pull/456)
- Blur plugin tweaks and integration with in-app-menu [`#451`](https://github.com/pear-devs/pear-desktop/pull/451)
- set resume on start url to songInfo.url [`#449`](https://github.com/pear-devs/pear-desktop/pull/449)
- quality-changer-plugin [`#446`](https://github.com/pear-devs/pear-desktop/pull/446)
- get songInfo from original API [`#443`](https://github.com/pear-devs/pear-desktop/pull/443)
- New plugin: Blur navigation bar [`#442`](https://github.com/pear-devs/pear-desktop/pull/442)
- Discord plugin: Clean Up Export (follow-up #380) [`#440`](https://github.com/pear-devs/pear-desktop/pull/440)
- remove upgrade button + makes images unselectable [`#434`](https://github.com/pear-devs/pear-desktop/pull/434)
- new auto confirm when paused [`#433`](https://github.com/pear-devs/pear-desktop/pull/433)
- fix: mpris instance not registering itself and media controls [`#431`](https://github.com/pear-devs/pear-desktop/pull/431)
- Audio compressor plugin [`#288`](https://github.com/pear-devs/pear-desktop/pull/288)
- precise-volume plugin fixes & updates [`#275`](https://github.com/pear-devs/pear-desktop/pull/275)
- Custom Prompt for changing options [`#243`](https://github.com/pear-devs/pear-desktop/pull/243)
- [Snyk] Upgrade async-mutex from 0.3.1 to 0.3.2 [`#412`](https://github.com/pear-devs/pear-desktop/pull/412)
- build(deps): bump tmpl from 1.0.4 to 1.0.5 [`#414`](https://github.com/pear-devs/pear-desktop/pull/414)
- [Snyk] Upgrade node-fetch from 2.6.1 to 2.6.2 [`#416`](https://github.com/pear-devs/pear-desktop/pull/416)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.5 to 1.22.6 [`#429`](https://github.com/pear-devs/pear-desktop/pull/429)
- build(deps-dev): bump electron from 12.0.8 to 12.1.0 [`#430`](https://github.com/pear-devs/pear-desktop/pull/430)
- Fix discord clearActivity, menu, listen along option [`#380`](https://github.com/pear-devs/pear-desktop/pull/380)
- Bump dev deps [`41a01ba`](https://github.com/pear-devs/pear-desktop/commit/41a01ba58a17056ba5143fdbd10d3bae11dd8d52)
- Discord add reconnecting functionality [`b5fd6b4`](https://github.com/pear-devs/pear-desktop/commit/b5fd6b4969a318b3738583e7f33eb2c0cf295237)
- add custom-electron-prompt [`e4eed2e`](https://github.com/pear-devs/pear-desktop/commit/e4eed2e51979378e62dab902e425218cae5108dc)
#### [v1.13.0](https://github.com/pear-devs/pear-desktop/compare/v1.12.2...v1.13.0)
> 19 September 2021
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.4 to 1.22.5 [`#406`](https://github.com/pear-devs/pear-desktop/pull/406)
- Fix incorrect Google alert caused by changing user agent coresponding to current platform [`#384`](https://github.com/pear-devs/pear-desktop/pull/384)
- [Snyk] Upgrade electron-updater from 4.4.3 to 4.4.6 [`#401`](https://github.com/pear-devs/pear-desktop/pull/401)
- [Snyk] Upgrade electron-updater from 4.4.0 to 4.4.1 [`#370`](https://github.com/pear-devs/pear-desktop/pull/370)
- Bump path-parse from 1.0.6 to 1.0.7 [`#375`](https://github.com/pear-devs/pear-desktop/pull/375)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.2 to 1.22.3 [`#385`](https://github.com/pear-devs/pear-desktop/pull/385)
- Bump jszip from 3.5.0 to 3.7.1 [`#388`](https://github.com/pear-devs/pear-desktop/pull/388)
- List missing plugins [`#382`](https://github.com/pear-devs/pear-desktop/pull/382)
- add tuna plugin for obs [`#397`](https://github.com/pear-devs/pear-desktop/pull/397)
- Update menu buttons to new format [`#389`](https://github.com/pear-devs/pear-desktop/pull/389)
- Plugin to fetch lyrics from Genius [`#387`](https://github.com/pear-devs/pear-desktop/pull/387)
- Add mpris support with cherry picked commit from previous PR https://github.com/pear-devs/pear-desktop/pull/394 [`#395`](https://github.com/pear-devs/pear-desktop/pull/395)
- Add "Listen Along" button, solve #353 [`#383`](https://github.com/pear-devs/pear-desktop/pull/383)
- Bump node to v14 [`#386`](https://github.com/pear-devs/pear-desktop/pull/386)
- [Snyk] Upgrade electron-updater from 4.3.9 to 4.3.10 [`#350`](https://github.com/pear-devs/pear-desktop/pull/350)
- [Snyk] Upgrade chokidar from 3.5.1 to 3.5.2 [`#354`](https://github.com/pear-devs/pear-desktop/pull/354)
- Bump ytdl/ytpl [`c01506d`](https://github.com/pear-devs/pear-desktop/commit/c01506dc441bfc538471dc2c552c1a8a2800c611)
- Add mpris support [`e255777`](https://github.com/pear-devs/pear-desktop/commit/e255777283c7b16611404cbfe260bfcca75a1e40)
- Add Genius lyrics plugin [`acbe0ac`](https://github.com/pear-devs/pear-desktop/commit/acbe0ac25d568c25fedb514e0e96c66497b0f2d6)
#### [v1.12.2](https://github.com/pear-devs/pear-desktop/compare/v1.12.1...v1.12.2)
> 1 July 2021
- Fix downloader plugin [`#339`](https://github.com/pear-devs/pear-desktop/pull/339)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.0 to 1.22.1 [`#337`](https://github.com/pear-devs/pear-desktop/pull/337)
- Update and simplify in-app-menu [`#249`](https://github.com/pear-devs/pear-desktop/pull/249)
- Bump hosted-git-info from 2.8.8 to 2.8.9 [`#331`](https://github.com/pear-devs/pear-desktop/pull/331)
- Bump lodash from 4.17.20 to 4.17.21 [`#330`](https://github.com/pear-devs/pear-desktop/pull/330)
- [Snyk] Upgrade ytdl-core from 4.8.0 to 4.8.2 [`#328`](https://github.com/pear-devs/pear-desktop/pull/328)
- [Snyk] Upgrade electron-updater from 4.3.8 to 4.3.9 [`#324`](https://github.com/pear-devs/pear-desktop/pull/324)
- Bump normalize-url from 4.5.0 to 4.5.1 [`#323`](https://github.com/pear-devs/pear-desktop/pull/323)
- Bump trim-newlines from 3.0.0 to 3.0.1 [`#320`](https://github.com/pear-devs/pear-desktop/pull/320)
- [Snyk] Upgrade @ffmpeg/core from 0.9.0 to 0.10.0 [`#317`](https://github.com/pear-devs/pear-desktop/pull/317)
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.8 to 0.10.0 [`#316`](https://github.com/pear-devs/pear-desktop/pull/316)
- [Snyk] Upgrade custom-electron-titlebar from 3.2.6 to 3.2.7 [`#311`](https://github.com/pear-devs/pear-desktop/pull/311)
- fix hidden webp thumbnail throwing MIME type error in downloader [`#318`](https://github.com/pear-devs/pear-desktop/pull/318)
- Add Sponsorblock plugin [`#308`](https://github.com/pear-devs/pear-desktop/pull/308)
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.7 to 0.9.8 [`#305`](https://github.com/pear-devs/pear-desktop/pull/305)
- Bump dependencies to fix vulnerabilities [`496836b`](https://github.com/pear-devs/pear-desktop/commit/496836b33b116e06b8d1361ce1f47ab6c9138cae)
- update refreshMenu() function [`33855f1`](https://github.com/pear-devs/pear-desktop/commit/33855f17dd80c099117a3d84bbd9b5021776771c)
- Add SponsorBlock plugin [`ca64a77`](https://github.com/pear-devs/pear-desktop/commit/ca64a77ed0236fd9cfb4b40e450578a186638dc7)
#### [v1.12.1](https://github.com/pear-devs/pear-desktop/compare/v1.12.0...v1.12.1)
> 28 May 2021
- Bump ws from 7.4.3 to 7.4.6 [`#303`](https://github.com/pear-devs/pear-desktop/pull/303)
- Bump browserslist from 4.16.3 to 4.16.6 [`#301`](https://github.com/pear-devs/pear-desktop/pull/301)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.20.4 to 1.20.5 [`#300`](https://github.com/pear-devs/pear-desktop/pull/300)
- [Snyk] Upgrade ytdl-core from 4.5.0 to 4.7.0 [`#299`](https://github.com/pear-devs/pear-desktop/pull/299)
- [Snyk] Upgrade @ffmpeg/core from 0.8.5 to 0.9.0 [`#298`](https://github.com/pear-devs/pear-desktop/pull/298)
- [Snyk] Upgrade filenamify from 4.2.0 to 4.3.0 [`#293`](https://github.com/pear-devs/pear-desktop/pull/293)
- [Snyk] Upgrade ytpl from 2.1.1 to 2.2.0 [`#285`](https://github.com/pear-devs/pear-desktop/pull/285)
- fix song-info callback duplication [`#269`](https://github.com/pear-devs/pear-desktop/pull/269)
- fix notification showing appID instead of app name on windows [`#270`](https://github.com/pear-devs/pear-desktop/pull/270)
- Upgrade electron to v12 [`#273`](https://github.com/pear-devs/pear-desktop/pull/273)
- fix last-fm overwrite config on each start [`#267`](https://github.com/pear-devs/pear-desktop/pull/267)
- Downloader tweaks + taskbar progress bar [`#265`](https://github.com/pear-devs/pear-desktop/pull/265)
- remove `open` dependency from last-fm plugin [`#262`](https://github.com/pear-devs/pear-desktop/pull/262)
- Fix downloader metadata if not currently playing [`#252`](https://github.com/pear-devs/pear-desktop/pull/252)
- fix playPause bugs by directly playPause video element [`#259`](https://github.com/pear-devs/pear-desktop/pull/259)
- Bump ua-parser-js from 0.7.23 to 0.7.28 [`#260`](https://github.com/pear-devs/pear-desktop/pull/260)
- Fix precise volume listener override [`#253`](https://github.com/pear-devs/pear-desktop/pull/253)
- fix css not inserting on reload [`#255`](https://github.com/pear-devs/pear-desktop/pull/255)
- playlist download progressBar using `chokidar` [`53bf7c5`](https://github.com/pear-devs/pear-desktop/commit/53bf7c5068fdc14f5aa469d47b3174d27f40e05c)
- download progress bar on taskbar [`a8ac2c3`](https://github.com/pear-devs/pear-desktop/commit/a8ac2c3af988f299be85010e7fea541096b7e261)
- fix: upgrade @cliqz/adblocker-electron from 1.20.4 to 1.20.5 [`c5f84b5`](https://github.com/pear-devs/pear-desktop/commit/c5f84b568b0c3480af1abc8ff111771e2170a50e)
#### [v1.12.0](https://github.com/pear-devs/pear-desktop/compare/v1.11.0...v1.12.0)
> 4 May 2021
- Menu tweaks [`#224`](https://github.com/pear-devs/pear-desktop/pull/224)
- Interactive notifications for windows [`#228`](https://github.com/pear-devs/pear-desktop/pull/228)
- [Plugin] Precise volume control [`#236`](https://github.com/pear-devs/pear-desktop/pull/236)
- [Snyk] Upgrade electron-store from 7.0.2 to 7.0.3 [`#244`](https://github.com/pear-devs/pear-desktop/pull/244)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.20.3 to 1.20.4 [`#233`](https://github.com/pear-devs/pear-desktop/pull/233)
- Dependencies update [`#231`](https://github.com/pear-devs/pear-desktop/pull/231)
- Fix downloader metadata [`#245`](https://github.com/pear-devs/pear-desktop/pull/245)
- Last.fm support [`#196`](https://github.com/pear-devs/pear-desktop/pull/196)
- simple fix for discord plugin [`#239`](https://github.com/pear-devs/pear-desktop/pull/239)
- In-app-menu plugin - rename plugin & configure menu builder [`#215`](https://github.com/pear-devs/pear-desktop/pull/215)
- Allows downloading songs that aren't currently playing [`#221`](https://github.com/pear-devs/pear-desktop/pull/221)
- Updated download plugin icon color to match other icons [`#222`](https://github.com/pear-devs/pear-desktop/pull/222)
- [Notification Plugin] Fix duplicate notification [`#216`](https://github.com/pear-devs/pear-desktop/pull/216)
- Pass metadata to front + use metadata URL in downloader [`#213`](https://github.com/pear-devs/pear-desktop/pull/213)
- Refresh menu on plugin enable/disable (show/hide submenu) [`#217`](https://github.com/pear-devs/pear-desktop/pull/217)
- remove 'shortcuts' from default plugins [`#218`](https://github.com/pear-devs/pear-desktop/pull/218)
- [Plugin] styled-bars [`#201`](https://github.com/pear-devs/pear-desktop/pull/201)
- Add configurable notification urgency [`#212`](https://github.com/pear-devs/pear-desktop/pull/212)
- add Download Folder Chooser [`#207`](https://github.com/pear-devs/pear-desktop/pull/207)
- Improved songinfo provider, by using the data from the '/player' request [`#194`](https://github.com/pear-devs/pear-desktop/pull/194)
- Download plugin directory chooser [`#10`](https://github.com/pear-devs/pear-desktop/pull/10)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.20.0 to 1.20.1 [`#180`](https://github.com/pear-devs/pear-desktop/pull/180)
- [Plugin] taskbar-mediacontrol (for Windows) [`#200`](https://github.com/pear-devs/pear-desktop/pull/200)
- merge source [`#3`](https://github.com/pear-devs/pear-desktop/pull/3)
- merge source [`#2`](https://github.com/pear-devs/pear-desktop/pull/2)
- Add playlist feature in downloader plugin + custom menus in plugin system [`#203`](https://github.com/pear-devs/pear-desktop/pull/203)
- Added Discord timeout [`#192`](https://github.com/pear-devs/pear-desktop/pull/192)
- Override hide(),show(),isVisible from inside plugin [`6427b34`](https://github.com/pear-devs/pear-desktop/commit/6427b3406c8d84c5b7ecbe6a28158d5dc895c3c2)
- added back original yarn.lock [`24fea5a`](https://github.com/pear-devs/pear-desktop/commit/24fea5a24afd4f547628549962d24756cca5e413)
- remove local prompt [`8dc486f`](https://github.com/pear-devs/pear-desktop/commit/8dc486f18fe02a218b149838dc7ab939ec1b698a)
#### [v1.11.0](https://github.com/pear-devs/pear-desktop/compare/v1.10.0...v1.11.0)
> 9 March 2021
- [Snyk] Upgrade electron-store from 7.0.1 to 7.0.2 [`#178`](https://github.com/pear-devs/pear-desktop/pull/178)
- Added function to toggle resuming of last song when app starts [`#177`](https://github.com/pear-devs/pear-desktop/pull/177)
- [Snyk] Upgrade discord-rpc from 3.1.4 to 3.2.0 [`#175`](https://github.com/pear-devs/pear-desktop/pull/175)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.19.0 to 1.20.0 [`#154`](https://github.com/pear-devs/pear-desktop/pull/154)
- Added metadata to downloader plugin, and updated packages [`dd1bdae`](https://github.com/pear-devs/pear-desktop/commit/dd1bdae9478ef831ee2a00b29be04c65626933f8)
- Fix download/speed menu item [`796a7aa`](https://github.com/pear-devs/pear-desktop/commit/796a7aaaf1ecaf80b2ef113137f2222499803e29)
- fix: upgrade @cliqz/adblocker-electron from 1.19.0 to 1.20.0 [`538ab52`](https://github.com/pear-devs/pear-desktop/commit/538ab52abd46c2e3c6abb529c5137b5286d29670)
#### [v1.10.0](https://github.com/pear-devs/pear-desktop/compare/v1.9.0...v1.10.0)
> 7 February 2021
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.6 to 0.9.7 [`#146`](https://github.com/pear-devs/pear-desktop/pull/146)
- Reuse the same notification, instead of creating a new one each time the song changes. [`#144`](https://github.com/pear-devs/pear-desktop/pull/144)
- [Snyk] Upgrade ytdl-core from 4.2.1 to 4.3.0 [`#136`](https://github.com/pear-devs/pear-desktop/pull/136)
- bring the new commits to this fork [`#1`](https://github.com/pear-devs/pear-desktop/pull/1)
- GH page [`3bcf409`](https://github.com/pear-devs/pear-desktop/commit/3bcf409f2b1629333714b187c606891cedb12512)
- Add plugin to control playback speed like in Movie Player (from 0.25 to 2) [`f7f3185`](https://github.com/pear-devs/pear-desktop/commit/f7f31850d3d9879002dc47326e4f6ec9a52c25a1)
- Update back.js [`1fdf241`](https://github.com/pear-devs/pear-desktop/commit/1fdf2416ad414035104bfb51b8450d82e566cb13)
#### [v1.9.0](https://github.com/pear-devs/pear-desktop/compare/v1.8.2...v1.9.0)
> 15 January 2021
- [Snyk] Upgrade electron-debug from 3.1.0 to 3.2.0 [`#121`](https://github.com/pear-devs/pear-desktop/pull/121)
- Refactor providers [`#125`](https://github.com/pear-devs/pear-desktop/pull/125)
- Added Discord rich presence and added extra properties to songInfo provider [`#124`](https://github.com/pear-devs/pear-desktop/pull/124)
- Fix plugins with context isolation [`#127`](https://github.com/pear-devs/pear-desktop/pull/127)
- Windows portable exe [`#126`](https://github.com/pear-devs/pear-desktop/pull/126)
- Split providers in 2 [`0743034`](https://github.com/pear-devs/pear-desktop/commit/0743034de0443e889ec11d7ea83727ff4fb96599)
- Added Discord rich presence and added extra properties to songinfo provider [`a8ce87f`](https://github.com/pear-devs/pear-desktop/commit/a8ce87f2ccb4f0fdbd36676883e6a0497bebc263)
- Update discord plugin for new provider + wait for ready [`aec542e`](https://github.com/pear-devs/pear-desktop/commit/aec542e95e2837f54bf19de675f311444789ea4e)
#### [v1.8.2](https://github.com/pear-devs/pear-desktop/compare/v1.8.1...v1.8.2)
> 12 January 2021
- Downloader plugin - custom audio format [`#118`](https://github.com/pear-devs/pear-desktop/pull/118)
- Globalized the song info and song controls, and updated Touch Bar for it. [`#102`](https://github.com/pear-devs/pear-desktop/pull/102)
- Bump electron to v11 [`#120`](https://github.com/pear-devs/pear-desktop/pull/120)
- Globalized the songinfo and song controls, and changed the pause/play button. [`9be3e1a`](https://github.com/pear-devs/pear-desktop/commit/9be3e1afe91f0aa3419040bba65e7b3b83b469c6)
- Simplifies the notification plugin to use the globalized song info [`5bffdbd`](https://github.com/pear-devs/pear-desktop/commit/5bffdbd6285a6816749c467d6e912d14748f9959)
- Loads providers before plugins [`3a5d9bd`](https://github.com/pear-devs/pear-desktop/commit/3a5d9bd973bdd67e77f8a7687c1430245a9490bd)
#### [v1.8.1](https://github.com/pear-devs/pear-desktop/compare/v1.8.0...v1.8.1)
> 8 January 2021
- [Snyk] Upgrade electron-updater from 4.3.5 to 4.3.6 [`#116`](https://github.com/pear-devs/pear-desktop/pull/116)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.18.8 to 1.19.0 [`#117`](https://github.com/pear-devs/pear-desktop/pull/117)
- [Snyk] Upgrade ytdl-core from 4.1.1 to 4.1.2 [`#109`](https://github.com/pear-devs/pear-desktop/pull/109)
- Bump node-notifier from 8.0.0 to 8.0.1 [`#104`](https://github.com/pear-devs/pear-desktop/pull/104)
- fix: upgrade electron-updater from 4.3.5 to 4.3.6 [`0bf77e5`](https://github.com/pear-devs/pear-desktop/commit/0bf77e592a87eb8a5222cf2c1588488a51044422)
- fix: upgrade @cliqz/adblocker-electron from 1.18.8 to 1.19.0 [`5c0cc08`](https://github.com/pear-devs/pear-desktop/commit/5c0cc08d80d60c46e8b27343c6fc302f64fe89e2)
- fix: upgrade ytdl-core from 4.1.1 to 4.1.2 [`e2cc262`](https://github.com/pear-devs/pear-desktop/commit/e2cc2628aea653739f878ec2cd2e72e2e70018a1)
#### [v1.8.0](https://github.com/pear-devs/pear-desktop/compare/v1.7.5...v1.8.0)
> 20 December 2020
- Added Touch Bar plugin [`#101`](https://github.com/pear-devs/pear-desktop/pull/101)
- [Snyk] Upgrade @ffmpeg/core from 0.8.4 to 0.8.5 [`#99`](https://github.com/pear-devs/pear-desktop/pull/99)
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.5 to 0.9.6 [`#100`](https://github.com/pear-devs/pear-desktop/pull/100)
- [Readme] Web folder for readme assets + new SVG animation [`#96`](https://github.com/pear-devs/pear-desktop/pull/96)
- Add new Linux targets (deb, freebsd, rpm) [`#94`](https://github.com/pear-devs/pear-desktop/pull/94)
- Web folder for readme assets + new svg animation [`01fc965`](https://github.com/pear-devs/pear-desktop/commit/01fc9651705f457da63615ff774f00957f783d3d)
- touchbar plugin - fixed code style [`7473677`](https://github.com/pear-devs/pear-desktop/commit/7473677477071ca5e7b18bda3193e345d7fd549f)
- added initial touchbar support [`c3e2c13`](https://github.com/pear-devs/pear-desktop/commit/c3e2c1380810d156d9d6863fffc804242171bec0)
#### [v1.7.5](https://github.com/pear-devs/pear-desktop/compare/v1.7.4...v1.7.5)
> 12 December 2020
- Bump ini from 1.3.5 to 1.3.7 [`#92`](https://github.com/pear-devs/pear-desktop/pull/92)
- Fix adblocking [`#90`](https://github.com/pear-devs/pear-desktop/pull/90)
- Bump adblocker dependency [`49497d0`](https://github.com/pear-devs/pear-desktop/commit/49497d0efb28ee0be5b16d0f1c3660efafcd289c)
- Fix adblocker preloading to inject scripts/styles [`66c5ce4`](https://github.com/pear-devs/pear-desktop/commit/66c5ce46caa85a7ae4ceb3d63a9e168827015c71)
- Add uBlock Origin filters to default sources [`79c7959`](https://github.com/pear-devs/pear-desktop/commit/79c795927a3be96456a2f45159285c64166a29b8)
#### [v1.7.4](https://github.com/pear-devs/pear-desktop/compare/v1.7.3...v1.7.4)
> 8 December 2020
#### [v1.7.3](https://github.com/pear-devs/pear-desktop/compare/v1.7.2...v1.7.3)
> 8 December 2020
- Adblocker: add option to disable default lists [`22c7f70`](https://github.com/pear-devs/pear-desktop/commit/22c7f70c938566a9db9c4d46a57224cfdee43df0)
#### [v1.7.2](https://github.com/pear-devs/pear-desktop/compare/v1.7.1...v1.7.2)
> 6 December 2020
- Add AUR badge + beautify badges [`#82`](https://github.com/pear-devs/pear-desktop/pull/82)
- Bugfix: only use cache with no additional blocklists [`467171a`](https://github.com/pear-devs/pear-desktop/commit/467171a17e648331d63f166c2da2f3134e95b37f)
- Add AUR tag + beautify tags [`d212206`](https://github.com/pear-devs/pear-desktop/commit/d21220693b9ffa26e05fe1963376b636b40b9952)
- Readme: add music-player logo to badges [`3022fac`](https://github.com/pear-devs/pear-desktop/commit/3022facbead40ccd81629c37b870ab33ce7fa106)
#### [v1.7.1](https://github.com/pear-devs/pear-desktop/compare/v1.7.0...v1.7.1)
> 3 December 2020
- Option to restart the app on config changes [`fd97576`](https://github.com/pear-devs/pear-desktop/commit/fd97576611ae80b959ffe7984e88ddc8d28a1ffc)
- Bump version to 1.7.1 [`e07cac2`](https://github.com/pear-devs/pear-desktop/commit/e07cac240691b1c9d6909e457824616182374c3a)
#### [v1.7.0](https://github.com/pear-devs/pear-desktop/compare/v1.6.5...v1.7.0)
> 3 December 2020
- Refactor config, custom plugin options [`#79`](https://github.com/pear-devs/pear-desktop/pull/79)
- Refactor config for simpler use and advanced options in plugins [`8ab2da0`](https://github.com/pear-devs/pear-desktop/commit/8ab2da0482b6211b6b6d43423ec06daed48dac4f)
- Allow editing config (advanced) [`f4fe5c2`](https://github.com/pear-devs/pear-desktop/commit/f4fe5c2a58e1ad555c321f27c00d2d78184fc687)
- Adblocker - advanced options (caching or not, additional lists) [`b94d0d4`](https://github.com/pear-devs/pear-desktop/commit/b94d0d4e8bd3a92bbb5e012a63fa782baa774be7)
#### [v1.6.5](https://github.com/pear-devs/pear-desktop/compare/v1.6.4...v1.6.5)
> 2 December 2020
- Add option to disable hardware acceleration [`#77`](https://github.com/pear-devs/pear-desktop/pull/77)
- Downloader plugin - retry and upgrade dependencies [`#76`](https://github.com/pear-devs/pear-desktop/pull/76)
- Reflect Arch Linux package name change [`#70`](https://github.com/pear-devs/pear-desktop/pull/70)
- Option to hide menu [`#67`](https://github.com/pear-devs/pear-desktop/pull/67)
- Add Arch Linux installation instructions [`#68`](https://github.com/pear-devs/pear-desktop/pull/68)
- Update ytdl-core to 4.1.1 [`33a11ef`](https://github.com/pear-devs/pear-desktop/commit/33a11efe9acad234e41ad9044ae9e67fd573b7f4)
- Autoupdate modal: add download/disable updates buttons [`ae5b85d`](https://github.com/pear-devs/pear-desktop/commit/ae5b85d8d748659f2e23d417560026f24ab8ce9c)
- Option to hide menu (win/linux) [`4bac3ac`](https://github.com/pear-devs/pear-desktop/commit/4bac3ace186c5be2cb9409d2b703f960bd662145)
#### [v1.6.4](https://github.com/pear-devs/pear-desktop/compare/v1.6.3...v1.6.4)
> 24 November 2020
#### [v1.6.3](https://github.com/pear-devs/pear-desktop/compare/v1.6.2...v1.6.3)
> 24 November 2020
- Improve CI [`#64`](https://github.com/pear-devs/pear-desktop/pull/64)
- Ensure menu is visible on all platforms [`#63`](https://github.com/pear-devs/pear-desktop/pull/63)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.18.3 to 1.18.4 [`#62`](https://github.com/pear-devs/pear-desktop/pull/62)
- fix: upgrade @cliqz/adblocker-electron from 1.18.3 to 1.18.4 [`2b243f6`](https://github.com/pear-devs/pear-desktop/commit/2b243f6dcb00d3b6f27fd066c093e7b16bb384e2)
- CI: cache yarn directory [`0fd4933`](https://github.com/pear-devs/pear-desktop/commit/0fd49330d3218ec5f1bc62b72ace28e79d02bc93)
- Run CI on every push/PR [`cf4827d`](https://github.com/pear-devs/pear-desktop/commit/cf4827d780fee510a27eecf42453b0505c52bcf9)
#### [v1.6.2](https://github.com/pear-devs/pear-desktop/compare/v1.6.0...v1.6.2)
> 22 November 2020
- Add github action to build/release [`#60`](https://github.com/pear-devs/pear-desktop/pull/60)
- Bump to node 12 [`#59`](https://github.com/pear-devs/pear-desktop/pull/59)
- Bump to node 12 [`#59`](https://github.com/pear-devs/pear-desktop/pull/59)
- Add downloader (video -> mp3) plugin (in music menu) [`e197087`](https://github.com/pear-devs/pear-desktop/commit/e197087a5027af1ca71ecde7bbdf6351137555b9)
- Delete AppVeyor/Travis CI integration [`941dd90`](https://github.com/pear-devs/pear-desktop/commit/941dd90d77a5c46ed5505918374693fcd892af1f)
- GH action to build/release [`fc4754a`](https://github.com/pear-devs/pear-desktop/commit/fc4754a1709e6eb70d662f89eafd360aa4a77aa2)
#### [v1.6.0](https://github.com/pear-devs/pear-desktop/compare/v1.5.0...v1.6.0)
> 11 November 2020
- [Snyk] Upgrade electron-store from 6.0.0 to 6.0.1 [`#54`](https://github.com/pear-devs/pear-desktop/pull/54)
- Add notifications plugin (notify of song on play event) [`bcff6e5`](https://github.com/pear-devs/pear-desktop/commit/bcff6e51348645395549c206717225fb16a29cda)
- Plugins/event handlers in each window [`9bc81da`](https://github.com/pear-devs/pear-desktop/commit/9bc81da6f2c7f5f35769489e179851bdd80a7da8)
- Option to toggle devtools [`3e97e93`](https://github.com/pear-devs/pear-desktop/commit/3e97e9307cf0991adc5584a603c292b03bc6202d)
#### [v1.5.0](https://github.com/pear-devs/pear-desktop/compare/v1.4.0...v1.5.0)
> 4 October 2020
- Bump node-fetch from 2.6.0 to 2.6.1 [`#45`](https://github.com/pear-devs/pear-desktop/pull/45)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.17.0 to 1.18.0 [`#47`](https://github.com/pear-devs/pear-desktop/pull/47)
- [Snyk] Upgrade electron-updater from 4.3.3 to 4.3.4 [`#40`](https://github.com/pear-devs/pear-desktop/pull/40)
- Bump elliptic from 6.5.2 to 6.5.3 [`#38`](https://github.com/pear-devs/pear-desktop/pull/38)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.16.0 to 1.16.1 [`#37`](https://github.com/pear-devs/pear-desktop/pull/37)
- Bump lodash from 4.17.15 to 4.17.19 [`#34`](https://github.com/pear-devs/pear-desktop/pull/34)
- Option to start at login [`#32`](https://github.com/pear-devs/pear-desktop/pull/32)
- Bump dependencies [`97dce5a`](https://github.com/pear-devs/pear-desktop/commit/97dce5ad41ba7ff7a12d4e57a6a0acfeccd666d8)
- Bump electron to v10 (+ remove devtron, bump spectron) [`5f0dcbb`](https://github.com/pear-devs/pear-desktop/commit/5f0dcbb3fc9b2912bba690db232184d32c599150)
- Navigation plugin: fix arrow style [`8d74a0a`](https://github.com/pear-devs/pear-desktop/commit/8d74a0a9b52c5b5a04b0986e5fbec9b47a35823e)
#### [v1.4.0](https://github.com/pear-devs/pear-desktop/compare/v1.3.3...v1.4.0)
> 12 July 2020
- Bump electron from 8.2.1 to 8.2.4 [`#31`](https://github.com/pear-devs/pear-desktop/pull/31)
- [Snyk] Upgrade electron-store from 5.1.1 to 5.2.0 [`#30`](https://github.com/pear-devs/pear-desktop/pull/30)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.14.4 to 1.15.0 [`#29`](https://github.com/pear-devs/pear-desktop/pull/29)
- [Snyk] Upgrade electron-debug from 3.0.1 to 3.1.0 [`#28`](https://github.com/pear-devs/pear-desktop/pull/28)
- [Snyk] Upgrade electron-updater from 4.3.1 to 4.3.2 [`#27`](https://github.com/pear-devs/pear-desktop/pull/27)
- [Snyk] Upgrade electron-updater from 4.3.0 to 4.3.1 [`#26`](https://github.com/pear-devs/pear-desktop/pull/26)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.14.1 to 1.14.2 [`#25`](https://github.com/pear-devs/pear-desktop/pull/25)
- [Tests] Add integration tests [`#24`](https://github.com/pear-devs/pear-desktop/pull/24)
- Add jest, spectron and getPort util for tests [`736a706`](https://github.com/pear-devs/pear-desktop/commit/736a70680108620cdecab2da9dd48e10354c713e)
- fix: upgrade electron-updater from 4.3.1 to 4.3.2 [`8c94510`](https://github.com/pear-devs/pear-desktop/commit/8c945100e24187885dbbe5bb7830b1da11e4eaa2)
- Add jest config and test environment to launch app [`bce5b7d`](https://github.com/pear-devs/pear-desktop/commit/bce5b7d8ebd96886d462a3c999d72e6c69b6f807)
#### [v1.3.3](https://github.com/pear-devs/pear-desktop/compare/v1.3.2...v1.3.3)
> 29 April 2020
- Move tray click callback in setUpTray [`4824dda`](https://github.com/pear-devs/pear-desktop/commit/4824dda5d52565deb5cd6ef4b51d2d742677a154)
- Bump version to 1.3.3 [`37cac19`](https://github.com/pear-devs/pear-desktop/commit/37cac19d9ccae59b89a68b995eaf7e08c7d24d11)
#### [v1.3.2](https://github.com/pear-devs/pear-desktop/compare/v1.3.1...v1.3.2)
> 26 April 2020
- [Snyk] Upgrade electron-updater from 4.2.5 to 4.3.0 [`#22`](https://github.com/pear-devs/pear-desktop/pull/22)
- fix: upgrade electron-updater from 4.2.5 to 4.3.0 [`9821300`](https://github.com/pear-devs/pear-desktop/commit/98213005d09d00bf013d2217809736bdc334ede6)
- Hide the app (no quit) on close if tray enabled [`430687f`](https://github.com/pear-devs/pear-desktop/commit/430687f4d6d301aaeaeeaa11ae34d971ac3280df)
- Show/hide window when clicking on tray [`058371a`](https://github.com/pear-devs/pear-desktop/commit/058371ace8fbd3d9f126454fdc7dbff86df05506)
#### [v1.3.1](https://github.com/pear-devs/pear-desktop/compare/v1.2.0...v1.3.1)
> 12 April 2020
- Add options and tray [`#21`](https://github.com/pear-devs/pear-desktop/pull/21)
- Upgrade outdated dependencies [`#20`](https://github.com/pear-devs/pear-desktop/pull/20)
- [Plugins] Migrate ad blocker [`#19`](https://github.com/pear-devs/pear-desktop/pull/19)
- Upgrade xo [`297de08`](https://github.com/pear-devs/pear-desktop/commit/297de08278c2704b3baf65c455bba72f72acc06f)
- Bump electron-builder (needed after electron upgrade) [`3d9e59d`](https://github.com/pear-devs/pear-desktop/commit/3d9e59dc90e0e994e20af55af9134477e68907a5)
- Migrate from adblock-rs to cliqz [`422c3fc`](https://github.com/pear-devs/pear-desktop/commit/422c3fc28d83da309a80447dcd5064a4346580e8)
#### [v1.2.0](https://github.com/pear-devs/pear-desktop/compare/v1.1.6...v1.2.0)
> 15 March 2020
- [Snyk] Upgrade electron-localshortcut from 3.1.0 to 3.2.1 [`#13`](https://github.com/pear-devs/pear-desktop/pull/13)
- [Snyk] Upgrade electron-updater from 4.0.6 to 4.2.2 [`#12`](https://github.com/pear-devs/pear-desktop/pull/12)
- [Snyk] Upgrade electron-debug from 2.1.0 to 2.2.0 [`#15`](https://github.com/pear-devs/pear-desktop/pull/15)
- Fix vulnerability [`#16`](https://github.com/pear-devs/pear-desktop/pull/16)
- Plugin: autoconfirm when paused [`#11`](https://github.com/pear-devs/pear-desktop/pull/11)
- Migrate to yarn to install packages without package.json (but keep npm rebuild) [`9371a48`](https://github.com/pear-devs/pear-desktop/commit/9371a4827e2312258a4f692c18f964155d57ceb8)
- Bump electron-store to fix a vulnerability [`7050dfc`](https://github.com/pear-devs/pear-desktop/commit/7050dfca5c6a545dabc334690572d7f88b37e027)
- Bump electron updater [`f25bb59`](https://github.com/pear-devs/pear-desktop/commit/f25bb59065d84cde202b5192688847c528c6ef61)
#### [v1.1.6](https://github.com/pear-devs/pear-desktop/compare/v1.1.5...v1.1.6)
> 11 September 2019
- Bump eslint-utils from 1.3.1 to 1.4.2 [`#7`](https://github.com/pear-devs/pear-desktop/pull/7)
- Bump lodash.mergewith from 4.6.1 to 4.6.2 [`#4`](https://github.com/pear-devs/pear-desktop/pull/4)
- Bump lodash from 4.17.11 to 4.17.14 [`#5`](https://github.com/pear-devs/pear-desktop/pull/5)
- npm audit fix [`1a72129`](https://github.com/pear-devs/pear-desktop/commit/1a72129108935cbe732621d93b877e90d11a4195)
- Fix Google login [`746b5f1`](https://github.com/pear-devs/pear-desktop/commit/746b5f13bb08c614df290e69946cfd116a550521)
- Bump version to 1.1.6 [`6fd10ea`](https://github.com/pear-devs/pear-desktop/commit/6fd10ea4a0f63e9a46e7307d811977f4e0f3213f)
#### [v1.1.5](https://github.com/pear-devs/pear-desktop/compare/v1.1.4...v1.1.5)
> 6 July 2019
- Fix navigation plugin [`b10a1bb`](https://github.com/pear-devs/pear-desktop/commit/b10a1bb32dbea187422a43487527c379a9ddbb26)
- Bump version to 1.1.5 [`07c4a42`](https://github.com/pear-devs/pear-desktop/commit/07c4a429c15f22b173629618518abb97d9ec0100)
#### [v1.1.4](https://github.com/pear-devs/pear-desktop/compare/v1.1.3...v1.1.4)
> 8 June 2019
- isDev -> is package [`a85325f`](https://github.com/pear-devs/pear-desktop/commit/a85325f33dbd40517b6029e500569fc1640af2ef)
- Add titlebar/frame only on MacOS [`b1c4cc9`](https://github.com/pear-devs/pear-desktop/commit/b1c4cc9c45cc48413118aec8ce54767b1983a3e7)
- Bump version to 1.1.4 [`0420f2e`](https://github.com/pear-devs/pear-desktop/commit/0420f2e49e295cede0db22dbb1f35ffafd6318ed)
#### [v1.1.3](https://github.com/pear-devs/pear-desktop/compare/v1.1.2...v1.1.3)
> 2 June 2019
- Bump fstream from 1.0.11 to 1.0.12 [`#3`](https://github.com/pear-devs/pear-desktop/pull/3)
- Version 1.1.3 + npm audit fix [`147ac48`](https://github.com/pear-devs/pear-desktop/commit/147ac48de6540c836e835fefe47e66e55dbdc9bc)
- Fix case for {en/dis}ablePlugin [`e86d63d`](https://github.com/pear-devs/pear-desktop/commit/e86d63da8cb083b89c2a26e6514a5b0df8868b13)
- Remove outdated download links [`ec58b5c`](https://github.com/pear-devs/pear-desktop/commit/ec58b5cbedda8d6f881f0e81f185a1707dbe5fab)
#### [v1.1.2](https://github.com/pear-devs/pear-desktop/compare/v1.1.1...v1.1.2)
> 1 May 2019
- Display error/retry in case of failure [`5a1d7fb`](https://github.com/pear-devs/pear-desktop/commit/5a1d7fbf230fcd840a3ea654f31602fb5f504852)
- Bump version to 1.1.2 [`eac2c5c`](https://github.com/pear-devs/pear-desktop/commit/eac2c5cf14d0a348704f7fbf0ff0bdce02758670)
#### [v1.1.1](https://github.com/pear-devs/pear-desktop/compare/v1.1.0...v1.1.1)
> 28 April 2019
- Update package lock [`2d3f77d`](https://github.com/pear-devs/pear-desktop/commit/2d3f77d96211460bb81a73c8c62b9e5407a7cf30)
- Add travis config [`5279a45`](https://github.com/pear-devs/pear-desktop/commit/5279a45f3537170006ba04cd5d59ac8b879d78a5)
- Add Appveyor config [`abc2bb8`](https://github.com/pear-devs/pear-desktop/commit/abc2bb8a4f749704f2daf376c0d392030f030caf)
#### [v1.1.0](https://github.com/pear-devs/pear-desktop/compare/v1.0.0...v1.1.0)
> 19 April 2019
- Build script + check for updates [`b3c24a5`](https://github.com/pear-devs/pear-desktop/commit/b3c24a521281c352c37d649e8334b581b2a1de4f)
- Add download section in readme [`828e8d4`](https://github.com/pear-devs/pear-desktop/commit/828e8d472ca3d76dea71d95a85f8fa726404b8e7)
- Add release/licence badge in readme [`9d343bf`](https://github.com/pear-devs/pear-desktop/commit/9d343bf779f2fa830302cc84c484bf4a93a25f36)
#### v1.0.0
> 19 April 2019
- Initial commit - app + 4 plugins [`8787b5c`](https://github.com/pear-devs/pear-desktop/commit/8787b5c175d02b52de65f2c559b411d999fa51e4)
- Fix screenshot shadow + compress image [`c5c128f`](https://github.com/pear-devs/pear-desktop/commit/c5c128fa0f77c69e9bf12f6ca551315b37c51e84)
- Missing quote in readme [`4b446ac`](https://github.com/pear-devs/pear-desktop/commit/4b446ac7c816c660cf369f3b8b6e420f766ee35f)
================================================
FILE: electron-builder.yml
================================================
appId: "com.github.th-ch.\u0079\u006f\u0075\u0074\u0075\u0062\u0065\u002d\u006d\u0075\u0073\u0069\u0063"
productName: "\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u0020\u004d\u0075\u0073\u0069\u0063"
files:
- '!*'
- dist
- assets
- license
- '!node_modules'
- 'node_modules/custom-electron-prompt/**'
- 'node_modules/@ghostery/adblocker-electron-preload/**'
- 'node_modules/@ffmpeg.wasm/core-mt/**'
- '!node_modules/**/*.map'
- '!node_modules/**/*.ts'
asarUnpack:
- assets
mac:
identity: null
target:
- target: dmg
arch:
- x64
- arm64
icon: assets/generated/icons/mac/icon.icon
compression: maximum
win:
icon: assets/generated/icons/win/icon.ico
target:
- target: nsis-web
arch:
- x64
- ia32
- arm64
- target: portable
arch:
- x64
- ia32
- arm64
compression: maximum
nsisWeb:
runAfterFinish: false
linux:
icon: assets/generated/icons/png
category: AudioVideo
desktop:
entry:
StartupWMClass: "com.github.th_ch.\u0079\u006f\u0075\u0074\u0075\u0062\u0065\u005f\u006d\u0075\u0073\u0069\u0063"
target:
- target: AppImage
arch:
- x64
- arm64
- armv7l
- target: flatpak
arch:
- x64
- target: deb
arch:
- x64
- arm64
- armv7l
- target: rpm
arch:
- x64
- arm64
- target: snap
arch:
- x64
- target: freebsd
arch:
- x64
- arm64
- armv7l
- target: tar.gz
arch:
- x64
- arm64
- armv7l
appImage:
description: "\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u0020\u004d\u0075\u0073\u0069\u0063 Desktop App bundled with custom plugins"
category: AudioVideo
flatpak:
description: "\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u0020\u004d\u0075\u0073\u0069\u0063 Desktop App bundled with custom plugins"
category: AudioVideo
runtimeVersion: '24.08'
baseVersion: '24.08'
finishArgs:
- '--socket=wayland'
- '--socket=x11'
- '--share=ipc'
- '--device=dri'
- '--socket=pulseaudio'
- '--share=network'
- '--filesystem=xdg-music:rw'
- '--talk-name=org.freedesktop.Notifications'
- '--talk-name=org.gnome.SessionManager'
- '--talk-name=org.kde.StatusNotifierWatcher'
- "--own-name=org.mpris.MediaPlayer2.\u0059\u006f\u0075\u0074\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063.*"
deb:
depends:
- libgtk-3-0
- libnotify4
- libnss3
- libxss1
- libxtst6
- xdg-utils
- libatspi2.0-0
- libuuid1
- libasound2
- libgbm1
rpm:
depends:
- libuuid
fpm:
- '--rpm-rpmbuild-define'
- _build_id_links none
snap:
slots:
- mpris:
interface: mpris
directories:
output: ./pack/
================================================
FILE: electron.vite.config.mts
================================================
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'electron-vite';
import builtinModules from 'builtin-modules';
import Inspect from 'vite-plugin-inspect';
import solidPlugin from 'vite-plugin-solid';
import viteResolve from 'vite-plugin-resolve';
import { withFilter, type UserConfig } from 'vite';
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs';
import pluginLoader from './vite-plugins/plugin-loader.mjs';
import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const resolveAlias = {
'@': resolve(__dirname, './src'),
'@assets': resolve(__dirname, './assets'),
};
export default defineConfig(({ mode }) => {
const isDev = mode === 'development';
const mainConfig: UserConfig = {
experimental: {
enableNativePlugin: true,
},
plugins: [
pluginLoader('backend'),
viteResolve({
'virtual:i18n': i18nImporter(),
'virtual:plugins': pluginVirtualModuleGenerator('main'),
}),
],
publicDir: 'assets',
define: {
__dirname: 'import.meta.dirname',
__filename: 'import.meta.filename',
},
build: {
lib: {
entry: 'src/index.ts',
formats: ['es'],
},
outDir: 'dist/main',
rolldownOptions: {
external: ['electron', 'custom-electron-prompt', ...builtinModules],
input: './src/index.ts',
},
minify: !isDev,
cssMinify: !isDev,
sourcemap: isDev ? 'inline' : undefined,
},
resolve: {
alias: resolveAlias,
},
};
const preloadConfig: UserConfig = {
experimental: {
enableNativePlugin: true,
},
plugins: [
pluginLoader('preload'),
viteResolve({
'virtual:i18n': i18nImporter(),
'virtual:plugins': pluginVirtualModuleGenerator('preload'),
}),
],
build: {
lib: {
entry: 'src/preload.ts',
formats: ['cjs'],
},
outDir: 'dist/preload',
commonjsOptions: {
ignoreDynamicRequires: true,
},
rolldownOptions: {
external: ['electron', 'custom-electron-prompt', ...builtinModules],
input: './src/preload.ts',
},
minify: !isDev,
cssMinify: !isDev,
sourcemap: isDev ? 'inline' : undefined,
},
resolve: {
alias: resolveAlias,
},
};
const rendererConfig: UserConfig = {
experimental: {
enableNativePlugin: !isDev, // Disable native plugin in development mode to avoid issues with HMR (bug in rolldown-vite)
},
plugins: [
pluginLoader('renderer'),
viteResolve({
'virtual:i18n': i18nImporter(),
'virtual:plugins': pluginVirtualModuleGenerator('renderer'),
}),
withFilter(solidPlugin(), {
load: { id: [/\.(tsx|jsx)$/, '/@solid-refresh'] },
}),
],
root: './src/',
build: {
lib: {
entry: 'src/index.html',
formats: ['iife'],
name: 'renderer',
},
outDir: 'dist/renderer',
rolldownOptions: {
external: ['electron', ...builtinModules],
input: './src/index.html',
},
minify: !isDev,
cssMinify: !isDev,
sourcemap: isDev ? 'inline' : undefined,
},
resolve: {
alias: resolveAlias,
},
server: {
cors: {
origin: 'https://music.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com',
},
},
};
if (isDev) {
mainConfig.plugins?.push(
Inspect({
build: true,
outputDir: join(__dirname, '.vite-inspect/backend'),
}),
);
preloadConfig.plugins?.push(
Inspect({
build: true,
outputDir: join(__dirname, '.vite-inspect/preload'),
}),
);
rendererConfig.plugins?.push(
Inspect({
build: true,
outputDir: join(__dirname, '.vite-inspect/renderer'),
}),
);
}
return {
main: mainConfig,
preload: preloadConfig,
renderer: rendererConfig,
};
});
================================================
FILE: eslint.config.mjs
================================================
//@ts-check
import eslint from '@eslint/js';
import prettier from 'eslint-plugin-prettier/recommended';
import solid from 'eslint-plugin-solid/configs/recommended';
import stylistic from '@stylistic/eslint-plugin';
import tsEslint from 'typescript-eslint';
import * as importPlugin from 'eslint-plugin-import';
export default tsEslint.config(
eslint.configs.recommended,
tsEslint.configs.eslintRecommended,
...tsEslint.configs.recommendedTypeChecked,
prettier,
solid,
{ ignores: ['dist', 'node_modules', '*.config.*js', '*.test.*js'] },
{
plugins: {
stylistic,
importPlugin,
},
languageOptions: {
parser: tsEslint.parser,
parserOptions: {
project: ['tsconfig.json', 'tsconfig.test.json'],
sourceType: 'module',
ecmaVersion: 'latest',
},
},
rules: {
'stylistic/arrow-parens': ['error', 'always'],
'stylistic/object-curly-spacing': ['error', 'always'],
'stylistic/jsx-pascal-case': 'error',
'stylistic/jsx-curly-spacing': [
'error',
{ when: 'never', children: true },
],
'stylistic/jsx-sort-props': 'error',
'prettier/prettier': [
'error',
{
singleQuote: true,
semi: true,
tabWidth: 2,
trailingComma: 'all',
quoteProps: 'preserve',
},
],
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': [
'off',
{ checksVoidReturn: false },
],
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
fixStyle: 'inline-type-imports',
prefer: 'type-imports',
disallowTypeAnnotations: false,
},
],
'importPlugin/first': 'error',
'importPlugin/newline-after-import': 'off',
'importPlugin/no-default-export': 'off',
'importPlugin/no-duplicates': 'error',
'importPlugin/no-unresolved': [
'error',
{
ignore: ['^virtual:', '\\?inline$', '\\?raw$', '\\?asset&asarUnpack'],
},
],
'importPlugin/order': [
'error',
{
'groups': [
'builtin',
'external',
['internal', 'index', 'sibling'],
'parent',
'type',
],
'newlines-between': 'always-and-inside-groups',
'alphabetize': { order: 'ignore', caseInsensitive: false },
},
],
'importPlugin/prefer-default-export': 'off',
'camelcase': ['error', { properties: 'never' }],
'class-methods-use-this': 'off',
'stylistic/lines-around-comment': [
'error',
{
beforeBlockComment: false,
afterBlockComment: false,
beforeLineComment: false,
afterLineComment: false,
},
],
'stylistic/max-len': 'off',
'stylistic/no-mixed-operators': 'warn', // prettier does not support no-mixed-operators
'stylistic/no-multi-spaces': ['error', { ignoreEOLComments: true }],
'stylistic/no-tabs': 'error',
'no-void': 'error',
'no-empty': 'off',
'prefer-promise-reject-errors': 'off',
'stylistic/quotes': [
'error',
'single',
{
avoidEscape: true,
allowTemplateLiterals: 'never',
},
],
'stylistic/quote-props': ['error', 'consistent'],
'stylistic/semi': ['error', 'always'],
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts'],
},
'import/resolver': {
typescript: {},
exports: {},
},
},
},
);
================================================
FILE: license
================================================
The MIT License (MIT)
Copyright (c) th-ch (https://github.com/pear-devs/pear-desktop)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "\u0079\u006f\u0075\u0074\u0075\u0062\u0065\u002d\u006d\u0075\u0073\u0069\u0063",
"desktopName": "com.github.th_ch.\u0079\u006f\u0075\u0074\u0075\u0062\u0065\u005f\u006d\u0075\u0073\u0069\u0063",
"productName": "\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u0020\u004d\u0075\u0073\u0069\u0063",
"version": "3.11.0",
"description": "\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u0020\u004d\u0075\u0073\u0069\u0063 Desktop App - including custom plugins",
"main": "./dist/main/index.js",
"type": "module",
"license": "MIT",
"repository": "pear-devs/pear-desktop",
"author": {
"name": "th-ch",
"email": "th-ch@users.noreply.github.com",
"url": "https://github.com/pear-devs/pear-desktop"
},
"scripts": {
"test": "pnpm playwright test",
"test:debug": "pnpm cross-env DEBUG=pw:*,-pw:test:protocol playwright test",
"build": "pnpm electron-vite build",
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
"start": "pnpm electron-vite preview",
"start:debug": "pnpm cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
"dev": "pnpm cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps electron-vite dev --watch",
"dev:renderer": "pnpm cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps electron-vite dev",
"dev:debug": "pnpm cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
"clean": "pnpm del-cli dist && pnpm del-cli pack && pnpm del-cli .vite-inspect",
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
"dist:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p never",
"dist:linux:deb-arm64": "pnpm clean && pnpm build && pnpm electron-builder --linux deb:arm64 -p never",
"dist:linux:rpm-arm64": "pnpm clean && pnpm build && pnpm electron-builder --linux rpm:arm64 -p never",
"dist:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:x64 -p never",
"dist:mac:arm64": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:arm64 -p never",
"dist:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p never",
"dist:win:x64": "pnpm clean && pnpm build && pnpm electron-builder --win nsis-web:x64 -p never",
"lint": "pnpm eslint ./src",
"changelog": "pnpm dlx auto-changelog",
"release:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac -p always",
"release:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p always",
"typecheck": "pnpm tsc -p tsconfig.json --noEmit"
},
"engines": {
"node": ">=22",
"pnpm": ">=10"
},
"pnpm": {
"overrides": {
"vite": "npm:rolldown-vite@7.3.1",
"node-gyp": "12.2.0",
"xml2js": "0.6.2",
"node-fetch": "3.3.2",
"@electron/universal": "3.0.2",
"@babel/runtime": "7.28.6"
},
"patchedDependencies": {
"vudio@2.1.1": "patches/vudio@2.1.1.patch",
"@malept/flatpak-bundler@0.4.0": "patches/@malept__flatpak-bundler@0.4.0.patch",
"kuromoji@0.1.2": "patches/kuromoji@0.1.2.patch",
"file-type@16.5.4": "patches/file-type@16.5.4.patch",
"electron-is@3.0.0": "patches/electron-is@3.0.0.patch",
"mdui@2.1.4": "patches/mdui@2.1.4.patch"
},
"neverBuiltDependencies": []
},
"dependencies": {
"@dehoist/romanize-thai": "1.0.0",
"@electron-toolkit/tsconfig": "2.0.0",
"@electron/remote": "2.1.3",
"@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.12.0",
"@floating-ui/dom": "1.7.5",
"@foobar404/wave": "2.0.5",
"@ghostery/adblocker-electron": "2.13.4",
"@ghostery/adblocker-electron-preload": "2.13.4",
"@hono/node-server": "1.19.9",
"@hono/node-ws": "1.3.0",
"@hono/swagger-ui": "0.5.3",
"@hono/zod-openapi": "1.2.2",
"@hono/zod-validator": "0.7.6",
"@indic-transliteration/sanscript": "1.3.3",
"@jellybrick/dbus-next": "0.10.3",
"@jellybrick/electron-better-web-request": "2.0.0",
"@jellybrick/mpris-service": "2.1.5",
"@jimp/plugin-color": "1.6.0",
"@mdui/icons": "1.0.3",
"@skyra/jaro-winkler": "1.1.1",
"@xhayper/discord-rpc": "1.3.0",
"async-mutex": "0.5.0",
"bgutils-js": "3.2.0",
"butterchurn": "3.0.0-beta.5",
"butterchurn-presets": "3.0.0-beta.4",
"chinese-conv": "^4.0.0",
"color": "5.0.3",
"conf": "15.1.0",
"custom-electron-prompt": "1.6.1",
"deepmerge-ts": "7.1.5",
"electron-debug": "4.1.0",
"electron-is": "3.0.0",
"electron-localshortcut": "3.2.1",
"electron-store": "11.0.2",
"electron-unhandled": "5.0.0",
"electron-updater": "6.7.3",
"es-hangul": "2.3.8",
"fast-average-color": "9.5.0",
"fast-equals": "6.0.0",
"fflate": "0.8.2",
"filenamify": "7.0.1",
"hanja": "1.1.5",
"happy-dom": "20.5.0",
"hono": "4.11.10",
"howler": "2.2.4",
"html-to-text": "9.0.5",
"i18next": "25.8.13",
"jimp": "1.6.0",
"keyboardevent-from-electron-accelerator": "2.0.0",
"keyboardevents-areequal": "0.2.2",
"kuromoji": "0.1.2",
"kuroshiro": "1.2.0",
"kuroshiro-analyzer-kuromoji": "1.1.0",
"lazy-var": "2.2.2",
"mdui": "2.1.4",
"node-html-parser": "7.0.2",
"node-id3": "0.2.9",
"peerjs": "1.5.5",
"pinyin-pro": "^3.27.0",
"semver": "7.7.4",
"serve": "14.2.5",
"socks": "2.8.7",
"solid-element": "1.9.1",
"solid-floating-ui": "0.3.1",
"solid-js": "1.9.11",
"solid-styled-components": "0.28.5",
"solid-transition-group": "0.3.0",
"tinyld": "1.3.4",
"virtua": "0.48.5",
"vudio": "2.1.1",
"x11": "2.3.0",
"youtubei.js": "16.0.1",
"zod": "4.3.6"
},
"devDependencies": {
"@electron-toolkit/tsconfig": "2.0.0",
"@eslint/js": "9.39.3",
"@malept/flatpak-bundler": "0.4.0",
"@playwright/test": "1.58.2",
"@stylistic/eslint-plugin": "5.7.1",
"@total-typescript/ts-reset": "0.6.1",
"@types/electron-localshortcut": "3.1.3",
"@types/howler": "2.2.12",
"@types/html-to-text": "9.0.4",
"@types/semver": "7.7.1",
"@types/trusted-types": "2.0.7",
"bufferutil": "4.1.0",
"builtin-modules": "5.0.0",
"cross-env": "10.1.0",
"del-cli": "7.0.0",
"discord-api-types": "0.38.40",
"electron": "40.1.0",
"electron-builder": "26.7.0",
"electron-builder-squirrel-windows": "26.7.0",
"electron-devtools-installer": "4.0.0",
"electron-vite": "5.0.0",
"eslint": "9.39.3",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-exports": "1.0.0-beta.5",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.5",
"eslint-plugin-solid": "0.14.5",
"glob": "13.0.6",
"node-gyp": "12.2.0",
"ts-morph": "27.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.54.0",
"utf-8-validate": "6.0.6",
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-inspect": "11.3.3",
"vite-plugin-resolve": "2.5.2",
"vite-plugin-solid": "2.11.10",
"ws": "8.19.0"
},
"auto-changelog": {
"hideCredit": true,
"package": true,
"unreleased": true,
"output": "changelog.md"
}
}
================================================
FILE: patches/@malept__flatpak-bundler@0.4.0.patch
================================================
diff --git a/index.js b/index.js
index 5968fcf47b69094993b0f861c03f5560e4a6a9b7..0fe16d4f40612c0abfa57898909ce0083f56944c 100644
--- a/index.js
+++ b/index.js
@@ -56,19 +56,23 @@ function getOptionsWithDefaults (options, manifest) {
async function spawnWithLogging (options, command, args, allowFail) {
return new Promise((resolve, reject) => {
logger(`$ ${command} ${args.join(' ')}`)
+ const output = []
const child = childProcess.spawn(command, args, { cwd: options['working-dir'] })
child.stdout.on('data', (data) => {
+ output.push(data)
logger(`1> ${data}`)
})
child.stderr.on('data', (data) => {
+ output.push(data)
logger(`2> ${data}`)
})
child.on('error', (error) => {
+ logger(`error - ${error.message} ${error.stack}`)
reject(error)
})
child.on('close', (code) => {
if (!allowFail && code !== 0) {
- reject(new Error(`${command} failed with status code ${code}`))
+ reject(new Error(`${command} ${args.join(' ')} failed with status code ${code} ${output.join(' ')}`))
}
resolve(code === 0)
})
================================================
FILE: patches/electron-is@3.0.0.patch
================================================
diff --git a/is.d.ts b/is.d.ts
index fb861f7b401914f0f89cb4edf25c51df5cb05812..82144733cd34d88e2deb2e4713b104418e673f2e 100644
--- a/is.d.ts
+++ b/is.d.ts
@@ -5,6 +5,7 @@ declare namespace is {
export function macOS(): boolean;
export function windows(): boolean;
export function linux(): boolean;
+ export function freebsd(): boolean;
export function x86(): boolean;
export function x64(): boolean;
export function production(): boolean;
diff --git a/is.js b/is.js
index a76bb1755a2728bde185b35d847031d3b8ea4ab0..f6b03406c17342f5af078de069e5bbbd2246e152 100644
--- a/is.js
+++ b/is.js
@@ -39,6 +39,10 @@ module.exports = {
linux: function () {
return process.platform === 'linux'
},
+ // Checks if we are under FreeBSD OS
+ freebsd: function () {
+ return process.platform === "freebsd"
+ },
// Checks if we are the processor's arch is x86
x86: function () {
return process.arch === 'ia32'
================================================
FILE: patches/file-type@16.5.4.patch
================================================
diff --git a/core.js b/core.js
index d653e66a4056c27cca777d4e25222acae3b2ec85..a91741d67df85fd9627889a6c7197ac4e6a3a813 100644
--- a/core.js
+++ b/core.js
@@ -1415,8 +1415,7 @@ async function _fromTokenizer(tokenizer) {
}
const stream = readableStream => new Promise((resolve, reject) => {
- // Using `eval` to work around issues when bundling with Webpack
- const stream = eval('require')('stream'); // eslint-disable-line no-eval
+ const stream = require('node:stream');
readableStream.on('error', reject);
readableStream.once('readable', async () => {
================================================
FILE: patches/kuromoji@0.1.2.patch
================================================
diff --git a/build/kuromoji.js b/build/kuromoji.js
index f0f4ae9183ff8965fda64a2042f29936f76506d1..fa481f01cb927401e89e432d39ac14950ce28260 100644
--- a/build/kuromoji.js
+++ b/build/kuromoji.js
@@ -1,5 +1,5 @@
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.kuromoji = f()}})(function(){var define,module,exports;return (function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1 && value % 1 == 0 && value < length);
+ (type == 'number' ||
+ (type != 'symbol' && reIsUint.test(value))) &&
+ (value > -1 && value % 1 == 0 && value < length);
}
/** `Object#toString` result references. */
@@ -755,6 +758,14 @@ var freeProcess = moduleExports$1 && freeGlobal.process;
/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
try {
+ // Use `util.types` for Node.js 10+.
+ var types = freeModule$1 && freeModule$1.require && freeModule$1.require('util').types;
+
+ if (types) {
+ return types;
+ }
+
+ // Legacy `process.binding('util')` for Node.js < 10.
return freeProcess && freeProcess.binding && freeProcess.binding('util');
} catch (e) {}
}());
@@ -939,6 +950,9 @@ function createObjectIterator(obj) {
var len = okeys.length;
return function next() {
var key = okeys[++i];
+ if (key === '__proto__') {
+ return next();
+ }
return i < len ? {value: obj[key], key: key} : null;
};
}
@@ -970,6 +984,7 @@ function _eachOfLimit(limit) {
var nextElem = iterator(obj);
var done = false;
var running = 0;
+ var looping = false;
function iterateeCallback(err, value) {
running -= 1;
@@ -981,12 +996,13 @@ function _eachOfLimit(limit) {
done = true;
return callback(null);
}
- else {
+ else if (!looping) {
replenish();
}
}
function replenish () {
+ looping = true;
while (running < limit && !done) {
var elem = nextElem();
if (elem === null) {
@@ -999,6 +1015,7 @@ function _eachOfLimit(limit) {
running += 1;
iteratee(elem.value, elem.key, onlyOnce(iterateeCallback));
}
+ looping = false;
}
replenish();
@@ -3819,7 +3836,7 @@ function memoize(fn, hasher) {
/**
* Calls `callback` on a later loop around the event loop. In Node.js this just
- * calls `process.nextTicl`. In the browser it will use `setImmediate` if
+ * calls `process.nextTick`. In the browser it will use `setImmediate` if
* available, otherwise `setTimeout(callback, 0)`, which means other higher
* priority events may precede the execution of `callback`.
*
@@ -5596,8 +5613,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
})));
-}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"_process":4}],2:[function(require,module,exports){
+}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate)
+},{"_process":5,"timers":6}],2:[function(require,module,exports){
// Copyright (c) 2014 Takuya Asano All Rights Reserved.
(function () {
@@ -6391,328 +6408,2792 @@ Object.defineProperty(exports, '__esModule', { value: true });
})();
},{}],3:[function(require,module,exports){
-(function (process){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-// resolves . and .. elements in a path array with directory names there
-// must be no slashes, empty elements, or device names (c:\) in the array
-// (so also no leading and trailing slashes - it does not distinguish
-// relative and absolute paths)
-function normalizeArray(parts, allowAboveRoot) {
- // if the path tries to go above the root, `up` ends up > 0
- var up = 0;
- for (var i = parts.length - 1; i >= 0; i--) {
- var last = parts[i];
- if (last === '.') {
- parts.splice(i, 1);
- } else if (last === '..') {
- parts.splice(i, 1);
- up++;
- } else if (up) {
- parts.splice(i, 1);
- up--;
- }
- }
-
- // if the path is allowed to go above the root, restore leading ..s
- if (allowAboveRoot) {
- for (; up--; up) {
- parts.unshift('..');
+"use strict";
+// DEFLATE is a complex format; to read this code, you should probably check the RFC first:
+// https://tools.ietf.org/html/rfc1951
+// You may also wish to take a look at the guide I made about this program:
+// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
+// Some of the following code is similar to that of UZIP.js:
+// https://github.com/photopea/UZIP.js
+// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.
+// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint
+// is better for memory in most engines (I *think*).
+var node_worker_1 = require("./node-worker.cjs");
+// aliases for shorter compressed code (most minifers don't do this)
+var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;
+// fixed length extra bits
+var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);
+// fixed distance extra bits
+var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);
+// code length index map
+var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
+// get base, reverse index map from extra bits
+var freb = function (eb, start) {
+ var b = new u16(31);
+ for (var i = 0; i < 31; ++i) {
+ b[i] = start += 1 << eb[i - 1];
+ }
+ // numbers here are at max 18 bits
+ var r = new i32(b[30]);
+ for (var i = 1; i < 30; ++i) {
+ for (var j = b[i]; j < b[i + 1]; ++j) {
+ r[j] = ((j - b[i]) << 5) | i;
+ }
}
- }
-
- return parts;
-}
-
-// Split a filename into [root, dir, basename, ext], unix version
-// 'root' is just a slash, or nothing.
-var splitPathRe =
- /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
-var splitPath = function(filename) {
- return splitPathRe.exec(filename).slice(1);
+ return { b: b, r: r };
};
-
-// path.resolve([from ...], to)
-// posix version
-exports.resolve = function() {
- var resolvedPath = '',
- resolvedAbsolute = false;
-
- for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
- var path = (i >= 0) ? arguments[i] : process.cwd();
-
- // Skip empty and invalid entries
- if (typeof path !== 'string') {
- throw new TypeError('Arguments to path.resolve must be strings');
- } else if (!path) {
- continue;
+var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;
+// we can ignore the fact that the other numbers are wrong; they never happen anyway
+fl[28] = 258, revfl[258] = 28;
+var _b = freb(fdeb, 0), fd = _b.b, revfd = _b.r;
+// map of value to reverse (assuming 16 bits)
+var rev = new u16(32768);
+for (var i = 0; i < 32768; ++i) {
+ // reverse table algorithm from SO
+ var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1);
+ x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2);
+ x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4);
+ rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1;
+}
+// create huffman tree from u8 "map": index -> code length for code index
+// mb (max bits) must be at most 15
+// TODO: optimize/split up?
+var hMap = (function (cd, mb, r) {
+ var s = cd.length;
+ // index
+ var i = 0;
+ // u16 "map": index -> # of codes with bit length = index
+ var l = new u16(mb);
+ // length of cd must be 288 (total # of codes)
+ for (; i < s; ++i) {
+ if (cd[i])
+ ++l[cd[i] - 1];
+ }
+ // u16 "map": index -> minimum code for bit length = index
+ var le = new u16(mb);
+ for (i = 1; i < mb; ++i) {
+ le[i] = (le[i - 1] + l[i - 1]) << 1;
+ }
+ var co;
+ if (r) {
+ // u16 "map": index -> number of actual bits, symbol for code
+ co = new u16(1 << mb);
+ // bits to remove for reverser
+ var rvb = 15 - mb;
+ for (i = 0; i < s; ++i) {
+ // ignore 0 lengths
+ if (cd[i]) {
+ // num encoding both symbol and bits read
+ var sv = (i << 4) | cd[i];
+ // free bits
+ var r_1 = mb - cd[i];
+ // start value
+ var v = le[cd[i] - 1]++ << r_1;
+ // m is end value
+ for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
+ // every 16 bit value starting with the code yields the same result
+ co[rev[v] >> rvb] = sv;
+ }
+ }
+ }
}
-
- resolvedPath = path + '/' + resolvedPath;
- resolvedAbsolute = path.charAt(0) === '/';
- }
-
- // At this point the path should be resolved to a full absolute path, but
- // handle relative paths to be safe (might happen when process.cwd() fails)
-
- // Normalize the path
- resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
- return !!p;
- }), !resolvedAbsolute).join('/');
-
- return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+ else {
+ co = new u16(s);
+ for (i = 0; i < s; ++i) {
+ if (cd[i]) {
+ co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);
+ }
+ }
+ }
+ return co;
+});
+// fixed length tree
+var flt = new u8(288);
+for (var i = 0; i < 144; ++i)
+ flt[i] = 8;
+for (var i = 144; i < 256; ++i)
+ flt[i] = 9;
+for (var i = 256; i < 280; ++i)
+ flt[i] = 7;
+for (var i = 280; i < 288; ++i)
+ flt[i] = 8;
+// fixed distance tree
+var fdt = new u8(32);
+for (var i = 0; i < 32; ++i)
+ fdt[i] = 5;
+// fixed length map
+var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1);
+// fixed distance map
+var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1);
+// find max of array
+var max = function (a) {
+ var m = a[0];
+ for (var i = 1; i < a.length; ++i) {
+ if (a[i] > m)
+ m = a[i];
+ }
+ return m;
};
-
-// path.normalize(path)
-// posix version
-exports.normalize = function(path) {
- var isAbsolute = exports.isAbsolute(path),
- trailingSlash = substr(path, -1) === '/';
-
- // Normalize the path
- path = normalizeArray(filter(path.split('/'), function(p) {
- return !!p;
- }), !isAbsolute).join('/');
-
- if (!path && !isAbsolute) {
- path = '.';
- }
- if (path && trailingSlash) {
- path += '/';
- }
-
- return (isAbsolute ? '/' : '') + path;
+// read d, starting at bit p and mask with m
+var bits = function (d, p, m) {
+ var o = (p / 8) | 0;
+ return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;
};
-
-// posix version
-exports.isAbsolute = function(path) {
- return path.charAt(0) === '/';
+// read d, starting at bit p continuing for at least 16 bits
+var bits16 = function (d, p) {
+ var o = (p / 8) | 0;
+ return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));
};
-
-// posix version
-exports.join = function() {
- var paths = Array.prototype.slice.call(arguments, 0);
- return exports.normalize(filter(paths, function(p, index) {
- if (typeof p !== 'string') {
- throw new TypeError('Arguments to path.join must be strings');
- }
- return p;
- }).join('/'));
+// get end of byte
+var shft = function (p) { return ((p + 7) / 8) | 0; };
+// typed array slice - allows garbage collector to free original reference,
+// while being more compatible than .slice
+var slc = function (v, s, e) {
+ if (s == null || s < 0)
+ s = 0;
+ if (e == null || e > v.length)
+ e = v.length;
+ // can't use .constructor in case user-supplied
+ return new u8(v.subarray(s, e));
};
-
-
-// path.relative(from, to)
-// posix version
-exports.relative = function(from, to) {
- from = exports.resolve(from).substr(1);
- to = exports.resolve(to).substr(1);
-
- function trim(arr) {
- var start = 0;
- for (; start < arr.length; start++) {
- if (arr[start] !== '') break;
- }
-
- var end = arr.length - 1;
- for (; end >= 0; end--) {
- if (arr[end] !== '') break;
+/**
+ * Codes for errors generated within this library
+ */
+exports.FlateErrorCode = {
+ UnexpectedEOF: 0,
+ InvalidBlockType: 1,
+ InvalidLengthLiteral: 2,
+ InvalidDistance: 3,
+ StreamFinished: 4,
+ NoStreamHandler: 5,
+ InvalidHeader: 6,
+ NoCallback: 7,
+ InvalidUTF8: 8,
+ ExtraFieldTooLong: 9,
+ InvalidDate: 10,
+ FilenameTooLong: 11,
+ StreamFinishing: 12,
+ InvalidZipData: 13,
+ UnknownCompressionMethod: 14
+};
+// error codes
+var ec = [
+ 'unexpected EOF',
+ 'invalid block type',
+ 'invalid length/literal',
+ 'invalid distance',
+ 'stream finished',
+ 'no stream handler',
+ ,
+ 'no callback',
+ 'invalid UTF-8 data',
+ 'extra field too long',
+ 'date not in range 1980-2099',
+ 'filename too long',
+ 'stream finishing',
+ 'invalid zip data'
+ // determined by unknown compression method
+];
+;
+var err = function (ind, msg, nt) {
+ var e = new Error(msg || ec[ind]);
+ e.code = ind;
+ if (Error.captureStackTrace)
+ Error.captureStackTrace(e, err);
+ if (!nt)
+ throw e;
+ return e;
+};
+// expands raw DEFLATE data
+var inflt = function (dat, st, buf, dict) {
+ // source length dict length
+ var sl = dat.length, dl = dict ? dict.length : 0;
+ if (!sl || st.f && !st.l)
+ return buf || new u8(0);
+ var noBuf = !buf;
+ // have to estimate size
+ var resize = noBuf || st.i != 2;
+ // no state
+ var noSt = st.i;
+ // Assumes roughly 33% compression ratio average
+ if (noBuf)
+ buf = new u8(sl * 3);
+ // ensure buffer can fit at least l elements
+ var cbuf = function (l) {
+ var bl = buf.length;
+ // need to increase size to fit
+ if (l > bl) {
+ // Double or set to necessary, whichever is greater
+ var nbuf = new u8(Math.max(bl * 2, l));
+ nbuf.set(buf);
+ buf = nbuf;
+ }
+ };
+ // last chunk bitpos bytes
+ var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
+ // total bits
+ var tbts = sl * 8;
+ do {
+ if (!lm) {
+ // BFINAL - this is only 1 when last chunk is next
+ final = bits(dat, pos, 1);
+ // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman
+ var type = bits(dat, pos + 1, 3);
+ pos += 3;
+ if (!type) {
+ // go to end of byte boundary
+ var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l;
+ if (t > sl) {
+ if (noSt)
+ err(0);
+ break;
+ }
+ // ensure size
+ if (resize)
+ cbuf(bt + l);
+ // Copy over uncompressed data
+ buf.set(dat.subarray(s, t), bt);
+ // Get new bitpos, update byte count
+ st.b = bt += l, st.p = pos = t * 8, st.f = final;
+ continue;
+ }
+ else if (type == 1)
+ lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
+ else if (type == 2) {
+ // literal lengths
+ var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
+ var tl = hLit + bits(dat, pos + 5, 31) + 1;
+ pos += 14;
+ // length+distance tree
+ var ldt = new u8(tl);
+ // code length tree
+ var clt = new u8(19);
+ for (var i = 0; i < hcLen; ++i) {
+ // use index map to get real code
+ clt[clim[i]] = bits(dat, pos + i * 3, 7);
+ }
+ pos += hcLen * 3;
+ // code lengths bits
+ var clb = max(clt), clbmsk = (1 << clb) - 1;
+ // code lengths map
+ var clm = hMap(clt, clb, 1);
+ for (var i = 0; i < tl;) {
+ var r = clm[bits(dat, pos, clbmsk)];
+ // bits read
+ pos += r & 15;
+ // symbol
+ var s = r >> 4;
+ // code length to copy
+ if (s < 16) {
+ ldt[i++] = s;
+ }
+ else {
+ // copy count
+ var c = 0, n = 0;
+ if (s == 16)
+ n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
+ else if (s == 17)
+ n = 3 + bits(dat, pos, 7), pos += 3;
+ else if (s == 18)
+ n = 11 + bits(dat, pos, 127), pos += 7;
+ while (n--)
+ ldt[i++] = c;
+ }
+ }
+ // length tree distance tree
+ var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
+ // max length bits
+ lbt = max(lt);
+ // max dist bits
+ dbt = max(dt);
+ lm = hMap(lt, lbt, 1);
+ dm = hMap(dt, dbt, 1);
+ }
+ else
+ err(1);
+ if (pos > tbts) {
+ if (noSt)
+ err(0);
+ break;
+ }
+ }
+ // Make sure the buffer can hold this + the largest possible addition
+ // Maximum chunk size (practically, theoretically infinite) is 2^17
+ if (resize)
+ cbuf(bt + 131072);
+ var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
+ var lpos = pos;
+ for (;; lpos = pos) {
+ // bits read, code
+ var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
+ pos += c & 15;
+ if (pos > tbts) {
+ if (noSt)
+ err(0);
+ break;
+ }
+ if (!c)
+ err(2);
+ if (sym < 256)
+ buf[bt++] = sym;
+ else if (sym == 256) {
+ lpos = pos, lm = null;
+ break;
+ }
+ else {
+ var add = sym - 254;
+ // no extra bits needed if less
+ if (sym > 264) {
+ // index
+ var i = sym - 257, b = fleb[i];
+ add = bits(dat, pos, (1 << b) - 1) + fl[i];
+ pos += b;
+ }
+ // dist
+ var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
+ if (!d)
+ err(3);
+ pos += d & 15;
+ var dt = fd[dsym];
+ if (dsym > 3) {
+ var b = fdeb[dsym];
+ dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
+ }
+ if (pos > tbts) {
+ if (noSt)
+ err(0);
+ break;
+ }
+ if (resize)
+ cbuf(bt + 131072);
+ var end = bt + add;
+ if (bt < dt) {
+ var shift = dl - dt, dend = Math.min(dt, end);
+ if (shift + bt < 0)
+ err(3);
+ for (; bt < dend; ++bt)
+ buf[bt] = dict[shift + bt];
+ }
+ for (; bt < end; ++bt)
+ buf[bt] = buf[bt - dt];
+ }
+ }
+ st.l = lm, st.p = lpos, st.b = bt, st.f = final;
+ if (lm)
+ final = 1, st.m = lbt, st.d = dm, st.n = dbt;
+ } while (!final);
+ // don't reallocate for streams or user buffers
+ return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
+};
+// starting at p, write the minimum number of bits that can hold v to d
+var wbits = function (d, p, v) {
+ v <<= p & 7;
+ var o = (p / 8) | 0;
+ d[o] |= v;
+ d[o + 1] |= v >> 8;
+};
+// starting at p, write the minimum number of bits (>8) that can hold v to d
+var wbits16 = function (d, p, v) {
+ v <<= p & 7;
+ var o = (p / 8) | 0;
+ d[o] |= v;
+ d[o + 1] |= v >> 8;
+ d[o + 2] |= v >> 16;
+};
+// creates code lengths from a frequency table
+var hTree = function (d, mb) {
+ // Need extra info to make a tree
+ var t = [];
+ for (var i = 0; i < d.length; ++i) {
+ if (d[i])
+ t.push({ s: i, f: d[i] });
+ }
+ var s = t.length;
+ var t2 = t.slice();
+ if (!s)
+ return { t: et, l: 0 };
+ if (s == 1) {
+ var v = new u8(t[0].s + 1);
+ v[t[0].s] = 1;
+ return { t: v, l: 1 };
+ }
+ t.sort(function (a, b) { return a.f - b.f; });
+ // after i2 reaches last ind, will be stopped
+ // freq must be greater than largest possible number of symbols
+ t.push({ s: -1, f: 25001 });
+ var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
+ t[0] = { s: -1, f: l.f + r.f, l: l, r: r };
+ // efficient algorithm from UZIP.js
+ // i0 is lookbehind, i2 is lookahead - after processing two low-freq
+ // symbols that combined have high freq, will start processing i2 (high-freq,
+ // non-composite) symbols instead
+ // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/
+ while (i1 != s - 1) {
+ l = t[t[i0].f < t[i2].f ? i0++ : i2++];
+ r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
+ t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };
+ }
+ var maxSym = t2[0].s;
+ for (var i = 1; i < s; ++i) {
+ if (t2[i].s > maxSym)
+ maxSym = t2[i].s;
+ }
+ // code lengths
+ var tr = new u16(maxSym + 1);
+ // max bits in tree
+ var mbt = ln(t[i1 - 1], tr, 0);
+ if (mbt > mb) {
+ // more algorithms from UZIP.js
+ // TODO: find out how this code works (debt)
+ // ind debt
+ var i = 0, dt = 0;
+ // left cost
+ var lft = mbt - mb, cst = 1 << lft;
+ t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });
+ for (; i < s; ++i) {
+ var i2_1 = t2[i].s;
+ if (tr[i2_1] > mb) {
+ dt += cst - (1 << (mbt - tr[i2_1]));
+ tr[i2_1] = mb;
+ }
+ else
+ break;
+ }
+ dt >>= lft;
+ while (dt > 0) {
+ var i2_2 = t2[i].s;
+ if (tr[i2_2] < mb)
+ dt -= 1 << (mb - tr[i2_2]++ - 1);
+ else
+ ++i;
+ }
+ for (; i >= 0 && dt; --i) {
+ var i2_3 = t2[i].s;
+ if (tr[i2_3] == mb) {
+ --tr[i2_3];
+ ++dt;
+ }
+ }
+ mbt = mb;
}
-
- if (start > end) return [];
- return arr.slice(start, end - start + 1);
- }
-
- var fromParts = trim(from.split('/'));
- var toParts = trim(to.split('/'));
-
- var length = Math.min(fromParts.length, toParts.length);
- var samePartsLength = length;
- for (var i = 0; i < length; i++) {
- if (fromParts[i] !== toParts[i]) {
- samePartsLength = i;
- break;
+ return { t: new u8(tr), l: mbt };
+};
+// get the max length and assign length codes
+var ln = function (n, l, d) {
+ return n.s == -1
+ ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))
+ : (l[n.s] = d);
+};
+// length codes generation
+var lc = function (c) {
+ var s = c.length;
+ // Note that the semicolon was intentional
+ while (s && !c[--s])
+ ;
+ var cl = new u16(++s);
+ // ind num streak
+ var cli = 0, cln = c[0], cls = 1;
+ var w = function (v) { cl[cli++] = v; };
+ for (var i = 1; i <= s; ++i) {
+ if (c[i] == cln && i != s)
+ ++cls;
+ else {
+ if (!cln && cls > 2) {
+ for (; cls > 138; cls -= 138)
+ w(32754);
+ if (cls > 2) {
+ w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);
+ cls = 0;
+ }
+ }
+ else if (cls > 3) {
+ w(cln), --cls;
+ for (; cls > 6; cls -= 6)
+ w(8304);
+ if (cls > 2)
+ w(((cls - 3) << 5) | 8208), cls = 0;
+ }
+ while (cls--)
+ w(cln);
+ cls = 1;
+ cln = c[i];
+ }
}
- }
-
- var outputParts = [];
- for (var i = samePartsLength; i < fromParts.length; i++) {
- outputParts.push('..');
- }
-
- outputParts = outputParts.concat(toParts.slice(samePartsLength));
-
- return outputParts.join('/');
+ return { c: cl.subarray(0, cli), n: s };
};
-
-exports.sep = '/';
-exports.delimiter = ':';
-
-exports.dirname = function(path) {
- var result = splitPath(path),
- root = result[0],
- dir = result[1];
-
- if (!root && !dir) {
- // No dirname whatsoever
- return '.';
- }
-
- if (dir) {
- // It has a dirname, strip trailing slash
- dir = dir.substr(0, dir.length - 1);
- }
-
- return root + dir;
+// calculate the length of output from tree, code lengths
+var clen = function (cf, cl) {
+ var l = 0;
+ for (var i = 0; i < cl.length; ++i)
+ l += cf[i] * cl[i];
+ return l;
};
-
-
-exports.basename = function(path, ext) {
- var f = splitPath(path)[2];
- // TODO: make this comparison case-insensitive on windows?
- if (ext && f.substr(-1 * ext.length) === ext) {
- f = f.substr(0, f.length - ext.length);
- }
- return f;
+// writes a fixed block
+// returns the new bit pos
+var wfblk = function (out, pos, dat) {
+ // no need to write 00 as type: TypedArray defaults to 0
+ var s = dat.length;
+ var o = shft(pos + 2);
+ out[o] = s & 255;
+ out[o + 1] = s >> 8;
+ out[o + 2] = out[o] ^ 255;
+ out[o + 3] = out[o + 1] ^ 255;
+ for (var i = 0; i < s; ++i)
+ out[o + i + 4] = dat[i];
+ return (o + 4 + s) * 8;
};
-
-
-exports.extname = function(path) {
- return splitPath(path)[3];
+// writes a block
+var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
+ wbits(out, p++, final);
+ ++lf[256];
+ var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l;
+ var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l;
+ var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
+ var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
+ var lcfreq = new u16(19);
+ for (var i = 0; i < lclt.length; ++i)
+ ++lcfreq[lclt[i] & 31];
+ for (var i = 0; i < lcdt.length; ++i)
+ ++lcfreq[lcdt[i] & 31];
+ var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
+ var nlcc = 19;
+ for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
+ ;
+ var flen = (bl + 5) << 3;
+ var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
+ var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
+ if (bs >= 0 && flen <= ftlen && flen <= dtlen)
+ return wfblk(out, p, dat.subarray(bs, bs + bl));
+ var lm, ll, dm, dl;
+ wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
+ if (dtlen < ftlen) {
+ lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
+ var llm = hMap(lct, mlcb, 0);
+ wbits(out, p, nlc - 257);
+ wbits(out, p + 5, ndc - 1);
+ wbits(out, p + 10, nlcc - 4);
+ p += 14;
+ for (var i = 0; i < nlcc; ++i)
+ wbits(out, p + 3 * i, lct[clim[i]]);
+ p += 3 * nlcc;
+ var lcts = [lclt, lcdt];
+ for (var it = 0; it < 2; ++it) {
+ var clct = lcts[it];
+ for (var i = 0; i < clct.length; ++i) {
+ var len = clct[i] & 31;
+ wbits(out, p, llm[len]), p += lct[len];
+ if (len > 15)
+ wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12;
+ }
+ }
+ }
+ else {
+ lm = flm, ll = flt, dm = fdm, dl = fdt;
+ }
+ for (var i = 0; i < li; ++i) {
+ var sym = syms[i];
+ if (sym > 255) {
+ var len = (sym >> 18) & 31;
+ wbits16(out, p, lm[len + 257]), p += ll[len + 257];
+ if (len > 7)
+ wbits(out, p, (sym >> 23) & 31), p += fleb[len];
+ var dst = sym & 31;
+ wbits16(out, p, dm[dst]), p += dl[dst];
+ if (dst > 3)
+ wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst];
+ }
+ else {
+ wbits16(out, p, lm[sym]), p += ll[sym];
+ }
+ }
+ wbits16(out, p, lm[256]);
+ return p + ll[256];
};
-
-function filter (xs, f) {
- if (xs.filter) return xs.filter(f);
- var res = [];
- for (var i = 0; i < xs.length; i++) {
- if (f(xs[i], i, xs)) res.push(xs[i]);
+// deflate options (nice << 13) | chain
+var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
+// empty
+var et = /*#__PURE__*/ new u8(0);
+// compresses data into a raw DEFLATE buffer
+var dflt = function (dat, lvl, plvl, pre, post, st) {
+ var s = st.z || dat.length;
+ var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);
+ // writing to this writes to the output buffer
+ var w = o.subarray(pre, o.length - post);
+ var lst = st.l;
+ var pos = (st.r || 0) & 7;
+ if (lvl) {
+ if (pos)
+ w[0] = st.r >> 3;
+ var opt = deo[lvl - 1];
+ var n = opt >> 13, c = opt & 8191;
+ var msk_1 = (1 << plvl) - 1;
+ // prev 2-byte val map curr 2-byte val map
+ var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
+ var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
+ var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };
+ // 24576 is an arbitrary number of maximum symbols per block
+ // 424 buffer for last block
+ var syms = new i32(25000);
+ // length/literal freq distance freq
+ var lf = new u16(288), df = new u16(32);
+ // l/lcnt exbits index l/lind waitdx blkpos
+ var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
+ for (; i + 2 < s; ++i) {
+ // hash value
+ var hv = hsh(i);
+ // index mod 32768 previous index mod
+ var imod = i & 32767, pimod = head[hv];
+ prev[imod] = pimod;
+ head[hv] = imod;
+ // We always should modify head and prev, but only add symbols if
+ // this data is not yet processed ("wait" for wait index)
+ if (wi <= i) {
+ // bytes remaining
+ var rem = s - i;
+ if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {
+ pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
+ li = lc_1 = eb = 0, bs = i;
+ for (var j = 0; j < 286; ++j)
+ lf[j] = 0;
+ for (var j = 0; j < 30; ++j)
+ df[j] = 0;
+ }
+ // len dist chain
+ var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
+ if (rem > 2 && hv == hsh(i - dif)) {
+ var maxn = Math.min(n, rem) - 1;
+ var maxd = Math.min(32767, i);
+ // max possible length
+ // not capped at dif because decompressors implement "rolling" index population
+ var ml = Math.min(258, rem);
+ while (dif <= maxd && --ch_1 && imod != pimod) {
+ if (dat[i + l] == dat[i + l - dif]) {
+ var nl = 0;
+ for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
+ ;
+ if (nl > l) {
+ l = nl, d = dif;
+ // break out early when we reach "nice" (we are satisfied enough)
+ if (nl > maxn)
+ break;
+ // now, find the rarest 2-byte sequence within this
+ // length of literals and search for that instead.
+ // Much faster than just using the start
+ var mmd = Math.min(dif, nl - 2);
+ var md = 0;
+ for (var j = 0; j < mmd; ++j) {
+ var ti = i - dif + j & 32767;
+ var pti = prev[ti];
+ var cd = ti - pti & 32767;
+ if (cd > md)
+ md = cd, pimod = ti;
+ }
+ }
+ }
+ // check the previous match
+ imod = pimod, pimod = prev[imod];
+ dif += imod - pimod & 32767;
+ }
+ }
+ // d will be nonzero only when a match was found
+ if (d) {
+ // store both dist and len data in one int32
+ // Make sure this is recognized as a len/dist with 28th bit (2^28)
+ syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];
+ var lin = revfl[l] & 31, din = revfd[d] & 31;
+ eb += fleb[lin] + fdeb[din];
+ ++lf[257 + lin];
+ ++df[din];
+ wi = i + l;
+ ++lc_1;
+ }
+ else {
+ syms[li++] = dat[i];
+ ++lf[dat[i]];
+ }
+ }
+ }
+ for (i = Math.max(i, wi); i < s; ++i) {
+ syms[li++] = dat[i];
+ ++lf[dat[i]];
+ }
+ pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
+ if (!lst) {
+ st.r = (pos & 7) | w[(pos / 8) | 0] << 3;
+ // shft(pos) now 1 less if pos & 7 != 0
+ pos -= 7;
+ st.h = head, st.p = prev, st.i = i, st.w = wi;
+ }
}
- return res;
-}
-
-// String.prototype.substr - negative index don't work in IE8
-var substr = 'ab'.substr(-1) === 'b'
- ? function (str, start, len) { return str.substr(start, len) }
- : function (str, start, len) {
- if (start < 0) start = str.length + start;
- return str.substr(start, len);
+ else {
+ for (var i = st.w || 0; i < s + lst; i += 65535) {
+ // end
+ var e = i + 65535;
+ if (e >= s) {
+ // write final block
+ w[(pos / 8) | 0] = lst;
+ e = s;
+ }
+ pos = wfblk(w, pos + 1, dat.subarray(i, e));
+ }
+ st.i = s;
}
+ return slc(o, 0, pre + shft(pos) + post);
+};
+// CRC32 table
+var crct = /*#__PURE__*/ (function () {
+ var t = new Int32Array(256);
+ for (var i = 0; i < 256; ++i) {
+ var c = i, k = 9;
+ while (--k)
+ c = ((c & 1) && -306674912) ^ (c >>> 1);
+ t[i] = c;
+ }
+ return t;
+})();
+// CRC32
+var crc = function () {
+ var c = -1;
+ return {
+ p: function (d) {
+ // closures have awful performance
+ var cr = c;
+ for (var i = 0; i < d.length; ++i)
+ cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);
+ c = cr;
+ },
+ d: function () { return ~c; }
+ };
+};
+// Adler32
+var adler = function () {
+ var a = 1, b = 0;
+ return {
+ p: function (d) {
+ // closures have awful performance
+ var n = a, m = b;
+ var l = d.length | 0;
+ for (var i = 0; i != l;) {
+ var e = Math.min(i + 2655, l);
+ for (; i < e; ++i)
+ m += n += d[i];
+ n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);
+ }
+ a = n, b = m;
+ },
+ d: function () {
+ a %= 65521, b %= 65521;
+ return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8);
+ }
+ };
+};
;
-
-}).call(this,require('_process'))
-},{"_process":4}],4:[function(require,module,exports){
-// shim for using process in browser
-var process = module.exports = {};
-
-// cached from whatever global is present so that test runners that stub it
-// don't break things. But we need to wrap it in a try catch in case it is
-// wrapped in strict mode code which doesn't define any globals. It's inside a
-// function because try/catches deoptimize in certain engines.
-
-var cachedSetTimeout;
-var cachedClearTimeout;
-
-function defaultSetTimout() {
- throw new Error('setTimeout has not been defined');
-}
-function defaultClearTimeout () {
- throw new Error('clearTimeout has not been defined');
-}
-(function () {
- try {
- if (typeof setTimeout === 'function') {
- cachedSetTimeout = setTimeout;
- } else {
- cachedSetTimeout = defaultSetTimout;
+// deflate with opts
+var dopt = function (dat, opt, pre, post, st) {
+ if (!st) {
+ st = { l: 1 };
+ if (opt.dictionary) {
+ var dict = opt.dictionary.subarray(-32768);
+ var newDat = new u8(dict.length + dat.length);
+ newDat.set(dict);
+ newDat.set(dat, dict.length);
+ dat = newDat;
+ st.w = dict.length;
}
- } catch (e) {
- cachedSetTimeout = defaultSetTimout;
}
- try {
- if (typeof clearTimeout === 'function') {
- cachedClearTimeout = clearTimeout;
- } else {
- cachedClearTimeout = defaultClearTimeout;
+ return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? (st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20) : (12 + opt.mem), pre, post, st);
+};
+// Walmart object spread
+var mrg = function (a, b) {
+ var o = {};
+ for (var k in a)
+ o[k] = a[k];
+ for (var k in b)
+ o[k] = b[k];
+ return o;
+};
+// worker clone
+// This is possibly the craziest part of the entire codebase, despite how simple it may seem.
+// The only parameter to this function is a closure that returns an array of variables outside of the function scope.
+// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.
+// We will return an object mapping of true variable name to value (basically, the current scope as a JS object).
+// The reason we can't just use the original variable names is minifiers mangling the toplevel scope.
+// This took me three weeks to figure out how to do.
+var wcln = function (fn, fnStr, td) {
+ var dt = fn();
+ var st = fn.toString();
+ var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\s+/g, '').split(',');
+ for (var i = 0; i < dt.length; ++i) {
+ var v = dt[i], k = ks[i];
+ if (typeof v == 'function') {
+ fnStr += ';' + k + '=';
+ var st_1 = v.toString();
+ if (v.prototype) {
+ // for global objects
+ if (st_1.indexOf('[native code]') != -1) {
+ var spInd = st_1.indexOf(' ', 8) + 1;
+ fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));
+ }
+ else {
+ fnStr += st_1;
+ for (var t in v.prototype)
+ fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();
+ }
+ }
+ else
+ fnStr += st_1;
}
- } catch (e) {
- cachedClearTimeout = defaultClearTimeout;
- }
-} ())
-function runTimeout(fun) {
- if (cachedSetTimeout === setTimeout) {
- //normal enviroments in sane situations
- return setTimeout(fun, 0);
+ else
+ td[k] = v;
}
- // if setTimeout wasn't available but was latter defined
- if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
- cachedSetTimeout = setTimeout;
- return setTimeout(fun, 0);
+ return fnStr;
+};
+var ch = [];
+// clone bufs
+var cbfs = function (v) {
+ var tl = [];
+ for (var k in v) {
+ if (v[k].buffer) {
+ tl.push((v[k] = new v[k].constructor(v[k])).buffer);
+ }
}
- try {
- // when when somebody has screwed with setTimeout but no I.E. maddness
- return cachedSetTimeout(fun, 0);
- } catch(e){
- try {
- // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
- return cachedSetTimeout.call(null, fun, 0);
- } catch(e){
- // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
- return cachedSetTimeout.call(this, fun, 0);
+ return tl;
+};
+// use a worker to execute code
+var wrkr = function (fns, init, id, cb) {
+ if (!ch[id]) {
+ var fnStr = '', td_1 = {}, m = fns.length - 1;
+ for (var i = 0; i < m; ++i)
+ fnStr = wcln(fns[i], fnStr, td_1);
+ ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 };
+ }
+ var td = mrg({}, ch[id].e);
+ return (0, node_worker_1.default)(ch[id].c + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb);
+};
+// base async inflate fn
+var bInflt = function () { return [u8, u16, i32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, ec, hMap, max, bits, bits16, shft, slc, err, inflt, inflateSync, pbf, gopt]; };
+var bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };
+// gzip extra
+var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };
+// gunzip extra
+var guze = function () { return [gzs, gzl]; };
+// zlib extra
+var zle = function () { return [zlh, wbytes, adler]; };
+// unzlib extra
+var zule = function () { return [zls]; };
+// post buf
+var pbf = function (msg) { return postMessage(msg, [msg.buffer]); };
+// get opts
+var gopt = function (o) { return o && {
+ out: o.size && new u8(o.size),
+ dictionary: o.dictionary
+}; };
+// async helper
+var cbify = function (dat, opts, fns, init, id, cb) {
+ var w = wrkr(fns, init, id, function (err, dat) {
+ w.terminate();
+ cb(err, dat);
+ });
+ w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);
+ return function () { w.terminate(); };
+};
+// auto stream
+var astrm = function (strm) {
+ strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); };
+ return function (ev) {
+ if (ev.data.length) {
+ strm.push(ev.data[0], ev.data[1]);
+ postMessage([ev.data[0].length]);
+ }
+ else
+ strm.flush();
+ };
+};
+// async stream attach
+var astrmify = function (fns, strm, opts, init, id, flush, ext) {
+ var t;
+ var w = wrkr(fns, init, id, function (err, dat) {
+ if (err)
+ w.terminate(), strm.ondata.call(strm, err);
+ else if (!Array.isArray(dat))
+ ext(dat);
+ else if (dat.length == 1) {
+ strm.queuedSize -= dat[0];
+ if (strm.ondrain)
+ strm.ondrain(dat[0]);
+ }
+ else {
+ if (dat[1])
+ w.terminate();
+ strm.ondata.call(strm, err, dat[0], dat[1]);
}
+ });
+ w.postMessage(opts);
+ strm.queuedSize = 0;
+ strm.push = function (d, f) {
+ if (!strm.ondata)
+ err(5);
+ if (t)
+ strm.ondata(err(4, 0, 1), null, !!f);
+ strm.queuedSize += d.length;
+ w.postMessage([d, t = f], [d.buffer]);
+ };
+ strm.terminate = function () { w.terminate(); };
+ if (flush) {
+ strm.flush = function () { w.postMessage([]); };
}
-
-
-}
-function runClearTimeout(marker) {
- if (cachedClearTimeout === clearTimeout) {
- //normal enviroments in sane situations
- return clearTimeout(marker);
+};
+// read 2 bytes
+var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); };
+// read 4 bytes
+var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; };
+var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); };
+// write bytes
+var wbytes = function (d, b, v) {
+ for (; v; ++b)
+ d[b] = v, v >>>= 8;
+};
+// gzip header
+var gzh = function (c, o) {
+ var fn = o.filename;
+ c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix
+ if (o.mtime != 0)
+ wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));
+ if (fn) {
+ c[3] = 8;
+ for (var i = 0; i <= fn.length; ++i)
+ c[i + 10] = fn.charCodeAt(i);
}
- // if clearTimeout wasn't available but was latter defined
- if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
- cachedClearTimeout = clearTimeout;
- return clearTimeout(marker);
+};
+// gzip footer: -8 to -4 = CRC, -4 to -0 is length
+// gzip start
+var gzs = function (d) {
+ if (d[0] != 31 || d[1] != 139 || d[2] != 8)
+ err(6, 'invalid gzip data');
+ var flg = d[3];
+ var st = 10;
+ if (flg & 4)
+ st += (d[10] | d[11] << 8) + 2;
+ for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++])
+ ;
+ return st + (flg & 2);
+};
+// gzip length
+var gzl = function (d) {
+ var l = d.length;
+ return (d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16 | d[l - 1] << 24) >>> 0;
+};
+// gzip header length
+var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };
+// zlib header
+var zlh = function (c, o) {
+ var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2;
+ c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32);
+ c[1] |= 31 - ((c[0] << 8) | c[1]) % 31;
+ if (o.dictionary) {
+ var h = adler();
+ h.p(o.dictionary);
+ wbytes(c, 2, h.d());
}
- try {
- // when when somebody has screwed with setTimeout but no I.E. maddness
- return cachedClearTimeout(marker);
- } catch (e){
- try {
- // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
- return cachedClearTimeout.call(null, marker);
- } catch (e){
- // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
- // Some versions of I.E. have different rules for clearTimeout vs setTimeout
- return cachedClearTimeout.call(this, marker);
+};
+// zlib start
+var zls = function (d, dict) {
+ if ((d[0] & 15) != 8 || (d[0] >> 4) > 7 || ((d[0] << 8 | d[1]) % 31))
+ err(6, 'invalid zlib data');
+ if ((d[1] >> 5 & 1) == +!dict)
+ err(6, 'invalid zlib data: ' + (d[1] & 32 ? 'need' : 'unexpected') + ' dictionary');
+ return (d[1] >> 3 & 4) + 2;
+};
+function StrmOpt(opts, cb) {
+ if (typeof opts == 'function')
+ cb = opts, opts = {};
+ this.ondata = cb;
+ return opts;
+}
+/**
+ * Streaming DEFLATE compression
+ */
+var Deflate = /*#__PURE__*/ (function () {
+ function Deflate(opts, cb) {
+ if (typeof opts == 'function')
+ cb = opts, opts = {};
+ this.ondata = cb;
+ this.o = opts || {};
+ this.s = { l: 0, i: 32768, w: 32768, z: 32768 };
+ // Buffer length must always be 0 mod 32768 for index calculations to be correct when modifying head and prev
+ // 98304 = 32768 (lookback) + 65536 (common chunk size)
+ this.b = new u8(98304);
+ if (this.o.dictionary) {
+ var dict = this.o.dictionary.subarray(-32768);
+ this.b.set(dict, 32768 - dict.length);
+ this.s.i = 32768 - dict.length;
}
}
+ Deflate.prototype.p = function (c, f) {
+ this.ondata(dopt(c, this.o, 0, 0, this.s), f);
+ };
+ /**
+ * Pushes a chunk to be deflated
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ Deflate.prototype.push = function (chunk, final) {
+ if (!this.ondata)
+ err(5);
+ if (this.s.l)
+ err(4);
+ var endLen = chunk.length + this.s.z;
+ if (endLen > this.b.length) {
+ if (endLen > 2 * this.b.length - 32768) {
+ var newBuf = new u8(endLen & -32768);
+ newBuf.set(this.b.subarray(0, this.s.z));
+ this.b = newBuf;
+ }
+ var split = this.b.length - this.s.z;
+ this.b.set(chunk.subarray(0, split), this.s.z);
+ this.s.z = this.b.length;
+ this.p(this.b, false);
+ this.b.set(this.b.subarray(-32768));
+ this.b.set(chunk.subarray(split), 32768);
+ this.s.z = chunk.length - split + 32768;
+ this.s.i = 32766, this.s.w = 32768;
+ }
+ else {
+ this.b.set(chunk, this.s.z);
+ this.s.z += chunk.length;
+ }
+ this.s.l = final & 1;
+ if (this.s.z > this.s.w + 8191 || final) {
+ this.p(this.b, final || false);
+ this.s.w = this.s.i, this.s.i -= 2;
+ }
+ };
+ /**
+ * Flushes buffered uncompressed data. Useful to immediately retrieve the
+ * deflated output for small inputs.
+ */
+ Deflate.prototype.flush = function () {
+ if (!this.ondata)
+ err(5);
+ if (this.s.l)
+ err(4);
+ this.p(this.b, false);
+ this.s.w = this.s.i, this.s.i -= 2;
+ };
+ return Deflate;
+}());
+exports.Deflate = Deflate;
+/**
+ * Asynchronous streaming DEFLATE compression
+ */
+var AsyncDeflate = /*#__PURE__*/ (function () {
+ function AsyncDeflate(opts, cb) {
+ astrmify([
+ bDflt,
+ function () { return [astrm, Deflate]; }
+ ], this, StrmOpt.call(this, opts, cb), function (ev) {
+ var strm = new Deflate(ev.data);
+ onmessage = astrm(strm);
+ }, 6, 1);
+ }
+ return AsyncDeflate;
+}());
+exports.AsyncDeflate = AsyncDeflate;
+function deflate(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ return cbify(data, opts, [
+ bDflt,
+ ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb);
+}
+exports.deflate = deflate;
+/**
+ * Compresses data with DEFLATE without any wrapper
+ * @param data The data to compress
+ * @param opts The compression options
+ * @returns The deflated version of the data
+ */
+function deflateSync(data, opts) {
+ return dopt(data, opts || {}, 0, 0);
+}
+exports.deflateSync = deflateSync;
+/**
+ * Streaming DEFLATE decompression
+ */
+var Inflate = /*#__PURE__*/ (function () {
+ function Inflate(opts, cb) {
+ // no StrmOpt here to avoid adding to workerizer
+ if (typeof opts == 'function')
+ cb = opts, opts = {};
+ this.ondata = cb;
+ var dict = opts && opts.dictionary && opts.dictionary.subarray(-32768);
+ this.s = { i: 0, b: dict ? dict.length : 0 };
+ this.o = new u8(32768);
+ this.p = new u8(0);
+ if (dict)
+ this.o.set(dict);
+ }
+ Inflate.prototype.e = function (c) {
+ if (!this.ondata)
+ err(5);
+ if (this.d)
+ err(4);
+ if (!this.p.length)
+ this.p = c;
+ else if (c.length) {
+ var n = new u8(this.p.length + c.length);
+ n.set(this.p), n.set(c, this.p.length), this.p = n;
+ }
+ };
+ Inflate.prototype.c = function (final) {
+ this.s.i = +(this.d = final || false);
+ var bts = this.s.b;
+ var dt = inflt(this.p, this.s, this.o);
+ this.ondata(slc(dt, bts, this.s.b), this.d);
+ this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length;
+ this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7;
+ };
+ /**
+ * Pushes a chunk to be inflated
+ * @param chunk The chunk to push
+ * @param final Whether this is the final chunk
+ */
+ Inflate.prototype.push = function (chunk, final) {
+ this.e(chunk), this.c(final);
+ };
+ return Inflate;
+}());
+exports.Inflate = Inflate;
+/**
+ * Asynchronous streaming DEFLATE decompression
+ */
+var AsyncInflate = /*#__PURE__*/ (function () {
+ function AsyncInflate(opts, cb) {
+ astrmify([
+ bInflt,
+ function () { return [astrm, Inflate]; }
+ ], this, StrmOpt.call(this, opts, cb), function (ev) {
+ var strm = new Inflate(ev.data);
+ onmessage = astrm(strm);
+ }, 7, 0);
+ }
+ return AsyncInflate;
+}());
+exports.AsyncInflate = AsyncInflate;
+function inflate(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ return cbify(data, opts, [
+ bInflt
+ ], function (ev) { return pbf(inflateSync(ev.data[0], gopt(ev.data[1]))); }, 1, cb);
+}
+exports.inflate = inflate;
+/**
+ * Expands DEFLATE data with no wrapper
+ * @param data The data to decompress
+ * @param opts The decompression options
+ * @returns The decompressed version of the data
+ */
+function inflateSync(data, opts) {
+ return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
+}
+exports.inflateSync = inflateSync;
+// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize.
+/**
+ * Streaming GZIP compression
+ */
+var Gzip = /*#__PURE__*/ (function () {
+ function Gzip(opts, cb) {
+ this.c = crc();
+ this.l = 0;
+ this.v = 1;
+ Deflate.call(this, opts, cb);
+ }
+ /**
+ * Pushes a chunk to be GZIPped
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ Gzip.prototype.push = function (chunk, final) {
+ this.c.p(chunk);
+ this.l += chunk.length;
+ Deflate.prototype.push.call(this, chunk, final);
+ };
+ Gzip.prototype.p = function (c, f) {
+ var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, this.s);
+ if (this.v)
+ gzh(raw, this.o), this.v = 0;
+ if (f)
+ wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l);
+ this.ondata(raw, f);
+ };
+ /**
+ * Flushes buffered uncompressed data. Useful to immediately retrieve the
+ * GZIPped output for small inputs.
+ */
+ Gzip.prototype.flush = function () {
+ Deflate.prototype.flush.call(this);
+ };
+ return Gzip;
+}());
+exports.Gzip = Gzip;
+exports.Compress = Gzip;
+/**
+ * Asynchronous streaming GZIP compression
+ */
+var AsyncGzip = /*#__PURE__*/ (function () {
+ function AsyncGzip(opts, cb) {
+ astrmify([
+ bDflt,
+ gze,
+ function () { return [astrm, Deflate, Gzip]; }
+ ], this, StrmOpt.call(this, opts, cb), function (ev) {
+ var strm = new Gzip(ev.data);
+ onmessage = astrm(strm);
+ }, 8, 1);
+ }
+ return AsyncGzip;
+}());
+exports.AsyncGzip = AsyncGzip;
+exports.AsyncCompress = AsyncGzip;
+function gzip(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ return cbify(data, opts, [
+ bDflt,
+ gze,
+ function () { return [gzipSync]; }
+ ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);
+}
+exports.gzip = gzip;
+exports.compress = gzip;
+/**
+ * Compresses data with GZIP
+ * @param data The data to compress
+ * @param opts The compression options
+ * @returns The gzipped version of the data
+ */
+function gzipSync(data, opts) {
+ if (!opts)
+ opts = {};
+ var c = crc(), l = data.length;
+ c.p(data);
+ var d = dopt(data, opts, gzhl(opts), 8), s = d.length;
+ return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;
+}
+exports.gzipSync = gzipSync;
+exports.compressSync = gzipSync;
+/**
+ * Streaming single or multi-member GZIP decompression
+ */
+var Gunzip = /*#__PURE__*/ (function () {
+ function Gunzip(opts, cb) {
+ this.v = 1;
+ this.r = 0;
+ Inflate.call(this, opts, cb);
+ }
+ /**
+ * Pushes a chunk to be GUNZIPped
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ Gunzip.prototype.push = function (chunk, final) {
+ Inflate.prototype.e.call(this, chunk);
+ this.r += chunk.length;
+ if (this.v) {
+ var p = this.p.subarray(this.v - 1);
+ var s = p.length > 3 ? gzs(p) : 4;
+ if (s > p.length) {
+ if (!final)
+ return;
+ }
+ else if (this.v > 1 && this.onmember) {
+ this.onmember(this.r - p.length);
+ }
+ this.p = p.subarray(s), this.v = 0;
+ }
+ // necessary to prevent TS from using the closure value
+ // This allows for workerization to function correctly
+ Inflate.prototype.c.call(this, final);
+ // process concatenated GZIP
+ if (this.s.f && !this.s.l && !final) {
+ this.v = shft(this.s.p) + 9;
+ this.s = { i: 0 };
+ this.o = new u8(0);
+ this.push(new u8(0), final);
+ }
+ };
+ return Gunzip;
+}());
+exports.Gunzip = Gunzip;
+/**
+ * Asynchronous streaming single or multi-member GZIP decompression
+ */
+var AsyncGunzip = /*#__PURE__*/ (function () {
+ function AsyncGunzip(opts, cb) {
+ var _this = this;
+ astrmify([
+ bInflt,
+ guze,
+ function () { return [astrm, Inflate, Gunzip]; }
+ ], this, StrmOpt.call(this, opts, cb), function (ev) {
+ var strm = new Gunzip(ev.data);
+ strm.onmember = function (offset) { return postMessage(offset); };
+ onmessage = astrm(strm);
+ }, 9, 0, function (offset) { return _this.onmember && _this.onmember(offset); });
+ }
+ return AsyncGunzip;
+}());
+exports.AsyncGunzip = AsyncGunzip;
+function gunzip(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ return cbify(data, opts, [
+ bInflt,
+ guze,
+ function () { return [gunzipSync]; }
+ ], function (ev) { return pbf(gunzipSync(ev.data[0], ev.data[1])); }, 3, cb);
+}
+exports.gunzip = gunzip;
+/**
+ * Expands GZIP data
+ * @param data The data to decompress
+ * @param opts The decompression options
+ * @returns The decompressed version of the data
+ */
+function gunzipSync(data, opts) {
+ var st = gzs(data);
+ if (st + 8 > data.length)
+ err(6, 'invalid gzip data');
+ return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary);
+}
+exports.gunzipSync = gunzipSync;
+/**
+ * Streaming Zlib compression
+ */
+var Zlib = /*#__PURE__*/ (function () {
+ function Zlib(opts, cb) {
+ this.c = adler();
+ this.v = 1;
+ Deflate.call(this, opts, cb);
+ }
+ /**
+ * Pushes a chunk to be zlibbed
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ Zlib.prototype.push = function (chunk, final) {
+ this.c.p(chunk);
+ Deflate.prototype.push.call(this, chunk, final);
+ };
+ Zlib.prototype.p = function (c, f) {
+ var raw = dopt(c, this.o, this.v && (this.o.dictionary ? 6 : 2), f && 4, this.s);
+ if (this.v)
+ zlh(raw, this.o), this.v = 0;
+ if (f)
+ wbytes(raw, raw.length - 4, this.c.d());
+ this.ondata(raw, f);
+ };
+ /**
+ * Flushes buffered uncompressed data. Useful to immediately retrieve the
+ * zlibbed output for small inputs.
+ */
+ Zlib.prototype.flush = function () {
+ Deflate.prototype.flush.call(this);
+ };
+ return Zlib;
+}());
+exports.Zlib = Zlib;
+/**
+ * Asynchronous streaming Zlib compression
+ */
+var AsyncZlib = /*#__PURE__*/ (function () {
+ function AsyncZlib(opts, cb) {
+ astrmify([
+ bDflt,
+ zle,
+ function () { return [astrm, Deflate, Zlib]; }
+ ], this, StrmOpt.call(this, opts, cb), function (ev) {
+ var strm = new Zlib(ev.data);
+ onmessage = astrm(strm);
+ }, 10, 1);
+ }
+ return AsyncZlib;
+}());
+exports.AsyncZlib = AsyncZlib;
+function zlib(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ return cbify(data, opts, [
+ bDflt,
+ zle,
+ function () { return [zlibSync]; }
+ ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb);
+}
+exports.zlib = zlib;
+/**
+ * Compress data with Zlib
+ * @param data The data to compress
+ * @param opts The compression options
+ * @returns The zlib-compressed version of the data
+ */
+function zlibSync(data, opts) {
+ if (!opts)
+ opts = {};
+ var a = adler();
+ a.p(data);
+ var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4);
+ return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d;
+}
+exports.zlibSync = zlibSync;
+/**
+ * Streaming Zlib decompression
+ */
+var Unzlib = /*#__PURE__*/ (function () {
+ function Unzlib(opts, cb) {
+ Inflate.call(this, opts, cb);
+ this.v = opts && opts.dictionary ? 2 : 1;
+ }
+ /**
+ * Pushes a chunk to be unzlibbed
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ Unzlib.prototype.push = function (chunk, final) {
+ Inflate.prototype.e.call(this, chunk);
+ if (this.v) {
+ if (this.p.length < 6 && !final)
+ return;
+ this.p = this.p.subarray(zls(this.p, this.v - 1)), this.v = 0;
+ }
+ if (final) {
+ if (this.p.length < 4)
+ err(6, 'invalid zlib data');
+ this.p = this.p.subarray(0, -4);
+ }
+ // necessary to prevent TS from using the closure value
+ // This allows for workerization to function correctly
+ Inflate.prototype.c.call(this, final);
+ };
+ return Unzlib;
+}());
+exports.Unzlib = Unzlib;
+/**
+ * Asynchronous streaming Zlib decompression
+ */
+var AsyncUnzlib = /*#__PURE__*/ (function () {
+ function AsyncUnzlib(opts, cb) {
+ astrmify([
+ bInflt,
+ zule,
+ function () { return [astrm, Inflate, Unzlib]; }
+ ], this, StrmOpt.call(this, opts, cb), function (ev) {
+ var strm = new Unzlib(ev.data);
+ onmessage = astrm(strm);
+ }, 11, 0);
+ }
+ return AsyncUnzlib;
+}());
+exports.AsyncUnzlib = AsyncUnzlib;
+function unzlib(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ return cbify(data, opts, [
+ bInflt,
+ zule,
+ function () { return [unzlibSync]; }
+ ], function (ev) { return pbf(unzlibSync(ev.data[0], gopt(ev.data[1]))); }, 5, cb);
+}
+exports.unzlib = unzlib;
+/**
+ * Expands Zlib data
+ * @param data The data to decompress
+ * @param opts The decompression options
+ * @returns The decompressed version of the data
+ */
+function unzlibSync(data, opts) {
+ return inflt(data.subarray(zls(data, opts && opts.dictionary), -4), { i: 2 }, opts && opts.out, opts && opts.dictionary);
+}
+exports.unzlibSync = unzlibSync;
+/**
+ * Streaming GZIP, Zlib, or raw DEFLATE decompression
+ */
+var Decompress = /*#__PURE__*/ (function () {
+ function Decompress(opts, cb) {
+ this.o = StrmOpt.call(this, opts, cb) || {};
+ this.G = Gunzip;
+ this.I = Inflate;
+ this.Z = Unzlib;
+ }
+ // init substream
+ // overriden by AsyncDecompress
+ Decompress.prototype.i = function () {
+ var _this = this;
+ this.s.ondata = function (dat, final) {
+ _this.ondata(dat, final);
+ };
+ };
+ /**
+ * Pushes a chunk to be decompressed
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ Decompress.prototype.push = function (chunk, final) {
+ if (!this.ondata)
+ err(5);
+ if (!this.s) {
+ if (this.p && this.p.length) {
+ var n = new u8(this.p.length + chunk.length);
+ n.set(this.p), n.set(chunk, this.p.length);
+ }
+ else
+ this.p = chunk;
+ if (this.p.length > 2) {
+ this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8)
+ ? new this.G(this.o)
+ : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31))
+ ? new this.I(this.o)
+ : new this.Z(this.o);
+ this.i();
+ this.s.push(this.p, final);
+ this.p = null;
+ }
+ }
+ else
+ this.s.push(chunk, final);
+ };
+ return Decompress;
+}());
+exports.Decompress = Decompress;
+/**
+ * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression
+ */
+var AsyncDecompress = /*#__PURE__*/ (function () {
+ function AsyncDecompress(opts, cb) {
+ Decompress.call(this, opts, cb);
+ this.queuedSize = 0;
+ this.G = AsyncGunzip;
+ this.I = AsyncInflate;
+ this.Z = AsyncUnzlib;
+ }
+ AsyncDecompress.prototype.i = function () {
+ var _this = this;
+ this.s.ondata = function (err, dat, final) {
+ _this.ondata(err, dat, final);
+ };
+ this.s.ondrain = function (size) {
+ _this.queuedSize -= size;
+ if (_this.ondrain)
+ _this.ondrain(size);
+ };
+ };
+ /**
+ * Pushes a chunk to be decompressed
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ AsyncDecompress.prototype.push = function (chunk, final) {
+ this.queuedSize += chunk.length;
+ Decompress.prototype.push.call(this, chunk, final);
+ };
+ return AsyncDecompress;
+}());
+exports.AsyncDecompress = AsyncDecompress;
+function decompress(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ return (data[0] == 31 && data[1] == 139 && data[2] == 8)
+ ? gunzip(data, opts, cb)
+ : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))
+ ? inflate(data, opts, cb)
+ : unzlib(data, opts, cb);
+}
+exports.decompress = decompress;
+/**
+ * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format
+ * @param data The data to decompress
+ * @param opts The decompression options
+ * @returns The decompressed version of the data
+ */
+function decompressSync(data, opts) {
+ return (data[0] == 31 && data[1] == 139 && data[2] == 8)
+ ? gunzipSync(data, opts)
+ : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))
+ ? inflateSync(data, opts)
+ : unzlibSync(data, opts);
+}
+exports.decompressSync = decompressSync;
+// flatten a directory structure
+var fltn = function (d, p, t, o) {
+ for (var k in d) {
+ var val = d[k], n = p + k, op = o;
+ if (Array.isArray(val))
+ op = mrg(o, val[1]), val = val[0];
+ if (val instanceof u8)
+ t[n] = [val, op];
+ else {
+ t[n += '/'] = [new u8(0), op];
+ fltn(val, n, t, o);
+ }
+ }
+};
+// text encoder
+var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder();
+// text decoder
+var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
+// text decoder stream
+var tds = 0;
+try {
+ td.decode(et, { stream: true });
+ tds = 1;
+}
+catch (e) { }
+// decode UTF8
+var dutf8 = function (d) {
+ for (var r = '', i = 0;;) {
+ var c = d[i++];
+ var eb = (c > 127) + (c > 223) + (c > 239);
+ if (i + eb > d.length)
+ return { s: r, r: slc(d, i - 1) };
+ if (!eb)
+ r += String.fromCharCode(c);
+ else if (eb == 3) {
+ c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536,
+ r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023));
+ }
+ else if (eb & 1)
+ r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63));
+ else
+ r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63));
+ }
+};
+/**
+ * Streaming UTF-8 decoding
+ */
+var DecodeUTF8 = /*#__PURE__*/ (function () {
+ /**
+ * Creates a UTF-8 decoding stream
+ * @param cb The callback to call whenever data is decoded
+ */
+ function DecodeUTF8(cb) {
+ this.ondata = cb;
+ if (tds)
+ this.t = new TextDecoder();
+ else
+ this.p = et;
+ }
+ /**
+ * Pushes a chunk to be decoded from UTF-8 binary
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ DecodeUTF8.prototype.push = function (chunk, final) {
+ if (!this.ondata)
+ err(5);
+ final = !!final;
+ if (this.t) {
+ this.ondata(this.t.decode(chunk, { stream: true }), final);
+ if (final) {
+ if (this.t.decode().length)
+ err(8);
+ this.t = null;
+ }
+ return;
+ }
+ if (!this.p)
+ err(4);
+ var dat = new u8(this.p.length + chunk.length);
+ dat.set(this.p);
+ dat.set(chunk, this.p.length);
+ var _a = dutf8(dat), s = _a.s, r = _a.r;
+ if (final) {
+ if (r.length)
+ err(8);
+ this.p = null;
+ }
+ else
+ this.p = r;
+ this.ondata(s, final);
+ };
+ return DecodeUTF8;
+}());
+exports.DecodeUTF8 = DecodeUTF8;
+/**
+ * Streaming UTF-8 encoding
+ */
+var EncodeUTF8 = /*#__PURE__*/ (function () {
+ /**
+ * Creates a UTF-8 decoding stream
+ * @param cb The callback to call whenever data is encoded
+ */
+ function EncodeUTF8(cb) {
+ this.ondata = cb;
+ }
+ /**
+ * Pushes a chunk to be encoded to UTF-8
+ * @param chunk The string data to push
+ * @param final Whether this is the last chunk
+ */
+ EncodeUTF8.prototype.push = function (chunk, final) {
+ if (!this.ondata)
+ err(5);
+ if (this.d)
+ err(4);
+ this.ondata(strToU8(chunk), this.d = final || false);
+ };
+ return EncodeUTF8;
+}());
+exports.EncodeUTF8 = EncodeUTF8;
+/**
+ * Converts a string into a Uint8Array for use with compression/decompression methods
+ * @param str The string to encode
+ * @param latin1 Whether or not to interpret the data as Latin-1. This should
+ * not need to be true unless decoding a binary string.
+ * @returns The string encoded in UTF-8/Latin-1 binary
+ */
+function strToU8(str, latin1) {
+ if (latin1) {
+ var ar_1 = new u8(str.length);
+ for (var i = 0; i < str.length; ++i)
+ ar_1[i] = str.charCodeAt(i);
+ return ar_1;
+ }
+ if (te)
+ return te.encode(str);
+ var l = str.length;
+ var ar = new u8(str.length + (str.length >> 1));
+ var ai = 0;
+ var w = function (v) { ar[ai++] = v; };
+ for (var i = 0; i < l; ++i) {
+ if (ai + 5 > ar.length) {
+ var n = new u8(ai + 8 + ((l - i) << 1));
+ n.set(ar);
+ ar = n;
+ }
+ var c = str.charCodeAt(i);
+ if (c < 128 || latin1)
+ w(c);
+ else if (c < 2048)
+ w(192 | (c >> 6)), w(128 | (c & 63));
+ else if (c > 55295 && c < 57344)
+ c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023),
+ w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));
+ else
+ w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));
+ }
+ return slc(ar, 0, ai);
+}
+exports.strToU8 = strToU8;
+/**
+ * Converts a Uint8Array to a string
+ * @param dat The data to decode to string
+ * @param latin1 Whether or not to interpret the data as Latin-1. This should
+ * not need to be true unless encoding to binary string.
+ * @returns The original UTF-8/Latin-1 string
+ */
+function strFromU8(dat, latin1) {
+ if (latin1) {
+ var r = '';
+ for (var i = 0; i < dat.length; i += 16384)
+ r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));
+ return r;
+ }
+ else if (td) {
+ return td.decode(dat);
+ }
+ else {
+ var _a = dutf8(dat), s = _a.s, r = _a.r;
+ if (r.length)
+ err(8);
+ return s;
+ }
+}
+exports.strFromU8 = strFromU8;
+;
+// deflate bit flag
+var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; };
+// skip local zip header
+var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); };
+// read zip header
+var zh = function (d, b, z) {
+ var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20);
+ var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2];
+ return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off];
+};
+// read zip64 extra field
+var z64e = function (d, b) {
+ for (; b2(d, b) != 1; b += 4 + b2(d, b + 2))
+ ;
+ return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)];
+};
+// extra field length
+var exfl = function (ex) {
+ var le = 0;
+ if (ex) {
+ for (var k in ex) {
+ var l = ex[k].length;
+ if (l > 65535)
+ err(9);
+ le += l + 4;
+ }
+ }
+ return le;
+};
+// write zip header
+var wzh = function (d, b, f, fn, u, c, ce, co) {
+ var fl = fn.length, ex = f.extra, col = co && co.length;
+ var exl = exfl(ex);
+ wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4;
+ if (ce != null)
+ d[b++] = 20, d[b++] = f.os;
+ d[b] = 20, b += 2; // spec compliance? what's that?
+ d[b++] = (f.flag << 1) | (c < 0 && 8), d[b++] = u && 8;
+ d[b++] = f.compression & 255, d[b++] = f.compression >> 8;
+ var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;
+ if (y < 0 || y > 119)
+ err(10);
+ wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >> 1)), b += 4;
+ if (c != -1) {
+ wbytes(d, b, f.crc);
+ wbytes(d, b + 4, c < 0 ? -c - 2 : c);
+ wbytes(d, b + 8, f.size);
+ }
+ wbytes(d, b + 12, fl);
+ wbytes(d, b + 14, exl), b += 16;
+ if (ce != null) {
+ wbytes(d, b, col);
+ wbytes(d, b + 6, f.attrs);
+ wbytes(d, b + 10, ce), b += 14;
+ }
+ d.set(fn, b);
+ b += fl;
+ if (exl) {
+ for (var k in ex) {
+ var exf = ex[k], l = exf.length;
+ wbytes(d, b, +k);
+ wbytes(d, b + 2, l);
+ d.set(exf, b + 4), b += 4 + l;
+ }
+ }
+ if (col)
+ d.set(co, b), b += col;
+ return b;
+};
+// write zip footer (end of central directory)
+var wzf = function (o, b, c, d, e) {
+ wbytes(o, b, 0x6054B50); // skip disk
+ wbytes(o, b + 8, c);
+ wbytes(o, b + 10, c);
+ wbytes(o, b + 12, d);
+ wbytes(o, b + 16, e);
+};
+/**
+ * A pass-through stream to keep data uncompressed in a ZIP archive.
+ */
+var ZipPassThrough = /*#__PURE__*/ (function () {
+ /**
+ * Creates a pass-through stream that can be added to ZIP archives
+ * @param filename The filename to associate with this data stream
+ */
+ function ZipPassThrough(filename) {
+ this.filename = filename;
+ this.c = crc();
+ this.size = 0;
+ this.compression = 0;
+ }
+ /**
+ * Processes a chunk and pushes to the output stream. You can override this
+ * method in a subclass for custom behavior, but by default this passes
+ * the data through. You must call this.ondata(err, chunk, final) at some
+ * point in this method.
+ * @param chunk The chunk to process
+ * @param final Whether this is the last chunk
+ */
+ ZipPassThrough.prototype.process = function (chunk, final) {
+ this.ondata(null, chunk, final);
+ };
+ /**
+ * Pushes a chunk to be added. If you are subclassing this with a custom
+ * compression algorithm, note that you must push data from the source
+ * file only, pre-compression.
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ ZipPassThrough.prototype.push = function (chunk, final) {
+ if (!this.ondata)
+ err(5);
+ this.c.p(chunk);
+ this.size += chunk.length;
+ if (final)
+ this.crc = this.c.d();
+ this.process(chunk, final || false);
+ };
+ return ZipPassThrough;
+}());
+exports.ZipPassThrough = ZipPassThrough;
+// I don't extend because TypeScript extension adds 1kB of runtime bloat
+/**
+ * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate
+ * for better performance
+ */
+var ZipDeflate = /*#__PURE__*/ (function () {
+ /**
+ * Creates a DEFLATE stream that can be added to ZIP archives
+ * @param filename The filename to associate with this data stream
+ * @param opts The compression options
+ */
+ function ZipDeflate(filename, opts) {
+ var _this = this;
+ if (!opts)
+ opts = {};
+ ZipPassThrough.call(this, filename);
+ this.d = new Deflate(opts, function (dat, final) {
+ _this.ondata(null, dat, final);
+ });
+ this.compression = 8;
+ this.flag = dbf(opts.level);
+ }
+ ZipDeflate.prototype.process = function (chunk, final) {
+ try {
+ this.d.push(chunk, final);
+ }
+ catch (e) {
+ this.ondata(e, null, final);
+ }
+ };
+ /**
+ * Pushes a chunk to be deflated
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ ZipDeflate.prototype.push = function (chunk, final) {
+ ZipPassThrough.prototype.push.call(this, chunk, final);
+ };
+ return ZipDeflate;
+}());
+exports.ZipDeflate = ZipDeflate;
+/**
+ * Asynchronous streaming DEFLATE compression for ZIP archives
+ */
+var AsyncZipDeflate = /*#__PURE__*/ (function () {
+ /**
+ * Creates an asynchronous DEFLATE stream that can be added to ZIP archives
+ * @param filename The filename to associate with this data stream
+ * @param opts The compression options
+ */
+ function AsyncZipDeflate(filename, opts) {
+ var _this = this;
+ if (!opts)
+ opts = {};
+ ZipPassThrough.call(this, filename);
+ this.d = new AsyncDeflate(opts, function (err, dat, final) {
+ _this.ondata(err, dat, final);
+ });
+ this.compression = 8;
+ this.flag = dbf(opts.level);
+ this.terminate = this.d.terminate;
+ }
+ AsyncZipDeflate.prototype.process = function (chunk, final) {
+ this.d.push(chunk, final);
+ };
+ /**
+ * Pushes a chunk to be deflated
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ AsyncZipDeflate.prototype.push = function (chunk, final) {
+ ZipPassThrough.prototype.push.call(this, chunk, final);
+ };
+ return AsyncZipDeflate;
+}());
+exports.AsyncZipDeflate = AsyncZipDeflate;
+// TODO: Better tree shaking
+/**
+ * A zippable archive to which files can incrementally be added
+ */
+var Zip = /*#__PURE__*/ (function () {
+ /**
+ * Creates an empty ZIP archive to which files can be added
+ * @param cb The callback to call whenever data for the generated ZIP archive
+ * is available
+ */
+ function Zip(cb) {
+ this.ondata = cb;
+ this.u = [];
+ this.d = 1;
+ }
+ /**
+ * Adds a file to the ZIP archive
+ * @param file The file stream to add
+ */
+ Zip.prototype.add = function (file) {
+ var _this = this;
+ if (!this.ondata)
+ err(5);
+ // finishing or finished
+ if (this.d & 2)
+ this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, false);
+ else {
+ var f = strToU8(file.filename), fl_1 = f.length;
+ var com = file.comment, o = com && strToU8(com);
+ var u = fl_1 != file.filename.length || (o && (com.length != o.length));
+ var hl_1 = fl_1 + exfl(file.extra) + 30;
+ if (fl_1 > 65535)
+ this.ondata(err(11, 0, 1), null, false);
+ var header = new u8(hl_1);
+ wzh(header, 0, file, f, u, -1);
+ var chks_1 = [header];
+ var pAll_1 = function () {
+ for (var _i = 0, chks_2 = chks_1; _i < chks_2.length; _i++) {
+ var chk = chks_2[_i];
+ _this.ondata(null, chk, false);
+ }
+ chks_1 = [];
+ };
+ var tr_1 = this.d;
+ this.d = 0;
+ var ind_1 = this.u.length;
+ var uf_1 = mrg(file, {
+ f: f,
+ u: u,
+ o: o,
+ t: function () {
+ if (file.terminate)
+ file.terminate();
+ },
+ r: function () {
+ pAll_1();
+ if (tr_1) {
+ var nxt = _this.u[ind_1 + 1];
+ if (nxt)
+ nxt.r();
+ else
+ _this.d = 1;
+ }
+ tr_1 = 1;
+ }
+ });
+ var cl_1 = 0;
+ file.ondata = function (err, dat, final) {
+ if (err) {
+ _this.ondata(err, dat, final);
+ _this.terminate();
+ }
+ else {
+ cl_1 += dat.length;
+ chks_1.push(dat);
+ if (final) {
+ var dd = new u8(16);
+ wbytes(dd, 0, 0x8074B50);
+ wbytes(dd, 4, file.crc);
+ wbytes(dd, 8, cl_1);
+ wbytes(dd, 12, file.size);
+ chks_1.push(dd);
+ uf_1.c = cl_1, uf_1.b = hl_1 + cl_1 + 16, uf_1.crc = file.crc, uf_1.size = file.size;
+ if (tr_1)
+ uf_1.r();
+ tr_1 = 1;
+ }
+ else if (tr_1)
+ pAll_1();
+ }
+ };
+ this.u.push(uf_1);
+ }
+ };
+ /**
+ * Ends the process of adding files and prepares to emit the final chunks.
+ * This *must* be called after adding all desired files for the resulting
+ * ZIP file to work properly.
+ */
+ Zip.prototype.end = function () {
+ var _this = this;
+ if (this.d & 2) {
+ this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, true);
+ return;
+ }
+ if (this.d)
+ this.e();
+ else
+ this.u.push({
+ r: function () {
+ if (!(_this.d & 1))
+ return;
+ _this.u.splice(-1, 1);
+ _this.e();
+ },
+ t: function () { }
+ });
+ this.d = 3;
+ };
+ Zip.prototype.e = function () {
+ var bt = 0, l = 0, tl = 0;
+ for (var _i = 0, _a = this.u; _i < _a.length; _i++) {
+ var f = _a[_i];
+ tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0);
+ }
+ var out = new u8(tl + 22);
+ for (var _b = 0, _c = this.u; _b < _c.length; _b++) {
+ var f = _c[_b];
+ wzh(out, bt, f, f.f, f.u, -f.c - 2, l, f.o);
+ bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b;
+ }
+ wzf(out, bt, this.u.length, tl, l);
+ this.ondata(null, out, true);
+ this.d = 2;
+ };
+ /**
+ * A method to terminate any internal workers used by the stream. Subsequent
+ * calls to add() will fail.
+ */
+ Zip.prototype.terminate = function () {
+ for (var _i = 0, _a = this.u; _i < _a.length; _i++) {
+ var f = _a[_i];
+ f.t();
+ }
+ this.d = 2;
+ };
+ return Zip;
+}());
+exports.Zip = Zip;
+function zip(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ var r = {};
+ fltn(data, '', r, opts);
+ var k = Object.keys(r);
+ var lft = k.length, o = 0, tot = 0;
+ var slft = lft, files = new Array(lft);
+ var term = [];
+ var tAll = function () {
+ for (var i = 0; i < term.length; ++i)
+ term[i]();
+ };
+ var cbd = function (a, b) {
+ mt(function () { cb(a, b); });
+ };
+ mt(function () { cbd = cb; });
+ var cbf = function () {
+ var out = new u8(tot + 22), oe = o, cdl = tot - o;
+ tot = 0;
+ for (var i = 0; i < slft; ++i) {
+ var f = files[i];
+ try {
+ var l = f.c.length;
+ wzh(out, tot, f, f.f, f.u, l);
+ var badd = 30 + f.f.length + exfl(f.extra);
+ var loc = tot + badd;
+ out.set(f.c, loc);
+ wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l;
+ }
+ catch (e) {
+ return cbd(e, null);
+ }
+ }
+ wzf(out, o, files.length, cdl, oe);
+ cbd(null, out);
+ };
+ if (!lft)
+ cbf();
+ var _loop_1 = function (i) {
+ var fn = k[i];
+ var _a = r[fn], file = _a[0], p = _a[1];
+ var c = crc(), size = file.length;
+ c.p(file);
+ var f = strToU8(fn), s = f.length;
+ var com = p.comment, m = com && strToU8(com), ms = m && m.length;
+ var exl = exfl(p.extra);
+ var compression = p.level == 0 ? 0 : 8;
+ var cbl = function (e, d) {
+ if (e) {
+ tAll();
+ cbd(e, null);
+ }
+ else {
+ var l = d.length;
+ files[i] = mrg(p, {
+ size: size,
+ crc: c.d(),
+ c: d,
+ f: f,
+ m: m,
+ u: s != fn.length || (m && (com.length != ms)),
+ compression: compression
+ });
+ o += 30 + s + exl + l;
+ tot += 76 + 2 * (s + exl) + (ms || 0) + l;
+ if (!--lft)
+ cbf();
+ }
+ };
+ if (s > 65535)
+ cbl(err(11, 0, 1), null);
+ if (!compression)
+ cbl(null, file);
+ else if (size < 160000) {
+ try {
+ cbl(null, deflateSync(file, p));
+ }
+ catch (e) {
+ cbl(e, null);
+ }
+ }
+ else
+ term.push(deflate(file, p, cbl));
+ };
+ // Cannot use lft because it can decrease
+ for (var i = 0; i < slft; ++i) {
+ _loop_1(i);
+ }
+ return tAll;
+}
+exports.zip = zip;
+/**
+ * Synchronously creates a ZIP file. Prefer using `zip` for better performance
+ * with more than one file.
+ * @param data The directory structure for the ZIP archive
+ * @param opts The main options, merged with per-file options
+ * @returns The generated ZIP archive
+ */
+function zipSync(data, opts) {
+ if (!opts)
+ opts = {};
+ var r = {};
+ var files = [];
+ fltn(data, '', r, opts);
+ var o = 0;
+ var tot = 0;
+ for (var fn in r) {
+ var _a = r[fn], file = _a[0], p = _a[1];
+ var compression = p.level == 0 ? 0 : 8;
+ var f = strToU8(fn), s = f.length;
+ var com = p.comment, m = com && strToU8(com), ms = m && m.length;
+ var exl = exfl(p.extra);
+ if (s > 65535)
+ err(11);
+ var d = compression ? deflateSync(file, p) : file, l = d.length;
+ var c = crc();
+ c.p(file);
+ files.push(mrg(p, {
+ size: file.length,
+ crc: c.d(),
+ c: d,
+ f: f,
+ m: m,
+ u: s != fn.length || (m && (com.length != ms)),
+ o: o,
+ compression: compression
+ }));
+ o += 30 + s + exl + l;
+ tot += 76 + 2 * (s + exl) + (ms || 0) + l;
+ }
+ var out = new u8(tot + 22), oe = o, cdl = tot - o;
+ for (var i = 0; i < files.length; ++i) {
+ var f = files[i];
+ wzh(out, f.o, f, f.f, f.u, f.c.length);
+ var badd = 30 + f.f.length + exfl(f.extra);
+ out.set(f.c, f.o + badd);
+ wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);
+ }
+ wzf(out, o, files.length, cdl, oe);
+ return out;
+}
+exports.zipSync = zipSync;
+/**
+ * Streaming pass-through decompression for ZIP archives
+ */
+var UnzipPassThrough = /*#__PURE__*/ (function () {
+ function UnzipPassThrough() {
+ }
+ UnzipPassThrough.prototype.push = function (data, final) {
+ this.ondata(null, data, final);
+ };
+ UnzipPassThrough.compression = 0;
+ return UnzipPassThrough;
+}());
+exports.UnzipPassThrough = UnzipPassThrough;
+/**
+ * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for
+ * better performance.
+ */
+var UnzipInflate = /*#__PURE__*/ (function () {
+ /**
+ * Creates a DEFLATE decompression that can be used in ZIP archives
+ */
+ function UnzipInflate() {
+ var _this = this;
+ this.i = new Inflate(function (dat, final) {
+ _this.ondata(null, dat, final);
+ });
+ }
+ UnzipInflate.prototype.push = function (data, final) {
+ try {
+ this.i.push(data, final);
+ }
+ catch (e) {
+ this.ondata(e, null, final);
+ }
+ };
+ UnzipInflate.compression = 8;
+ return UnzipInflate;
+}());
+exports.UnzipInflate = UnzipInflate;
+/**
+ * Asynchronous streaming DEFLATE decompression for ZIP archives
+ */
+var AsyncUnzipInflate = /*#__PURE__*/ (function () {
+ /**
+ * Creates a DEFLATE decompression that can be used in ZIP archives
+ */
+ function AsyncUnzipInflate(_, sz) {
+ var _this = this;
+ if (sz < 320000) {
+ this.i = new Inflate(function (dat, final) {
+ _this.ondata(null, dat, final);
+ });
+ }
+ else {
+ this.i = new AsyncInflate(function (err, dat, final) {
+ _this.ondata(err, dat, final);
+ });
+ this.terminate = this.i.terminate;
+ }
+ }
+ AsyncUnzipInflate.prototype.push = function (data, final) {
+ if (this.i.terminate)
+ data = slc(data, 0);
+ this.i.push(data, final);
+ };
+ AsyncUnzipInflate.compression = 8;
+ return AsyncUnzipInflate;
+}());
+exports.AsyncUnzipInflate = AsyncUnzipInflate;
+/**
+ * A ZIP archive decompression stream that emits files as they are discovered
+ */
+var Unzip = /*#__PURE__*/ (function () {
+ /**
+ * Creates a ZIP decompression stream
+ * @param cb The callback to call whenever a file in the ZIP archive is found
+ */
+ function Unzip(cb) {
+ this.onfile = cb;
+ this.k = [];
+ this.o = {
+ 0: UnzipPassThrough
+ };
+ this.p = et;
+ }
+ /**
+ * Pushes a chunk to be unzipped
+ * @param chunk The chunk to push
+ * @param final Whether this is the last chunk
+ */
+ Unzip.prototype.push = function (chunk, final) {
+ var _this = this;
+ if (!this.onfile)
+ err(5);
+ if (!this.p)
+ err(4);
+ if (this.c > 0) {
+ var len = Math.min(this.c, chunk.length);
+ var toAdd = chunk.subarray(0, len);
+ this.c -= len;
+ if (this.d)
+ this.d.push(toAdd, !this.c);
+ else
+ this.k[0].push(toAdd);
+ chunk = chunk.subarray(len);
+ if (chunk.length)
+ return this.push(chunk, final);
+ }
+ else {
+ var f = 0, i = 0, is = void 0, buf = void 0;
+ if (!this.p.length)
+ buf = chunk;
+ else if (!chunk.length)
+ buf = this.p;
+ else {
+ buf = new u8(this.p.length + chunk.length);
+ buf.set(this.p), buf.set(chunk, this.p.length);
+ }
+ var l = buf.length, oc = this.c, add = oc && this.d;
+ var _loop_2 = function () {
+ var _a;
+ var sig = b4(buf, i);
+ if (sig == 0x4034B50) {
+ f = 1, is = i;
+ this_1.d = null;
+ this_1.c = 0;
+ var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28);
+ if (l > i + 30 + fnl + es) {
+ var chks_3 = [];
+ this_1.k.unshift(chks_3);
+ f = 2;
+ var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22);
+ var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u);
+ if (sc_1 == 4294967295) {
+ _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1];
+ }
+ else if (dd)
+ sc_1 = -1;
+ i += es;
+ this_1.c = sc_1;
+ var d_1;
+ var file_1 = {
+ name: fn_1,
+ compression: cmp_1,
+ start: function () {
+ if (!file_1.ondata)
+ err(5);
+ if (!sc_1)
+ file_1.ondata(null, et, true);
+ else {
+ var ctr = _this.o[cmp_1];
+ if (!ctr)
+ file_1.ondata(err(14, 'unknown compression type ' + cmp_1, 1), null, false);
+ d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1);
+ d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); };
+ for (var _i = 0, chks_4 = chks_3; _i < chks_4.length; _i++) {
+ var dat = chks_4[_i];
+ d_1.push(dat, false);
+ }
+ if (_this.k[0] == chks_3 && _this.c)
+ _this.d = d_1;
+ else
+ d_1.push(et, true);
+ }
+ },
+ terminate: function () {
+ if (d_1 && d_1.terminate)
+ d_1.terminate();
+ }
+ };
+ if (sc_1 >= 0)
+ file_1.size = sc_1, file_1.originalSize = su_1;
+ this_1.onfile(file_1);
+ }
+ return "break";
+ }
+ else if (oc) {
+ if (sig == 0x8074B50) {
+ is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0;
+ return "break";
+ }
+ else if (sig == 0x2014B50) {
+ is = i -= 4, f = 3, this_1.c = 0;
+ return "break";
+ }
+ }
+ };
+ var this_1 = this;
+ for (; i < l - 4; ++i) {
+ var state_1 = _loop_2();
+ if (state_1 === "break")
+ break;
+ }
+ this.p = et;
+ if (oc < 0) {
+ var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i);
+ if (add)
+ add.push(dat, !!f);
+ else
+ this.k[+(f == 2)].push(dat);
+ }
+ if (f & 2)
+ return this.push(buf.subarray(i), final);
+ this.p = buf.subarray(i);
+ }
+ if (final) {
+ if (this.c)
+ err(13);
+ this.p = null;
+ }
+ };
+ /**
+ * Registers a decoder with the stream, allowing for files compressed with
+ * the compression type provided to be expanded correctly
+ * @param decoder The decoder constructor
+ */
+ Unzip.prototype.register = function (decoder) {
+ this.o[decoder.compression] = decoder;
+ };
+ return Unzip;
+}());
+exports.Unzip = Unzip;
+var mt = typeof queueMicrotask == 'function' ? queueMicrotask : typeof setTimeout == 'function' ? setTimeout : function (fn) { fn(); };
+function unzip(data, opts, cb) {
+ if (!cb)
+ cb = opts, opts = {};
+ if (typeof cb != 'function')
+ err(7);
+ var term = [];
+ var tAll = function () {
+ for (var i = 0; i < term.length; ++i)
+ term[i]();
+ };
+ var files = {};
+ var cbd = function (a, b) {
+ mt(function () { cb(a, b); });
+ };
+ mt(function () { cbd = cb; });
+ var e = data.length - 22;
+ for (; b4(data, e) != 0x6054B50; --e) {
+ if (!e || data.length - e > 65558) {
+ cbd(err(13, 0, 1), null);
+ return tAll;
+ }
+ }
+ ;
+ var lft = b2(data, e + 8);
+ if (lft) {
+ var c = lft;
+ var o = b4(data, e + 16);
+ var z = o == 4294967295 || c == 65535;
+ if (z) {
+ var ze = b4(data, e - 12);
+ z = b4(data, ze) == 0x6064B50;
+ if (z) {
+ c = lft = b4(data, ze + 32);
+ o = b4(data, ze + 48);
+ }
+ }
+ var fltr = opts && opts.filter;
+ var _loop_3 = function (i) {
+ var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);
+ o = no;
+ var cbl = function (e, d) {
+ if (e) {
+ tAll();
+ cbd(e, null);
+ }
+ else {
+ if (d)
+ files[fn] = d;
+ if (!--lft)
+ cbd(null, files);
+ }
+ };
+ if (!fltr || fltr({
+ name: fn,
+ size: sc,
+ originalSize: su,
+ compression: c_1
+ })) {
+ if (!c_1)
+ cbl(null, slc(data, b, b + sc));
+ else if (c_1 == 8) {
+ var infl = data.subarray(b, b + sc);
+ // Synchronously decompress under 512KB, or barely-compressed data
+ if (su < 524288 || sc > 0.8 * su) {
+ try {
+ cbl(null, inflateSync(infl, { out: new u8(su) }));
+ }
+ catch (e) {
+ cbl(e, null);
+ }
+ }
+ else
+ term.push(inflate(infl, { size: su }, cbl));
+ }
+ else
+ cbl(err(14, 'unknown compression type ' + c_1, 1), null);
+ }
+ else
+ cbl(null, null);
+ };
+ for (var i = 0; i < c; ++i) {
+ _loop_3(i);
+ }
+ }
+ else
+ cbd(null, {});
+ return tAll;
+}
+exports.unzip = unzip;
+/**
+ * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better
+ * performance with more than one file.
+ * @param data The raw compressed ZIP file
+ * @param opts The ZIP extraction options
+ * @returns The decompressed files
+ */
+function unzipSync(data, opts) {
+ var files = {};
+ var e = data.length - 22;
+ for (; b4(data, e) != 0x6054B50; --e) {
+ if (!e || data.length - e > 65558)
+ err(13);
+ }
+ ;
+ var c = b2(data, e + 8);
+ if (!c)
+ return {};
+ var o = b4(data, e + 16);
+ var z = o == 4294967295 || c == 65535;
+ if (z) {
+ var ze = b4(data, e - 12);
+ z = b4(data, ze) == 0x6064B50;
+ if (z) {
+ c = b4(data, ze + 32);
+ o = b4(data, ze + 48);
+ }
+ }
+ var fltr = opts && opts.filter;
+ for (var i = 0; i < c; ++i) {
+ var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);
+ o = no;
+ if (!fltr || fltr({
+ name: fn,
+ size: sc,
+ originalSize: su,
+ compression: c_2
+ })) {
+ if (!c_2)
+ files[fn] = slc(data, b, b + sc);
+ else if (c_2 == 8)
+ files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
+ else
+ err(14, 'unknown compression type ' + c_2);
+ }
+ }
+ return files;
+}
+exports.unzipSync = unzipSync;
+
+},{"./node-worker.cjs":4}],4:[function(require,module,exports){
+"use strict";
+var ch2 = {};
+exports.default = (function (c, id, msg, transfer, cb) {
+ var w = new Worker(ch2[id] || (ch2[id] = URL.createObjectURL(new Blob([
+ c + ';addEventListener("error",function(e){e=e.error;postMessage({$e$:[e.message,e.code,e.stack]})})'
+ ], { type: 'text/javascript' }))));
+ w.onmessage = function (e) {
+ var d = e.data, ed = d.$e$;
+ if (ed) {
+ var err = new Error(ed[0]);
+ err['code'] = ed[1];
+ err.stack = ed[2];
+ cb(err, null);
+ }
+ else
+ cb(null, d);
+ };
+ w.postMessage(msg, transfer);
+ return w;
+});
+
+},{}],5:[function(require,module,exports){
+// shim for using process in browser
+var process = module.exports = {};
+
+// cached from whatever global is present so that test runners that stub it
+// don't break things. But we need to wrap it in a try catch in case it is
+// wrapped in strict mode code which doesn't define any globals. It's inside a
+// function because try/catches deoptimize in certain engines.
+
+var cachedSetTimeout;
+var cachedClearTimeout;
+
+function defaultSetTimout() {
+ throw new Error('setTimeout has not been defined');
+}
+function defaultClearTimeout () {
+ throw new Error('clearTimeout has not been defined');
+}
+(function () {
+ try {
+ if (typeof setTimeout === 'function') {
+ cachedSetTimeout = setTimeout;
+ } else {
+ cachedSetTimeout = defaultSetTimout;
+ }
+ } catch (e) {
+ cachedSetTimeout = defaultSetTimout;
+ }
+ try {
+ if (typeof clearTimeout === 'function') {
+ cachedClearTimeout = clearTimeout;
+ } else {
+ cachedClearTimeout = defaultClearTimeout;
+ }
+ } catch (e) {
+ cachedClearTimeout = defaultClearTimeout;
+ }
+} ())
+function runTimeout(fun) {
+ if (cachedSetTimeout === setTimeout) {
+ //normal enviroments in sane situations
+ return setTimeout(fun, 0);
+ }
+ // if setTimeout wasn't available but was latter defined
+ if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
+ cachedSetTimeout = setTimeout;
+ return setTimeout(fun, 0);
+ }
+ try {
+ // when when somebody has screwed with setTimeout but no I.E. maddness
+ return cachedSetTimeout(fun, 0);
+ } catch(e){
+ try {
+ // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
+ return cachedSetTimeout.call(null, fun, 0);
+ } catch(e){
+ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
+ return cachedSetTimeout.call(this, fun, 0);
+ }
+ }
+
+
+}
+function runClearTimeout(marker) {
+ if (cachedClearTimeout === clearTimeout) {
+ //normal enviroments in sane situations
+ return clearTimeout(marker);
+ }
+ // if clearTimeout wasn't available but was latter defined
+ if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
+ cachedClearTimeout = clearTimeout;
+ return clearTimeout(marker);
+ }
+ try {
+ // when when somebody has screwed with setTimeout but no I.E. maddness
+ return cachedClearTimeout(marker);
+ } catch (e){
+ try {
+ // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
+ return cachedClearTimeout.call(null, marker);
+ } catch (e){
+ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
+ // Some versions of I.E. have different rules for clearTimeout vs setTimeout
+ return cachedClearTimeout.call(this, marker);
+ }
+ }
+
+
+
+}
+var queue = [];
+var draining = false;
+var currentQueue;
+var queueIndex = -1;
-
-
-}
-var queue = [];
-var draining = false;
-var currentQueue;
-var queueIndex = -1;
-
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
@@ -6804,35 +9285,86 @@ process.chdir = function (dir) {
};
process.umask = function() { return 0; };
-},{}],5:[function(require,module,exports){
-/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function n(e){throw e;}var p=void 0,aa=this;function t(e,b){var d=e.split("."),c=aa;!(d[0]in c)&&c.execScript&&c.execScript("var "+d[0]);for(var a;d.length&&(a=d.shift());)!d.length&&b!==p?c[a]=b:c=c[a]?c[a]:c[a]={}};var x="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;new (x?Uint8Array:Array)(256);var y;for(y=0;256>y;++y)for(var A=y,ba=7,A=A>>>1;A;A>>>=1)--ba;function B(e,b,d){var c,a="number"===typeof b?b:b=0,f="number"===typeof d?d:e.length;c=-1;for(a=f&7;a--;++b)c=c>>>8^C[(c^e[b])&255];for(a=f>>3;a--;b+=8)c=c>>>8^C[(c^e[b])&255],c=c>>>8^C[(c^e[b+1])&255],c=c>>>8^C[(c^e[b+2])&255],c=c>>>8^C[(c^e[b+3])&255],c=c>>>8^C[(c^e[b+4])&255],c=c>>>8^C[(c^e[b+5])&255],c=c>>>8^C[(c^e[b+6])&255],c=c>>>8^C[(c^e[b+7])&255];return(c^4294967295)>>>0}
-var D=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,
-2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,
-2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,
-2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,
-3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,
-936918E3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117],C=x?new Uint32Array(D):D;function E(){}E.prototype.getName=function(){return this.name};E.prototype.getData=function(){return this.data};E.prototype.G=function(){return this.H};function G(e){var b=e.length,d=0,c=Number.POSITIVE_INFINITY,a,f,k,l,m,r,q,g,h,v;for(g=0;gd&&(d=e[g]),e[g]>=1;v=k<<16|g;for(h=r;hK;K++)switch(!0){case 143>=K:J.push([K+48,8]);break;case 255>=K:J.push([K-144+400,9]);break;case 279>=K:J.push([K-256+0,7]);break;case 287>=K:J.push([K-280+192,8]);break;default:n("invalid literal: "+K)}
-var ca=function(){function e(a){switch(!0){case 3===a:return[257,a-3,0];case 4===a:return[258,a-4,0];case 5===a:return[259,a-5,0];case 6===a:return[260,a-6,0];case 7===a:return[261,a-7,0];case 8===a:return[262,a-8,0];case 9===a:return[263,a-9,0];case 10===a:return[264,a-10,0];case 12>=a:return[265,a-11,1];case 14>=a:return[266,a-13,1];case 16>=a:return[267,a-15,1];case 18>=a:return[268,a-17,1];case 22>=a:return[269,a-19,2];case 26>=a:return[270,a-23,2];case 30>=a:return[271,a-27,2];case 34>=a:return[272,
-a-31,2];case 42>=a:return[273,a-35,3];case 50>=a:return[274,a-43,3];case 58>=a:return[275,a-51,3];case 66>=a:return[276,a-59,3];case 82>=a:return[277,a-67,4];case 98>=a:return[278,a-83,4];case 114>=a:return[279,a-99,4];case 130>=a:return[280,a-115,4];case 162>=a:return[281,a-131,5];case 194>=a:return[282,a-163,5];case 226>=a:return[283,a-195,5];case 257>=a:return[284,a-227,5];case 258===a:return[285,a-258,0];default:n("invalid length: "+a)}}var b=[],d,c;for(d=3;258>=d;d++)c=e(d),b[d]=c[2]<<24|c[1]<<
-16|c[0];return b}();x&&new Uint32Array(ca);function L(e,b){this.i=[];this.j=32768;this.d=this.f=this.c=this.n=0;this.input=x?new Uint8Array(e):e;this.o=!1;this.k=M;this.w=!1;if(b||!(b={}))b.index&&(this.c=b.index),b.bufferSize&&(this.j=b.bufferSize),b.bufferType&&(this.k=b.bufferType),b.resize&&(this.w=b.resize);switch(this.k){case N:this.a=32768;this.b=new (x?Uint8Array:Array)(32768+this.j+258);break;case M:this.a=0;this.b=new (x?Uint8Array:Array)(this.j);this.e=this.D;this.q=this.A;this.l=this.C;break;default:n(Error("invalid inflate mode"))}}
-var N=0,M=1;
-L.prototype.g=function(){for(;!this.o;){var e=P(this,3);e&1&&(this.o=!0);e>>>=1;switch(e){case 0:var b=this.input,d=this.c,c=this.b,a=this.a,f=b.length,k=p,l=p,m=c.length,r=p;this.d=this.f=0;d+1>=f&&n(Error("invalid uncompressed block header: LEN"));k=b[d++]|b[d++]<<8;d+1>=f&&n(Error("invalid uncompressed block header: NLEN"));l=b[d++]|b[d++]<<8;k===~l&&n(Error("invalid uncompressed block header: length verify"));d+k>b.length&&n(Error("input buffer is broken"));switch(this.k){case N:for(;a+k>c.length;){r=
-m-a;k-=r;if(x)c.set(b.subarray(d,d+r),a),a+=r,d+=r;else for(;r--;)c[a++]=b[d++];this.a=a;c=this.e();a=this.a}break;case M:for(;a+k>c.length;)c=this.e({t:2});break;default:n(Error("invalid inflate mode"))}if(x)c.set(b.subarray(d,d+k),a),a+=k,d+=k;else for(;k--;)c[a++]=b[d++];this.c=d;this.a=a;this.b=c;break;case 1:this.l(da,ea);break;case 2:for(var q=P(this,5)+257,g=P(this,5)+1,h=P(this,4)+4,v=new (x?Uint8Array:Array)(Q.length),s=p,F=p,H=p,w=p,z=p,O=p,I=p,u=p,Z=p,u=0;u=W?8:255>=W?9:279>=W?7:8;var da=G(V),X=new (x?Uint8Array:Array)(30),Y,ma;Y=0;for(ma=X.length;Y=k&&n(Error("input buffer is broken")),d|=a[f++]<>>b;e.d=c-b;e.c=f;return l}
-function R(e,b){for(var d=e.f,c=e.d,a=e.input,f=e.c,k=a.length,l=b[0],m=b[1],r,q;c=k);)d|=a[f++]<>>16;q>c&&n(Error("invalid code length: "+q));e.f=d>>q;e.d=c-q;e.c=f;return r&65535}
-L.prototype.l=function(e,b){var d=this.b,c=this.a;this.r=e;for(var a=d.length-258,f,k,l,m;256!==(f=R(this,e));)if(256>f)c>=a&&(this.a=c,d=this.e(),c=this.a),d[c++]=f;else{k=f-257;m=ga[k];0=a&&(this.a=c,d=this.e(),c=this.a);for(;m--;)d[c]=d[c++-l]}for(;8<=this.d;)this.d-=8,this.c--;this.a=c};
-L.prototype.C=function(e,b){var d=this.b,c=this.a;this.r=e;for(var a=d.length,f,k,l,m;256!==(f=R(this,e));)if(256>f)c>=a&&(d=this.e(),a=d.length),d[c++]=f;else{k=f-257;m=ga[k];0a&&(d=this.e(),a=d.length);for(;m--;)d[c]=d[c++-l]}for(;8<=this.d;)this.d-=8,this.c--;this.a=c};
-L.prototype.e=function(){var e=new (x?Uint8Array:Array)(this.a-32768),b=this.a-32768,d,c,a=this.b;if(x)e.set(a.subarray(32768,e.length));else{d=0;for(c=e.length;dd;++d)a[d]=a[b+d];this.a=32768;return a};
-L.prototype.D=function(e){var b,d=this.input.length/this.c+1|0,c,a,f,k=this.input,l=this.b;e&&("number"===typeof e.t&&(d=e.t),"number"===typeof e.z&&(d+=e.z));2>d?(c=(k.length-this.c)/this.r[2],f=258*(c/2)|0,a=fb&&(this.b.length=b),e=this.b);return this.buffer=e};function $(e){this.input=e;this.c=0;this.m=[];this.s=!1}$.prototype.F=function(){this.s||this.g();return this.m.slice()};
-$.prototype.g=function(){for(var e=this.input.length;this.c>>0;B(a,p,p)!==q&&n(Error("invalid CRC-32 checksum: 0x"+B(a,p,p).toString(16)+" / 0x"+q.toString(16)));b.L=
-d=(g[h++]|g[h++]<<8|g[h++]<<16|g[h++]<<24)>>>0;(a.length&4294967295)!==d&&n(Error("invalid input size: "+(a.length&4294967295)+" / "+d));this.m.push(b);this.c=h}this.s=!0;var v=this.m,s,F,H=0,w=0,z;s=0;for(F=v.length;s= 0) {
+ item._idleTimeoutId = setTimeout(function onTimeout() {
+ if (item._onTimeout)
+ item._onTimeout();
+ }, msecs);
+ }
+};
+
+// That's not how node.js implements it but the exposed api is the same.
+exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) {
+ var id = nextImmediateId++;
+ var args = arguments.length < 2 ? false : slice.call(arguments, 1);
+
+ immediateIds[id] = true;
+
+ nextTick(function onNextTick() {
+ if (immediateIds[id]) {
+ // fn.call() is faster so we optimize for the common use-case
+ // @see http://jsperf.com/call-apply-segu
+ if (args) {
+ fn.apply(null, args);
+ } else {
+ fn.call(null);
+ }
+ // Prevent ids from leaking
+ exports.clearImmediate(id);
+ }
+ });
+
+ return id;
+};
+
+exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) {
+ delete immediateIds[id];
+};
+}).call(this)}).call(this,require("timers").setImmediate,require("timers").clearImmediate)
+},{"process/browser.js":5,"timers":6}],7:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -6852,11 +9384,11 @@ d=(g[h++]|g[h++]<<8|g[h++]<<16|g[h++]<<24)>>>0;(a.length&4294967295)!==d&&n(Erro
"use strict";
-var ViterbiBuilder = require("./viterbi/ViterbiBuilder");
-var ViterbiSearcher = require("./viterbi/ViterbiSearcher");
-var IpadicFormatter = require("./util/IpadicFormatter");
+const ViterbiBuilder = require("./viterbi/ViterbiBuilder");
+const ViterbiSearcher = require("./viterbi/ViterbiSearcher");
+const IpadicFormatter = require("./util/IpadicFormatter");
-var PUNCTUATION = /、|。/;
+const PUNCTUATION = /、|。/;
/**
* Tokenizer
@@ -6877,14 +9409,14 @@ function Tokenizer(dic) {
* @returns {Array.} Sentences end with punctuation
*/
Tokenizer.splitByPunctuation = function (input) {
- var sentences = [];
- var tail = input;
- while (true) {
+ const sentences = [];
+ let tail = input;
+ while (true) {
if (tail === "") {
break;
}
- var index = tail.search(PUNCTUATION);
- if (index < 0) {
+ const index = tail.search(PUNCTUATION);
+ if (index < 0) {
sentences.push(tail);
break;
}
@@ -6900,11 +9432,11 @@ Tokenizer.splitByPunctuation = function (input) {
* @returns {Array} Tokens
*/
Tokenizer.prototype.tokenize = function (text) {
- var sentences = Tokenizer.splitByPunctuation(text);
- var tokens = [];
- for (var i = 0; i < sentences.length; i++) {
- var sentence = sentences[i];
- this.tokenizeForSentence(sentence, tokens);
+ const sentences = Tokenizer.splitByPunctuation(text);
+ const tokens = [];
+ for (let i = 0; i < sentences.length; i++) {
+ const sentence = sentences[i];
+ this.tokenizeForSentence(sentence, tokens);
}
return tokens;
};
@@ -6913,18 +9445,18 @@ Tokenizer.prototype.tokenizeForSentence = function (sentence, tokens) {
if (tokens == null) {
tokens = [];
}
- var lattice = this.getLattice(sentence);
- var best_path = this.viterbi_searcher.search(lattice);
- var last_pos = 0;
- if (tokens.length > 0) {
+ const lattice = this.getLattice(sentence);
+ const best_path = this.viterbi_searcher.search(lattice);
+ let last_pos = 0;
+ if (tokens.length > 0) {
last_pos = tokens[tokens.length - 1].word_position;
}
- for (var j = 0; j < best_path.length; j++) {
- var node = best_path[j];
+ for (let j = 0; j < best_path.length; j++) {
+ const node = best_path[j];
- var token, features, features_line;
- if (node.type === "KNOWN") {
+ let token, features, features_line;
+ if (node.type === "KNOWN") {
features_line = this.token_info_dictionary.getFeatures(node.name);
if (features_line == null) {
features = [];
@@ -6963,7 +9495,7 @@ Tokenizer.prototype.getLattice = function (text) {
module.exports = Tokenizer;
-},{"./util/IpadicFormatter":22,"./viterbi/ViterbiBuilder":24,"./viterbi/ViterbiSearcher":27}],7:[function(require,module,exports){
+},{"./util/IpadicFormatter":23,"./viterbi/ViterbiBuilder":25,"./viterbi/ViterbiSearcher":28}],8:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -6983,8 +9515,8 @@ module.exports = Tokenizer;
"use strict";
-var Tokenizer = require("./Tokenizer");
-var DictionaryLoader = require("./loader/NodeDictionaryLoader");
+const Tokenizer = require("./Tokenizer");
+const BrowserDictionaryLoader = require("./loader/BrowserDictionaryLoader");
/**
* TokenizerBuilder create Tokenizer instance.
@@ -7005,8 +9537,8 @@ function TokenizerBuilder(option) {
* @param {TokenizerBuilder~onLoad} callback Callback function
*/
TokenizerBuilder.prototype.build = function (callback) {
- var loader = new DictionaryLoader(this.dic_path);
- loader.load(function (err, dic) {
+ const loader = new BrowserDictionaryLoader(this.dic_path);
+ loader.load(function (err, dic) {
callback(err, new Tokenizer(dic));
});
};
@@ -7020,7 +9552,7 @@ TokenizerBuilder.prototype.build = function (callback) {
module.exports = TokenizerBuilder;
-},{"./Tokenizer":6,"./loader/NodeDictionaryLoader":19}],8:[function(require,module,exports){
+},{"./Tokenizer":7,"./loader/BrowserDictionaryLoader":20}],9:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7059,7 +9591,7 @@ function CharacterClass(class_id, class_name, is_always_invoke, is_grouping, max
module.exports = CharacterClass;
-},{}],9:[function(require,module,exports){
+},{}],10:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7079,11 +9611,11 @@ module.exports = CharacterClass;
"use strict";
-var InvokeDefinitionMap = require("./InvokeDefinitionMap");
-var CharacterClass = require("./CharacterClass");
-var SurrogateAwareString = require("../util/SurrogateAwareString");
+const InvokeDefinitionMap = require("./InvokeDefinitionMap");
+const CharacterClass = require("./CharacterClass");
+const SurrogateAwareString = require("../util/SurrogateAwareString");
-var DEFAULT_CATEGORY = "DEFAULT";
+const DEFAULT_CATEGORY = "DEFAULT";
/**
* CharacterDefinition represents char.def file and
@@ -7104,19 +9636,19 @@ function CharacterDefinition() {
* @returns {CharacterDefinition}
*/
CharacterDefinition.load = function (cat_map_buffer, compat_cat_map_buffer, invoke_def_buffer) {
- var char_def = new CharacterDefinition();
- char_def.character_category_map = cat_map_buffer;
+ const char_def = new CharacterDefinition();
+ char_def.character_category_map = cat_map_buffer;
char_def.compatible_category_map = compat_cat_map_buffer;
char_def.invoke_definition_map = InvokeDefinitionMap.load(invoke_def_buffer);
return char_def;
};
CharacterDefinition.parseCharCategory = function (class_id, parsed_category_def) {
- var category = parsed_category_def[1];
- var invoke = parseInt(parsed_category_def[2]);
- var grouping = parseInt(parsed_category_def[3]);
- var max_length = parseInt(parsed_category_def[4]);
- if (!isFinite(invoke) || (invoke !== 0 && invoke !== 1)) {
+ const category = parsed_category_def[1];
+ const invoke = parseInt(parsed_category_def[2]);
+ const grouping = parseInt(parsed_category_def[3]);
+ const max_length = parseInt(parsed_category_def[4]);
+ if (!isFinite(invoke) || (invoke !== 0 && invoke !== 1)) {
console.log("char.def parse error. INVOKE is 0 or 1 in:" + invoke);
return null;
}
@@ -7128,28 +9660,28 @@ CharacterDefinition.parseCharCategory = function (class_id, parsed_category_def)
console.log("char.def parse error. LENGTH is 1 to n:" + max_length);
return null;
}
- var is_invoke = (invoke === 1);
- var is_grouping = (grouping === 1);
+ const is_invoke = (invoke === 1);
+ const is_grouping = (grouping === 1);
- return new CharacterClass(class_id, category, is_invoke, is_grouping, max_length);
+ return new CharacterClass(class_id, category, is_invoke, is_grouping, max_length);
};
CharacterDefinition.parseCategoryMapping = function (parsed_category_mapping) {
- var start = parseInt(parsed_category_mapping[1]);
- var default_category = parsed_category_mapping[2];
- var compatible_category = (3 < parsed_category_mapping.length) ? parsed_category_mapping.slice(3) : [];
- if (!isFinite(start) || start < 0 || start > 0xFFFF) {
+ const start = parseInt(parsed_category_mapping[1]);
+ const default_category = parsed_category_mapping[2];
+ const compatible_category = (3 < parsed_category_mapping.length) ? parsed_category_mapping.slice(3) : [];
+ if (!isFinite(start) || start < 0 || start > 0xFFFF) {
console.log("char.def parse error. CODE is invalid:" + start);
}
return { start: start, default: default_category, compatible: compatible_category};
};
CharacterDefinition.parseRangeCategoryMapping = function (parsed_category_mapping) {
- var start = parseInt(parsed_category_mapping[1]);
- var end = parseInt(parsed_category_mapping[2]);
- var default_category = parsed_category_mapping[3];
- var compatible_category = (4 < parsed_category_mapping.length) ? parsed_category_mapping.slice(4) : [];
- if (!isFinite(start) || start < 0 || start > 0xFFFF) {
+ const start = parseInt(parsed_category_mapping[1]);
+ const end = parseInt(parsed_category_mapping[2]);
+ const default_category = parsed_category_mapping[3];
+ const compatible_category = (4 < parsed_category_mapping.length) ? parsed_category_mapping.slice(4) : [];
+ if (!isFinite(start) || start < 0 || start > 0xFFFF) {
console.log("char.def parse error. CODE is invalid:" + start);
}
if (!isFinite(end) || end < 0 || end > 0xFFFF) {
@@ -7164,35 +9696,35 @@ CharacterDefinition.parseRangeCategoryMapping = function (parsed_category_mappin
*/
CharacterDefinition.prototype.initCategoryMappings = function (category_mapping) {
// Initialize map by DEFAULT class
- var code_point;
- if (category_mapping != null) {
- for (var i = 0; i < category_mapping.length; i++) {
- var mapping = category_mapping[i];
- var end = mapping.end || mapping.start;
- for (code_point = mapping.start; code_point <= end; code_point++) {
+ let code_point;
+ if (category_mapping != null) {
+ for (let i = 0; i < category_mapping.length; i++) {
+ const mapping = category_mapping[i];
+ const end = mapping.end || mapping.start;
+ for (code_point = mapping.start; code_point <= end; code_point++) {
// Default Category class ID
this.character_category_map[code_point] = this.invoke_definition_map.lookup(mapping.default);
- for (var j = 0; j < mapping.compatible.length; j++) {
- var bitset = this.compatible_category_map[code_point];
- var compatible_category = mapping.compatible[j];
- if (compatible_category == null) {
+ for (let j = 0; j < mapping.compatible.length; j++) {
+ let bitset = this.compatible_category_map[code_point];
+ const compatible_category = mapping.compatible[j];
+ if (compatible_category == null) {
continue;
}
- var class_id = this.invoke_definition_map.lookup(compatible_category); // Default Category
+ const class_id = this.invoke_definition_map.lookup(compatible_category); // Default Category
if (class_id == null) {
continue;
}
- var class_id_bit = 1 << class_id;
- bitset = bitset | class_id_bit; // Set a bit of class ID 例えば、class_idが3のとき、3ビット目に1を立てる
+ const class_id_bit = 1 << class_id;
+ bitset = bitset | class_id_bit; // Set a bit of class ID 例えば、class_idが3のとき、3ビット目に1を立てる
this.compatible_category_map[code_point] = bitset;
}
}
}
}
- var default_id = this.invoke_definition_map.lookup(DEFAULT_CATEGORY);
- if (default_id == null) {
+ const default_id = this.invoke_definition_map.lookup(DEFAULT_CATEGORY);
+ if (default_id == null) {
return;
}
for (code_point = 0; code_point < this.character_category_map.length; code_point++) {
@@ -7210,16 +9742,16 @@ CharacterDefinition.prototype.initCategoryMappings = function (category_mapping)
* @returns {Array.} character classes
*/
CharacterDefinition.prototype.lookupCompatibleCategory = function (ch) {
- var classes = [];
-
- /*
- if (SurrogateAwareString.isSurrogatePair(ch)) {
- // Surrogate pair character codes can not be defined by char.def
- return classes;
- }*/
- var code = ch.charCodeAt(0);
- var integer;
- if (code < this.compatible_category_map.length) {
+ const classes = [];
+
+ /*
+ if (SurrogateAwareString.isSurrogatePair(ch)) {
+ // Surrogate pair character codes can not be defined by char.def
+ return classes;
+ }*/
+ const code = ch.charCodeAt(0);
+ let integer;
+ if (code < this.compatible_category_map.length) {
integer = this.compatible_category_map[code]; // Bitset
}
@@ -7227,10 +9759,10 @@ CharacterDefinition.prototype.lookupCompatibleCategory = function (ch) {
return classes;
}
- for (var bit = 0; bit < 32; bit++) { // Treat "bit" as a class ID
+ for (let bit = 0; bit < 32; bit++) { // Treat "bit" as a class ID
if (((integer << (31 - bit)) >>> 31) === 1) {
- var character_class = this.invoke_definition_map.getCharacterClass(bit);
- if (character_class == null) {
+ const character_class = this.invoke_definition_map.getCharacterClass(bit);
+ if (character_class == null) {
continue;
}
classes.push(character_class);
@@ -7247,10 +9779,10 @@ CharacterDefinition.prototype.lookupCompatibleCategory = function (ch) {
*/
CharacterDefinition.prototype.lookup = function (ch) {
- var class_id;
+ let class_id;
- var code = ch.charCodeAt(0);
- if (SurrogateAwareString.isSurrogatePair(ch)) {
+ const code = ch.charCodeAt(0);
+ if (SurrogateAwareString.isSurrogatePair(ch)) {
// Surrogate pair character codes can not be defined by char.def, so set DEFAULT category
class_id = this.invoke_definition_map.lookup(DEFAULT_CATEGORY);
} else if (code < this.character_category_map.length) {
@@ -7266,7 +9798,7 @@ CharacterDefinition.prototype.lookup = function (ch) {
module.exports = CharacterDefinition;
-},{"../util/SurrogateAwareString":23,"./CharacterClass":8,"./InvokeDefinitionMap":12}],10:[function(require,module,exports){
+},{"../util/SurrogateAwareString":24,"./CharacterClass":9,"./InvokeDefinitionMap":13}],11:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7304,16 +9836,16 @@ function ConnectionCosts(forward_dimension, backward_dimension) {
}
ConnectionCosts.prototype.put = function (forward_id, backward_id, cost) {
- var index = forward_id * this.backward_dimension + backward_id + 2;
- if (this.buffer.length < index + 1) {
+ const index = forward_id * this.backward_dimension + backward_id + 2;
+ if (this.buffer.length < index + 1) {
throw "ConnectionCosts buffer overflow";
}
this.buffer[index] = cost;
};
ConnectionCosts.prototype.get = function (forward_id, backward_id) {
- var index = forward_id * this.backward_dimension + backward_id + 2;
- if (this.buffer.length < index + 1) {
+ const index = forward_id * this.backward_dimension + backward_id + 2;
+ if (this.buffer.length < index + 1) {
throw "ConnectionCosts buffer overflow";
}
return this.buffer[index];
@@ -7327,7 +9859,7 @@ ConnectionCosts.prototype.loadConnectionCosts = function (connection_costs_buffe
module.exports = ConnectionCosts;
-},{}],11:[function(require,module,exports){
+},{}],12:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7347,10 +9879,10 @@ module.exports = ConnectionCosts;
"use strict";
-var doublearray = require("doublearray");
-var TokenInfoDictionary = require("./TokenInfoDictionary");
-var ConnectionCosts = require("./ConnectionCosts");
-var UnknownDictionary = require("./UnknownDictionary");
+const doublearray = require("doublearray");
+const TokenInfoDictionary = require("./TokenInfoDictionary");
+const ConnectionCosts = require("./ConnectionCosts");
+const UnknownDictionary = require("./UnknownDictionary");
/**
* Dictionaries container for Tokenizer
@@ -7411,7 +9943,7 @@ DynamicDictionaries.prototype.loadUnknownDictionaries = function (unk_buffer, un
module.exports = DynamicDictionaries;
-},{"./ConnectionCosts":10,"./TokenInfoDictionary":13,"./UnknownDictionary":14,"doublearray":2}],12:[function(require,module,exports){
+},{"./ConnectionCosts":11,"./TokenInfoDictionary":14,"./UnknownDictionary":15,"doublearray":2}],13:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7431,8 +9963,8 @@ module.exports = DynamicDictionaries;
"use strict";
-var ByteBuffer = require("../util/ByteBuffer");
-var CharacterClass = require("./CharacterClass");
+const ByteBuffer = require("../util/ByteBuffer");
+const CharacterClass = require("./CharacterClass");
/**
* InvokeDefinitionMap represents invoke definition a part of char.def
@@ -7449,17 +9981,17 @@ function InvokeDefinitionMap() {
* @returns {InvokeDefinitionMap}
*/
InvokeDefinitionMap.load = function (invoke_def_buffer) {
- var invoke_def = new InvokeDefinitionMap();
- var character_category_definition = [];
+ const invoke_def = new InvokeDefinitionMap();
+ const character_category_definition = [];
- var buffer = new ByteBuffer(invoke_def_buffer);
- while (buffer.position + 1 < buffer.size()) {
- var class_id = character_category_definition.length;
- var is_always_invoke = buffer.get();
- var is_grouping = buffer.get();
- var max_length = buffer.getInt();
- var class_name = buffer.getString();
- character_category_definition.push(new CharacterClass(class_id, class_name, is_always_invoke, is_grouping, max_length));
+ const buffer = new ByteBuffer(invoke_def_buffer);
+ while (buffer.position + 1 < buffer.size()) {
+ const class_id = character_category_definition.length;
+ const is_always_invoke = buffer.get();
+ const is_grouping = buffer.get();
+ const max_length = buffer.getInt();
+ const class_name = buffer.getString();
+ character_category_definition.push(new CharacterClass(class_id, class_name, is_always_invoke, is_grouping, max_length));
}
invoke_def.init(character_category_definition);
@@ -7475,9 +10007,9 @@ InvokeDefinitionMap.prototype.init = function (character_category_definition) {
if (character_category_definition == null) {
return;
}
- for (var i = 0; i < character_category_definition.length; i++) {
- var character_class = character_category_definition[i];
- this.map[i] = character_class;
+ for (let i = 0; i < character_category_definition.length; i++) {
+ const character_class = character_category_definition[i];
+ this.map[i] = character_class;
this.lookup_table[character_class.class_name] = i;
}
};
@@ -7497,8 +10029,8 @@ InvokeDefinitionMap.prototype.getCharacterClass = function (class_id) {
* @returns {number} class_id
*/
InvokeDefinitionMap.prototype.lookup = function (class_name) {
- var class_id = this.lookup_table[class_name];
- if (class_id == null) {
+ const class_id = this.lookup_table[class_name];
+ if (class_id == null) {
return null;
}
return class_id;
@@ -7509,10 +10041,10 @@ InvokeDefinitionMap.prototype.lookup = function (class_name) {
* @returns {Uint8Array}
*/
InvokeDefinitionMap.prototype.toBuffer = function () {
- var buffer = new ByteBuffer();
- for (var i = 0; i < this.map.length; i++) {
- var char_class = this.map[i];
- buffer.put(char_class.is_always_invoke);
+ const buffer = new ByteBuffer();
+ for (let i = 0; i < this.map.length; i++) {
+ const char_class = this.map[i];
+ buffer.put(char_class.is_always_invoke);
buffer.put(char_class.is_grouping);
buffer.putInt(char_class.max_length);
buffer.putString(char_class.class_name);
@@ -7523,7 +10055,7 @@ InvokeDefinitionMap.prototype.toBuffer = function () {
module.exports = InvokeDefinitionMap;
-},{"../util/ByteBuffer":21,"./CharacterClass":8}],13:[function(require,module,exports){
+},{"../util/ByteBuffer":22,"./CharacterClass":9}],14:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7543,7 +10075,7 @@ module.exports = InvokeDefinitionMap;
"use strict";
-var ByteBuffer = require("../util/ByteBuffer");
+const ByteBuffer = require("../util/ByteBuffer");
/**
* TokenInfoDictionary
@@ -7558,28 +10090,28 @@ function TokenInfoDictionary() {
// left_id right_id word_cost ...
// ^ this position is token_info_id
TokenInfoDictionary.prototype.buildDictionary = function (entries) {
- var dictionary_entries = {}; // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
+ const dictionary_entries = {}; // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
- for (var i = 0; i < entries.length; i++) {
- var entry = entries[i];
+ for (let i = 0; i < entries.length; i++) {
+ const entry = entries[i];
- if (entry.length < 4) {
+ if (entry.length < 4) {
continue;
}
- var surface_form = entry[0];
- var left_id = entry[1];
- var right_id = entry[2];
- var word_cost = entry[3];
- var feature = entry.slice(4).join(","); // TODO Optimize
+ const surface_form = entry[0];
+ const left_id = entry[1];
+ const right_id = entry[2];
+ const word_cost = entry[3];
+ const feature = entry.slice(4).join(","); // TODO Optimize
// Assertion
if (!isFinite(left_id) || !isFinite(right_id) || !isFinite(word_cost)) {
console.log(entry);
}
- var token_info_id = this.put(left_id, right_id, word_cost, surface_form, feature);
- dictionary_entries[token_info_id] = surface_form;
+ const token_info_id = this.put(left_id, right_id, word_cost, surface_form, feature);
+ dictionary_entries[token_info_id] = surface_form;
}
// Remove last unused area
@@ -7590,10 +10122,10 @@ TokenInfoDictionary.prototype.buildDictionary = function (entries) {
};
TokenInfoDictionary.prototype.put = function (left_id, right_id, word_cost, surface_form, feature) {
- var token_info_id = this.dictionary.position;
- var pos_id = this.pos_buffer.position;
+ const token_info_id = this.dictionary.position;
+ const pos_id = this.pos_buffer.position;
- this.dictionary.putShort(left_id);
+ this.dictionary.putShort(left_id);
this.dictionary.putShort(right_id);
this.dictionary.putShort(word_cost);
this.dictionary.putInt(pos_id);
@@ -7603,8 +10135,8 @@ TokenInfoDictionary.prototype.put = function (left_id, right_id, word_cost, surf
};
TokenInfoDictionary.prototype.addMapping = function (source, target) {
- var mapping = this.target_map[source];
- if (mapping == null) {
+ let mapping = this.target_map[source];
+ if (mapping == null) {
mapping = [];
}
mapping.push(target);
@@ -7613,15 +10145,15 @@ TokenInfoDictionary.prototype.addMapping = function (source, target) {
};
TokenInfoDictionary.prototype.targetMapToBuffer = function () {
- var buffer = new ByteBuffer();
- var map_keys_size = Object.keys(this.target_map).length;
- buffer.putInt(map_keys_size);
- for (var key in this.target_map) {
- var values = this.target_map[key]; // Array
- var map_values_size = values.length;
- buffer.putInt(parseInt(key));
+ const buffer = new ByteBuffer();
+ const map_keys_size = Object.keys(this.target_map).length;
+ buffer.putInt(map_keys_size);
+ for (let key in this.target_map) {
+ const values = this.target_map[key]; // Array
+ const map_values_size = values.length;
+ buffer.putInt(parseInt(key));
buffer.putInt(map_values_size);
- for (var i = 0; i < values.length; i++) {
+ for (let i = 0; i < values.length; i++) {
buffer.putInt(values[i]);
}
}
@@ -7642,19 +10174,19 @@ TokenInfoDictionary.prototype.loadPosVector = function (array_buffer) {
// from tid_map.dat
TokenInfoDictionary.prototype.loadTargetMap = function (array_buffer) {
- var buffer = new ByteBuffer(array_buffer);
- buffer.position = 0;
+ const buffer = new ByteBuffer(array_buffer);
+ buffer.position = 0;
this.target_map = {};
buffer.readInt(); // map_keys_size
while (true) {
if (buffer.buffer.length < buffer.position + 1) {
break;
}
- var key = buffer.readInt();
- var map_values_size = buffer.readInt();
- for (var i = 0; i < map_values_size; i++) {
- var value = buffer.readInt();
- this.addMapping(key, value);
+ const key = buffer.readInt();
+ const map_values_size = buffer.readInt();
+ for (let i = 0; i < map_values_size; i++) {
+ const value = buffer.readInt();
+ this.addMapping(key, value);
}
}
return this;
@@ -7666,18 +10198,18 @@ TokenInfoDictionary.prototype.loadTargetMap = function (array_buffer) {
* @returns {string} Features string concatenated by ","
*/
TokenInfoDictionary.prototype.getFeatures = function (token_info_id_str) {
- var token_info_id = parseInt(token_info_id_str);
- if (isNaN(token_info_id)) {
+ const token_info_id = parseInt(token_info_id_str);
+ if (isNaN(token_info_id)) {
// TODO throw error
return "";
}
- var pos_id = this.dictionary.getInt(token_info_id + 6);
- return this.pos_buffer.getString(pos_id);
+ const pos_id = this.dictionary.getInt(token_info_id + 6);
+ return this.pos_buffer.getString(pos_id);
};
module.exports = TokenInfoDictionary;
-},{"../util/ByteBuffer":21}],14:[function(require,module,exports){
+},{"../util/ByteBuffer":22}],15:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7697,9 +10229,9 @@ module.exports = TokenInfoDictionary;
"use strict";
-var TokenInfoDictionary = require("./TokenInfoDictionary");
-var CharacterDefinition = require("./CharacterDefinition");
-var ByteBuffer = require("../util/ByteBuffer");
+const TokenInfoDictionary = require("./TokenInfoDictionary");
+const CharacterDefinition = require("./CharacterDefinition");
+const ByteBuffer = require("../util/ByteBuffer");
/**
* UnknownDictionary
@@ -7737,7 +10269,7 @@ UnknownDictionary.prototype.loadUnknownDictionaries = function (unk_buffer, unk_
module.exports = UnknownDictionary;
-},{"../util/ByteBuffer":21,"./CharacterDefinition":9,"./TokenInfoDictionary":13}],15:[function(require,module,exports){
+},{"../util/ByteBuffer":22,"./CharacterDefinition":10,"./TokenInfoDictionary":14}],16:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7757,12 +10289,12 @@ module.exports = UnknownDictionary;
"use strict";
-var CharacterDefinition = require("../CharacterDefinition");
-var InvokeDefinitionMap = require("../InvokeDefinitionMap");
+const CharacterDefinition = require("../CharacterDefinition");
+const InvokeDefinitionMap = require("../InvokeDefinitionMap");
-var CATEGORY_DEF_PATTERN = /^(\w+)\s+(\d)\s+(\d)\s+(\d)/;
-var CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
-var RANGE_CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})\.\.(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
+const CATEGORY_DEF_PATTERN = /^(\w+)\s+(\d)\s+(\d)\s+(\d)/;
+const CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
+const RANGE_CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})\.\.(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
/**
* CharacterDefinitionBuilder
@@ -7776,25 +10308,25 @@ function CharacterDefinitionBuilder() {
}
CharacterDefinitionBuilder.prototype.putLine = function (line) {
- var parsed_category_def = CATEGORY_DEF_PATTERN.exec(line);
- if (parsed_category_def != null) {
- var class_id = this.character_category_definition.length;
- var char_class = CharacterDefinition.parseCharCategory(class_id, parsed_category_def);
- if (char_class == null) {
+ const parsed_category_def = CATEGORY_DEF_PATTERN.exec(line);
+ if (parsed_category_def != null) {
+ const class_id = this.character_category_definition.length;
+ const char_class = CharacterDefinition.parseCharCategory(class_id, parsed_category_def);
+ if (char_class == null) {
return;
}
this.character_category_definition.push(char_class);
return;
}
- var parsed_category_mapping = CATEGORY_MAPPING_PATTERN.exec(line);
- if (parsed_category_mapping != null) {
- var mapping = CharacterDefinition.parseCategoryMapping(parsed_category_mapping);
- this.category_mapping.push(mapping);
+ const parsed_category_mapping = CATEGORY_MAPPING_PATTERN.exec(line);
+ if (parsed_category_mapping != null) {
+ const mapping = CharacterDefinition.parseCategoryMapping(parsed_category_mapping);
+ this.category_mapping.push(mapping);
}
- var parsed_range_category_mapping = RANGE_CATEGORY_MAPPING_PATTERN.exec(line);
- if (parsed_range_category_mapping != null) {
- var range_mapping = CharacterDefinition.parseRangeCategoryMapping(parsed_range_category_mapping);
- this.category_mapping.push(range_mapping);
+ const parsed_range_category_mapping = RANGE_CATEGORY_MAPPING_PATTERN.exec(line);
+ if (parsed_range_category_mapping != null) {
+ const range_mapping = CharacterDefinition.parseRangeCategoryMapping(parsed_range_category_mapping);
+ this.category_mapping.push(range_mapping);
}
};
@@ -7807,7 +10339,7 @@ CharacterDefinitionBuilder.prototype.build = function () {
module.exports = CharacterDefinitionBuilder;
-},{"../CharacterDefinition":9,"../InvokeDefinitionMap":12}],16:[function(require,module,exports){
+},{"../CharacterDefinition":10,"../InvokeDefinitionMap":13}],17:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7827,7 +10359,7 @@ module.exports = CharacterDefinitionBuilder;
"use strict";
-var ConnectionCosts = require("../ConnectionCosts");
+const ConnectionCosts = require("../ConnectionCosts");
/**
* Builder class for constructing ConnectionCosts object
@@ -7840,11 +10372,11 @@ function ConnectionCostsBuilder() {
ConnectionCostsBuilder.prototype.putLine = function (line) {
if (this.lines === 0) {
- var dimensions = line.split(" ");
- var forward_dimension = dimensions[0];
- var backward_dimension = dimensions[1];
+ const dimensions = line.split(" ");
+ const forward_dimension = dimensions[0];
+ const backward_dimension = dimensions[1];
- if (forward_dimension < 0 || backward_dimension < 0) {
+ if (forward_dimension < 0 || backward_dimension < 0) {
throw "Parse error of matrix.def";
}
@@ -7853,17 +10385,17 @@ ConnectionCostsBuilder.prototype.putLine = function (line) {
return this;
}
- var costs = line.split(" ");
+ const costs = line.split(" ");
- if (costs.length !== 3) {
+ if (costs.length !== 3) {
return this;
}
- var forward_id = parseInt(costs[0]);
- var backward_id = parseInt(costs[1]);
- var cost = parseInt(costs[2]);
+ const forward_id = parseInt(costs[0]);
+ const backward_id = parseInt(costs[1]);
+ const cost = parseInt(costs[2]);
- if (forward_id < 0 || backward_id < 0 || !isFinite(forward_id) || !isFinite(backward_id) ||
+ if (forward_id < 0 || backward_id < 0 || !isFinite(forward_id) || !isFinite(backward_id) ||
this.connection_cost.forward_dimension <= forward_id || this.connection_cost.backward_dimension <= backward_id) {
throw "Parse error of matrix.def";
}
@@ -7879,7 +10411,7 @@ ConnectionCostsBuilder.prototype.build = function () {
module.exports = ConnectionCostsBuilder;
-},{"../ConnectionCosts":10}],17:[function(require,module,exports){
+},{"../ConnectionCosts":11}],18:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -7899,12 +10431,12 @@ module.exports = ConnectionCostsBuilder;
"use strict";
-var doublearray = require("doublearray");
-var DynamicDictionaries = require("../DynamicDictionaries");
-var TokenInfoDictionary = require("../TokenInfoDictionary");
-var ConnectionCostsBuilder = require("./ConnectionCostsBuilder");
-var CharacterDefinitionBuilder = require("./CharacterDefinitionBuilder");
-var UnknownDictionary = require("../UnknownDictionary");
+const doublearray = require("doublearray");
+const DynamicDictionaries = require("../DynamicDictionaries");
+const TokenInfoDictionary = require("../TokenInfoDictionary");
+const ConnectionCostsBuilder = require("./ConnectionCostsBuilder");
+const CharacterDefinitionBuilder = require("./CharacterDefinitionBuilder");
+const UnknownDictionary = require("../UnknownDictionary");
/**
* Build dictionaries (token info, connection costs)
@@ -7928,8 +10460,8 @@ function DictionaryBuilder() {
}
DictionaryBuilder.prototype.addTokenInfoDictionary = function (line) {
- var new_entry = line.split(",");
- this.tid_entries.push(new_entry);
+ const new_entry = line.split(",");
+ this.tid_entries.push(new_entry);
return this;
};
@@ -7957,10 +10489,10 @@ DictionaryBuilder.prototype.putUnkDefLine = function (line) {
};
DictionaryBuilder.prototype.build = function () {
- var dictionaries = this.buildTokenInfoDictionary();
- var unknown_dictionary = this.buildUnknownDictionary();
+ const dictionaries = this.buildTokenInfoDictionary();
+ const unknown_dictionary = this.buildUnknownDictionary();
- return new DynamicDictionaries(dictionaries.trie, dictionaries.token_info_dictionary, this.cc_builder.build(), unknown_dictionary);
+ return new DynamicDictionaries(dictionaries.trie, dictionaries.token_info_dictionary, this.cc_builder.build(), unknown_dictionary);
};
/**
@@ -7970,18 +10502,18 @@ DictionaryBuilder.prototype.build = function () {
*/
DictionaryBuilder.prototype.buildTokenInfoDictionary = function () {
- var token_info_dictionary = new TokenInfoDictionary();
+ const token_info_dictionary = new TokenInfoDictionary();
- // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
- var dictionary_entries = token_info_dictionary.buildDictionary(this.tid_entries);
+ // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
+ const dictionary_entries = token_info_dictionary.buildDictionary(this.tid_entries);
- var trie = this.buildDoubleArray();
+ const trie = this.buildDoubleArray();
- for (var token_info_id in dictionary_entries) {
- var surface_form = dictionary_entries[token_info_id];
- var trie_id = trie.lookup(surface_form);
+ for (let token_info_id in dictionary_entries) {
+ const surface_form = dictionary_entries[token_info_id];
+ const trie_id = trie.lookup(surface_form);
- // Assertion
+ // Assertion
// if (trie_id < 0) {
// console.log("Not Found:" + surface_form);
// }
@@ -7997,20 +10529,20 @@ DictionaryBuilder.prototype.buildTokenInfoDictionary = function () {
DictionaryBuilder.prototype.buildUnknownDictionary = function () {
- var unk_dictionary = new UnknownDictionary();
+ const unk_dictionary = new UnknownDictionary();
- // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
- var dictionary_entries = unk_dictionary.buildDictionary(this.unk_entries);
+ // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
+ const dictionary_entries = unk_dictionary.buildDictionary(this.unk_entries);
- var char_def = this.cd_builder.build(); // Create CharacterDefinition
+ const char_def = this.cd_builder.build(); // Create CharacterDefinition
unk_dictionary.characterDefinition(char_def);
- for (var token_info_id in dictionary_entries) {
- var class_name = dictionary_entries[token_info_id];
- var class_id = char_def.invoke_definition_map.lookup(class_name);
+ for (let token_info_id in dictionary_entries) {
+ const class_name = dictionary_entries[token_info_id];
+ const class_id = char_def.invoke_definition_map.lookup(class_name);
- // Assertion
+ // Assertion
// if (trie_id < 0) {
// console.log("Not Found:" + surface_form);
// }
@@ -8027,19 +10559,19 @@ DictionaryBuilder.prototype.buildUnknownDictionary = function () {
* @returns {DoubleArray} Double-Array trie
*/
DictionaryBuilder.prototype.buildDoubleArray = function () {
- var trie_id = 0;
- var words = this.tid_entries.map(function (entry) {
- var surface_form = entry[0];
- return { k: surface_form, v: trie_id++ };
- });
-
- var builder = doublearray.builder(1024 * 1024);
- return builder.build(words);
+ let trie_id = 0;
+ const words = this.tid_entries.map(function (entry) {
+ const surface_form = entry[0];
+ return {k: surface_form, v: trie_id++};
+ });
+
+ const builder = doublearray.builder(1024 * 1024);
+ return builder.build(words);
};
module.exports = DictionaryBuilder;
-},{"../DynamicDictionaries":11,"../TokenInfoDictionary":13,"../UnknownDictionary":14,"./CharacterDefinitionBuilder":15,"./ConnectionCostsBuilder":16,"doublearray":2}],18:[function(require,module,exports){
+},{"../DynamicDictionaries":12,"../TokenInfoDictionary":14,"../UnknownDictionary":15,"./CharacterDefinitionBuilder":16,"./ConnectionCostsBuilder":17,"doublearray":2}],19:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8059,22 +10591,22 @@ module.exports = DictionaryBuilder;
"use strict";
-var TokenizerBuilder = require("./TokenizerBuilder");
-var DictionaryBuilder = require("./dict/builder/DictionaryBuilder");
+const TokenizerBuilder = require("./TokenizerBuilder");
+const DictionaryBuilder = require("./dict/builder/DictionaryBuilder");
// Public methods
-var kuromoji = {
- builder: function (option) {
- return new TokenizerBuilder(option);
- },
- dictionaryBuilder: function () {
- return new DictionaryBuilder();
- }
+const kuromoji = {
+ builder: function (option) {
+ return new TokenizerBuilder(option);
+ },
+ dictionaryBuilder: function () {
+ return new DictionaryBuilder();
+ }
};
module.exports = kuromoji;
-},{"./TokenizerBuilder":7,"./dict/builder/DictionaryBuilder":17}],19:[function(require,module,exports){
+},{"./TokenizerBuilder":8,"./dict/builder/DictionaryBuilder":18}],20:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8094,8 +10626,8 @@ module.exports = kuromoji;
"use strict";
-var zlib = require("zlibjs/bin/gunzip.min.js");
-var DictionaryLoader = require("./DictionaryLoader");
+const fflate = require("fflate");
+const DictionaryLoader = require("./DictionaryLoader");
/**
* BrowserDictionaryLoader inherits DictionaryLoader, using jQuery XHR for download
@@ -8114,24 +10646,17 @@ BrowserDictionaryLoader.prototype = Object.create(DictionaryLoader.prototype);
* @param {BrowserDictionaryLoader~onLoad} callback Callback function
*/
BrowserDictionaryLoader.prototype.loadArrayBuffer = function (url, callback) {
- var xhr = new XMLHttpRequest();
- xhr.open("GET", url, true);
- xhr.responseType = "arraybuffer";
- xhr.onload = function () {
- if (this.status > 0 && this.status !== 200) {
- callback(xhr.statusText, null);
- return;
- }
- var arraybuffer = this.response;
-
- var gz = new zlib.Zlib.Gunzip(new Uint8Array(arraybuffer));
- var typed_array = gz.decompress();
- callback(null, typed_array.buffer);
- };
- xhr.onerror = function (err) {
- callback(err, null);
- };
- xhr.send();
+ fetch(url).then(function (response) {
+ if (!response.ok) {
+ callback(response.statusText, null);
+ }
+ response.arrayBuffer().then(function (arraybuffer) {
+ const gz = fflate.gunzipSync(new Uint8Array(arraybuffer));
+ callback(null, gz.buffer);
+ });
+ }).catch(function (exception) {
+ callback(exception, null);
+ });
};
/**
@@ -8143,7 +10668,7 @@ BrowserDictionaryLoader.prototype.loadArrayBuffer = function (url, callback) {
module.exports = BrowserDictionaryLoader;
-},{"./DictionaryLoader":20,"zlibjs/bin/gunzip.min.js":5}],20:[function(require,module,exports){
+},{"./DictionaryLoader":21,"fflate":3}],21:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8163,9 +10688,8 @@ module.exports = BrowserDictionaryLoader;
"use strict";
-var path = require("path");
-var async = require("async");
-var DynamicDictionaries = require("../dict/DynamicDictionaries");
+const async = require("async");
+const DynamicDictionaries = require("../dict/DynamicDictionaries");
/**
* DictionaryLoader base constructor
@@ -8186,15 +10710,15 @@ DictionaryLoader.prototype.loadArrayBuffer = function (file, callback) {
* @param {DictionaryLoader~onLoad} load_callback Callback function called after loaded
*/
DictionaryLoader.prototype.load = function (load_callback) {
- var dic = this.dic;
- var dic_path = this.dic_path;
- var loadArrayBuffer = this.loadArrayBuffer;
+ const dic = this.dic;
+ const dic_path = this.dic_path;
+ const loadArrayBuffer = this.loadArrayBuffer;
- async.parallel([
+ async.parallel([
// Trie
function (callback) {
async.map([ "base.dat.gz", "check.dat.gz" ], function (filename, _callback) {
- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
+ loadArrayBuffer(dic_path + filename, function (err, buffer) {
if(err) {
return _callback(err);
}
@@ -8204,17 +10728,17 @@ DictionaryLoader.prototype.load = function (load_callback) {
if(err) {
return callback(err);
}
- var base_buffer = new Int32Array(buffers[0]);
- var check_buffer = new Int32Array(buffers[1]);
+ const base_buffer = new Int32Array(buffers[0]);
+ const check_buffer = new Int32Array(buffers[1]);
- dic.loadTrie(base_buffer, check_buffer);
+ dic.loadTrie(base_buffer, check_buffer);
callback(null);
});
},
// Token info dictionaries
function (callback) {
async.map([ "tid.dat.gz", "tid_pos.dat.gz", "tid_map.dat.gz" ], function (filename, _callback) {
- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
+ loadArrayBuffer(dic_path + filename, function (err, buffer) {
if(err) {
return _callback(err);
}
@@ -8224,29 +10748,29 @@ DictionaryLoader.prototype.load = function (load_callback) {
if(err) {
return callback(err);
}
- var token_info_buffer = new Uint8Array(buffers[0]);
- var pos_buffer = new Uint8Array(buffers[1]);
- var target_map_buffer = new Uint8Array(buffers[2]);
+ const token_info_buffer = new Uint8Array(buffers[0]);
+ const pos_buffer = new Uint8Array(buffers[1]);
+ const target_map_buffer = new Uint8Array(buffers[2]);
- dic.loadTokenInfoDictionaries(token_info_buffer, pos_buffer, target_map_buffer);
+ dic.loadTokenInfoDictionaries(token_info_buffer, pos_buffer, target_map_buffer);
callback(null);
});
},
// Connection cost matrix
function (callback) {
- loadArrayBuffer(path.join(dic_path, "cc.dat.gz"), function (err, buffer) {
+ loadArrayBuffer(dic_path + "cc.dat.gz", function (err, buffer) {
if(err) {
return callback(err);
}
- var cc_buffer = new Int16Array(buffer);
- dic.loadConnectionCosts(cc_buffer);
+ const cc_buffer = new Int16Array(buffer);
+ dic.loadConnectionCosts(cc_buffer);
callback(null);
});
},
// Unknown dictionaries
function (callback) {
async.map([ "unk.dat.gz", "unk_pos.dat.gz", "unk_map.dat.gz", "unk_char.dat.gz", "unk_compat.dat.gz", "unk_invoke.dat.gz" ], function (filename, _callback) {
- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
+ loadArrayBuffer(dic_path + filename, function (err, buffer) {
if(err) {
return _callback(err);
}
@@ -8256,14 +10780,14 @@ DictionaryLoader.prototype.load = function (load_callback) {
if(err) {
return callback(err);
}
- var unk_buffer = new Uint8Array(buffers[0]);
- var unk_pos_buffer = new Uint8Array(buffers[1]);
- var unk_map_buffer = new Uint8Array(buffers[2]);
- var cat_map_buffer = new Uint8Array(buffers[3]);
- var compat_cat_map_buffer = new Uint32Array(buffers[4]);
- var invoke_def_buffer = new Uint8Array(buffers[5]);
-
- dic.loadUnknownDictionaries(unk_buffer, unk_pos_buffer, unk_map_buffer, cat_map_buffer, compat_cat_map_buffer, invoke_def_buffer);
+ const unk_buffer = new Uint8Array(buffers[0]);
+ const unk_pos_buffer = new Uint8Array(buffers[1]);
+ const unk_map_buffer = new Uint8Array(buffers[2]);
+ const cat_map_buffer = new Uint8Array(buffers[3]);
+ const compat_cat_map_buffer = new Uint32Array(buffers[4]);
+ const invoke_def_buffer = new Uint8Array(buffers[5]);
+
+ dic.loadUnknownDictionaries(unk_buffer, unk_pos_buffer, unk_map_buffer, cat_map_buffer, compat_cat_map_buffer, invoke_def_buffer);
// dic.loadUnknownDictionaries(char_buffer, unk_buffer);
callback(null);
});
@@ -8282,7 +10806,7 @@ DictionaryLoader.prototype.load = function (load_callback) {
module.exports = DictionaryLoader;
-},{"../dict/DynamicDictionaries":11,"async":1,"path":3}],21:[function(require,module,exports){
+},{"../dict/DynamicDictionaries":12,"async":1}],22:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8308,63 +10832,64 @@ module.exports = DictionaryLoader;
* @param {String} str UTF-16 string to convert
* @return {Uint8Array} Byte sequence encoded by UTF-8
*/
-var stringToUtf8Bytes = function (str) {
+const stringToUtf8Bytes = function (str) {
- // Max size of 1 character is 4 bytes
- var bytes = new Uint8Array(str.length * 4);
+ // Max size of 1 character is 4 bytes
+ const bytes = new Uint8Array(str.length * 4);
- var i = 0, j = 0;
+ let i = 0;
+ let j = 0;
- while (i < str.length) {
- var unicode_code;
+ while (i < str.length) {
+ let unicode_code;
- var utf16_code = str.charCodeAt(i++);
- if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
- // surrogate pair
- var upper = utf16_code; // high surrogate
- var lower = str.charCodeAt(i++); // low surrogate
-
- if (lower >= 0xDC00 && lower <= 0xDFFF) {
- unicode_code =
- (upper - 0xD800) * (1 << 10) + (1 << 16) +
- (lower - 0xDC00);
- } else {
- // malformed surrogate pair
- return null;
- }
- } else {
- // not surrogate code
- unicode_code = utf16_code;
- }
+ const utf16_code = str.charCodeAt(i++);
+ if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
+ // surrogate pair
+ const upper = utf16_code; // high surrogate
+ const lower = str.charCodeAt(i++); // low surrogate
+
+ if (lower >= 0xDC00 && lower <= 0xDFFF) {
+ unicode_code =
+ (upper - 0xD800) * (1 << 10) + (1 << 16) +
+ (lower - 0xDC00);
+ } else {
+ // malformed surrogate pair
+ return null;
+ }
+ } else {
+ // not surrogate code
+ unicode_code = utf16_code;
+ }
- if (unicode_code < 0x80) {
- // 1-byte
- bytes[j++] = unicode_code;
+ if (unicode_code < 0x80) {
+ // 1-byte
+ bytes[j++] = unicode_code;
- } else if (unicode_code < (1 << 11)) {
- // 2-byte
- bytes[j++] = (unicode_code >>> 6) | 0xC0;
- bytes[j++] = (unicode_code & 0x3F) | 0x80;
+ } else if (unicode_code < (1 << 11)) {
+ // 2-byte
+ bytes[j++] = (unicode_code >>> 6) | 0xC0;
+ bytes[j++] = (unicode_code & 0x3F) | 0x80;
- } else if (unicode_code < (1 << 16)) {
- // 3-byte
- bytes[j++] = (unicode_code >>> 12) | 0xE0;
- bytes[j++] = ((unicode_code >> 6) & 0x3f) | 0x80;
- bytes[j++] = (unicode_code & 0x3F) | 0x80;
+ } else if (unicode_code < (1 << 16)) {
+ // 3-byte
+ bytes[j++] = (unicode_code >>> 12) | 0xE0;
+ bytes[j++] = ((unicode_code >> 6) & 0x3f) | 0x80;
+ bytes[j++] = (unicode_code & 0x3F) | 0x80;
- } else if (unicode_code < (1 << 21)) {
- // 4-byte
- bytes[j++] = (unicode_code >>> 18) | 0xF0;
- bytes[j++] = ((unicode_code >> 12) & 0x3F) | 0x80;
- bytes[j++] = ((unicode_code >> 6) & 0x3F) | 0x80;
- bytes[j++] = (unicode_code & 0x3F) | 0x80;
+ } else if (unicode_code < (1 << 21)) {
+ // 4-byte
+ bytes[j++] = (unicode_code >>> 18) | 0xF0;
+ bytes[j++] = ((unicode_code >> 12) & 0x3F) | 0x80;
+ bytes[j++] = ((unicode_code >> 6) & 0x3F) | 0x80;
+ bytes[j++] = (unicode_code & 0x3F) | 0x80;
- } else {
- // malformed UCS4 code
- }
+ } else {
+ // malformed UCS4 code
}
+ }
- return bytes.subarray(0, j);
+ return bytes.subarray(0, j);
};
/**
@@ -8373,48 +10898,48 @@ var stringToUtf8Bytes = function (str) {
* @param {Array} bytes UTF-8 byte sequence to convert
* @return {String} String encoded by UTF-16
*/
-var utf8BytesToString = function (bytes) {
+const utf8BytesToString = function (bytes) {
- var str = "";
- var code, b1, b2, b3, b4, upper, lower;
- var i = 0;
+ let str = "";
+ let code, b1, b2, b3, b4, upper, lower;
+ let i = 0;
- while (i < bytes.length) {
-
- b1 = bytes[i++];
-
- if (b1 < 0x80) {
- // 1 byte
- code = b1;
- } else if ((b1 >> 5) === 0x06) {
- // 2 bytes
- b2 = bytes[i++];
- code = ((b1 & 0x1f) << 6) | (b2 & 0x3f);
- } else if ((b1 >> 4) === 0x0e) {
- // 3 bytes
- b2 = bytes[i++];
- b3 = bytes[i++];
- code = ((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
- } else {
- // 4 bytes
- b2 = bytes[i++];
- b3 = bytes[i++];
- b4 = bytes[i++];
- code = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
- }
+ while (i < bytes.length) {
- if (code < 0x10000) {
- str += String.fromCharCode(code);
- } else {
- // surrogate pair
- code -= 0x10000;
- upper = (0xD800 | (code >> 10));
- lower = (0xDC00 | (code & 0x3FF));
- str += String.fromCharCode(upper, lower);
- }
+ b1 = bytes[i++];
+
+ if (b1 < 0x80) {
+ // 1 byte
+ code = b1;
+ } else if ((b1 >> 5) === 0x06) {
+ // 2 bytes
+ b2 = bytes[i++];
+ code = ((b1 & 0x1f) << 6) | (b2 & 0x3f);
+ } else if ((b1 >> 4) === 0x0e) {
+ // 3 bytes
+ b2 = bytes[i++];
+ b3 = bytes[i++];
+ code = ((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
+ } else {
+ // 4 bytes
+ b2 = bytes[i++];
+ b3 = bytes[i++];
+ b4 = bytes[i++];
+ code = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
+ }
+
+ if (code < 0x10000) {
+ str += String.fromCharCode(code);
+ } else {
+ // surrogate pair
+ code -= 0x10000;
+ upper = (0xD800 | (code >> 10));
+ lower = (0xDC00 | (code & 0x3FF));
+ str += String.fromCharCode(upper, lower);
}
+ }
- return str;
+ return str;
};
/**
@@ -8423,8 +10948,8 @@ var utf8BytesToString = function (bytes) {
* @constructor
*/
function ByteBuffer(arg) {
- var initial_size;
- if (arg == null) {
+ let initial_size;
+ if (arg == null) {
initial_size = 1024 * 1024;
} else if (typeof arg === "number") {
initial_size = arg;
@@ -8446,8 +10971,8 @@ ByteBuffer.prototype.size = function () {
};
ByteBuffer.prototype.reallocate = function () {
- var new_array = new Uint8Array(this.buffer.length * 2);
- new_array.set(this.buffer);
+ const new_array = new Uint8Array(this.buffer.length * 2);
+ new_array.set(this.buffer);
this.buffer = new_array;
};
@@ -8479,9 +11004,9 @@ ByteBuffer.prototype.putShort = function (num) {
if (0xFFFF < num) {
throw num + " is over short value";
}
- var lower = (0x00FF & num);
- var upper = (0xFF00 & num) >> 8;
- this.put(lower);
+ const lower = (0x00FF & num);
+ const upper = (0xFF00 & num) >> 8;
+ this.put(lower);
this.put(upper);
};
@@ -8494,10 +11019,10 @@ ByteBuffer.prototype.getShort = function (index) {
if (this.buffer.length < index + 2) {
return 0;
}
- var lower = this.buffer[index];
- var upper = this.buffer[index + 1];
- var value = (upper << 8) + lower;
- if (value & 0x8000) {
+ const lower = this.buffer[index];
+ const upper = this.buffer[index + 1];
+ let value = (upper << 8) + lower;
+ if (value & 0x8000) {
value = -((value - 1) ^ 0xFFFF);
}
return value;
@@ -8508,11 +11033,11 @@ ByteBuffer.prototype.putInt = function (num) {
if (0xFFFFFFFF < num) {
throw num + " is over integer value";
}
- var b0 = (0x000000FF & num);
- var b1 = (0x0000FF00 & num) >> 8;
- var b2 = (0x00FF0000 & num) >> 16;
- var b3 = (0xFF000000 & num) >> 24;
- this.put(b0);
+ const b0 = (0x000000FF & num);
+ const b1 = (0x0000FF00 & num) >> 8;
+ const b2 = (0x00FF0000 & num) >> 16;
+ const b3 = (0xFF000000 & num) >> 24;
+ this.put(b0);
this.put(b1);
this.put(b2);
this.put(b3);
@@ -8527,23 +11052,23 @@ ByteBuffer.prototype.getInt = function (index) {
if (this.buffer.length < index + 4) {
return 0;
}
- var b0 = this.buffer[index];
- var b1 = this.buffer[index + 1];
- var b2 = this.buffer[index + 2];
- var b3 = this.buffer[index + 3];
+ const b0 = this.buffer[index];
+ const b1 = this.buffer[index + 1];
+ const b2 = this.buffer[index + 2];
+ const b3 = this.buffer[index + 3];
- return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
+ return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
};
ByteBuffer.prototype.readInt = function () {
- var pos = this.position;
- this.position += 4;
+ const pos = this.position;
+ this.position += 4;
return this.getInt(pos);
};
ByteBuffer.prototype.putString = function (str) {
- var bytes = stringToUtf8Bytes(str);
- for (var i = 0; i < bytes.length; i++) {
+ const bytes = stringToUtf8Bytes(str);
+ for (let i = 0; i < bytes.length; i++) {
this.put(bytes[i]);
}
// put null character as terminal character
@@ -8551,9 +11076,9 @@ ByteBuffer.prototype.putString = function (str) {
};
ByteBuffer.prototype.getString = function (index) {
- var buf = [],
- ch;
- if (index == null) {
+ const buf = [];
+ let ch;
+ if (index == null) {
index = this.position;
}
while (true) {
@@ -8573,7 +11098,7 @@ ByteBuffer.prototype.getString = function (index) {
module.exports = ByteBuffer;
-},{}],22:[function(require,module,exports){
+},{}],23:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8601,8 +11126,8 @@ function IpadicFormatter() {
}
IpadicFormatter.prototype.formatEntry = function (word_id, position, type, features) {
- var token = {};
- token.word_id = word_id;
+ const token = {};
+ token.word_id = word_id;
token.word_type = type;
token.word_position = position;
@@ -8621,8 +11146,8 @@ IpadicFormatter.prototype.formatEntry = function (word_id, position, type, featu
};
IpadicFormatter.prototype.formatUnknownEntry = function (word_id, position, type, features, surface_form) {
- var token = {};
- token.word_id = word_id;
+ const token = {};
+ token.word_id = word_id;
token.word_type = type;
token.word_position = position;
@@ -8642,7 +11167,7 @@ IpadicFormatter.prototype.formatUnknownEntry = function (word_id, position, type
module.exports = IpadicFormatter;
-},{}],23:[function(require,module,exports){
+},{}],24:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8671,9 +11196,9 @@ function SurrogateAwareString(str) {
this.str = str;
this.index_mapping = [];
- for (var pos = 0; pos < str.length; pos++) {
- var ch = str.charAt(pos);
- this.index_mapping.push(pos);
+ for (let pos = 0; pos < str.length; pos++) {
+ const ch = str.charAt(pos);
+ this.index_mapping.push(pos);
if (SurrogateAwareString.isSurrogatePair(ch)) {
pos++;
}
@@ -8686,18 +11211,18 @@ SurrogateAwareString.prototype.slice = function (index) {
if (this.index_mapping.length <= index) {
return "";
}
- var surrogate_aware_index = this.index_mapping[index];
- return this.str.slice(surrogate_aware_index);
+ const surrogate_aware_index = this.index_mapping[index];
+ return this.str.slice(surrogate_aware_index);
};
SurrogateAwareString.prototype.charAt = function (index) {
if (this.str.length <= index) {
return "";
}
- var surrogate_aware_start_index = this.index_mapping[index];
- var surrogate_aware_end_index = this.index_mapping[index + 1];
+ const surrogate_aware_start_index = this.index_mapping[index];
+ const surrogate_aware_end_index = this.index_mapping[index + 1];
- if (surrogate_aware_end_index == null) {
+ if (surrogate_aware_end_index == null) {
return this.str.slice(surrogate_aware_start_index);
}
return this.str.slice(surrogate_aware_start_index, surrogate_aware_end_index);
@@ -8707,10 +11232,10 @@ SurrogateAwareString.prototype.charCodeAt = function (index) {
if (this.index_mapping.length <= index) {
return NaN;
}
- var surrogate_aware_index = this.index_mapping[index];
- var upper = this.str.charCodeAt(surrogate_aware_index);
- var lower;
- if (upper >= 0xD800 && upper <= 0xDBFF && surrogate_aware_index < this.str.length) {
+ const surrogate_aware_index = this.index_mapping[index];
+ const upper = this.str.charCodeAt(surrogate_aware_index);
+ let lower;
+ if (upper >= 0xD800 && upper <= 0xDBFF && surrogate_aware_index < this.str.length) {
lower = this.str.charCodeAt(surrogate_aware_index + 1);
if (lower >= 0xDC00 && lower <= 0xDFFF) {
return (upper - 0xD800) * 0x400 + lower - 0xDC00 + 0x10000;
@@ -8724,8 +11249,8 @@ SurrogateAwareString.prototype.toString = function () {
};
SurrogateAwareString.isSurrogatePair = function (ch) {
- var utf16_code = ch.charCodeAt(0);
- if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
+ const utf16_code = ch.charCodeAt(0);
+ if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
// surrogate pair
return true;
} else {
@@ -8735,7 +11260,7 @@ SurrogateAwareString.isSurrogatePair = function (ch) {
module.exports = SurrogateAwareString;
-},{}],24:[function(require,module,exports){
+},{}],25:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8755,9 +11280,9 @@ module.exports = SurrogateAwareString;
"use strict";
-var ViterbiNode = require("./ViterbiNode");
-var ViterbiLattice = require("./ViterbiLattice");
-var SurrogateAwareString = require("../util/SurrogateAwareString");
+const ViterbiNode = require("./ViterbiNode");
+const ViterbiLattice = require("./ViterbiLattice");
+const SurrogateAwareString = require("../util/SurrogateAwareString");
/**
* ViterbiBuilder builds word lattice (ViterbiLattice)
@@ -8776,22 +11301,22 @@ function ViterbiBuilder(dic) {
* @returns {ViterbiLattice} Word lattice
*/
ViterbiBuilder.prototype.build = function (sentence_str) {
- var lattice = new ViterbiLattice();
- var sentence = new SurrogateAwareString(sentence_str);
-
- var key, trie_id, left_id, right_id, word_cost;
- for (var pos = 0; pos < sentence.length; pos++) {
- var tail = sentence.slice(pos);
- var vocabulary = this.trie.commonPrefixSearch(tail);
- for (var n = 0; n < vocabulary.length; n++) { // Words in dictionary do not have surrogate pair (only UCS2 set)
+ const lattice = new ViterbiLattice();
+ const sentence = new SurrogateAwareString(sentence_str);
+
+ let key, trie_id, left_id, right_id, word_cost;
+ for (let pos = 0; pos < sentence.length; pos++) {
+ const tail = sentence.slice(pos);
+ const vocabulary = this.trie.commonPrefixSearch(tail);
+ for (let n = 0; n < vocabulary.length; n++) { // Words in dictionary do not have surrogate pair (only UCS2 set)
trie_id = vocabulary[n].v;
key = vocabulary[n].k;
- var token_info_ids = this.token_info_dictionary.target_map[trie_id];
- for (var i = 0; i < token_info_ids.length; i++) {
- var token_info_id = parseInt(token_info_ids[i]);
+ const token_info_ids = this.token_info_dictionary.target_map[trie_id];
+ for (let i = 0; i < token_info_ids.length; i++) {
+ const token_info_id = parseInt(token_info_ids[i]);
- left_id = this.token_info_dictionary.dictionary.getShort(token_info_id);
+ left_id = this.token_info_dictionary.dictionary.getShort(token_info_id);
right_id = this.token_info_dictionary.dictionary.getShort(token_info_id + 2);
word_cost = this.token_info_dictionary.dictionary.getShort(token_info_id + 4);
@@ -8801,28 +11326,28 @@ ViterbiBuilder.prototype.build = function (sentence_str) {
}
// Unknown word processing
- var surrogate_aware_tail = new SurrogateAwareString(tail);
- var head_char = new SurrogateAwareString(surrogate_aware_tail.charAt(0));
- var head_char_class = this.unknown_dictionary.lookup(head_char.toString());
- if (vocabulary == null || vocabulary.length === 0 || head_char_class.is_always_invoke === 1) {
+ const surrogate_aware_tail = new SurrogateAwareString(tail);
+ const head_char = new SurrogateAwareString(surrogate_aware_tail.charAt(0));
+ const head_char_class = this.unknown_dictionary.lookup(head_char.toString());
+ if (vocabulary == null || vocabulary.length === 0 || head_char_class.is_always_invoke === 1) {
// Process unknown word
key = head_char;
if (head_char_class.is_grouping === 1 && 1 < surrogate_aware_tail.length) {
- for (var k = 1; k < surrogate_aware_tail.length; k++) {
- var next_char = surrogate_aware_tail.charAt(k);
- var next_char_class = this.unknown_dictionary.lookup(next_char);
- if (head_char_class.class_name !== next_char_class.class_name) {
+ for (let k = 1; k < surrogate_aware_tail.length; k++) {
+ const next_char = surrogate_aware_tail.charAt(k);
+ const next_char_class = this.unknown_dictionary.lookup(next_char);
+ if (head_char_class.class_name !== next_char_class.class_name) {
break;
}
key += next_char;
}
}
- var unk_ids = this.unknown_dictionary.target_map[head_char_class.class_id];
- for (var j = 0; j < unk_ids.length; j++) {
- var unk_id = parseInt(unk_ids[j]);
+ const unk_ids = this.unknown_dictionary.target_map[head_char_class.class_id];
+ for (let j = 0; j < unk_ids.length; j++) {
+ const unk_id = parseInt(unk_ids[j]);
- left_id = this.unknown_dictionary.dictionary.getShort(unk_id);
+ left_id = this.unknown_dictionary.dictionary.getShort(unk_id);
right_id = this.unknown_dictionary.dictionary.getShort(unk_id + 2);
word_cost = this.unknown_dictionary.dictionary.getShort(unk_id + 4);
@@ -8838,7 +11363,7 @@ ViterbiBuilder.prototype.build = function (sentence_str) {
module.exports = ViterbiBuilder;
-},{"../util/SurrogateAwareString":23,"./ViterbiLattice":25,"./ViterbiNode":26}],25:[function(require,module,exports){
+},{"../util/SurrogateAwareString":24,"./ViterbiLattice":26,"./ViterbiNode":27}],26:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8858,7 +11383,7 @@ module.exports = ViterbiBuilder;
"use strict";
-var ViterbiNode = require("./ViterbiNode");
+const ViterbiNode = require("./ViterbiNode");
/**
* ViterbiLattice is a lattice in Viterbi algorithm
@@ -8875,13 +11400,13 @@ function ViterbiLattice() {
* @param {ViterbiNode} node
*/
ViterbiLattice.prototype.append = function (node) {
- var last_pos = node.start_pos + node.length - 1;
- if (this.eos_pos < last_pos) {
+ const last_pos = node.start_pos + node.length - 1;
+ if (this.eos_pos < last_pos) {
this.eos_pos = last_pos;
}
- var prev_nodes = this.nodes_end_at[last_pos];
- if (prev_nodes == null) {
+ let prev_nodes = this.nodes_end_at[last_pos];
+ if (prev_nodes == null) {
prev_nodes = [];
}
prev_nodes.push(node);
@@ -8893,14 +11418,14 @@ ViterbiLattice.prototype.append = function (node) {
* Set ends with EOS (End of Statement)
*/
ViterbiLattice.prototype.appendEos = function () {
- var last_index = this.nodes_end_at.length;
- this.eos_pos++;
+ const last_index = this.nodes_end_at.length;
+ this.eos_pos++;
this.nodes_end_at[last_index] = [ new ViterbiNode(-1, 0, this.eos_pos, 0, "EOS", 0, 0, "") ];
};
module.exports = ViterbiLattice;
-},{"./ViterbiNode":26}],26:[function(require,module,exports){
+},{"./ViterbiNode":27}],27:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8951,7 +11476,7 @@ function ViterbiNode(node_name, node_cost, start_pos, length, type, left_id, rig
module.exports = ViterbiNode;
-},{}],27:[function(require,module,exports){
+},{}],28:[function(require,module,exports){
/*
* Copyright 2014 Takuya Asano
* Copyright 2010-2014 Atilika Inc. and contributors
@@ -8991,27 +11516,27 @@ ViterbiSearcher.prototype.search = function (lattice) {
};
ViterbiSearcher.prototype.forward = function (lattice) {
- var i, j, k;
- for (i = 1; i <= lattice.eos_pos; i++) {
- var nodes = lattice.nodes_end_at[i];
- if (nodes == null) {
+ let i, j, k;
+ for (i = 1; i <= lattice.eos_pos; i++) {
+ const nodes = lattice.nodes_end_at[i];
+ if (nodes == null) {
continue;
}
for (j = 0; j < nodes.length; j++) {
- var node = nodes[j];
- var cost = Number.MAX_VALUE;
- var shortest_prev_node;
+ const node = nodes[j];
+ let cost = Number.MAX_VALUE;
+ let shortest_prev_node;
- var prev_nodes = lattice.nodes_end_at[node.start_pos - 1];
- if (prev_nodes == null) {
+ const prev_nodes = lattice.nodes_end_at[node.start_pos - 1];
+ if (prev_nodes == null) {
// TODO process unknown words (repair word lattice)
continue;
}
for (k = 0; k < prev_nodes.length; k++) {
- var prev_node = prev_nodes[k];
+ const prev_node = prev_nodes[k];
- var edge_cost;
- if (node.left_id == null || prev_node.right_id == null) {
+ let edge_cost;
+ if (node.left_id == null || prev_node.right_id == null) {
// TODO assert
console.log("Left or right is null");
edge_cost = 0;
@@ -9019,8 +11544,8 @@ ViterbiSearcher.prototype.forward = function (lattice) {
edge_cost = this.connection_costs.get(prev_node.right_id, node.left_id);
}
- var _cost = prev_node.shortest_cost + edge_cost + node.cost;
- if (_cost < cost) {
+ const _cost = prev_node.shortest_cost + edge_cost + node.cost;
+ if (_cost < cost) {
shortest_prev_node = prev_node;
cost = _cost;
}
@@ -9034,11 +11559,11 @@ ViterbiSearcher.prototype.forward = function (lattice) {
};
ViterbiSearcher.prototype.backward = function (lattice) {
- var shortest_path = [];
- var eos = lattice.nodes_end_at[lattice.nodes_end_at.length - 1][0];
+ const shortest_path = [];
+ const eos = lattice.nodes_end_at[lattice.nodes_end_at.length - 1][0];
- var node_back = eos.prev;
- if (node_back == null) {
+ let node_back = eos.prev;
+ if (node_back == null) {
return [];
}
while (node_back.type !== "BOS") {
@@ -9055,5 +11580,5 @@ ViterbiSearcher.prototype.backward = function (lattice) {
module.exports = ViterbiSearcher;
-},{}]},{},[18])(18)
+},{}]},{},[19])(19)
});
diff --git a/package.json b/package.json
index 6e2d399607e6154a107113b817012694fc1143af..52972980b1ef237244eecc80f12b50774459883c 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,13 @@
"bugs": {
"url": "https://github.com/takuyaa/kuromoji.js/issues"
},
+ "overrides": {
+ "graceful-fs": "^4.2.3"
+ },
"dependencies": {
- "async": "^2.0.1",
+ "async": "^2.3.0",
"doublearray": "0.0.2",
- "zlibjs": "^0.3.1"
+ "fflate": "0.8.2"
},
"devDependencies": {
"browserify": "^16.1.1",
diff --git a/src/Tokenizer.js b/src/Tokenizer.js
index 6c3da9b76a9ea7abada97c0ca63618b449f4661e..75fc74807ba66af70b8de9cbfa2b21c4a5e034b5 100644
--- a/src/Tokenizer.js
+++ b/src/Tokenizer.js
@@ -17,11 +17,11 @@
"use strict";
-var ViterbiBuilder = require("./viterbi/ViterbiBuilder");
-var ViterbiSearcher = require("./viterbi/ViterbiSearcher");
-var IpadicFormatter = require("./util/IpadicFormatter");
+const ViterbiBuilder = require("./viterbi/ViterbiBuilder");
+const ViterbiSearcher = require("./viterbi/ViterbiSearcher");
+const IpadicFormatter = require("./util/IpadicFormatter");
-var PUNCTUATION = /、|。/;
+const PUNCTUATION = /、|。/;
/**
* Tokenizer
@@ -42,14 +42,14 @@ function Tokenizer(dic) {
* @returns {Array.} Sentences end with punctuation
*/
Tokenizer.splitByPunctuation = function (input) {
- var sentences = [];
- var tail = input;
- while (true) {
+ const sentences = [];
+ let tail = input;
+ while (true) {
if (tail === "") {
break;
}
- var index = tail.search(PUNCTUATION);
- if (index < 0) {
+ const index = tail.search(PUNCTUATION);
+ if (index < 0) {
sentences.push(tail);
break;
}
@@ -65,11 +65,11 @@ Tokenizer.splitByPunctuation = function (input) {
* @returns {Array} Tokens
*/
Tokenizer.prototype.tokenize = function (text) {
- var sentences = Tokenizer.splitByPunctuation(text);
- var tokens = [];
- for (var i = 0; i < sentences.length; i++) {
- var sentence = sentences[i];
- this.tokenizeForSentence(sentence, tokens);
+ const sentences = Tokenizer.splitByPunctuation(text);
+ const tokens = [];
+ for (let i = 0; i < sentences.length; i++) {
+ const sentence = sentences[i];
+ this.tokenizeForSentence(sentence, tokens);
}
return tokens;
};
@@ -78,18 +78,18 @@ Tokenizer.prototype.tokenizeForSentence = function (sentence, tokens) {
if (tokens == null) {
tokens = [];
}
- var lattice = this.getLattice(sentence);
- var best_path = this.viterbi_searcher.search(lattice);
- var last_pos = 0;
- if (tokens.length > 0) {
+ const lattice = this.getLattice(sentence);
+ const best_path = this.viterbi_searcher.search(lattice);
+ let last_pos = 0;
+ if (tokens.length > 0) {
last_pos = tokens[tokens.length - 1].word_position;
}
- for (var j = 0; j < best_path.length; j++) {
- var node = best_path[j];
+ for (let j = 0; j < best_path.length; j++) {
+ const node = best_path[j];
- var token, features, features_line;
- if (node.type === "KNOWN") {
+ let token, features, features_line;
+ if (node.type === "KNOWN") {
features_line = this.token_info_dictionary.getFeatures(node.name);
if (features_line == null) {
features = [];
diff --git a/src/TokenizerBuilder.js b/src/TokenizerBuilder.js
index 9ef5c6a2efc63e8b12735a8a9f1cb08d6c52c20c..223e0c6d5856912b59fba59b255dfae63f7b0d54 100644
--- a/src/TokenizerBuilder.js
+++ b/src/TokenizerBuilder.js
@@ -17,8 +17,8 @@
"use strict";
-var Tokenizer = require("./Tokenizer");
-var DictionaryLoader = require("./loader/NodeDictionaryLoader");
+const Tokenizer = require("./Tokenizer");
+const BrowserDictionaryLoader = require("./loader/BrowserDictionaryLoader");
/**
* TokenizerBuilder create Tokenizer instance.
@@ -39,8 +39,8 @@ function TokenizerBuilder(option) {
* @param {TokenizerBuilder~onLoad} callback Callback function
*/
TokenizerBuilder.prototype.build = function (callback) {
- var loader = new DictionaryLoader(this.dic_path);
- loader.load(function (err, dic) {
+ const loader = new BrowserDictionaryLoader(this.dic_path);
+ loader.load(function (err, dic) {
callback(err, new Tokenizer(dic));
});
};
diff --git a/src/dict/CharacterDefinition.js b/src/dict/CharacterDefinition.js
index 11bb531e3dc1ec686d1f59564c1defb69fcfe974..b728e4da64e1de0b79047b8bdbb67724be8f1067 100644
--- a/src/dict/CharacterDefinition.js
+++ b/src/dict/CharacterDefinition.js
@@ -17,11 +17,11 @@
"use strict";
-var InvokeDefinitionMap = require("./InvokeDefinitionMap");
-var CharacterClass = require("./CharacterClass");
-var SurrogateAwareString = require("../util/SurrogateAwareString");
+const InvokeDefinitionMap = require("./InvokeDefinitionMap");
+const CharacterClass = require("./CharacterClass");
+const SurrogateAwareString = require("../util/SurrogateAwareString");
-var DEFAULT_CATEGORY = "DEFAULT";
+const DEFAULT_CATEGORY = "DEFAULT";
/**
* CharacterDefinition represents char.def file and
@@ -42,19 +42,19 @@ function CharacterDefinition() {
* @returns {CharacterDefinition}
*/
CharacterDefinition.load = function (cat_map_buffer, compat_cat_map_buffer, invoke_def_buffer) {
- var char_def = new CharacterDefinition();
- char_def.character_category_map = cat_map_buffer;
+ const char_def = new CharacterDefinition();
+ char_def.character_category_map = cat_map_buffer;
char_def.compatible_category_map = compat_cat_map_buffer;
char_def.invoke_definition_map = InvokeDefinitionMap.load(invoke_def_buffer);
return char_def;
};
CharacterDefinition.parseCharCategory = function (class_id, parsed_category_def) {
- var category = parsed_category_def[1];
- var invoke = parseInt(parsed_category_def[2]);
- var grouping = parseInt(parsed_category_def[3]);
- var max_length = parseInt(parsed_category_def[4]);
- if (!isFinite(invoke) || (invoke !== 0 && invoke !== 1)) {
+ const category = parsed_category_def[1];
+ const invoke = parseInt(parsed_category_def[2]);
+ const grouping = parseInt(parsed_category_def[3]);
+ const max_length = parseInt(parsed_category_def[4]);
+ if (!isFinite(invoke) || (invoke !== 0 && invoke !== 1)) {
console.log("char.def parse error. INVOKE is 0 or 1 in:" + invoke);
return null;
}
@@ -66,28 +66,28 @@ CharacterDefinition.parseCharCategory = function (class_id, parsed_category_def)
console.log("char.def parse error. LENGTH is 1 to n:" + max_length);
return null;
}
- var is_invoke = (invoke === 1);
- var is_grouping = (grouping === 1);
+ const is_invoke = (invoke === 1);
+ const is_grouping = (grouping === 1);
- return new CharacterClass(class_id, category, is_invoke, is_grouping, max_length);
+ return new CharacterClass(class_id, category, is_invoke, is_grouping, max_length);
};
CharacterDefinition.parseCategoryMapping = function (parsed_category_mapping) {
- var start = parseInt(parsed_category_mapping[1]);
- var default_category = parsed_category_mapping[2];
- var compatible_category = (3 < parsed_category_mapping.length) ? parsed_category_mapping.slice(3) : [];
- if (!isFinite(start) || start < 0 || start > 0xFFFF) {
+ const start = parseInt(parsed_category_mapping[1]);
+ const default_category = parsed_category_mapping[2];
+ const compatible_category = (3 < parsed_category_mapping.length) ? parsed_category_mapping.slice(3) : [];
+ if (!isFinite(start) || start < 0 || start > 0xFFFF) {
console.log("char.def parse error. CODE is invalid:" + start);
}
return { start: start, default: default_category, compatible: compatible_category};
};
CharacterDefinition.parseRangeCategoryMapping = function (parsed_category_mapping) {
- var start = parseInt(parsed_category_mapping[1]);
- var end = parseInt(parsed_category_mapping[2]);
- var default_category = parsed_category_mapping[3];
- var compatible_category = (4 < parsed_category_mapping.length) ? parsed_category_mapping.slice(4) : [];
- if (!isFinite(start) || start < 0 || start > 0xFFFF) {
+ const start = parseInt(parsed_category_mapping[1]);
+ const end = parseInt(parsed_category_mapping[2]);
+ const default_category = parsed_category_mapping[3];
+ const compatible_category = (4 < parsed_category_mapping.length) ? parsed_category_mapping.slice(4) : [];
+ if (!isFinite(start) || start < 0 || start > 0xFFFF) {
console.log("char.def parse error. CODE is invalid:" + start);
}
if (!isFinite(end) || end < 0 || end > 0xFFFF) {
@@ -102,35 +102,35 @@ CharacterDefinition.parseRangeCategoryMapping = function (parsed_category_mappin
*/
CharacterDefinition.prototype.initCategoryMappings = function (category_mapping) {
// Initialize map by DEFAULT class
- var code_point;
- if (category_mapping != null) {
- for (var i = 0; i < category_mapping.length; i++) {
- var mapping = category_mapping[i];
- var end = mapping.end || mapping.start;
- for (code_point = mapping.start; code_point <= end; code_point++) {
+ let code_point;
+ if (category_mapping != null) {
+ for (let i = 0; i < category_mapping.length; i++) {
+ const mapping = category_mapping[i];
+ const end = mapping.end || mapping.start;
+ for (code_point = mapping.start; code_point <= end; code_point++) {
// Default Category class ID
this.character_category_map[code_point] = this.invoke_definition_map.lookup(mapping.default);
- for (var j = 0; j < mapping.compatible.length; j++) {
- var bitset = this.compatible_category_map[code_point];
- var compatible_category = mapping.compatible[j];
- if (compatible_category == null) {
+ for (let j = 0; j < mapping.compatible.length; j++) {
+ let bitset = this.compatible_category_map[code_point];
+ const compatible_category = mapping.compatible[j];
+ if (compatible_category == null) {
continue;
}
- var class_id = this.invoke_definition_map.lookup(compatible_category); // Default Category
+ const class_id = this.invoke_definition_map.lookup(compatible_category); // Default Category
if (class_id == null) {
continue;
}
- var class_id_bit = 1 << class_id;
- bitset = bitset | class_id_bit; // Set a bit of class ID 例えば、class_idが3のとき、3ビット目に1を立てる
+ const class_id_bit = 1 << class_id;
+ bitset = bitset | class_id_bit; // Set a bit of class ID 例えば、class_idが3のとき、3ビット目に1を立てる
this.compatible_category_map[code_point] = bitset;
}
}
}
}
- var default_id = this.invoke_definition_map.lookup(DEFAULT_CATEGORY);
- if (default_id == null) {
+ const default_id = this.invoke_definition_map.lookup(DEFAULT_CATEGORY);
+ if (default_id == null) {
return;
}
for (code_point = 0; code_point < this.character_category_map.length; code_point++) {
@@ -148,16 +148,16 @@ CharacterDefinition.prototype.initCategoryMappings = function (category_mapping)
* @returns {Array.} character classes
*/
CharacterDefinition.prototype.lookupCompatibleCategory = function (ch) {
- var classes = [];
-
- /*
- if (SurrogateAwareString.isSurrogatePair(ch)) {
- // Surrogate pair character codes can not be defined by char.def
- return classes;
- }*/
- var code = ch.charCodeAt(0);
- var integer;
- if (code < this.compatible_category_map.length) {
+ const classes = [];
+
+ /*
+ if (SurrogateAwareString.isSurrogatePair(ch)) {
+ // Surrogate pair character codes can not be defined by char.def
+ return classes;
+ }*/
+ const code = ch.charCodeAt(0);
+ let integer;
+ if (code < this.compatible_category_map.length) {
integer = this.compatible_category_map[code]; // Bitset
}
@@ -165,10 +165,10 @@ CharacterDefinition.prototype.lookupCompatibleCategory = function (ch) {
return classes;
}
- for (var bit = 0; bit < 32; bit++) { // Treat "bit" as a class ID
+ for (let bit = 0; bit < 32; bit++) { // Treat "bit" as a class ID
if (((integer << (31 - bit)) >>> 31) === 1) {
- var character_class = this.invoke_definition_map.getCharacterClass(bit);
- if (character_class == null) {
+ const character_class = this.invoke_definition_map.getCharacterClass(bit);
+ if (character_class == null) {
continue;
}
classes.push(character_class);
@@ -185,10 +185,10 @@ CharacterDefinition.prototype.lookupCompatibleCategory = function (ch) {
*/
CharacterDefinition.prototype.lookup = function (ch) {
- var class_id;
+ let class_id;
- var code = ch.charCodeAt(0);
- if (SurrogateAwareString.isSurrogatePair(ch)) {
+ const code = ch.charCodeAt(0);
+ if (SurrogateAwareString.isSurrogatePair(ch)) {
// Surrogate pair character codes can not be defined by char.def, so set DEFAULT category
class_id = this.invoke_definition_map.lookup(DEFAULT_CATEGORY);
} else if (code < this.character_category_map.length) {
diff --git a/src/dict/ConnectionCosts.js b/src/dict/ConnectionCosts.js
index ba69170ab5a5481ed64c1a585f166c34b302e643..1192b71c419540dbf75353947d36ed00084ae309 100644
--- a/src/dict/ConnectionCosts.js
+++ b/src/dict/ConnectionCosts.js
@@ -35,16 +35,16 @@ function ConnectionCosts(forward_dimension, backward_dimension) {
}
ConnectionCosts.prototype.put = function (forward_id, backward_id, cost) {
- var index = forward_id * this.backward_dimension + backward_id + 2;
- if (this.buffer.length < index + 1) {
+ const index = forward_id * this.backward_dimension + backward_id + 2;
+ if (this.buffer.length < index + 1) {
throw "ConnectionCosts buffer overflow";
}
this.buffer[index] = cost;
};
ConnectionCosts.prototype.get = function (forward_id, backward_id) {
- var index = forward_id * this.backward_dimension + backward_id + 2;
- if (this.buffer.length < index + 1) {
+ const index = forward_id * this.backward_dimension + backward_id + 2;
+ if (this.buffer.length < index + 1) {
throw "ConnectionCosts buffer overflow";
}
return this.buffer[index];
diff --git a/src/dict/DynamicDictionaries.js b/src/dict/DynamicDictionaries.js
index 452955aa375f54dec5192f73915f5fe74f4b07dc..2b4006a33c751cbb9885ca7f34afaa01c88535f2 100644
--- a/src/dict/DynamicDictionaries.js
+++ b/src/dict/DynamicDictionaries.js
@@ -17,10 +17,10 @@
"use strict";
-var doublearray = require("doublearray");
-var TokenInfoDictionary = require("./TokenInfoDictionary");
-var ConnectionCosts = require("./ConnectionCosts");
-var UnknownDictionary = require("./UnknownDictionary");
+const doublearray = require("doublearray");
+const TokenInfoDictionary = require("./TokenInfoDictionary");
+const ConnectionCosts = require("./ConnectionCosts");
+const UnknownDictionary = require("./UnknownDictionary");
/**
* Dictionaries container for Tokenizer
diff --git a/src/dict/InvokeDefinitionMap.js b/src/dict/InvokeDefinitionMap.js
index d97128b70274cb366f7027cbc34614726f6f595f..0425005b58fad7554a85f912e04dba745998f60f 100644
--- a/src/dict/InvokeDefinitionMap.js
+++ b/src/dict/InvokeDefinitionMap.js
@@ -17,8 +17,8 @@
"use strict";
-var ByteBuffer = require("../util/ByteBuffer");
-var CharacterClass = require("./CharacterClass");
+const ByteBuffer = require("../util/ByteBuffer");
+const CharacterClass = require("./CharacterClass");
/**
* InvokeDefinitionMap represents invoke definition a part of char.def
@@ -35,17 +35,17 @@ function InvokeDefinitionMap() {
* @returns {InvokeDefinitionMap}
*/
InvokeDefinitionMap.load = function (invoke_def_buffer) {
- var invoke_def = new InvokeDefinitionMap();
- var character_category_definition = [];
+ const invoke_def = new InvokeDefinitionMap();
+ const character_category_definition = [];
- var buffer = new ByteBuffer(invoke_def_buffer);
- while (buffer.position + 1 < buffer.size()) {
- var class_id = character_category_definition.length;
- var is_always_invoke = buffer.get();
- var is_grouping = buffer.get();
- var max_length = buffer.getInt();
- var class_name = buffer.getString();
- character_category_definition.push(new CharacterClass(class_id, class_name, is_always_invoke, is_grouping, max_length));
+ const buffer = new ByteBuffer(invoke_def_buffer);
+ while (buffer.position + 1 < buffer.size()) {
+ const class_id = character_category_definition.length;
+ const is_always_invoke = buffer.get();
+ const is_grouping = buffer.get();
+ const max_length = buffer.getInt();
+ const class_name = buffer.getString();
+ character_category_definition.push(new CharacterClass(class_id, class_name, is_always_invoke, is_grouping, max_length));
}
invoke_def.init(character_category_definition);
@@ -61,9 +61,9 @@ InvokeDefinitionMap.prototype.init = function (character_category_definition) {
if (character_category_definition == null) {
return;
}
- for (var i = 0; i < character_category_definition.length; i++) {
- var character_class = character_category_definition[i];
- this.map[i] = character_class;
+ for (let i = 0; i < character_category_definition.length; i++) {
+ const character_class = character_category_definition[i];
+ this.map[i] = character_class;
this.lookup_table[character_class.class_name] = i;
}
};
@@ -83,8 +83,8 @@ InvokeDefinitionMap.prototype.getCharacterClass = function (class_id) {
* @returns {number} class_id
*/
InvokeDefinitionMap.prototype.lookup = function (class_name) {
- var class_id = this.lookup_table[class_name];
- if (class_id == null) {
+ const class_id = this.lookup_table[class_name];
+ if (class_id == null) {
return null;
}
return class_id;
@@ -95,10 +95,10 @@ InvokeDefinitionMap.prototype.lookup = function (class_name) {
* @returns {Uint8Array}
*/
InvokeDefinitionMap.prototype.toBuffer = function () {
- var buffer = new ByteBuffer();
- for (var i = 0; i < this.map.length; i++) {
- var char_class = this.map[i];
- buffer.put(char_class.is_always_invoke);
+ const buffer = new ByteBuffer();
+ for (let i = 0; i < this.map.length; i++) {
+ const char_class = this.map[i];
+ buffer.put(char_class.is_always_invoke);
buffer.put(char_class.is_grouping);
buffer.putInt(char_class.max_length);
buffer.putString(char_class.class_name);
diff --git a/src/dict/TokenInfoDictionary.js b/src/dict/TokenInfoDictionary.js
index 2fccee7bf399abf070ca8adb1f634a141d3e351b..79d7a6cfc3b7a6be71bd786deabbe36c299ae376 100644
--- a/src/dict/TokenInfoDictionary.js
+++ b/src/dict/TokenInfoDictionary.js
@@ -17,7 +17,7 @@
"use strict";
-var ByteBuffer = require("../util/ByteBuffer");
+const ByteBuffer = require("../util/ByteBuffer");
/**
* TokenInfoDictionary
@@ -32,28 +32,28 @@ function TokenInfoDictionary() {
// left_id right_id word_cost ...
// ^ this position is token_info_id
TokenInfoDictionary.prototype.buildDictionary = function (entries) {
- var dictionary_entries = {}; // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
+ const dictionary_entries = {}; // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
- for (var i = 0; i < entries.length; i++) {
- var entry = entries[i];
+ for (let i = 0; i < entries.length; i++) {
+ const entry = entries[i];
- if (entry.length < 4) {
+ if (entry.length < 4) {
continue;
}
- var surface_form = entry[0];
- var left_id = entry[1];
- var right_id = entry[2];
- var word_cost = entry[3];
- var feature = entry.slice(4).join(","); // TODO Optimize
+ const surface_form = entry[0];
+ const left_id = entry[1];
+ const right_id = entry[2];
+ const word_cost = entry[3];
+ const feature = entry.slice(4).join(","); // TODO Optimize
// Assertion
if (!isFinite(left_id) || !isFinite(right_id) || !isFinite(word_cost)) {
console.log(entry);
}
- var token_info_id = this.put(left_id, right_id, word_cost, surface_form, feature);
- dictionary_entries[token_info_id] = surface_form;
+ const token_info_id = this.put(left_id, right_id, word_cost, surface_form, feature);
+ dictionary_entries[token_info_id] = surface_form;
}
// Remove last unused area
@@ -64,10 +64,10 @@ TokenInfoDictionary.prototype.buildDictionary = function (entries) {
};
TokenInfoDictionary.prototype.put = function (left_id, right_id, word_cost, surface_form, feature) {
- var token_info_id = this.dictionary.position;
- var pos_id = this.pos_buffer.position;
+ const token_info_id = this.dictionary.position;
+ const pos_id = this.pos_buffer.position;
- this.dictionary.putShort(left_id);
+ this.dictionary.putShort(left_id);
this.dictionary.putShort(right_id);
this.dictionary.putShort(word_cost);
this.dictionary.putInt(pos_id);
@@ -77,8 +77,8 @@ TokenInfoDictionary.prototype.put = function (left_id, right_id, word_cost, surf
};
TokenInfoDictionary.prototype.addMapping = function (source, target) {
- var mapping = this.target_map[source];
- if (mapping == null) {
+ let mapping = this.target_map[source];
+ if (mapping == null) {
mapping = [];
}
mapping.push(target);
@@ -87,15 +87,15 @@ TokenInfoDictionary.prototype.addMapping = function (source, target) {
};
TokenInfoDictionary.prototype.targetMapToBuffer = function () {
- var buffer = new ByteBuffer();
- var map_keys_size = Object.keys(this.target_map).length;
- buffer.putInt(map_keys_size);
- for (var key in this.target_map) {
- var values = this.target_map[key]; // Array
- var map_values_size = values.length;
- buffer.putInt(parseInt(key));
+ const buffer = new ByteBuffer();
+ const map_keys_size = Object.keys(this.target_map).length;
+ buffer.putInt(map_keys_size);
+ for (let key in this.target_map) {
+ const values = this.target_map[key]; // Array
+ const map_values_size = values.length;
+ buffer.putInt(parseInt(key));
buffer.putInt(map_values_size);
- for (var i = 0; i < values.length; i++) {
+ for (let i = 0; i < values.length; i++) {
buffer.putInt(values[i]);
}
}
@@ -116,19 +116,19 @@ TokenInfoDictionary.prototype.loadPosVector = function (array_buffer) {
// from tid_map.dat
TokenInfoDictionary.prototype.loadTargetMap = function (array_buffer) {
- var buffer = new ByteBuffer(array_buffer);
- buffer.position = 0;
+ const buffer = new ByteBuffer(array_buffer);
+ buffer.position = 0;
this.target_map = {};
buffer.readInt(); // map_keys_size
while (true) {
if (buffer.buffer.length < buffer.position + 1) {
break;
}
- var key = buffer.readInt();
- var map_values_size = buffer.readInt();
- for (var i = 0; i < map_values_size; i++) {
- var value = buffer.readInt();
- this.addMapping(key, value);
+ const key = buffer.readInt();
+ const map_values_size = buffer.readInt();
+ for (let i = 0; i < map_values_size; i++) {
+ const value = buffer.readInt();
+ this.addMapping(key, value);
}
}
return this;
@@ -140,13 +140,13 @@ TokenInfoDictionary.prototype.loadTargetMap = function (array_buffer) {
* @returns {string} Features string concatenated by ","
*/
TokenInfoDictionary.prototype.getFeatures = function (token_info_id_str) {
- var token_info_id = parseInt(token_info_id_str);
- if (isNaN(token_info_id)) {
+ const token_info_id = parseInt(token_info_id_str);
+ if (isNaN(token_info_id)) {
// TODO throw error
return "";
}
- var pos_id = this.dictionary.getInt(token_info_id + 6);
- return this.pos_buffer.getString(pos_id);
+ const pos_id = this.dictionary.getInt(token_info_id + 6);
+ return this.pos_buffer.getString(pos_id);
};
module.exports = TokenInfoDictionary;
diff --git a/src/dict/UnknownDictionary.js b/src/dict/UnknownDictionary.js
index 9814c77ca197812c97170970012e661b69c0db9a..f467dbf4df885cc145c344e818105b3ebb74b37b 100644
--- a/src/dict/UnknownDictionary.js
+++ b/src/dict/UnknownDictionary.js
@@ -17,9 +17,9 @@
"use strict";
-var TokenInfoDictionary = require("./TokenInfoDictionary");
-var CharacterDefinition = require("./CharacterDefinition");
-var ByteBuffer = require("../util/ByteBuffer");
+const TokenInfoDictionary = require("./TokenInfoDictionary");
+const CharacterDefinition = require("./CharacterDefinition");
+const ByteBuffer = require("../util/ByteBuffer");
/**
* UnknownDictionary
diff --git a/src/dict/builder/CharacterDefinitionBuilder.js b/src/dict/builder/CharacterDefinitionBuilder.js
index 771e3c73c31c8f8f8da333bd40bf4c6f86727790..ec8446ef6cf38988a466016a8c2ebdec20b275a3 100644
--- a/src/dict/builder/CharacterDefinitionBuilder.js
+++ b/src/dict/builder/CharacterDefinitionBuilder.js
@@ -17,12 +17,12 @@
"use strict";
-var CharacterDefinition = require("../CharacterDefinition");
-var InvokeDefinitionMap = require("../InvokeDefinitionMap");
+const CharacterDefinition = require("../CharacterDefinition");
+const InvokeDefinitionMap = require("../InvokeDefinitionMap");
-var CATEGORY_DEF_PATTERN = /^(\w+)\s+(\d)\s+(\d)\s+(\d)/;
-var CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
-var RANGE_CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})\.\.(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
+const CATEGORY_DEF_PATTERN = /^(\w+)\s+(\d)\s+(\d)\s+(\d)/;
+const CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
+const RANGE_CATEGORY_MAPPING_PATTERN = /^(0x[0-9A-F]{4})\.\.(0x[0-9A-F]{4})(?:\s+([^#\s]+))(?:\s+([^#\s]+))*/;
/**
* CharacterDefinitionBuilder
@@ -36,25 +36,25 @@ function CharacterDefinitionBuilder() {
}
CharacterDefinitionBuilder.prototype.putLine = function (line) {
- var parsed_category_def = CATEGORY_DEF_PATTERN.exec(line);
- if (parsed_category_def != null) {
- var class_id = this.character_category_definition.length;
- var char_class = CharacterDefinition.parseCharCategory(class_id, parsed_category_def);
- if (char_class == null) {
+ const parsed_category_def = CATEGORY_DEF_PATTERN.exec(line);
+ if (parsed_category_def != null) {
+ const class_id = this.character_category_definition.length;
+ const char_class = CharacterDefinition.parseCharCategory(class_id, parsed_category_def);
+ if (char_class == null) {
return;
}
this.character_category_definition.push(char_class);
return;
}
- var parsed_category_mapping = CATEGORY_MAPPING_PATTERN.exec(line);
- if (parsed_category_mapping != null) {
- var mapping = CharacterDefinition.parseCategoryMapping(parsed_category_mapping);
- this.category_mapping.push(mapping);
+ const parsed_category_mapping = CATEGORY_MAPPING_PATTERN.exec(line);
+ if (parsed_category_mapping != null) {
+ const mapping = CharacterDefinition.parseCategoryMapping(parsed_category_mapping);
+ this.category_mapping.push(mapping);
}
- var parsed_range_category_mapping = RANGE_CATEGORY_MAPPING_PATTERN.exec(line);
- if (parsed_range_category_mapping != null) {
- var range_mapping = CharacterDefinition.parseRangeCategoryMapping(parsed_range_category_mapping);
- this.category_mapping.push(range_mapping);
+ const parsed_range_category_mapping = RANGE_CATEGORY_MAPPING_PATTERN.exec(line);
+ if (parsed_range_category_mapping != null) {
+ const range_mapping = CharacterDefinition.parseRangeCategoryMapping(parsed_range_category_mapping);
+ this.category_mapping.push(range_mapping);
}
};
diff --git a/src/dict/builder/ConnectionCostsBuilder.js b/src/dict/builder/ConnectionCostsBuilder.js
index c09997a0c15b377c9d6e02e98afa9e7d04605edd..e499ac5a275c97e9cfdbd7badd8c05a83559a26f 100644
--- a/src/dict/builder/ConnectionCostsBuilder.js
+++ b/src/dict/builder/ConnectionCostsBuilder.js
@@ -17,7 +17,7 @@
"use strict";
-var ConnectionCosts = require("../ConnectionCosts");
+const ConnectionCosts = require("../ConnectionCosts");
/**
* Builder class for constructing ConnectionCosts object
@@ -30,11 +30,11 @@ function ConnectionCostsBuilder() {
ConnectionCostsBuilder.prototype.putLine = function (line) {
if (this.lines === 0) {
- var dimensions = line.split(" ");
- var forward_dimension = dimensions[0];
- var backward_dimension = dimensions[1];
+ const dimensions = line.split(" ");
+ const forward_dimension = dimensions[0];
+ const backward_dimension = dimensions[1];
- if (forward_dimension < 0 || backward_dimension < 0) {
+ if (forward_dimension < 0 || backward_dimension < 0) {
throw "Parse error of matrix.def";
}
@@ -43,17 +43,17 @@ ConnectionCostsBuilder.prototype.putLine = function (line) {
return this;
}
- var costs = line.split(" ");
+ const costs = line.split(" ");
- if (costs.length !== 3) {
+ if (costs.length !== 3) {
return this;
}
- var forward_id = parseInt(costs[0]);
- var backward_id = parseInt(costs[1]);
- var cost = parseInt(costs[2]);
+ const forward_id = parseInt(costs[0]);
+ const backward_id = parseInt(costs[1]);
+ const cost = parseInt(costs[2]);
- if (forward_id < 0 || backward_id < 0 || !isFinite(forward_id) || !isFinite(backward_id) ||
+ if (forward_id < 0 || backward_id < 0 || !isFinite(forward_id) || !isFinite(backward_id) ||
this.connection_cost.forward_dimension <= forward_id || this.connection_cost.backward_dimension <= backward_id) {
throw "Parse error of matrix.def";
}
diff --git a/src/dict/builder/DictionaryBuilder.js b/src/dict/builder/DictionaryBuilder.js
index 216c769f721c4d0513937f8b289ca4368ba6c526..b1f08243da88e90417db2b7f3263024488183b4a 100644
--- a/src/dict/builder/DictionaryBuilder.js
+++ b/src/dict/builder/DictionaryBuilder.js
@@ -17,12 +17,12 @@
"use strict";
-var doublearray = require("doublearray");
-var DynamicDictionaries = require("../DynamicDictionaries");
-var TokenInfoDictionary = require("../TokenInfoDictionary");
-var ConnectionCostsBuilder = require("./ConnectionCostsBuilder");
-var CharacterDefinitionBuilder = require("./CharacterDefinitionBuilder");
-var UnknownDictionary = require("../UnknownDictionary");
+const doublearray = require("doublearray");
+const DynamicDictionaries = require("../DynamicDictionaries");
+const TokenInfoDictionary = require("../TokenInfoDictionary");
+const ConnectionCostsBuilder = require("./ConnectionCostsBuilder");
+const CharacterDefinitionBuilder = require("./CharacterDefinitionBuilder");
+const UnknownDictionary = require("../UnknownDictionary");
/**
* Build dictionaries (token info, connection costs)
@@ -46,8 +46,8 @@ function DictionaryBuilder() {
}
DictionaryBuilder.prototype.addTokenInfoDictionary = function (line) {
- var new_entry = line.split(",");
- this.tid_entries.push(new_entry);
+ const new_entry = line.split(",");
+ this.tid_entries.push(new_entry);
return this;
};
@@ -75,10 +75,10 @@ DictionaryBuilder.prototype.putUnkDefLine = function (line) {
};
DictionaryBuilder.prototype.build = function () {
- var dictionaries = this.buildTokenInfoDictionary();
- var unknown_dictionary = this.buildUnknownDictionary();
+ const dictionaries = this.buildTokenInfoDictionary();
+ const unknown_dictionary = this.buildUnknownDictionary();
- return new DynamicDictionaries(dictionaries.trie, dictionaries.token_info_dictionary, this.cc_builder.build(), unknown_dictionary);
+ return new DynamicDictionaries(dictionaries.trie, dictionaries.token_info_dictionary, this.cc_builder.build(), unknown_dictionary);
};
/**
@@ -88,18 +88,18 @@ DictionaryBuilder.prototype.build = function () {
*/
DictionaryBuilder.prototype.buildTokenInfoDictionary = function () {
- var token_info_dictionary = new TokenInfoDictionary();
+ const token_info_dictionary = new TokenInfoDictionary();
- // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
- var dictionary_entries = token_info_dictionary.buildDictionary(this.tid_entries);
+ // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
+ const dictionary_entries = token_info_dictionary.buildDictionary(this.tid_entries);
- var trie = this.buildDoubleArray();
+ const trie = this.buildDoubleArray();
- for (var token_info_id in dictionary_entries) {
- var surface_form = dictionary_entries[token_info_id];
- var trie_id = trie.lookup(surface_form);
+ for (let token_info_id in dictionary_entries) {
+ const surface_form = dictionary_entries[token_info_id];
+ const trie_id = trie.lookup(surface_form);
- // Assertion
+ // Assertion
// if (trie_id < 0) {
// console.log("Not Found:" + surface_form);
// }
@@ -115,20 +115,20 @@ DictionaryBuilder.prototype.buildTokenInfoDictionary = function () {
DictionaryBuilder.prototype.buildUnknownDictionary = function () {
- var unk_dictionary = new UnknownDictionary();
+ const unk_dictionary = new UnknownDictionary();
- // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
- var dictionary_entries = unk_dictionary.buildDictionary(this.unk_entries);
+ // using as hashmap, string -> string (word_id -> surface_form) to build dictionary
+ const dictionary_entries = unk_dictionary.buildDictionary(this.unk_entries);
- var char_def = this.cd_builder.build(); // Create CharacterDefinition
+ const char_def = this.cd_builder.build(); // Create CharacterDefinition
unk_dictionary.characterDefinition(char_def);
- for (var token_info_id in dictionary_entries) {
- var class_name = dictionary_entries[token_info_id];
- var class_id = char_def.invoke_definition_map.lookup(class_name);
+ for (let token_info_id in dictionary_entries) {
+ const class_name = dictionary_entries[token_info_id];
+ const class_id = char_def.invoke_definition_map.lookup(class_name);
- // Assertion
+ // Assertion
// if (trie_id < 0) {
// console.log("Not Found:" + surface_form);
// }
@@ -145,14 +145,14 @@ DictionaryBuilder.prototype.buildUnknownDictionary = function () {
* @returns {DoubleArray} Double-Array trie
*/
DictionaryBuilder.prototype.buildDoubleArray = function () {
- var trie_id = 0;
- var words = this.tid_entries.map(function (entry) {
- var surface_form = entry[0];
- return { k: surface_form, v: trie_id++ };
- });
-
- var builder = doublearray.builder(1024 * 1024);
- return builder.build(words);
+ let trie_id = 0;
+ const words = this.tid_entries.map(function (entry) {
+ const surface_form = entry[0];
+ return {k: surface_form, v: trie_id++};
+ });
+
+ const builder = doublearray.builder(1024 * 1024);
+ return builder.build(words);
};
module.exports = DictionaryBuilder;
diff --git a/src/kuromoji.js b/src/kuromoji.js
index 4820d23e6a257e736cfa958b345cab5c03e3b4e5..c2d548dae6e45a21973d12eea893469d6aff742e 100644
--- a/src/kuromoji.js
+++ b/src/kuromoji.js
@@ -17,17 +17,17 @@
"use strict";
-var TokenizerBuilder = require("./TokenizerBuilder");
-var DictionaryBuilder = require("./dict/builder/DictionaryBuilder");
+const TokenizerBuilder = require("./TokenizerBuilder");
+const DictionaryBuilder = require("./dict/builder/DictionaryBuilder");
// Public methods
-var kuromoji = {
- builder: function (option) {
- return new TokenizerBuilder(option);
- },
- dictionaryBuilder: function () {
- return new DictionaryBuilder();
- }
+const kuromoji = {
+ builder: function (option) {
+ return new TokenizerBuilder(option);
+ },
+ dictionaryBuilder: function () {
+ return new DictionaryBuilder();
+ }
};
module.exports = kuromoji;
diff --git a/src/loader/BrowserDictionaryLoader.js b/src/loader/BrowserDictionaryLoader.js
index 04bfdcd1c16b66960b3377152afb357e268ea872..d0473f53250179e9dabfc81e8fb2b206a7823327 100644
--- a/src/loader/BrowserDictionaryLoader.js
+++ b/src/loader/BrowserDictionaryLoader.js
@@ -17,8 +17,8 @@
"use strict";
-var zlib = require("zlibjs/bin/gunzip.min.js");
-var DictionaryLoader = require("./DictionaryLoader");
+const fflate = require("fflate");
+const DictionaryLoader = require("./DictionaryLoader");
/**
* BrowserDictionaryLoader inherits DictionaryLoader, using jQuery XHR for download
@@ -37,24 +37,17 @@ BrowserDictionaryLoader.prototype = Object.create(DictionaryLoader.prototype);
* @param {BrowserDictionaryLoader~onLoad} callback Callback function
*/
BrowserDictionaryLoader.prototype.loadArrayBuffer = function (url, callback) {
- var xhr = new XMLHttpRequest();
- xhr.open("GET", url, true);
- xhr.responseType = "arraybuffer";
- xhr.onload = function () {
- if (this.status > 0 && this.status !== 200) {
- callback(xhr.statusText, null);
- return;
- }
- var arraybuffer = this.response;
-
- var gz = new zlib.Zlib.Gunzip(new Uint8Array(arraybuffer));
- var typed_array = gz.decompress();
- callback(null, typed_array.buffer);
- };
- xhr.onerror = function (err) {
- callback(err, null);
- };
- xhr.send();
+ fetch(url).then(function (response) {
+ if (!response.ok) {
+ callback(response.statusText, null);
+ }
+ response.arrayBuffer().then(function (arraybuffer) {
+ const gz = fflate.gunzipSync(new Uint8Array(arraybuffer));
+ callback(null, gz.buffer);
+ });
+ }).catch(function (exception) {
+ callback(exception, null);
+ });
};
/**
diff --git a/src/loader/DictionaryLoader.js b/src/loader/DictionaryLoader.js
index 5f88c0b7f9a786dd8c072a7b84ae86a6f31412cb..402e9271c677f549e9521b959f34e1043faaf7a6 100644
--- a/src/loader/DictionaryLoader.js
+++ b/src/loader/DictionaryLoader.js
@@ -17,9 +17,8 @@
"use strict";
-var path = require("path");
-var async = require("async");
-var DynamicDictionaries = require("../dict/DynamicDictionaries");
+const async = require("async");
+const DynamicDictionaries = require("../dict/DynamicDictionaries");
/**
* DictionaryLoader base constructor
@@ -40,15 +39,15 @@ DictionaryLoader.prototype.loadArrayBuffer = function (file, callback) {
* @param {DictionaryLoader~onLoad} load_callback Callback function called after loaded
*/
DictionaryLoader.prototype.load = function (load_callback) {
- var dic = this.dic;
- var dic_path = this.dic_path;
- var loadArrayBuffer = this.loadArrayBuffer;
+ const dic = this.dic;
+ const dic_path = this.dic_path;
+ const loadArrayBuffer = this.loadArrayBuffer;
- async.parallel([
+ async.parallel([
// Trie
function (callback) {
async.map([ "base.dat.gz", "check.dat.gz" ], function (filename, _callback) {
- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
+ loadArrayBuffer(dic_path + filename, function (err, buffer) {
if(err) {
return _callback(err);
}
@@ -58,17 +57,17 @@ DictionaryLoader.prototype.load = function (load_callback) {
if(err) {
return callback(err);
}
- var base_buffer = new Int32Array(buffers[0]);
- var check_buffer = new Int32Array(buffers[1]);
+ const base_buffer = new Int32Array(buffers[0]);
+ const check_buffer = new Int32Array(buffers[1]);
- dic.loadTrie(base_buffer, check_buffer);
+ dic.loadTrie(base_buffer, check_buffer);
callback(null);
});
},
// Token info dictionaries
function (callback) {
async.map([ "tid.dat.gz", "tid_pos.dat.gz", "tid_map.dat.gz" ], function (filename, _callback) {
- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
+ loadArrayBuffer(dic_path + filename, function (err, buffer) {
if(err) {
return _callback(err);
}
@@ -78,29 +77,29 @@ DictionaryLoader.prototype.load = function (load_callback) {
if(err) {
return callback(err);
}
- var token_info_buffer = new Uint8Array(buffers[0]);
- var pos_buffer = new Uint8Array(buffers[1]);
- var target_map_buffer = new Uint8Array(buffers[2]);
+ const token_info_buffer = new Uint8Array(buffers[0]);
+ const pos_buffer = new Uint8Array(buffers[1]);
+ const target_map_buffer = new Uint8Array(buffers[2]);
- dic.loadTokenInfoDictionaries(token_info_buffer, pos_buffer, target_map_buffer);
+ dic.loadTokenInfoDictionaries(token_info_buffer, pos_buffer, target_map_buffer);
callback(null);
});
},
// Connection cost matrix
function (callback) {
- loadArrayBuffer(path.join(dic_path, "cc.dat.gz"), function (err, buffer) {
+ loadArrayBuffer(dic_path + "cc.dat.gz", function (err, buffer) {
if(err) {
return callback(err);
}
- var cc_buffer = new Int16Array(buffer);
- dic.loadConnectionCosts(cc_buffer);
+ const cc_buffer = new Int16Array(buffer);
+ dic.loadConnectionCosts(cc_buffer);
callback(null);
});
},
// Unknown dictionaries
function (callback) {
async.map([ "unk.dat.gz", "unk_pos.dat.gz", "unk_map.dat.gz", "unk_char.dat.gz", "unk_compat.dat.gz", "unk_invoke.dat.gz" ], function (filename, _callback) {
- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
+ loadArrayBuffer(dic_path + filename, function (err, buffer) {
if(err) {
return _callback(err);
}
@@ -110,14 +109,14 @@ DictionaryLoader.prototype.load = function (load_callback) {
if(err) {
return callback(err);
}
- var unk_buffer = new Uint8Array(buffers[0]);
- var unk_pos_buffer = new Uint8Array(buffers[1]);
- var unk_map_buffer = new Uint8Array(buffers[2]);
- var cat_map_buffer = new Uint8Array(buffers[3]);
- var compat_cat_map_buffer = new Uint32Array(buffers[4]);
- var invoke_def_buffer = new Uint8Array(buffers[5]);
+ const unk_buffer = new Uint8Array(buffers[0]);
+ const unk_pos_buffer = new Uint8Array(buffers[1]);
+ const unk_map_buffer = new Uint8Array(buffers[2]);
+ const cat_map_buffer = new Uint8Array(buffers[3]);
+ const compat_cat_map_buffer = new Uint32Array(buffers[4]);
+ const invoke_def_buffer = new Uint8Array(buffers[5]);
- dic.loadUnknownDictionaries(unk_buffer, unk_pos_buffer, unk_map_buffer, cat_map_buffer, compat_cat_map_buffer, invoke_def_buffer);
+ dic.loadUnknownDictionaries(unk_buffer, unk_pos_buffer, unk_map_buffer, cat_map_buffer, compat_cat_map_buffer, invoke_def_buffer);
// dic.loadUnknownDictionaries(char_buffer, unk_buffer);
callback(null);
});
diff --git a/src/loader/NodeDictionaryLoader.js b/src/loader/NodeDictionaryLoader.js
deleted file mode 100644
index 26eb79249121efe39bd5ae77c17e1caa197fb4ce..0000000000000000000000000000000000000000
diff --git a/src/util/ByteBuffer.js b/src/util/ByteBuffer.js
index 1efa453a8cce6fe697653be424782405468dbbf2..8148d612cfec33aa1ef02eb0963582805d69ac6c 100644
--- a/src/util/ByteBuffer.js
+++ b/src/util/ByteBuffer.js
@@ -23,63 +23,64 @@
* @param {String} str UTF-16 string to convert
* @return {Uint8Array} Byte sequence encoded by UTF-8
*/
-var stringToUtf8Bytes = function (str) {
-
- // Max size of 1 character is 4 bytes
- var bytes = new Uint8Array(str.length * 4);
-
- var i = 0, j = 0;
-
- while (i < str.length) {
- var unicode_code;
-
- var utf16_code = str.charCodeAt(i++);
- if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
- // surrogate pair
- var upper = utf16_code; // high surrogate
- var lower = str.charCodeAt(i++); // low surrogate
-
- if (lower >= 0xDC00 && lower <= 0xDFFF) {
- unicode_code =
- (upper - 0xD800) * (1 << 10) + (1 << 16) +
- (lower - 0xDC00);
- } else {
- // malformed surrogate pair
- return null;
- }
- } else {
- // not surrogate code
- unicode_code = utf16_code;
- }
+const stringToUtf8Bytes = function (str) {
+
+ // Max size of 1 character is 4 bytes
+ const bytes = new Uint8Array(str.length * 4);
+
+ let i = 0;
+ let j = 0;
+
+ while (i < str.length) {
+ let unicode_code;
+
+ const utf16_code = str.charCodeAt(i++);
+ if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
+ // surrogate pair
+ const upper = utf16_code; // high surrogate
+ const lower = str.charCodeAt(i++); // low surrogate
+
+ if (lower >= 0xDC00 && lower <= 0xDFFF) {
+ unicode_code =
+ (upper - 0xD800) * (1 << 10) + (1 << 16) +
+ (lower - 0xDC00);
+ } else {
+ // malformed surrogate pair
+ return null;
+ }
+ } else {
+ // not surrogate code
+ unicode_code = utf16_code;
+ }
- if (unicode_code < 0x80) {
- // 1-byte
- bytes[j++] = unicode_code;
+ if (unicode_code < 0x80) {
+ // 1-byte
+ bytes[j++] = unicode_code;
- } else if (unicode_code < (1 << 11)) {
- // 2-byte
- bytes[j++] = (unicode_code >>> 6) | 0xC0;
- bytes[j++] = (unicode_code & 0x3F) | 0x80;
+ } else if (unicode_code < (1 << 11)) {
+ // 2-byte
+ bytes[j++] = (unicode_code >>> 6) | 0xC0;
+ bytes[j++] = (unicode_code & 0x3F) | 0x80;
- } else if (unicode_code < (1 << 16)) {
- // 3-byte
- bytes[j++] = (unicode_code >>> 12) | 0xE0;
- bytes[j++] = ((unicode_code >> 6) & 0x3f) | 0x80;
- bytes[j++] = (unicode_code & 0x3F) | 0x80;
+ } else if (unicode_code < (1 << 16)) {
+ // 3-byte
+ bytes[j++] = (unicode_code >>> 12) | 0xE0;
+ bytes[j++] = ((unicode_code >> 6) & 0x3f) | 0x80;
+ bytes[j++] = (unicode_code & 0x3F) | 0x80;
- } else if (unicode_code < (1 << 21)) {
- // 4-byte
- bytes[j++] = (unicode_code >>> 18) | 0xF0;
- bytes[j++] = ((unicode_code >> 12) & 0x3F) | 0x80;
- bytes[j++] = ((unicode_code >> 6) & 0x3F) | 0x80;
- bytes[j++] = (unicode_code & 0x3F) | 0x80;
+ } else if (unicode_code < (1 << 21)) {
+ // 4-byte
+ bytes[j++] = (unicode_code >>> 18) | 0xF0;
+ bytes[j++] = ((unicode_code >> 12) & 0x3F) | 0x80;
+ bytes[j++] = ((unicode_code >> 6) & 0x3F) | 0x80;
+ bytes[j++] = (unicode_code & 0x3F) | 0x80;
- } else {
- // malformed UCS4 code
- }
+ } else {
+ // malformed UCS4 code
}
+ }
- return bytes.subarray(0, j);
+ return bytes.subarray(0, j);
};
/**
@@ -88,48 +89,48 @@ var stringToUtf8Bytes = function (str) {
* @param {Array} bytes UTF-8 byte sequence to convert
* @return {String} String encoded by UTF-16
*/
-var utf8BytesToString = function (bytes) {
-
- var str = "";
- var code, b1, b2, b3, b4, upper, lower;
- var i = 0;
-
- while (i < bytes.length) {
-
- b1 = bytes[i++];
-
- if (b1 < 0x80) {
- // 1 byte
- code = b1;
- } else if ((b1 >> 5) === 0x06) {
- // 2 bytes
- b2 = bytes[i++];
- code = ((b1 & 0x1f) << 6) | (b2 & 0x3f);
- } else if ((b1 >> 4) === 0x0e) {
- // 3 bytes
- b2 = bytes[i++];
- b3 = bytes[i++];
- code = ((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
- } else {
- // 4 bytes
- b2 = bytes[i++];
- b3 = bytes[i++];
- b4 = bytes[i++];
- code = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
- }
+const utf8BytesToString = function (bytes) {
+
+ let str = "";
+ let code, b1, b2, b3, b4, upper, lower;
+ let i = 0;
+
+ while (i < bytes.length) {
+
+ b1 = bytes[i++];
+
+ if (b1 < 0x80) {
+ // 1 byte
+ code = b1;
+ } else if ((b1 >> 5) === 0x06) {
+ // 2 bytes
+ b2 = bytes[i++];
+ code = ((b1 & 0x1f) << 6) | (b2 & 0x3f);
+ } else if ((b1 >> 4) === 0x0e) {
+ // 3 bytes
+ b2 = bytes[i++];
+ b3 = bytes[i++];
+ code = ((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
+ } else {
+ // 4 bytes
+ b2 = bytes[i++];
+ b3 = bytes[i++];
+ b4 = bytes[i++];
+ code = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
+ }
- if (code < 0x10000) {
- str += String.fromCharCode(code);
- } else {
- // surrogate pair
- code -= 0x10000;
- upper = (0xD800 | (code >> 10));
- lower = (0xDC00 | (code & 0x3FF));
- str += String.fromCharCode(upper, lower);
- }
+ if (code < 0x10000) {
+ str += String.fromCharCode(code);
+ } else {
+ // surrogate pair
+ code -= 0x10000;
+ upper = (0xD800 | (code >> 10));
+ lower = (0xDC00 | (code & 0x3FF));
+ str += String.fromCharCode(upper, lower);
}
+ }
- return str;
+ return str;
};
/**
@@ -138,8 +139,8 @@ var utf8BytesToString = function (bytes) {
* @constructor
*/
function ByteBuffer(arg) {
- var initial_size;
- if (arg == null) {
+ let initial_size;
+ if (arg == null) {
initial_size = 1024 * 1024;
} else if (typeof arg === "number") {
initial_size = arg;
@@ -161,8 +162,8 @@ ByteBuffer.prototype.size = function () {
};
ByteBuffer.prototype.reallocate = function () {
- var new_array = new Uint8Array(this.buffer.length * 2);
- new_array.set(this.buffer);
+ const new_array = new Uint8Array(this.buffer.length * 2);
+ new_array.set(this.buffer);
this.buffer = new_array;
};
@@ -194,9 +195,9 @@ ByteBuffer.prototype.putShort = function (num) {
if (0xFFFF < num) {
throw num + " is over short value";
}
- var lower = (0x00FF & num);
- var upper = (0xFF00 & num) >> 8;
- this.put(lower);
+ const lower = (0x00FF & num);
+ const upper = (0xFF00 & num) >> 8;
+ this.put(lower);
this.put(upper);
};
@@ -209,10 +210,10 @@ ByteBuffer.prototype.getShort = function (index) {
if (this.buffer.length < index + 2) {
return 0;
}
- var lower = this.buffer[index];
- var upper = this.buffer[index + 1];
- var value = (upper << 8) + lower;
- if (value & 0x8000) {
+ const lower = this.buffer[index];
+ const upper = this.buffer[index + 1];
+ let value = (upper << 8) + lower;
+ if (value & 0x8000) {
value = -((value - 1) ^ 0xFFFF);
}
return value;
@@ -223,11 +224,11 @@ ByteBuffer.prototype.putInt = function (num) {
if (0xFFFFFFFF < num) {
throw num + " is over integer value";
}
- var b0 = (0x000000FF & num);
- var b1 = (0x0000FF00 & num) >> 8;
- var b2 = (0x00FF0000 & num) >> 16;
- var b3 = (0xFF000000 & num) >> 24;
- this.put(b0);
+ const b0 = (0x000000FF & num);
+ const b1 = (0x0000FF00 & num) >> 8;
+ const b2 = (0x00FF0000 & num) >> 16;
+ const b3 = (0xFF000000 & num) >> 24;
+ this.put(b0);
this.put(b1);
this.put(b2);
this.put(b3);
@@ -242,23 +243,23 @@ ByteBuffer.prototype.getInt = function (index) {
if (this.buffer.length < index + 4) {
return 0;
}
- var b0 = this.buffer[index];
- var b1 = this.buffer[index + 1];
- var b2 = this.buffer[index + 2];
- var b3 = this.buffer[index + 3];
+ const b0 = this.buffer[index];
+ const b1 = this.buffer[index + 1];
+ const b2 = this.buffer[index + 2];
+ const b3 = this.buffer[index + 3];
- return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
+ return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
};
ByteBuffer.prototype.readInt = function () {
- var pos = this.position;
- this.position += 4;
+ const pos = this.position;
+ this.position += 4;
return this.getInt(pos);
};
ByteBuffer.prototype.putString = function (str) {
- var bytes = stringToUtf8Bytes(str);
- for (var i = 0; i < bytes.length; i++) {
+ const bytes = stringToUtf8Bytes(str);
+ for (let i = 0; i < bytes.length; i++) {
this.put(bytes[i]);
}
// put null character as terminal character
@@ -266,9 +267,9 @@ ByteBuffer.prototype.putString = function (str) {
};
ByteBuffer.prototype.getString = function (index) {
- var buf = [],
- ch;
- if (index == null) {
+ const buf = [];
+ let ch;
+ if (index == null) {
index = this.position;
}
while (true) {
diff --git a/src/util/IpadicFormatter.js b/src/util/IpadicFormatter.js
index 285e05c13b259dc75a473bf3190df90d3762fb56..5578e4a6a5a9d0a3272cc39a384e58be908feff3 100644
--- a/src/util/IpadicFormatter.js
+++ b/src/util/IpadicFormatter.js
@@ -25,8 +25,8 @@ function IpadicFormatter() {
}
IpadicFormatter.prototype.formatEntry = function (word_id, position, type, features) {
- var token = {};
- token.word_id = word_id;
+ const token = {};
+ token.word_id = word_id;
token.word_type = type;
token.word_position = position;
@@ -45,8 +45,8 @@ IpadicFormatter.prototype.formatEntry = function (word_id, position, type, featu
};
IpadicFormatter.prototype.formatUnknownEntry = function (word_id, position, type, features, surface_form) {
- var token = {};
- token.word_id = word_id;
+ const token = {};
+ token.word_id = word_id;
token.word_type = type;
token.word_position = position;
diff --git a/src/util/SurrogateAwareString.js b/src/util/SurrogateAwareString.js
index bfd88ceaa360567e582534805675ef816d8d69a3..0ef2ee2e1b8f8125e9f3c8858c722339b288e114 100644
--- a/src/util/SurrogateAwareString.js
+++ b/src/util/SurrogateAwareString.js
@@ -26,9 +26,9 @@ function SurrogateAwareString(str) {
this.str = str;
this.index_mapping = [];
- for (var pos = 0; pos < str.length; pos++) {
- var ch = str.charAt(pos);
- this.index_mapping.push(pos);
+ for (let pos = 0; pos < str.length; pos++) {
+ const ch = str.charAt(pos);
+ this.index_mapping.push(pos);
if (SurrogateAwareString.isSurrogatePair(ch)) {
pos++;
}
@@ -41,18 +41,18 @@ SurrogateAwareString.prototype.slice = function (index) {
if (this.index_mapping.length <= index) {
return "";
}
- var surrogate_aware_index = this.index_mapping[index];
- return this.str.slice(surrogate_aware_index);
+ const surrogate_aware_index = this.index_mapping[index];
+ return this.str.slice(surrogate_aware_index);
};
SurrogateAwareString.prototype.charAt = function (index) {
if (this.str.length <= index) {
return "";
}
- var surrogate_aware_start_index = this.index_mapping[index];
- var surrogate_aware_end_index = this.index_mapping[index + 1];
+ const surrogate_aware_start_index = this.index_mapping[index];
+ const surrogate_aware_end_index = this.index_mapping[index + 1];
- if (surrogate_aware_end_index == null) {
+ if (surrogate_aware_end_index == null) {
return this.str.slice(surrogate_aware_start_index);
}
return this.str.slice(surrogate_aware_start_index, surrogate_aware_end_index);
@@ -62,10 +62,10 @@ SurrogateAwareString.prototype.charCodeAt = function (index) {
if (this.index_mapping.length <= index) {
return NaN;
}
- var surrogate_aware_index = this.index_mapping[index];
- var upper = this.str.charCodeAt(surrogate_aware_index);
- var lower;
- if (upper >= 0xD800 && upper <= 0xDBFF && surrogate_aware_index < this.str.length) {
+ const surrogate_aware_index = this.index_mapping[index];
+ const upper = this.str.charCodeAt(surrogate_aware_index);
+ let lower;
+ if (upper >= 0xD800 && upper <= 0xDBFF && surrogate_aware_index < this.str.length) {
lower = this.str.charCodeAt(surrogate_aware_index + 1);
if (lower >= 0xDC00 && lower <= 0xDFFF) {
return (upper - 0xD800) * 0x400 + lower - 0xDC00 + 0x10000;
@@ -79,8 +79,8 @@ SurrogateAwareString.prototype.toString = function () {
};
SurrogateAwareString.isSurrogatePair = function (ch) {
- var utf16_code = ch.charCodeAt(0);
- if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
+ const utf16_code = ch.charCodeAt(0);
+ if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
// surrogate pair
return true;
} else {
diff --git a/src/viterbi/ViterbiBuilder.js b/src/viterbi/ViterbiBuilder.js
index b5b8a5f4f37beb40e4395af401dcd87b8c853123..bcb401cdeb2ceba7d952202736455d2ada876d67 100644
--- a/src/viterbi/ViterbiBuilder.js
+++ b/src/viterbi/ViterbiBuilder.js
@@ -17,9 +17,9 @@
"use strict";
-var ViterbiNode = require("./ViterbiNode");
-var ViterbiLattice = require("./ViterbiLattice");
-var SurrogateAwareString = require("../util/SurrogateAwareString");
+const ViterbiNode = require("./ViterbiNode");
+const ViterbiLattice = require("./ViterbiLattice");
+const SurrogateAwareString = require("../util/SurrogateAwareString");
/**
* ViterbiBuilder builds word lattice (ViterbiLattice)
@@ -38,22 +38,22 @@ function ViterbiBuilder(dic) {
* @returns {ViterbiLattice} Word lattice
*/
ViterbiBuilder.prototype.build = function (sentence_str) {
- var lattice = new ViterbiLattice();
- var sentence = new SurrogateAwareString(sentence_str);
+ const lattice = new ViterbiLattice();
+ const sentence = new SurrogateAwareString(sentence_str);
- var key, trie_id, left_id, right_id, word_cost;
- for (var pos = 0; pos < sentence.length; pos++) {
- var tail = sentence.slice(pos);
- var vocabulary = this.trie.commonPrefixSearch(tail);
- for (var n = 0; n < vocabulary.length; n++) { // Words in dictionary do not have surrogate pair (only UCS2 set)
+ let key, trie_id, left_id, right_id, word_cost;
+ for (let pos = 0; pos < sentence.length; pos++) {
+ const tail = sentence.slice(pos);
+ const vocabulary = this.trie.commonPrefixSearch(tail);
+ for (let n = 0; n < vocabulary.length; n++) { // Words in dictionary do not have surrogate pair (only UCS2 set)
trie_id = vocabulary[n].v;
key = vocabulary[n].k;
- var token_info_ids = this.token_info_dictionary.target_map[trie_id];
- for (var i = 0; i < token_info_ids.length; i++) {
- var token_info_id = parseInt(token_info_ids[i]);
+ const token_info_ids = this.token_info_dictionary.target_map[trie_id];
+ for (let i = 0; i < token_info_ids.length; i++) {
+ const token_info_id = parseInt(token_info_ids[i]);
- left_id = this.token_info_dictionary.dictionary.getShort(token_info_id);
+ left_id = this.token_info_dictionary.dictionary.getShort(token_info_id);
right_id = this.token_info_dictionary.dictionary.getShort(token_info_id + 2);
word_cost = this.token_info_dictionary.dictionary.getShort(token_info_id + 4);
@@ -63,28 +63,28 @@ ViterbiBuilder.prototype.build = function (sentence_str) {
}
// Unknown word processing
- var surrogate_aware_tail = new SurrogateAwareString(tail);
- var head_char = new SurrogateAwareString(surrogate_aware_tail.charAt(0));
- var head_char_class = this.unknown_dictionary.lookup(head_char.toString());
- if (vocabulary == null || vocabulary.length === 0 || head_char_class.is_always_invoke === 1) {
+ const surrogate_aware_tail = new SurrogateAwareString(tail);
+ const head_char = new SurrogateAwareString(surrogate_aware_tail.charAt(0));
+ const head_char_class = this.unknown_dictionary.lookup(head_char.toString());
+ if (vocabulary == null || vocabulary.length === 0 || head_char_class.is_always_invoke === 1) {
// Process unknown word
key = head_char;
if (head_char_class.is_grouping === 1 && 1 < surrogate_aware_tail.length) {
- for (var k = 1; k < surrogate_aware_tail.length; k++) {
- var next_char = surrogate_aware_tail.charAt(k);
- var next_char_class = this.unknown_dictionary.lookup(next_char);
- if (head_char_class.class_name !== next_char_class.class_name) {
+ for (let k = 1; k < surrogate_aware_tail.length; k++) {
+ const next_char = surrogate_aware_tail.charAt(k);
+ const next_char_class = this.unknown_dictionary.lookup(next_char);
+ if (head_char_class.class_name !== next_char_class.class_name) {
break;
}
key += next_char;
}
}
- var unk_ids = this.unknown_dictionary.target_map[head_char_class.class_id];
- for (var j = 0; j < unk_ids.length; j++) {
- var unk_id = parseInt(unk_ids[j]);
+ const unk_ids = this.unknown_dictionary.target_map[head_char_class.class_id];
+ for (let j = 0; j < unk_ids.length; j++) {
+ const unk_id = parseInt(unk_ids[j]);
- left_id = this.unknown_dictionary.dictionary.getShort(unk_id);
+ left_id = this.unknown_dictionary.dictionary.getShort(unk_id);
right_id = this.unknown_dictionary.dictionary.getShort(unk_id + 2);
word_cost = this.unknown_dictionary.dictionary.getShort(unk_id + 4);
diff --git a/src/viterbi/ViterbiLattice.js b/src/viterbi/ViterbiLattice.js
index 0ab121d22db35150ef92208fd205049702f7df51..4b5c314fae31704128cfc919983243aeda06ec12 100644
--- a/src/viterbi/ViterbiLattice.js
+++ b/src/viterbi/ViterbiLattice.js
@@ -17,7 +17,7 @@
"use strict";
-var ViterbiNode = require("./ViterbiNode");
+const ViterbiNode = require("./ViterbiNode");
/**
* ViterbiLattice is a lattice in Viterbi algorithm
@@ -34,13 +34,13 @@ function ViterbiLattice() {
* @param {ViterbiNode} node
*/
ViterbiLattice.prototype.append = function (node) {
- var last_pos = node.start_pos + node.length - 1;
- if (this.eos_pos < last_pos) {
+ const last_pos = node.start_pos + node.length - 1;
+ if (this.eos_pos < last_pos) {
this.eos_pos = last_pos;
}
- var prev_nodes = this.nodes_end_at[last_pos];
- if (prev_nodes == null) {
+ let prev_nodes = this.nodes_end_at[last_pos];
+ if (prev_nodes == null) {
prev_nodes = [];
}
prev_nodes.push(node);
@@ -52,8 +52,8 @@ ViterbiLattice.prototype.append = function (node) {
* Set ends with EOS (End of Statement)
*/
ViterbiLattice.prototype.appendEos = function () {
- var last_index = this.nodes_end_at.length;
- this.eos_pos++;
+ const last_index = this.nodes_end_at.length;
+ this.eos_pos++;
this.nodes_end_at[last_index] = [ new ViterbiNode(-1, 0, this.eos_pos, 0, "EOS", 0, 0, "") ];
};
diff --git a/src/viterbi/ViterbiSearcher.js b/src/viterbi/ViterbiSearcher.js
index 832e55432b1b461afa61fe4ecf29d9f5d15419e9..454eb544215581c9b293bd11dd3a3791510ef1ed 100644
--- a/src/viterbi/ViterbiSearcher.js
+++ b/src/viterbi/ViterbiSearcher.js
@@ -37,27 +37,27 @@ ViterbiSearcher.prototype.search = function (lattice) {
};
ViterbiSearcher.prototype.forward = function (lattice) {
- var i, j, k;
- for (i = 1; i <= lattice.eos_pos; i++) {
- var nodes = lattice.nodes_end_at[i];
- if (nodes == null) {
+ let i, j, k;
+ for (i = 1; i <= lattice.eos_pos; i++) {
+ const nodes = lattice.nodes_end_at[i];
+ if (nodes == null) {
continue;
}
for (j = 0; j < nodes.length; j++) {
- var node = nodes[j];
- var cost = Number.MAX_VALUE;
- var shortest_prev_node;
+ const node = nodes[j];
+ let cost = Number.MAX_VALUE;
+ let shortest_prev_node;
- var prev_nodes = lattice.nodes_end_at[node.start_pos - 1];
- if (prev_nodes == null) {
+ const prev_nodes = lattice.nodes_end_at[node.start_pos - 1];
+ if (prev_nodes == null) {
// TODO process unknown words (repair word lattice)
continue;
}
for (k = 0; k < prev_nodes.length; k++) {
- var prev_node = prev_nodes[k];
+ const prev_node = prev_nodes[k];
- var edge_cost;
- if (node.left_id == null || prev_node.right_id == null) {
+ let edge_cost;
+ if (node.left_id == null || prev_node.right_id == null) {
// TODO assert
console.log("Left or right is null");
edge_cost = 0;
@@ -65,8 +65,8 @@ ViterbiSearcher.prototype.forward = function (lattice) {
edge_cost = this.connection_costs.get(prev_node.right_id, node.left_id);
}
- var _cost = prev_node.shortest_cost + edge_cost + node.cost;
- if (_cost < cost) {
+ const _cost = prev_node.shortest_cost + edge_cost + node.cost;
+ if (_cost < cost) {
shortest_prev_node = prev_node;
cost = _cost;
}
@@ -80,11 +80,11 @@ ViterbiSearcher.prototype.forward = function (lattice) {
};
ViterbiSearcher.prototype.backward = function (lattice) {
- var shortest_path = [];
- var eos = lattice.nodes_end_at[lattice.nodes_end_at.length - 1][0];
+ const shortest_path = [];
+ const eos = lattice.nodes_end_at[lattice.nodes_end_at.length - 1][0];
- var node_back = eos.prev;
- if (node_back == null) {
+ let node_back = eos.prev;
+ if (node_back == null) {
return [];
}
while (node_back.type !== "BOS") {
================================================
FILE: patches/mdui@2.1.4.patch
================================================
diff --git a/jsx.en.d.ts b/jsx.en.d.ts
index 514d455dcdb436aaf7b2ee88deaefe01943c8b4b..48dff045dead4315936afd931336198996c88217 100644
--- a/jsx.en.d.ts
+++ b/jsx.en.d.ts
@@ -1,12 +1,8 @@
-import React from 'react';
import { JQ } from '@mdui/jq';
-type HTMLElementProps = React.DetailedHTMLProps, HTMLElement>;
+type HTMLElementProps = import("solid-js").JSX.HTMLAttributes;
-declare global {
- namespace React {
- namespace JSX {
- interface IntrinsicElements {
+export interface IntrinsicElements {
/**
* Avatar Component
*
@@ -3296,7 +3292,4 @@ declare global {
*/
'order'?: number;
} & HTMLElementProps;
- }
- }
- }
}
diff --git a/package.json b/package.json
index 3fa3eeb471ce4c31d7ac1c9bcb2d6823947e91ca..c4b062020bf20b8db34ccfea500fa682a4af19a6 100644
--- a/package.json
+++ b/package.json
@@ -60,5 +60,8 @@
"tslib": "^2.8.1",
"@mdui/shared": "^1.0.8",
"@mdui/jq": "^3.0.3"
+ },
+ "peerDependencies": {
+ "solid-js": "^1.9.7"
}
}
================================================
FILE: patches/vudio@2.1.1.patch
================================================
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) {
================================================
FILE: renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"labels": ["dependencies"],
"postUpdateOptions": ["pnpmDedupe"]
}
================================================
FILE: src/config/defaults.ts
================================================
export interface WindowSizeConfig {
width: number;
height: number;
}
export interface WindowPositionConfig {
x: number;
y: number;
}
export interface DefaultConfig {
'window-size': WindowSizeConfig;
'window-maximized': boolean;
'window-position': WindowPositionConfig;
'url': string;
'options': {
language?: string;
tray: boolean;
appVisible: boolean;
autoUpdates: boolean;
alwaysOnTop: boolean;
hideMenu: boolean;
hideMenuWarned: boolean;
startAtLogin: boolean;
disableHardwareAcceleration: boolean;
removeUpgradeButton: boolean;
restartOnConfigChanges: boolean;
trayClickPlayPause: boolean;
autoResetAppCache: boolean;
resumeOnStart: boolean;
likeButtons: string;
swapLikeButtonsOrder: boolean;
proxy: string;
startingPage: string;
backgroundMaterial?: 'none' | 'mica' | 'acrylic' | 'tabbed';
overrideUserAgent: boolean;
usePodcastParticipantAsArtist: boolean;
themes: string[];
customWindowTitle?: string;
};
'plugins': Record;
}
export const defaultConfig: DefaultConfig = {
'window-size': {
width: 1100,
height: 550,
},
'window-maximized': false,
'window-position': {
x: -1,
y: -1,
},
'url': 'https://music.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com',
'options': {
tray: false,
appVisible: true,
autoUpdates: true,
alwaysOnTop: false,
hideMenu: false,
hideMenuWarned: false,
startAtLogin: false,
disableHardwareAcceleration: false,
removeUpgradeButton: false,
restartOnConfigChanges: false,
trayClickPlayPause: false,
autoResetAppCache: false,
resumeOnStart: true,
likeButtons: '',
swapLikeButtonsOrder: false,
proxy: '',
startingPage: '',
overrideUserAgent: false,
usePodcastParticipantAsArtist: false,
themes: [],
},
'plugins': {},
};
================================================
FILE: src/config/index.ts
================================================
import { deepmergeCustom } from 'deepmerge-ts';
import { store, type IStore } from './store';
import { restart } from '@/providers/app-controls';
import type { defaultConfig } from './defaults';
const deepmerge = deepmergeCustom({
mergeArrays: false,
});
export { defaultConfig } from './defaults';
export * as plugins from './plugins';
export const set = (key: string, value: unknown) => {
store.set(key, value);
};
export const setPartial = (
key: string,
value: object,
defaultValue?: object,
) => {
const newValue = deepmerge(defaultValue ?? {}, store.get(key) ?? {}, value);
store.set(key, newValue);
};
export const setMenuOption = (key: string, value: unknown) => {
set(key, value);
if (store.get('options.restartOnConfigChanges')) {
restart();
}
};
// MAGIC OF TYPESCRIPT
type Prev = [
never,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
...0[],
];
type Join = K extends string | number
? P extends string | number
? `${K}${'' extends P ? '' : '.'}${P}`
: never
: never;
type Paths = [D] extends [never]
? never
: T extends object
? {
[K in keyof T]-?: K extends string | number
? `${K}` | Join>
: never;
}[keyof T]
: '';
type SplitKey = K extends `${infer A}.${infer B}` ? [A, B] : [K, string];
type PathValue =
SplitKey extends [infer A extends keyof T, infer B extends string]
? PathValue
: T;
export const get = >(key: Key) =>
store.get(key) as PathValue;
export const edit = () => store.openInEditor();
export const watch = (cb: Parameters[0]) => {
store.onDidAnyChange(cb);
};
================================================
FILE: src/config/plugins.ts
================================================
import { deepmerge } from 'deepmerge-ts';
import { allPlugins } from 'virtual:plugins';
import { store } from './store';
import { restart } from '@/providers/app-controls';
import type { PluginConfig } from '@/types/plugins';
export function getPlugins() {
return store.get('plugins') as Record;
}
export async function isEnabled(plugin: string) {
const pluginConfig = deepmerge(
(await allPlugins())[plugin]?.config ?? { enabled: false },
(store.get('plugins') as Record)[plugin] ?? {},
);
return pluginConfig !== undefined && pluginConfig.enabled;
}
/**
* Set options for a plugin
* @param plugin Plugin name
* @param options Options to set
* @param exclude Options to exclude from the options object
*/
export function setOptions(
plugin: string,
options: T,
exclude: string[] = ['enabled'],
) {
const plugins = store.get('plugins') as Record;
// HACK: This is a workaround for preventing changed options from being overwritten
exclude.forEach((key) => {
if (Object.prototype.hasOwnProperty.call(options, key)) {
delete options[key as keyof T];
}
});
store.set('plugins', {
...plugins,
[plugin]: {
...plugins[plugin],
...options,
},
});
}
export function setMenuOptions(
plugin: string,
options: T,
exclude: string[] = ['enabled'],
) {
setOptions(plugin, options, exclude);
if (store.get('options.restartOnConfigChanges')) {
restart();
}
}
export function getOptions(plugin: string): T {
return (store.get('plugins') as Record)[plugin];
}
export function enable(plugin: string) {
setMenuOptions(plugin, { enabled: true }, []);
}
export function disable(plugin: string) {
setMenuOptions(plugin, { enabled: false }, []);
}
================================================
FILE: src/config/store.ts
================================================
import Store from 'electron-store';
import { defaultConfig as defaults } from './defaults';
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
import type { SyncedLyricsPluginConfig } from '@/plugins/synced-lyrics/types';
export type IStore = InstanceType<
typeof import('conf').default>
>;
const migrations = {
'>=3.10.0'(store: IStore) {
const lyricGeniusConfig = store.get('plugins.lyrics-genius') as
| {
enabled?: boolean;
romanizedLyrics?: boolean;
}
| undefined;
if (lyricGeniusConfig) {
const syncedLyricsConfig = store.get('plugins.synced-lyrics') as
| SyncedLyricsPluginConfig
| undefined;
if (
!syncedLyricsConfig ||
syncedLyricsConfig?.enabled !== lyricGeniusConfig?.enabled
) {
store.set('plugins.synced-lyrics', {
...syncedLyricsConfig,
enabled: lyricGeniusConfig.enabled,
});
}
store.delete('plugins.lyrics-genius');
}
},
'>=3.3.0'(store: IStore) {
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: IStore) {
const discordConfig = store.get('plugins.discord') as Record<
string,
unknown
>;
if (discordConfig) {
const oldActivityTimoutEnabled = store.get(
'plugins.discord.activityTimoutEnabled',
) as boolean | undefined;
const oldActivityTimoutTime = store.get(
'plugins.discord.activityTimoutTime',
) as number | undefined;
if (oldActivityTimoutEnabled !== undefined) {
discordConfig.activityTimeoutEnabled = oldActivityTimoutEnabled;
store.set('plugins.discord', discordConfig);
}
if (oldActivityTimoutTime !== undefined) {
discordConfig.activityTimeoutTime = oldActivityTimoutTime;
store.set('plugins.discord', discordConfig);
}
}
},
'>=2.1.3'(store: IStore) {
const listenAlong = store.get('plugins.discord.listenAlong');
if (listenAlong !== undefined) {
store.set(
'plugins.discord.playOn\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063',
listenAlong,
);
store.delete('plugins.discord.listenAlong');
}
},
'>=2.1.0'(store: IStore) {
const originalPreset = store.get('plugins.downloader.preset') as
| string
| undefined;
if (originalPreset) {
if (originalPreset !== 'opus') {
store.set('plugins.downloader.selectedPreset', 'Custom');
store.set('plugins.downloader.customPresetSetting', {
extension: 'mp3',
ffmpegArgs:
(store.get('plugins.downloader.ffmpegArgs') as string[]) ??
DefaultPresetList['mp3 (256kbps)'].ffmpegArgs,
} satisfies Preset);
} else {
store.set('plugins.downloader.selectedPreset', 'Source');
store.set('plugins.downloader.customPresetSetting', {
extension: null,
ffmpegArgs:
(store.get('plugins.downloader.ffmpegArgs') as string[]) ?? [],
} satisfies Preset);
}
store.delete('plugins.downloader.preset');
store.delete('plugins.downloader.ffmpegArgs');
}
},
'>=1.20.0'(store: IStore) {
store.delete('plugins.visualizer'); // default value is now in the plugin
if (store.get('plugins.notifications.toastStyle') === undefined) {
const pluginOptions = store.get('plugins.notifications') || {};
store.set('plugins.notifications', {
...pluginOptions,
});
}
if (store.get('options.ForceShowLikeButtons')) {
store.delete('options.ForceShowLikeButtons');
store.set('options.likeButtons', 'force');
}
},
'>=1.17.0'(store: IStore) {
store.delete('plugins.picture-in-picture'); // default value is now in the plugin
if (store.get('plugins.video-toggle.mode') === undefined) {
store.set('plugins.video-toggle.mode', 'custom');
}
},
'>=1.14.0'(store: IStore) {
if (
typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object'
) {
store.set('plugins.precise-volume.globalShortcuts', {});
}
if (store.get('plugins.hide-video-player.enabled')) {
store.delete('plugins.hide-video-player');
store.set('plugins.video-toggle.enabled', true);
}
},
'>=1.13.0'(store: IStore) {
if (store.get('plugins.discord.listenAlong') === undefined) {
store.set('plugins.discord.listenAlong', true);
}
},
'>=1.12.0'(store: IStore) {
const options = store.get('plugins.shortcuts') as
| Record<
string,
| {
action: string;
shortcut: unknown;
}[]
| Record
>
| undefined;
if (options) {
let updated = false;
for (const optionType of ['global', 'local']) {
if (
Object.hasOwn(options, optionType) &&
Array.isArray(options[optionType])
) {
const optionsArray = options[optionType] as {
action: string;
shortcut: unknown;
}[];
const updatedOptions: Record = {};
for (const optionObject of optionsArray) {
if (optionObject.action && optionObject.shortcut) {
updatedOptions[optionObject.action] = optionObject.shortcut;
}
}
options[optionType] = updatedOptions;
updated = true;
}
}
if (updated) {
store.set('plugins.shortcuts', options);
}
}
},
'>=1.11.0'(store: IStore) {
if (store.get('options.resumeOnStart') === undefined) {
store.set('options.resumeOnStart', true);
}
},
'>=1.7.0'(store: IStore) {
const enabledPlugins = store.get('plugins') as string[];
if (!Array.isArray(enabledPlugins)) {
console.warn('Plugins are not in array format, cannot migrate');
return;
}
// Include custom options
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const plugins: Record = {
adblocker: {
enabled: true,
cache: true,
additionalBlockLists: [],
},
downloader: {
enabled: false,
ffmpegArgs: [], // E.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
downloadFolder: undefined, // Custom download folder (absolute path)
},
};
for (const enabledPlugin of enabledPlugins) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
plugins[enabledPlugin] = {
...plugins[enabledPlugin],
enabled: true,
};
}
store.set('plugins', plugins);
},
};
export const store = new Store({
defaults: {
...defaults,
// README: 'plugin' uses deepmerge to populate the default values, so it is not necessary to include it here
},
clearInvalidConfig: false,
migrations,
}) as Store & IStore;
================================================
FILE: src/custom-electron-prompt.d.ts
================================================
declare module 'custom-electron-prompt' {
import { type BrowserWindow } from 'electron';
export type SelectOptions = Record;
export interface CounterOptions {
minimum?: number;
maximum?: number;
multiFire?: boolean;
}
export interface KeybindOptions {
value: string;
label: string;
default?: string;
}
export interface InputOptions {
label: string;
value: unknown;
inputAttrs?: Partial;
selectOptions?: SelectOptions;
}
interface BasePromptOptions {
type?: T;
width?: number;
height?: number;
resizable?: boolean;
title?: string;
label?: string;
buttonLabels?: {
ok?: string;
cancel?: string;
};
alwaysOnTop?: boolean;
value?: unknown;
icon?: string;
useHtmlLabel?: boolean;
customStylesheet?: string;
menuBarVisible?: boolean;
skipTaskbar?: boolean;
frame?: boolean;
customScript?: string;
enableRemoteModule?: boolean;
inputAttrs?: Partial;
}
export type InputPromptOptions = BasePromptOptions<'input'>;
export interface SelectPromptOptions extends BasePromptOptions<'select'> {
selectOptions: SelectOptions;
}
export interface CounterPromptOptions extends BasePromptOptions<'counter'> {
counterOptions: CounterOptions;
}
export interface MultiInputPromptOptions
extends BasePromptOptions<'multiInput'> {
multiInputOptions: InputOptions[];
}
export interface KeybindPromptOptions extends BasePromptOptions<'keybind'> {
keybindOptions: KeybindOptions[];
}
export type PromptOptions = T extends 'input'
? InputPromptOptions
: T extends 'select'
? SelectPromptOptions
: T extends 'counter'
? CounterPromptOptions
: T extends 'keybind'
? KeybindPromptOptions
: T extends 'multiInput'
? MultiInputPromptOptions
: never;
type PromptResult = T extends 'input'
? string
: T extends 'select'
? string
: T extends 'counter'
? number
: T extends 'keybind'
? {
value: string;
accelerator: string;
}[]
: T extends 'multiInput'
? string[]
: never;
const prompt: (
options?: PromptOptions & { type: T },
parent?: BrowserWindow,
) => Promise | null>;
export default prompt;
}
================================================
FILE: src/i18n/index.ts
================================================
import i18next, { init, t as i18t, changeLanguage } from 'i18next';
import { languageResources } from 'virtual:i18n';
export const APPLICATION_NAME =
'\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u0020\u004d\u0075\u0073\u0069\u0063';
export const loadI18n = async () =>
await init({
resources: await languageResources(),
lng: 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
});
export const setLanguage = async (language: string) =>
await changeLanguage(language);
export const t = i18t.bind(i18next);
================================================
FILE: src/i18n/resources/@types/index.ts
================================================
export interface LanguageResources {
[lang: string]: {
translation: Record & {
language?: {
'name': string;
'local-name': string;
'code': string;
};
};
};
}
================================================
FILE: src/i18n/resources/ar.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "فشل بدأ الاضافة {{pluginName}}::{{contextName}}",
"executed-at-ms": "تم بدأ الاضافة {{pluginName}}::{{contextName}} خلال {{ms}} جزء من الثانية",
"initialize-failed": "فشل تشغيل الاضافة \"{{pluginName}}\"",
"load-all": "جار تحميل جميع الاضافات",
"load-failed": "فشل في تحميل الاضافة \"{{pluginName}}\"",
"loaded": "تم تحميل الاضافة \"{{pluginName}}\"",
"unload-failed": "فشل ازالة الاضافة \"{{pluginName}}\"",
"unloaded": "تم ازالة الاضافة \"{{pluginName}}\""
}
}
},
"language": {
"code": "ar",
"local-name": "العربية",
"name": "Arabic"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "انتهى التحميل, تم فتح قائمة المطور"
},
"i18n": {
"loaded": "تم تحميل i18n"
},
"second-instance": {
"receive-command": "تم الحصول على أمر عن طريق: \"{{command}}\""
},
"theme": {
"css-file-not-found": "ملف \"{{cssFile}}\" غير متواجد، سيتم التجاهل"
},
"unresponsive": {
"details": "خطء عدم استجابة!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "ازالة ذاكرة التخزين المؤقت للتطبيق"
},
"window": {
"tried-to-render-offscreen": "تم محاولة فتح الصفحة خارج الشاشة, حجم الصفحة={{windowSize}}, حجم النافذة={{displaySize}}, المكان={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "تم اخفاء القائمة, استخدم 'Alt' لاظهار القائمة (أو '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": "نسخ الرابط الحالي",
"go-back": "عودة",
"go-forward": "تقدم",
"quit": "الخروج",
"restart": "اعادة تشغيل التطبيق"
}
},
"options": {
"label": "الاعدادات",
"submenu": {
"advanced-options": {
"label": "الاعدادات المتقدمة",
"submenu": {
"auto-reset-app-cache": "إعادة ضبط ذاكرة التخزين المؤقت للتطبيق عند بدء التشغيل",
"disable-hardware-acceleration": "اطفاء تسريع الأجهزة",
"edit-config-json": "تعديل ملف الاعدادات",
"override-user-agent": "تجاوز وكيل المستخدم",
"restart-on-config-changes": "اعادة التشغيل بعد تعديل الاعدادات",
"set-proxy": {
"label": "تعيين الوكيل",
"prompt": {
"label": "أدخل عنوان الوكيل: (اتركه فارغًا لإطفائه)",
"placeholder": "مثال: SOCKS5://127.0.0.1:9999",
"title": "ضع proxy"
}
},
"toggle-dev-tools": "تثبيت أدوات التطوير"
}
},
"always-on-top": "دائما في المقدمة",
"auto-update": "تحديث تلقائي",
"hide-menu": {
"dialog": {
"message": "سيتم إخفاء القائمة عند التشغيل التالي، استخدم [Alt] لإظهارها (أو اضغط [`] في حالة استخدام القائمة التي داخل التطبيق)",
"title": "إخفاء القائمة مفعل"
},
"label": "إخفاء القائمة"
},
"language": {
"dialog": {
"message": "سيتم تغيير اللغة بعد اعادة التشغيل",
"title": "تم تغير اللغة"
},
"label": "اللغة",
"submenu": {
"to-help-translate": "تريد المساعدة في الترجمة؟ اضغط هنا"
}
},
"resume-on-start": "استأنف الأغنية الأخيرة عند بدأ التشغيل",
"single-instance-lock": "قفل مثيل واحد",
"start-at-login": "ابدأ عند تسجيل الدخول",
"starting-page": {
"label": "صفحة البداية",
"unset": "عدم تعيين"
},
"tray": {
"label": "قائمة",
"submenu": {
"disabled": "غير مفعل",
"enabled-and-hide-app": "مفعل وإخفاء التطبيق",
"enabled-and-show-app": "مفعل وأظهر التطبيق",
"play-pause-on-click": "تشغيل/إيقاف عند النقر"
}
},
"visual-tweaks": {
"label": "تعديلات المظهر",
"submenu": {
"custom-window-title": {
"label": "عنوان نافذة مخصص",
"prompt": {
"label": "ادخل عنوان مخصص للنافذة: (اتركه فارغًا إلغاء التفعيل)",
"placeholder": "مثال: {{applicationName}}"
}
},
"like-buttons": {
"default": "الافتراضي",
"force-show": "اجبار الظهور",
"hide": "اخفاء",
"label": "أزرار الاعجاب"
},
"remove-upgrade-button": "ازالة زر التطوير",
"theme": {
"dialog": {
"button": {
"cancel": "إلغاء",
"remove": "ازالة"
},
"remove-theme": "هل أنت متأكد أنك تريد إزالة السمة المخصصة؟",
"remove-theme-message": "سيؤدي هذا إلى إزالة السمة المخصصة"
},
"label": "السمة",
"submenu": {
"import-css-file": "استيراد ملف CSS مخصص",
"no-theme": "بدون سمة"
}
}
}
}
}
},
"plugins": {
"enabled": "مفعل",
"label": "الاضافات",
"new": "جديد"
},
"view": {
"label": "اظهار",
"submenu": {
"force-reload": "اجبار اعادة التحميل",
"reload": "اعادة التحميل",
"reset-zoom": "الحجم الحقيقي",
"toggle-fullscreen": "ملء الشاشة",
"zoom-in": "تكبير",
"zoom-out": "تصغير"
}
}
},
"tray": {
"next": "التالي",
"play-pause": "تشغيل/إيقاف",
"previous": "السابق",
"quit": "خروج",
"restart": "إعادة تشغيل التطبيق",
"show": "عرض النافدة",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "إذا تم عرض إعلان, فإن الصوت سيتم كتمانه وسيتم وضع سرعة التشغيل الى 16x",
"name": "تسريع الإعلان"
},
"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": "الوضع المحيطي"
},
"amuse": {
"description": "تكامل دعم {{applicationName}} مع ويدجت Amuse لعرض الأغنية التي قيد التشغيل، من إنتاج 6K Labs",
"name": "تلسيه",
"response": {
"query": "خادم Amuse API قيد التشغيل. استخدم GET /query للحصول على معلومات الأغنية."
}
},
"api-server": {
"description": "يضيف API للتحكم في المشغل",
"dialog": {
"request": {
"buttons": {
"allow": "سماح",
"deny": "رفض"
},
"message": "السماح لـ {{ID}} ({{origin}}) بالوصول إلى الAPI؟",
"title": "طلب السماح بالوصول إلى الAPI"
}
},
"menu": {
"auth-strategy": {
"label": "استراتيجية التفويض",
"submenu": {
"auth-at-first": {
"label": "التفويض عند الطلب الأول"
},
"none": {
"label": "بدون تفويض"
}
}
},
"hostname": {
"label": "اسم المضيف"
},
"port": {
"label": "المنفذ"
}
},
"name": "خادم API [تجريبي]",
"prompt": {
"hostname": {
"label": "أدخل اسم المضيف (مثل 0.0.0.0) لخادم API:",
"title": "اسم الخادم"
},
"port": {
"label": "أدخل المنفذ لخادم API:",
"title": "منفذ"
}
}
},
"audio-compressor": {
"description": "تطبيق الضغط على الصوت (يخفض مستوى صوت الأجزاء الأعلى من الإشارة ويرفع مستوى صوت الأجزاء الأكثر نعومة)",
"name": "ضاغط الصوت"
},
"auth-proxy-adapter": {
"description": "دعم استخدام خدمات proxy للإثبات",
"menu": {
"disable": "تعطيل مكيف الوكيل",
"enable": "تفعيل مكيف proxy للصداقة",
"hostname": {
"label": "إسم المستضيف"
},
"port": {
"label": "المدخل"
}
},
"name": "مكيّف proxy للمصادقة",
"prompt": {
"hostname": {
"label": "أدخل اسم المستضيف لخادم proxy المحلي (يتطلب إعادة التشغيل):",
"title": "إسم مستضيف proxy"
},
"port": {
"label": "أدخل مدخلًا لخادم proxy المحلي (يتطلب إعادة التشغيل):",
"title": "مدخل proxy"
}
}
},
"blur-nav-bar": {
"description": "يجعل شريط التنقل شفاف و مطموس",
"name": "طمس شريط التنقل"
},
"bypass-age-restrictions": {
"description": "تجاوز تَحَقّق مشغل الموسيقى من السن",
"name": "تجاوز التحقق من السن"
},
"captions-selector": {
"description": "محدد ترجمات المقاطع الصوتية ل{{applicationName}}",
"menu": {
"autoload": "اختار اخر ترجمة مستخدمة تلقائيا",
"disable-captions": "لا توجد ترجمات بشكل افتراضي"
},
"name": "محدد الترجمة",
"prompt": {
"selector": {
"label": "لغة الترجمة الحالية: {{language}}",
"none": "لا شيء",
"title": "اختار لغة الترجمة"
}
},
"templates": {
"title": "فتح محدد الترجمة"
},
"toast": {
"caption-changed": "تم تغيير الترجمة الى {{language}}",
"caption-disabled": "الترجمة غير مفعلة",
"no-captions": "الترجمة لهاته الأغنية غير متاحة"
}
},
"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": "خيارات التداخل"
}
}
},
"custom-output-device": {
"description": "ضبط مخرج جهاز وسائط مخصص للأغاني",
"menu": {
"device-selector": "اختر جهاز"
},
"name": "جهاز اخراج مخصص",
"prompt": {
"device-selector": {
"label": "اختر جهاز الوسائط الذي سيتم استخدامه للاخراج",
"title": "اختر الجهاز الإخراج"
}
}
},
"disable-autoplay": {
"description": "يجعل الأغنية تبدأ في وضع \"الإيقاف\"",
"menu": {
"apply-once": "ينطبق فقط عند بدء التشغيل"
},
"name": "تعطيل التشغيل التلقائي"
},
"discord": {
"backend": {
"already-connected": "تمت محاولة الاتصال بالاتصال النشط",
"connected": "متصل بDiscord",
"disconnected": "انقطع الاتصال بDiscord"
},
"description": "أظهر لأصدقائك ما تستمع إليه من خلال Rich Presence",
"menu": {
"auto-reconnect": "إعادة اتصال تلقائي",
"clear-activity": "مسح النشاط",
"clear-activity-after-timeout": "مسح النشاط بعد انتهاء المهلة",
"connected": "متصل",
"disconnected": "غير متصل",
"hide-duration-left": "إخفاء المدة المتبقية",
"hide-github-button": "إخفاء زر رابط GitHub",
"play-on-application": "شغل في {{applicationName}}",
"set-inactivity-timeout": "ضبط مهلة عدم النشاط",
"set-status-display-type": {
"label": "نص الحالة",
"submenu": {
"artist": "جار السمع ل{artist}",
"application": "جار السمع ل{{applicationName}}",
"title": "جار السمع ل{song title}"
}
}
},
"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": "لم يتم العثور على معرف قائمة التشغيل",
"playlist-is-empty": "قائمة التشغيل فارغة",
"playlist-is-mix-or-private": "حدث خطأ أثناء الحصول على معلومات قائمة التشغيل: تأكد من أنها ليست قائمة تشغيل خاصة أو قائمة تشغيل \"مختلطة لك\"\n\n{{error}}",
"preparing-file": "جار تجهيز الملف…",
"saving": "جار الحفظ…",
"trying-to-get-playlist-id": "جار محاولة الحصول على معرف قائمة التشغيل: {{playlistId}}",
"video-id-not-found": "لم يتم ايجاد الفيديو",
"writing-id3": "جار كتابة علامات ID3…"
}
},
"description": "يقوم بتنزيل ملفات MP3/مصدر الصوت مباشرة من الواجهة",
"menu": {
"choose-download-folder": "اختر مكان التنزيل",
"download-finish-settings": {
"label": "تنزيل عند الانتهاء",
"prompt": {
"last-percent": "بعد x بالمئة",
"last-seconds": "آخر x ثانية",
"title": "تكوين وقت التنزيل"
},
"submenu": {
"advanced": "متقدم",
"enabled": "مفعل",
"mode": "وضع الوقت",
"percent": "نسبة",
"seconds": "ثواني"
}
},
"download-playlist": "تنزيل قائمة التشغيل",
"presets": "الإعدادات المسبقة",
"skip-existing": "تخطي الملفات الموجودة بالفعل"
},
"name": "أداة التنزيل",
"renderer": {
"can-not-update-progress": "لا يمكن تحديث التقدم"
},
"templates": {
"button": "تنزيل"
}
},
"equalizer": {
"description": "يضيف معادل صوتي للمشغل",
"menu": {
"presets": {
"label": "إعدادات مسبقة",
"list": {
"bass-booster": "مزود البيس"
}
}
},
"name": "معادل صوتي"
},
"exponential-volume": {
"description": "يجعل شريط تمرير مستوى الصوت أسيًا بحيث يسهل تحديد مستويات الصوت الأقل.",
"name": "الصوت الأسي"
},
"in-app-menu": {
"description": "يعطي أشرطة القوائم مظهرًا أنيقًا و داكنًا أو بلون الألبوم",
"menu": {
"hide-dom-window-controls": "إخفاء عناصر التحكم في نافذة DOM"
},
"name": "قائمة التي داخل التطبيق"
},
"lumiastream": {
"description": "يضيف دعم Lumia Stream",
"name": "Lumia Stream [تجريبي]"
},
"lyrics-genius": {
"description": "يضيف دعم الكلمات لمعظم الأغاني",
"menu": {
"romanized-lyrics": "كلمات مكتوبة بحروف رومانية"
},
"name": "كلمات الأغاني من Genius",
"renderer": {
"fetched-lyrics": "تم جلب الكلمات من Genius"
}
},
"music-together": {
"description": "مشاركة قائمة تشغيل مع الآخرين. عندما يقوم المضيف بتشغيل أغنية، سيسمع الجميع نفس الأغنية",
"dialog": {
"enter-host": "أدخل معرف المضيف"
},
"internal": {
"save": "حفظ",
"track-source": "تتبع مصدر الاغنية",
"unknown-user": "مستخدم مجهول"
},
"menu": {
"click-to-copy-id": "نسخ معرف المستضيف",
"close": "إغلاق الموسيقى معًا",
"connected-users": "المستخدمون المتصلون",
"disconnect": "قطع اتصال من الموسيقى معًا",
"empty-user": "لا يوجد مستعملون متصلون",
"host": "مضيف الموسيقى معًا",
"join": "الانضمام إلى الموسيقى معا",
"permission": {
"all": "السماح للضيوف بالتحكم في قائمة التشغيل والمشغل",
"host-only": "فقط المضيف يستطيع التحكم بالقائمة و المشغل",
"playlist": "السماح للضيوف بالتحكم بقائمة التشغيل"
},
"set-permission": "تغيير إذن التحكم",
"status": {
"disconnected": "غير متصل",
"guest": "متصل كضيف",
"host": "متصل كمضيف"
}
},
"name": "الموسيقى معا [تجريبي]",
"toast": {
"add-song-failed": "فشل في إضافة أغنية",
"closed": "تم إغلاق الموسيقى معا",
"disconnected": "تم قطع اتصال الموسيقى معًا",
"host-failed": "فشل في استضافة الموسيقى معا",
"id-copied": "تم نسخ معرف المضيف",
"id-copy-failed": "فشل نسخ معرف المضيف",
"join-failed": "فشل الانضمام إلى الموسيقى معا",
"joined": "تم الانضمام إلى الموسيقى معا",
"permission-changed": "تم تغيير إذن الموسيقى معًا إلى \"{{permission}}\"",
"remove-song-failed": "فشل في إزالة الأغنية",
"user-connected": "{{name}} انضم إلى الموسيقى معًا",
"user-disconnected": "{{name}} غادر الموسيقى معًا"
}
},
"navigation": {
"description": "أسهم التنقل \"التالي/السابق\" مدمجة مباشرة في الواجهة، كما في متصفحك المفضل",
"name": "التنقل",
"templates": {
"back": {
"title": "العودة إلى الصفحة السابقة"
},
"forward": {
"title": "إذهب إلى الصفحة المقبلة"
}
}
},
"no-google-login": {
"description": "إزالة أزرار وروابط تسجيل الدخول بجوجل من الواجهة",
"name": "لا يوجد تسجيل دخول بجوجل"
},
"notifications": {
"description": "عرض إشعار عندما تبدأ الأغنية (الإشعارات التفاعلية متوفرة على ويندوز)",
"menu": {
"interactive": "إشعارات تفاعلية",
"interactive-settings": {
"label": "إعدادات تفاعلية",
"submenu": {
"hide-button-text": "إخفاء نص الزر",
"refresh-on-play-pause": "اعادة تحميل عند التشغيل/الإيقاف",
"tray-controls": "فتح/إغلاق عند النقر على علامة الشريط"
}
},
"priority": "أولوية الإشعار",
"toast-style": "تنسيق النخب",
"unpause-notification": "إظهار إشعار عند استئناف التشغيل"
},
"name": "الإشعارات"
},
"performance-improvement": {
"description": "تحسين الأداء عبر تفعيل السكربتات التجريبية",
"name": "تحسين الأداء [تجريبي]"
},
"picture-in-picture": {
"description": "يسمح بتحويل التطبيق إلى وضع الصورة داخل الصورة",
"menu": {
"always-on-top": "دائمًا في المقدمة",
"hotkey": {
"label": "مفتاح اختصار",
"prompt": {
"keybind-options": {
"hotkey": "مفتاح اختصار"
},
"label": "اختر مفتاح اختصار لتبديل وضع الصورة داخل الصورة",
"title": "مفتاح اختصار الصورة داخل الصورة"
}
},
"save-window-position": "حفظ موقع النافذة",
"save-window-size": "حفظ حجم النافذة",
"use-native-pip": "استخدام وضع الصورة داخل الصورة الأصلي للمتصفح"
},
"name": "وضع الصورة داخل الصورة",
"templates": {
"button": "وضع الصورة داخل الصورة"
}
},
"playback-speed": {
"description": "استمع بسرعة، استمع ببطء! يضيف شريط تمرير يتحكم في سرعة الأغنية",
"name": "سرعة التشغيل",
"templates": {
"button": "السرعة"
}
},
"precise-volume": {
"description": "التحكم في مستوى الصوت بدقة باستخدام عجلة الفأرة/مفاتيح الاختصار، مع واجهة مستخدم مخصصة وخطوات صوتية قابلة للتخصيص",
"menu": {
"arrows-shortcuts": "عناصر التحكم بأسهم المفاتيح",
"custom-volume-steps": "تعيين خطوات صوتية خاصة",
"global-shortcuts": "مفاتيح اختصار عام"
},
"name": "مستوى صوت دقيق",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "تقليل مستوى الصوت",
"increase": "زيادة مستوى الصوت"
},
"label": "اختر اختصارات لوحة المفاتيح للتحكم بمستوى الصوت:",
"title": "اختصارات لوحة المفاتيح للتحكم بمستوى الصوت"
},
"volume-steps": {
"label": "اختر خطوات زيادة/تقليل مستوى الصوت",
"title": "خطوات زيادة الصوت"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "الجودة الحالية: {{quality}}",
"message": "اختر جودة الفيديو:",
"title": "اختر جودة الفيديو"
}
}
},
"description": "يسمح بتغيير جودة الفيديو باستخدام زر على صورة الفيديو",
"name": "مغير جودة الفيديو",
"renderer": {
"quality-settings-button": {
"label": "إفتح مغير الجودة"
}
}
},
"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-alternative-artist": "استخدم فنانين بديلين",
"scrobble-alternative-title": "استخدم عناوين بديلة",
"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": {
"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"
},
"synced-lyrics": {
"description": "يوفر كلمات الأغاني المتزامنة باستخدام مزودين مثل LRClib.",
"errors": {
"fetch": "⚠️ حدث خطأ أثناء جلب كلمات الأغنية.\nيرجى المحاولة مرة أخرى لاحقًا.",
"not-found": "⚠️ لم يتم العثور على كلمات لهذه الأغنية."
},
"menu": {
"default-text-string": {
"label": "المسافة الافتراضية بين كلمات الأغاني",
"tooltip": "اختر الحرف الافتراضي لاستخدامه في الفجوة بين كلمات الأغنية"
},
"line-effect": {
"label": "تأثير الخط",
"submenu": {
"fancy": {
"label": "فاخر",
"tooltip": "استخدم تأثيرات كبيرة تشبه التطبيقات على السطر الحالي"
},
"focus": {
"label": "تركيز",
"tooltip": "اجعل السطر الحالي فقط باللون الأبيض"
},
"offset": {
"label": "مزاح",
"tooltip": "مزاح الى يمين السطر الحالي"
},
"scale": {
"label": "تحجيم",
"tooltip": "حجم السطر الحالي"
}
},
"tooltip": "اختر التأثير لتطبيقه على السطر الحالي"
},
"precise-timing": {
"label": "اجعل كلمات الأغنية متزامنة بشكل مثالي",
"tooltip": "احسب بدقة الملي ثانية عرض السطر التالي (قد يكون له تأثير صغير على الأداء)"
},
"preferred-provider": {
"label": "المزود المفضل",
"none": {
"label": "لا شيء",
"tooltip": "لا يوجد مزود مفضل"
},
"tooltip": "اختر المزود المفضل للإستخدام"
},
"romanization": {
"label": "اجعل الكلمات رومانية",
"tooltip": "إذا كانت كلمات الأغنية بلغة مختلفة، حاول عرض نسخة بالحروف اللاتينية."
},
"show-lyrics-even-if-inexact": {
"label": "أظهر كلمات الأغنية حتى لو كانت غير دقيقة",
"tooltip": "إذا لم يتم العثور على الأغنية، سوف يتم البحث مرة أخرى باستخدام استعلام بحث مختلف.\nقد لا تكون النتيجة من المحاولة الثانية دقيقة."
},
"show-time-codes": {
"label": "أظهر الرموز الزمنية",
"tooltip": "أظهر الرموز الزمنية بجانب كلمات الأغنية"
}
},
"name": "كلمات متزامنة",
"refetch-btn": {
"fetching": "جارٍ الجلب...",
"normal": "إعادة جلب كلمات الأغنية"
},
"warnings": {
"duration-mismatch": "⚠️ - قد تكون الكلمات غير متزامنة بسبب عدم تطابق المدة.",
"inexact": "⚠️ - قد لا تكون كلمات هذه الأغنية دقيقة",
"instrumental": "⚠️ - هذه أغنية آلية (بدون كلمات)"
}
},
"taskbar-mediacontrol": {
"description": "التحكم في المشغل من شريط مهام ويندوز",
"name": "التحكم بالوسائط من شريط المهام"
},
"touchbar": {
"description": "يضيف أداة TouchBar لمستخدمي macOS",
"name": "شريط اللمس (TouchBar)"
},
"transparent-player": {
"description": "يجعل نافذة التطبيق شفافة",
"menu": {
"opacity": {
"label": "الشفافية",
"submenu": {
"percent": "{{opacity}}٪"
}
},
"type": {
"label": "النوع",
"submenu": {
"acrylic": "أكريليك",
"mica": "ميكا",
"none": "لاشيء",
"tabbed": "بديل ميكا"
}
}
},
"name": "مشغل شفاف"
},
"tuna-obs": {
"description": "التكامل مع الإضافة\" Tuna\" الخاصة بـ OBS",
"name": "إضافة Tuna OBS"
},
"unobtrusive-player": {
"description": "يمنع المشغل من الظهور عند تشغيل أغنية",
"name": "مشغل غير مزعج"
},
"video-toggle": {
"description": "يضيف زرًا للتبديل بين وضع الفيديو/الأغنية. يمكن أيضًا اختياريًا إزالة علامة الفيديو بالكامل",
"menu": {
"align": {
"label": "المحاذاة",
"submenu": {
"left": "يسار",
"middle": "المنتصف",
"right": "يمين"
}
},
"force-hide": "إزالة علامة تبويب الفيديو",
"mode": {
"label": "وضع",
"submenu": {
"custom": "زر مخصص",
"disabled": "غير مفعل",
"native": "زر طبيعي"
}
}
},
"name": "زر الفيديو",
"templates": {
"button-song": "أغنية",
"button-video": "فيديو"
}
},
"visualizer": {
"description": "يضيف معاينًا بصريًا للمشغل",
"menu": {
"visualizer-type": "نوع المعاين البصري"
},
"name": "معاين بصري"
}
}
}
================================================
FILE: src/i18n/resources/az.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Plagini icra etmək mümkün olmadı {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plagin {{pluginName}}::{{contextName}} {{ms}} millisaniyədə icra edildi",
"initialize-failed": "\"{{pluginName}}\" plaginini başlatmaq mümkün olmadı",
"load-all": "Bütün plaginlər yüklənir",
"load-failed": "\"{{pluginName}}\" plaginini yükləmək mümkün olmadı",
"loaded": "\"{{pluginName}}\" plagini yükləndi",
"unload-failed": "\"{{pluginName}}\" plaqinini yükləmək mümkün olmadı",
"unloaded": "\"{{pluginName}}\" plaqini yükləmədən çıxarıldı"
}
}
},
"language": {
"code": "az",
"local-name": "Azərbaycan dili",
"name": "Azerbaijani"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Yükləmə tamamlandı. DevTools açıldı"
},
"i18n": {
"loaded": "i18n yükləndi"
},
"second-instance": {
"receive-command": "Protokol üzərindən əmr alındı: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS faylı \"{{cssFile}}\" mövcud deyil, nəzərə alınmır"
},
"unresponsive": {
"details": "Cavabsız Səhv!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Proqram keşi təmizlənir"
},
"window": {
"tried-to-render-offscreen": "Pəncərə ekran kənarında göstərilməyə çalışıldı, PəncərəÖlçüsü={{windowSize}}, EkranÖlçüsü={{displaySize}}, Vəziyyət={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu gizlədildi, yenidən göstərmək üçün 'Alt' istifadə edin (Proqramiçi menu üçün 'Esc')",
"message": "Gizlət menusu aktivləşdirildi",
"title": "Gizlət menusu aktivləşdirildi"
},
"need-to-restart": {
"buttons": {
"later": "Sonra",
"restart-now": "Yenidən başlat"
},
"detail": "\"{{pluginName}}\" plaginin işləməsi üçün proqramı yenidən başladın",
"message": "\"{{pluginName}}\" üçün proqram yenidən başlamalıdır",
"title": "Yenidən başlatmaq tələb edilir"
},
"unresponsive": {
"buttons": {
"quit": "Tərk et",
"relaunch": "Yenidən işə sal",
"wait": "Gözləyin"
},
"detail": "Narahatçılıq üçün üzr istəyirik! Nə etməli olduğumuzu seçin:",
"message": "Proqram cavab vermir",
"title": "Pəncərə Cavab vermir"
},
"update-available": {
"buttons": {
"disable": "Yeniləmələri deaktiv et",
"download": "Yüklə",
"ok": "Oldu"
},
"detail": "Yeni versiya mövcuddur və bu linkdən yüklənə bilər {{downloadLink}}",
"message": "Yeni versiya mövcuddur",
"title": "Yeniləmə mövcuddur"
}
},
"menu": {
"about": "Haqqında",
"navigation": {
"label": "İstiqamət",
"submenu": {
"copy-current-url": "Hazırkı linki kopyala",
"go-back": "Geri qayıt",
"go-forward": "İrəli get",
"quit": "Çıx",
"restart": "Proqramı Yenidən Başlat"
}
},
"options": {
"label": "Seçimlər",
"submenu": {
"advanced-options": {
"label": "Əlavə seçimlər",
"submenu": {
"auto-reset-app-cache": "Proqram başlayanda keşi təmizlə",
"disable-hardware-acceleration": "Aparat təminatı sürətlənməsini deaktiv et",
"edit-config-json": "Config.json dəyiş",
"override-user-agent": "User-Agent dəyişdirildi",
"restart-on-config-changes": "Konfiqurasiya dəyişikliklərində yenidən başladılır",
"set-proxy": {
"label": "Proxy təyin et",
"prompt": {
"label": "Proxy Ünvanını daxil edin: (deaktiv etmək üçün boş buraxın)",
"placeholder": "Nümunə: SOCKS5://127.0.0.1:9999",
"title": "Proxy təyin et"
}
},
"toggle-dev-tools": "DevTools-u açıb bağla"
}
},
"always-on-top": "Həmişə üst tərəfdə",
"auto-update": "Avtomatik Yeniləmə",
"hide-menu": {
"dialog": {
"message": "Menyu növbəti açılışda gizlədiləcək, göstərmək üçün [Alt] düyməsini basın (proqramdaxili menyudan istifadə edildikdə isə, [`] düyməsi).",
"title": "Menyu gizlətmə aktivdir"
},
"label": "Menyunu gizlət"
},
"language": {
"dialog": {
"message": "Dil yenidən başlatmadan sonra dəyişiləcək",
"title": "Dil dəyişdirildi"
},
"label": "Dil",
"submenu": {
"to-help-translate": "Tərcüməyə kömək etmək istəyirsiniz? Buraya basın"
}
},
"resume-on-start": "Tətbiq başladıqda son mahnıdan davam et",
"single-instance-lock": "Tək proqram kilidi",
"start-at-login": "Giriş səhifəsində başlat",
"starting-page": {
"label": "Giriş səhifəsi",
"unset": "Təyin edilməyib"
},
"tray": {
"submenu": {
"disabled": "Deaktiv edilib"
}
},
"visual-tweaks": {
"label": "Vizual düzəlişlər",
"submenu": {
"custom-window-title": {
"prompt": {
"placeholder": "Nümunə: {{applicationName}}"
}
},
"like-buttons": {
"default": "Standart",
"hide": "Gizlət"
},
"remove-upgrade-button": "Yeniləmə düyməsini sil",
"theme": {
"dialog": {
"button": {
"cancel": "Ləğv et",
"remove": "Sil"
}
}
}
}
}
}
}
}
}
}
================================================
FILE: src/i18n/resources/be.json
================================================
{}
================================================
FILE: src/i18n/resources/bg.json
================================================
{
"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": {
"custom-window-title": {
"label": "Персонализирано заглавие на прозорец",
"prompt": {
"label": "Въведи персонализирано заглавие: (остави празно за да изключиш)",
"placeholder": "Пример: {{applicationName}}"
}
},
"like-buttons": {
"default": "По подразбиране",
"force-show": "Принудително показване",
"hide": "Скриване",
"label": "Показване на \"Харесвам\" бутони"
},
"remove-upgrade-button": "Премахване на \"Ъпгрейд\" бутона",
"theme": {
"dialog": {
"button": {
"cancel": "Отказ",
"remove": "Премахни"
},
"remove-theme": "Сигурни ли сте, че искате да премахнете персонализираната тема?",
"remove-theme-message": "Това ще премахне персонализираната тема"
},
"label": "Тема",
"submenu": {
"import-css-file": "Импортиране на потребителски CSS файл",
"no-theme": "Без тема"
}
}
}
}
}
},
"plugins": {
"enabled": "Активирани",
"label": "Плъгини",
"new": "НОВО"
},
"view": {
"label": "Преглед",
"submenu": {
"force-reload": "Принудително презареждане",
"reload": "Презареди",
"reset-zoom": "Действителен размер",
"toggle-fullscreen": "Превключване на цял екран",
"zoom-in": "Увеличаване",
"zoom-out": "Намаляване"
}
}
},
"tray": {
"next": "Следващ",
"play-pause": "Възпроизвеждане/Пауза",
"previous": "Предишен",
"quit": "Изход",
"restart": "Рестартирай приложението",
"show": "Покажи прозорец",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Ако се пусне реклама, заглушава аудиото и задава скорост на възпроизвеждане 16x",
"name": "Ускоряване на рекламите"
},
"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": "Атмосферен режим"
},
"amuse": {
"description": "Добавя поддръжка на {{applicationName}} за джаджата Amuse Now Play от 6K Labs",
"name": "Забавление",
"response": {
"query": "Сървърът на Amuse API работи. Изпратете GET /query за информация за песента."
}
},
"api-server": {
"description": "Добавя API сървър за контрол на плейъра",
"dialog": {
"request": {
"buttons": {
"allow": "Разрешавам",
"deny": "Отказвам"
},
"message": "Позволяваш ли {{ID}} {{origin}} да достъпва API-то?",
"title": "Заявка за авторизация на API"
}
},
"menu": {
"auth-strategy": {
"label": "Стратегия за авторизация",
"submenu": {
"auth-at-first": {
"label": "Авторизиране при първата заявка"
},
"none": {
"label": "Без авторизация"
}
}
},
"hostname": {
"label": "Име на хост"
},
"port": {
"label": "Порт"
}
},
"name": "API сървър [Бета]",
"prompt": {
"hostname": {
"label": "Въведете името на хоста (като 0.0.0.0) за API сървъра:",
"title": "Име на хост"
},
"port": {
"label": "Въведете порта за API сървъра:",
"title": "Порт"
}
}
},
"audio-compressor": {
"description": "Прилага компресия на аудиото (намалява обема на най-силните части от сигнала и увеличава обема на най-тихите части)",
"name": "Аудио компресор"
},
"auth-proxy-adapter": {
"description": "Поддръжка за използване на услуги за удостоверяване чрез прокси",
"menu": {
"disable": "Деактивирай адаптера за удостоверяване чрез прокси",
"enable": "Активирай адаптера за удостоверяване чрез прокси",
"hostname": {
"label": "Име на хост"
},
"port": {
"label": "Порт"
}
},
"name": "Адаптер за удостоверяване чрез прокси",
"prompt": {
"hostname": {
"label": "Въведи име за локалния прокси сървър (необходимо е рестартиране):",
"title": "Име на прокси хост"
},
"port": {
"label": "Въведи порт за локалния прокси сървър (необходимо е рестартиране):",
"title": "Прокси порт"
}
}
},
"blur-nav-bar": {
"description": "Прави навигационната лента прозрачна и размазана",
"name": "Размазанa навигационна лента"
},
"bypass-age-restrictions": {
"description": "Избягване на възрастова верификация на Music Player",
"name": "Избягване на възрастови ограничения"
},
"captions-selector": {
"description": "Избор на надписи за аудио тракове в {{applicationName}}",
"menu": {
"autoload": "Автоматично избиране на последно използвания надпис",
"disable-captions": "Без надписи по подразбиране"
},
"name": "Избор на надписи",
"prompt": {
"selector": {
"label": "Език на надписи: {{language}}",
"none": "Нищо",
"title": "Избери език на надписите"
}
},
"templates": {
"title": "Отвори избора на надписи"
},
"toast": {
"caption-changed": "Надписите са сменени на {{language}}",
"caption-disabled": "Надписите са деактивирани",
"no-captions": "Няма налични надписи за тази песен"
}
},
"compact-sidebar": {
"description": "Винаги настройвай страничната лента в компактен режим",
"name": "Компактна странична лента"
},
"crossfade": {
"description": "Плавно преминаване през песните",
"menu": {
"advanced": "Разширено"
},
"name": "Плавно преминаване [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Продължителност на преливането (милисекунди)",
"fade-out-duration": "Продължителност на затихването (милисекунди)",
"fade-scaling": {
"label": "Скалиране на избледняването",
"linear": "Линейно",
"logarithmic": "Логаритмично"
},
"seconds-before-end": "Преливане N секунди преди края"
},
"title": "Опции за преливане"
}
}
},
"custom-output-device": {
"description": "Конфигуриране на изходно медийно устройство за песни",
"menu": {
"device-selector": "Избери устройство"
},
"name": "Персонализирано изходно устройство",
"prompt": {
"device-selector": {
"label": "Избери изходното медийно устройство",
"title": "Избери изходно устройство"
}
}
},
"disable-autoplay": {
"description": "Започва песента в паузиран режим",
"menu": {
"apply-once": "Важи само на стартиране"
},
"name": "Изключи автоматичното пускане"
},
"discord": {
"backend": {
"already-connected": "Опит за свързване с активна връзка",
"connected": "Свързано с Discord",
"disconnected": "Прекъсната връзка с Discord"
},
"description": "Покажи на приятелите си какво слушате с Rich Presence",
"menu": {
"auto-reconnect": "Автоматично повторно свързване",
"clear-activity": "Изчистване на активността",
"clear-activity-after-timeout": "Изчистване на активността след изтичане на времето",
"connected": "Свързано",
"disconnected": "Прекъснато",
"hide-duration-left": "Скрий оставащото време",
"hide-github-button": "Скрий бутона за линк към GitHub",
"play-on-application": "Възпроизведи в {{applicationName}}",
"set-inactivity-timeout": "Задай таймаут за неактивност",
"set-status-display-type": {
"label": "Статус текст",
"submenu": {
"artist": "Слушам {artist}",
"title": "Слушам {song title}",
"application": "Слушам {{applicationName}}"
}
}
},
"name": "Дискорд Разширен статус",
"prompt": {
"set-inactivity-timeout": {
"label": "Въведете таймаута за неактивност в секунди:",
"title": "Задайте таймаут за неактивност"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "ОК"
},
"message": "Ох! Извинявайте, изтеглянето не успя…",
"title": "Грешка при изтегляне!"
},
"start-download-playlist": {
"buttons": {
"ok": "ОК"
},
"detail": "({{playlistSize}} песни)",
"message": "Изтегляне на плейлист {{playlistTitle}}",
"title": "Изтеглянето започна"
}
},
"feedback": {
"conversion-progress": "Конвертиране: {{percent}}%",
"converting": "Превръщане…",
"done": "Готово: {{filePath}}",
"download-info": "Изтегляне на {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Изтегляне: {{percent}}%",
"downloading": "Изтегляне…",
"downloading-counter": "Изтегляне {{current}}/{{total}}…",
"downloading-playlist": "Изтегляне на плейлист \"{{playlistTitle}}\" - {{playlistSize}} песни ({{playlistId}})",
"error-while-downloading": "Грешка при изтегляне на \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Папката {{playlistFolder}} вече съществува",
"getting-playlist-info": "Получаване на информация за плейлист…",
"loading": "Зареждане…",
"playlist-has-only-one-song": "Плейлистът съдържа само един елемент, изтегля се директно",
"playlist-id-not-found": "Не е намерен ID на плейлист",
"playlist-is-empty": "Плейлистът е празен",
"playlist-is-mix-or-private": "Грешка при получаване на информация за плейлист: уверете се, че не е частен или \"Смесено за вас\" плейлист\n\n{{error}}",
"preparing-file": "Подготвяне на файла…",
"saving": "Записване…",
"trying-to-get-playlist-id": "Опитвам се да получа ID на плейлист: {{playlistId}}",
"video-id-not-found": "Видео не е намерено",
"writing-id3": "Записване на ID3 тагове…"
}
},
"description": "Изтегля MP3 / източниково аудио директно от интерфейса",
"menu": {
"choose-download-folder": "Изберете папка за изтегляне",
"download-finish-settings": {
"label": "Изтегляне при завършване",
"prompt": {
"last-percent": "След x процента",
"last-seconds": "Последни x секунди",
"title": "Конфигурирайте кога да изтеглите"
},
"submenu": {
"advanced": "Разширени настройки",
"enabled": "Активирано",
"mode": "Режим на време",
"percent": "Процент",
"seconds": "Секунди"
}
},
"download-playlist": "Изтегляне на плейлист",
"presets": "Предварително зададени настройки",
"skip-existing": "Пропусни съществуващите файлове"
},
"name": "Изтегляч",
"renderer": {
"can-not-update-progress": "Не може да се актуализира напредъкът"
},
"templates": {
"button": "Изтегляне"
}
},
"equalizer": {
"description": "Добавя еквалайзер към плеъра",
"menu": {
"presets": {
"label": "Предварителни настройки",
"list": {
"bass-booster": "Усилвател на басове"
}
}
},
"name": "Еквалайзер"
},
"exponential-volume": {
"description": "Прави плъзгача за сила на звука експоненциален, така че да е по-лесно да се избират по-ниски нива на звук.",
"name": "Експоненциален звук"
},
"in-app-menu": {
"description": "Придава на меню баровете стилен, тъмен или с цвят на албума вид",
"menu": {
"hide-dom-window-controls": "Скрий контролните елементи на DOM прозореца"
},
"name": "Меню в приложението"
},
"lumiastream": {
"description": "Добавя поддръжка за Lumia Stream",
"name": "Lumia Stream [Бета]"
},
"lyrics-genius": {
"description": "Добавя поддръжка за текстове за повечето песни",
"menu": {
"romanized-lyrics": "Романизирани текстове"
},
"name": "Текстове от Genius",
"renderer": {
"fetched-lyrics": "Изтеглени текстове от Genius"
}
},
"music-together": {
"description": "Сподели плейлист с други. Когато хостът пусне песен, всички останали ще чуят същата песен",
"dialog": {
"enter-host": "Въведи ID на хоста"
},
"internal": {
"save": "Запазване",
"track-source": "Източник на трак",
"unknown-user": "Неизвестен потребител"
},
"menu": {
"click-to-copy-id": "Копирай ID на хост",
"close": "Затвори Music Together",
"connected-users": "Свързани потребители",
"disconnect": "Прекъсни Music Together",
"empty-user": "Няма свързани потребители",
"host": "Хост на Music Together",
"join": "Присъедини се към Music Together",
"permission": {
"all": "Позволи на гостите да управляват плейлист и плеър",
"host-only": "Само хостът може да управлява плейлист и плеър",
"playlist": "Позволи на гостите да управляват плейлист"
},
"set-permission": "Промени разрешението за управление",
"status": {
"disconnected": "Прекъснато",
"guest": "Свързан като гост",
"host": "Свързан като хост"
}
},
"name": "Music Together [Бета]",
"toast": {
"add-song-failed": "Неуспешно добавяне на песен",
"closed": "Music Together е затворена",
"disconnected": "Music Together е прекъсната",
"host-failed": "Неуспешно хостване на Music Together",
"id-copied": "ID на хоста е копиран в клипборда",
"id-copy-failed": "Неуспешно копиране на ID на хоста в клипборда",
"join-failed": "Неуспешно присъединяване към Music Together",
"joined": "Присъединен към Music Together",
"permission-changed": "Разрешението за Music Together е променено на \"{{permission}}\"",
"remove-song-failed": "Неуспешно премахване на песен",
"user-connected": "{{name}} се присъедини към Music Together",
"user-disconnected": "{{name}} напусна Music Together"
}
},
"navigation": {
"description": "Навигационни стрелки Напред/Назад, директно интегрирани в интерфейса, както в любимия ви браузър",
"name": "Навигация",
"templates": {
"back": {
"title": "Предишна страница"
},
"forward": {
"title": "Следваща страница"
}
}
},
"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": "Стил на toast (кратки изскачащи известия)",
"unpause-notification": "Показване на известие при възобновяване"
},
"name": "Известия"
},
"performance-improvement": {
"description": "Подобри производителността като пуснеш експериментални скриптове",
"name": "Производителни подобрения"
},
"picture-in-picture": {
"description": "Позволява превключване на приложението в режим картинка във картинка",
"menu": {
"always-on-top": "Винаги на преден план",
"hotkey": {
"label": "Клавишна комбинация",
"prompt": {
"keybind-options": {
"hotkey": "Клавишна комбинация"
},
"label": "Изберете клавишна комбинация за превключване на картинка във картинка",
"title": "Клавишна комбинация за картинка във картинка"
}
},
"save-window-position": "Запомняне на позицията на прозореца",
"save-window-size": "Запомняне на размера на прозореца",
"use-native-pip": "Използвайте вградения картинка във картинка на браузера"
},
"name": "Картинка във картинка",
"templates": {
"button": "Картинка във картинка"
}
},
"playback-speed": {
"description": "Слушай бързо, слушай бавно! Добавя плъзгач, който управлява скоростта на песните",
"name": "Скорост на възпроизвеждане",
"templates": {
"button": "Скорост"
}
},
"precise-volume": {
"description": "Управлявайте прецизно силата на звука чрез колелото на мишката или бързи клавиши, с персонализиран HUD и настройвани нива на звука",
"menu": {
"arrows-shortcuts": "Локални контроли със стрелки",
"custom-volume-steps": "Задайте персонализирани нива на звука",
"global-shortcuts": "Глобални бързи клавиши"
},
"name": "Точна сила на звука",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Намаляване на звука",
"increase": "Усилване на звука"
},
"label": "Изберете глобални клавишни комбинации за сила на звука:",
"title": "Глобални клавишни комбинации за звук"
},
"volume-steps": {
"label": "Изберете стъпки за увеличаване/намаляване на звука",
"title": "Стъпки на звука"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Текущо качество: {{quality}}",
"message": "Изберете качество на видеото:",
"title": "Изберете качество на видеото"
}
}
},
"description": "Позволява промяна на качеството на видеото с бутон върху видеото",
"name": "Промяна на качеството на видеото",
"renderer": {
"quality-settings-button": {
"label": "Отвори настройките за качество на плейъра"
}
}
},
"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-alternative-artist": "Използвай алтернативни изпълнители",
"scrobble-alternative-title": "Използвай алтернативни заглавия",
"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 за търсене, включване на 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"
},
"synced-lyrics": {
"description": "Предоставя синхронизирани текстове на песни, използвайки доставчици като LRClib.",
"errors": {
"fetch": "⚠️\tВъзникна грешка при извличане на текста.\n\tМоля, опитайте по-късно.",
"not-found": "⚠️ За тази песен не са намерени текстове."
},
"menu": {
"default-text-string": {
"label": "Подразбиращ се знак между текстовете",
"tooltip": "Изберете знака, който да се използва за интервала между текстовете"
},
"line-effect": {
"label": "Линеен ефект",
"submenu": {
"fancy": {
"label": "Украсено",
"tooltip": "Използвайте големи, приложно-подобни ефекти на текущия ред"
},
"focus": {
"label": "Фокус",
"tooltip": "Направете само текущия ред бял"
},
"offset": {
"label": "Отместване",
"tooltip": "Отместване на текущия ред вдясно"
},
"scale": {
"label": "Мащаб",
"tooltip": "Мащабирайте текущия ред"
}
},
"tooltip": "Изберете ефекта, който да се приложи към текущия ред"
},
"precise-timing": {
"label": "Направете текстовете перфектно синхронизирани",
"tooltip": "Изчислете до милисекунда показването на следващия ред (може да има малък ефект върху производителността)"
},
"preferred-provider": {
"label": "Предпочитан доставчик",
"none": {
"label": "Празно",
"tooltip": "Без предпочитан доставчик"
},
"tooltip": "Изберете доставчик по подразбиране"
},
"romanization": {
"label": "Романизиране на текстовете",
"tooltip": "Ако текстовете са на друг език, опитайте да покажете латинска версия."
},
"show-lyrics-even-if-inexact": {
"label": "Показване на текстовете, дори ако са неточни",
"tooltip": "Ако песента не бъде намерена, добавката се опитва отново с различен поисков запрос.\nРезултатът от втория опит може да не е точен."
},
"show-time-codes": {
"label": "Показване на временни кодове",
"tooltip": "Показване на временни кодове до текстовете"
}
},
"name": "Синхронизирани текстове",
"refetch-btn": {
"fetching": "Извличане...",
"normal": "Повторно извличане на текстовете"
},
"warnings": {
"duration-mismatch": "⚠️ - Текстовете може да не са синхронизирани поради несъответствие в продължителността.",
"inexact": "⚠️ - Текстовете за тази песен може да не са точни",
"instrumental": "⚠️ - Това е инструментална песен"
}
},
"taskbar-mediacontrol": {
"description": "Управление на възпроизвеждането от лентата с задачи на Windows",
"name": "Управление на медията от лентата със задачи"
},
"touchbar": {
"description": "Добавя уиджет за TouchBar за потребители на macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Прави прозореца на приложението прозрачен",
"menu": {
"opacity": {
"label": "Прозрачност",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Тип",
"submenu": {
"acrylic": "Акрил",
"mica": "Слюда",
"none": "Празно",
"tabbed": "С раздели"
}
}
},
"name": "Прозрачен плейър"
},
"tuna-obs": {
"description": "Интеграция с плъгина Tuna за OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Предотвратява изскачането на плеъра при възпроизвеждане на песен",
"name": "Неназойлив плеър"
},
"video-toggle": {
"description": "Добавя бутон за превключване между видео/песен режим. Също така може по избор да премахва целия раздел за видео",
"menu": {
"align": {
"label": "Подравняване",
"submenu": {
"left": "Ляво",
"middle": "В средата",
"right": "Дясно"
}
},
"force-hide": "Принудително премахване на раздела за видео",
"mode": {
"label": "Режим",
"submenu": {
"custom": "Персонализиран превключвател",
"disabled": "Изключено",
"native": "Вграден превключвател"
}
}
},
"name": "Превключване на видео",
"templates": {
"button-song": "Песен",
"button-video": "Видео"
}
},
"visualizer": {
"description": "Добавя визуализатор към плеъра",
"menu": {
"visualizer-type": "Тип визуализатор"
},
"name": "Визуализатор"
}
}
}
================================================
FILE: src/i18n/resources/bn.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "প্লাগইন {{pluginName}}::{{contextName}} কার্যকর করতে ব্যর্থ হয়েছে",
"executed-at-ms": "প্লাগইন {{pluginName}}::{{contextName}} {{ms}}মিলিসেকেন্ডে কার্যকর হয়েছে",
"initialize-failed": "প্লাগইন \"{{pluginName}}\" চালু করতে ব্যর্থ হয়েছে",
"load-all": "সকল প্লাগইন লোড করা হচ্ছে",
"load-failed": "প্লাগইন \"{{pluginName}}\" লোড হতে ব্যর্থ হয়েছে",
"loaded": "প্লাগইন \"{{pluginName}}\" লোড হয়েছে",
"unload-failed": "প্লাগইন \"{{pluginName}}\" আনলোড করতে ব্যর্থ হয়েছে",
"unloaded": "প্লাগইন \"{{pluginName}}\" আনলোড হয়েছে"
}
}
},
"language": {
"code": "bn",
"local-name": "বাংলা",
"name": "Bengali"
},
"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": "ইউজার এজেন্ট বদলান",
"restart-on-config-changes": "কনফিগ পরিবর্তন হলে আবার চালু করুন",
"set-proxy": {
"label": "প্রক্সি সেট করুন",
"prompt": {
"label": "প্রক্সি ঠিকানা দিন: (বন্ধ করতে ফাকা রাখুন)",
"placeholder": "যেমন: SOCKS5://127.0.0.1:9999",
"title": "প্রক্সি সেট করুন"
}
},
"toggle-dev-tools": "ডেভ টুল চালু/বন্ধ"
}
},
"always-on-top": "সবসময় উপরে রাখুন",
"auto-update": "স্বয়ংক্রিয় আপডেট চালু করুন",
"hide-menu": {
"dialog": {
"message": "পরবর্তী চালুর সময় মেনু লুকানো থাকবে, দেখতে [Alt] চাপুন (অথবা ইন-অ্যাপ মেনু হলে [`] ব্যবহার করুন)",
"title": "মেনু লুকানো হয়েছে"
},
"label": "মেনু লুকান"
},
"language": {
"dialog": {
"message": "পুনরায় চালু করলে ভাষা পরিবর্তন হয়ে যাবে",
"title": "ভাষা নির্বাচন করুন"
},
"label": "ভাষা",
"submenu": {
"to-help-translate": "অনুবাদে সাহায্য করতে চান? এখানে চাপ দিন"
}
},
"resume-on-start": "অ্যাপ চালু হলে শেষ গানটি আবার চালু করুন",
"single-instance-lock": "একক ইনস্ট্যান্স লক",
"start-at-login": "লগইনের সময় চালু করুন",
"starting-page": {
"label": "শুরুর পেজ",
"unset": "নির্ধারণ করা হয়নি"
},
"tray": {
"label": "ট্রে",
"submenu": {
"disabled": "ট্রে বন্ধ",
"enabled-and-hide-app": "ট্রে চালু ও অ্যাপ লুকান",
"enabled-and-show-app": "ট্রে চালু ও অ্যাপ দেখান",
"play-pause-on-click": "ট্রে ক্লিক করলে প্লে/পজ"
}
},
"visual-tweaks": {
"label": "দৃষ্টিনন্দন পরিবর্তন",
"submenu": {
"custom-window-title": {
"label": "কাস্টম উইন্ডো টাইটেল",
"prompt": {
"label": "নিজস্ব উইন্ডোর টাইটেল দিন (বন্ধ করতে ফাঁকা রাখুন)",
"placeholder": "উদাহরণস্বরূপ: {{applicationName}}"
}
},
"like-buttons": {
"default": "ডিফল্ট লাইক বাটন",
"force-show": "সবসময় লাইক বাটন দেখান",
"hide": "লাইক বাটন লুকান",
"label": "লাইক বাটনের নিয়ন্ত্রণ",
"swap": "বাটনের ধারাবাহিকতা পাল্টাও"
},
"remove-upgrade-button": "আপগ্রেড বাটন সরান",
"theme": {
"dialog": {
"button": {
"cancel": "বাতিল",
"remove": "থিম সরান"
},
"remove-theme": "থিম সরাতে চান?",
"remove-theme-message": "এটি কাস্টম থিমটি বাদ দিয়ে দিবে"
},
"label": "থিম",
"submenu": {
"import-css-file": "CSS ফাইল ইমপোর্ট করুন",
"no-theme": "কোন থিম নেই"
}
}
}
}
}
},
"plugins": {
"enabled": "প্লাগইন চালু",
"label": "প্লাগইন",
"new": "নতুন প্লাগইন"
},
"view": {
"label": "দেখুন",
"submenu": {
"force-reload": "জোর করে রিফ্রেশ করুন",
"reload": "রিফ্রেশ করুন",
"reset-zoom": "জুম রিসেট করুন",
"toggle-fullscreen": "ফুলস্ক্রিন চালু/বন্ধ",
"zoom-in": "বড় করুন",
"zoom-out": "ছোট করুন"
}
}
},
"tray": {
"next": "নেক্সট",
"play-pause": "চালু/বন্ধ",
"previous": "পূর্ববর্তী",
"quit": "বন্ধ",
"restart": "অ্যাপ পুনরায় চালু করুন",
"show": "উইন্ডো দেখান",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "বিজ্ঞাপন চললে, এটি অডিও বন্ধ করে দেয় এবং প্লেব্যাক গতি ১৬ গুণ করে দেয়",
"name": "বিজ্ঞাপন দ্রুত করুন"
},
"adblocker": {
"description": "সব বিজ্ঞাপন ও ট্র্যাকিং শুরু থেকেই ব্লক করুন",
"menu": {
"blocker": "ব্লকার"
},
"name": "এড ব্লকার"
},
"album-actions": {
"description": "প্লেলিস্ট বা অ্যালবামের সব গানেই প্রয়োগ করতে আনডিসলাইক, ডিসলাইক, লাইক ও আনলাইক বাটন যোগ করে",
"name": "অ্যালবাম অ্যাকশনসমূহ"
},
"album-color-theme": {
"description": "অ্যালবামের রঙের উপর ভিত্তি করে ডাইনামিক থিম ও ভিজ্যুয়াল ইফেক্ট প্রয়োগ করুন",
"menu": {
"color-mix-ratio": {
"label": "কালার মিক্স রেশিও",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "সিকবার এর রঙ সক্রিয় করো"
},
"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": "পরিবেষ্টিত মোড"
},
"amuse": {
"description": "ইউটিউব মিউজিকে অ্যামিউজ সমর্থন যোগ করা হয়েছে যা এখন ৬কে ল্যাবস উইজেটে চলছে",
"name": "মনোরঞ্জন",
"response": {
"query": "অ্যামিউজ API সার্ভার চলছে। /query ব্যবহার করে গান সম্পর্কিত তথ্য পান।"
}
},
"api-server": {
"description": "প্লেয়ার নিয়ন্ত্রণের জন্য একটি API সার্ভার যোগ করা হয়েছে",
"dialog": {
"request": {
"buttons": {
"allow": "অনুমতি দিন",
"deny": "অস্বীকার করা"
},
"message": "{{ID}} ({{origin}}) কে API ব্যবহারের অনুমতি দিবেন?",
"title": "API অথোরাইজ এর অনুরোধ"
}
},
"menu": {
"auth-strategy": {
"label": "অথোরাইজেশন স্ট্র্যাটেজি",
"submenu": {
"auth-at-first": {
"label": "প্রথম অনুরোধে অথোরাইজ করুন"
},
"none": {
"label": "কোন অথোরাইজেশন নেই"
}
}
},
"hostname": {
"label": "হোস্টনেম"
},
"https": {
"label": "HTTPS ও অনুমতিপত্র",
"submenu": {
"cert": {
"dialogTitle": "HTTPS অনুমতিপত্র ফাইল নির্বাচন করো",
"label": "অনুমতিপত্র ফাইল (.crt/.pem)"
},
"enable-https": {
"label": "HTTPS সক্রিয় করো"
},
"key": {
"dialogTitle": "HTTPS ব্যক্তিগত চাবির ফাইল নির্বাচন করো",
"label": "ব্যক্তিগত চাবির ফাইল (.key/.pem)"
}
}
},
"port": {
"label": "পোর্ট"
}
},
"name": "API সার্ভার [বেটা]",
"prompt": {
"hostname": {
"label": "API সার্ভারের জন্য হোস্টনেম (যেমন 0.0.0.0) লিখুন:",
"title": "হোস্টনেম"
},
"port": {
"label": "API সার্ভারের জন্য পোর্ট লিখুন:",
"title": "পোর্ট"
}
}
},
"audio-compressor": {
"description": "অডিওতে কম্প্রেশন প্রয়োগ করুন (উচ্চতর শব্দের অংশগুলোর ভলিউম কমিয়ে দেয় এবং নীরবতম অংশগুলোর ভলিউম বাড়িয়ে দেয়)",
"name": "অডিও কম্প্রেসর"
},
"auth-proxy-adapter": {
"description": "অথেনটিকেশন প্রক্সি সার্ভিস ব্যবহারের সাপোর্ট",
"menu": {
"disable": "প্রক্সি অ্যাডাপ্টার বন্ধ করুন",
"enable": "প্রক্সি অ্যাডাপ্টার চালু করুন",
"hostname": {
"label": "হোস্টনেম"
},
"port": {
"label": "পোর্ট"
}
},
"name": "অথেনটিকেশন প্রক্সি অ্যাডাপ্টার",
"prompt": {
"hostname": {
"label": "লোকাল প্রক্সি সার্ভারের জন্য হোস্টনেম লিখুন (পুনরায় চালু করতে হবে):",
"title": "প্রক্সি হোস্টনেম"
},
"port": {
"label": "লোকাল প্রক্সি সার্ভারের জন্য পোর্ট লিখুন (পুনরায় চালু করতে হবে):",
"title": "প্রক্সি পোর্ট"
}
}
},
"blur-nav-bar": {
"description": "নেভিগেশন বারকে স্বচ্ছ এবং ঝাপসা করে",
"name": "নেভিগেশন বার ঝাপসা করুন"
},
"bypass-age-restrictions": {
"description": "ইউটিউবের বয়স যাচাইকরণ এড়িয়ে যান",
"name": "বয়স সীমাবদ্ধতা এড়ান"
},
"captions-selector": {
"description": "ইউটিউব মিউজিক অডিও ট্র্যাকের জন্য ক্যাপশন নির্বাচক",
"menu": {
"autoload": "সর্বশেষ ব্যবহৃত ক্যাপশন স্বয়ংক্রিয়ভাবে নির্বাচন করুন",
"disable-captions": "ডিফল্টভাবে কোন ক্যাপশন নেই"
},
"name": "ক্যাপশন নির্বাচক",
"prompt": {
"selector": {
"label": "বর্তমান ক্যাপশন ভাষা: {{language}}",
"none": "কোনোটি নয়",
"title": "ক্যাপশন ভাষা নির্বাচন করুন"
}
},
"templates": {
"title": "ক্যাপশন নির্বাচক খুলুন"
},
"toast": {
"caption-changed": "ক্যাপশন {{language}} ভাষায় পরিবর্তিত হয়েছে",
"caption-disabled": "ক্যাপশন বন্ধ করা হয়েছে",
"no-captions": "এই গানটির জন্য কোনো ক্যাপশন উপলব্ধ নেই"
}
},
"clock": {
"description": "ন্যাভিগেশন বারে ঘড়ি যোগ করো",
"menu": {
"format": {
"24-hour-format": "২৪ ঘণ্টায় দেখাও",
"display-seconds": "সেকেন্ডে দেখাও",
"label": "গঠন"
}
},
"name": "ঘড়ি"
},
"compact-sidebar": {
"description": "সাইডবারকে সবসময় কম্প্যাক্ট মোডে সেট করুন",
"name": "কম্প্যাক্ট সাইডবার"
},
"crossfade": {
"description": "গানগুলির মধ্যে ক্রসফেড করুন",
"menu": {
"advanced": "এডভান্স"
},
"name": "ক্রসফেড [বেটা]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "ফেড-ইন সময়কাল (ms)",
"fade-out-duration": "ফেড-আউট সময়কাল (ms)",
"fade-scaling": {
"label": "ফেড স্কেলিং",
"linear": "রৈখিক",
"logarithmic": "লগারিদমিক"
},
"seconds-before-end": "শেষ হওয়ার N সেকেন্ড আগে ক্রসফেড করুন"
},
"title": "ক্রসফেড অপশনসমূহ"
}
}
},
"custom-output-device": {
"menu": {
"device-selector": "ডিভাইস নির্বাচন করো"
},
"name": "আউটপুট ডিভাইস নির্বাচন করো"
},
"disable-autoplay": {
"description": "গান \"পজ\" মোডে শুরু করে",
"menu": {
"apply-once": "শুধুমাত্র স্টার্টআপে প্রয়োগ হয়"
},
"name": "অটোপ্লে বন্ধ করুন"
},
"discord": {
"backend": {
"already-connected": "একটিভ সংযোগের সাথে সংযোগ করার চেষ্টা করা হয়েছে",
"connected": "ডিসকর্ডের সাথে সংযুক্ত",
"disconnected": "ডিসকর্ড থেকে সংযোগ বিচ্ছিন্ন"
},
"description": "রিচ প্রেজেন্স ব্যবহার করে আপনি কি শুনছেন তা আপনার বন্ধুদের দেখান",
"menu": {
"auto-reconnect": "স্বয়ংক্রিয় রিকানেক্ট",
"clear-activity": "কার্যকলাপ মুছুন",
"clear-activity-after-timeout": "সময়সীমা শেষ হওয়ার পরে কার্যকলাপ মুছুন",
"connected": "সংযুক্ত",
"disconnected": "সংযোগ বিচ্ছিন্ন",
"hide-duration-left": "অবশিষ্ট সময় লুকান",
"hide-github-button": "গিটহাব লিঙ্ক বাটন লুকান",
"play-on-application": "ইউটিউব মিউজিকে চালান",
"set-inactivity-timeout": "নিষ্ক্রিয়তার সময়সীমা সেট করুন"
},
"name": "ডিসকর্ড রিচ প্রেজেন্স",
"prompt": {
"set-inactivity-timeout": {
"label": "নিষ্ক্রিয়তার সময়সীমা সেকেন্ডে লিখুন:",
"title": "নিষ্ক্রিয়তার সময়সীমা সেট করুন"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "ঠিক আছে"
},
"message": "আহ! দুঃখিত, ডাউনলোড ব্যর্থ হয়েছে…",
"title": "ডাউনলোডে সমস্যা!"
},
"start-download-playlist": {
"buttons": {
"ok": "ঠিক আছে"
},
"detail": "({{playlistSize}} টি গান)",
"message": "প্লেলিস্ট {{playlistTitle}} ডাউনলোড করা হচ্ছে",
"title": "ডাউনলোড শুরু হয়েছে"
}
},
"feedback": {
"conversion-progress": "রূপান্তর: {{percent}}%",
"converting": "রূপান্তর করা হচ্ছে…",
"done": "সম্পন্ন: {{filePath}}",
"download-info": "ডাউনলোড হচ্ছে {{artist}} - {{title}} [{{videoId}}",
"download-progress": "ডাউনলোড: {{percent}}%",
"downloading": "ডাউনলোড হচ্ছে…",
"downloading-counter": "ডাউনলোড হচ্ছে {{current}}/{{total}}…",
"downloading-playlist": "প্লেলিস্ট \"{{playlistTitle}}\" - {{playlistSize}}টি গান ({{playlistId}}) ডাউনলোড হচ্ছে",
"error-while-downloading": "\"{{author}} - {{title}}\" ডাউনলোড করতে সমস্যা: {{error}}",
"folder-already-exists": "{{playlistFolder}} ফোল্ডারটি ইতিমধ্যে বিদ্যমান",
"getting-playlist-info": "প্লেলিস্ট তথ্য নেওয়া হচ্ছে…",
"loading": "লোড হচ্ছে…",
"playlist-has-only-one-song": "প্লেলিস্টে শুধুমাত্র একটি আইটেম আছে, সরাসরি ডাউনলোড করা হচ্ছে",
"playlist-id-not-found": "কোনো প্লেলিস্ট আইডি পাওয়া যায়নি",
"playlist-is-empty": "প্লেলিস্ট খালি",
"playlist-is-mix-or-private": "প্লেলিস্ট তথ্য পেতে সমস্যা: নিশ্চিত করুন এটি প্রাইভেট বা \"আপনার জন্য মিক্সড\" প্লেলিস্ট নয়\n\n{{error}}",
"preparing-file": "ফাইল প্রস্তুত করা হচ্ছে…",
"saving": "সংরক্ষণ করা হচ্ছে…",
"trying-to-get-playlist-id": "প্লেলিস্ট আইডি পাওয়ার চেষ্টা করা হচ্ছে: {{playlistId}}",
"video-id-not-found": "ভিডিও পাওয়া যায়নি",
"writing-id3": "ID3 ট্যাগ লেখা হচ্ছে…"
}
},
"description": "ইন্টারফেস থেকে সরাসরি MP3 / উৎস অডিও ডাউনলোড করে",
"menu": {
"choose-download-folder": "ডাউনলোড ফোল্ডার বেছে নিন",
"download-finish-settings": {
"label": "শেষ হলে ডাউনলোড করুন",
"prompt": {
"last-percent": "x শতাংশ পরে",
"last-seconds": "শেষ x সেকেন্ড",
"title": "কখন ডাউনলোড করবেন তা কনফিগার করুন"
},
"submenu": {
"advanced": "উন্নত",
"enabled": "সক্রিয়",
"mode": "টাইম মোড",
"percent": "শতাংশ",
"seconds": "সেকেন্ড"
}
},
"download-playlist": "প্লেলিস্ট ডাউনলোড করুন",
"presets": "প্রিসেট",
"skip-existing": "বিদ্যমান ফাইলগুলি এড়িয়ে যান"
},
"name": "ডাউনলোডার",
"renderer": {
"can-not-update-progress": "প্রগ্রেস আপডেট করা যাচ্ছে না"
},
"templates": {
"button": "ডাউনলোড"
}
},
"equalizer": {
"description": "প্লেয়ারে একটি ইকুয়ালাইজার যোগ করে",
"menu": {
"presets": {
"label": "প্রিসেট",
"list": {
"bass-booster": "বেস বুস্টার"
}
}
},
"name": "ইকুয়ালাইজার"
},
"exponential-volume": {
"description": "ভলিউম স্লাইডারকে এক্সপোটেনশিয়াল করে তোলে যাতে কম ভলিউম নির্বাচন করা সহজ হয়।",
"name": "এক্সপোটেনশিয়াল ভলিউম"
},
"in-app-menu": {
"description": "মেনু-বারগুলোকে আকর্ষণীয়, গাঢ় বা অ্যালবাম-রঙের চেহারা দেয়",
"menu": {
"hide-dom-window-controls": "DOM উইন্ডো কন্ট্রোলগুলো লুকান"
},
"name": "অ্যাপ-ভিতরের মেনু"
},
"lumiastream": {
"description": "লুমিয়া স্ট্রিম সমর্থন যোগ করে",
"name": "লুমিয়া স্ট্রিম [বেটা]"
},
"lyrics-genius": {
"description": "বেশিরভাগ গানের জন্য লিরিক্স সমর্থন যোগ করে",
"menu": {
"romanized-lyrics": "রোমানাইজড লিরিক্স"
},
"name": "লিরিক্স জিনিয়াস",
"renderer": {
"fetched-lyrics": "জিনিয়াসের জন্য লিরিক্স সংগ্রহ করা হয়েছে"
}
},
"music-together": {
"description": "অন্যদের সাথে প্লেলিস্ট শেয়ার করুন। যখন হোস্ট একটি গান বাজায়, অন্য সবাই একই গানটি শুনবে",
"dialog": {
"enter-host": "হোস্ট আইডি লিখুন"
},
"internal": {
"save": "সংরক্ষণ করুন",
"track-source": "ট্র্যাক উৎস",
"unknown-user": "অজানা ব্যবহারকারী"
},
"menu": {
"click-to-copy-id": "হোস্ট আইডি কপি করুন",
"close": "মিউজিক টুগেদার বন্ধ করুন",
"connected-users": "সংযুক্ত ব্যবহারকারীরা",
"disconnect": "মিউজিক টুগেদার সংযোগ বিচ্ছিন্ন করুন",
"empty-user": "কোন সংযুক্ত ব্যবহারকারী নেই",
"host": "মিউজিক টুগেদার হোস্ট",
"join": "মিউজিক টুগেদারে যোগ দিন",
"permission": {
"all": "অতিথিদের প্লেলিস্ট এবং প্লেয়ার নিয়ন্ত্রণ করতে দিন",
"host-only": "শুধুমাত্র হোস্ট প্লেলিস্ট এবং প্লেয়ার নিয়ন্ত্রণ করতে পারবেন",
"playlist": "অতিথিদের প্লেলিস্ট নিয়ন্ত্রণ করতে দিন"
},
"set-permission": "নিয়ন্ত্রণ অনুমতি পরিবর্তন করুন",
"status": {
"disconnected": "সংযোগ বিচ্ছিন্ন",
"guest": "অতিথি হিসাবে সংযুক্ত",
"host": "হোস্ট হিসাবে সংযুক্ত"
}
},
"name": "মিউজিক টুগেদার [বেটা]",
"toast": {
"add-song-failed": "গান যোগ করা ব্যর্থ হয়েছে",
"closed": "মিউজিক টুগেদার বন্ধ হয়েছে",
"disconnected": "মিউজিক টুগেদার সংযোগ বিচ্ছিন্ন হয়েছে",
"host-failed": "মিউজিক টুগেদার হোস্ট করা ব্যর্থ হয়েছে",
"id-copied": "হোস্ট আইডি ক্লিপবোর্ডে কপি করা হয়েছে",
"id-copy-failed": "হোস্ট আইডি ক্লিপবোর্ডে কপি করা ব্যর্থ হয়েছে",
"join-failed": "মিউজিক টুগেদারে যোগদান ব্যর্থ হয়েছে",
"joined": "মিউজিক টুগেদারে যোগ দেওয়া হয়েছে",
"permission-changed": "মিউজিক টুগেদার অনুমতি \"{{permission}}\" এ পরিবর্তন করা হয়েছে",
"remove-song-failed": "গান সরানো ব্যর্থ হয়েছে",
"user-connected": "{{name}} মিউজিক টুগেদারে যোগ দিয়েছেন",
"user-disconnected": "{{name}} মিউজিক টুগেদার ছেড়ে চলে গেছেন"
}
},
"navigation": {
"description": "পরবর্তী/পূর্ববর্তী নেভিগেশন তীরগুলি আপনার প্রিয় ব্রাউজারের মতো সরাসরি ইন্টারফেসে অন্তর্ভুক্ত করা হয়েছে",
"name": "নেভিগেশন",
"templates": {
"back": {
"title": "আগের পাতায় যান"
},
"forward": {
"title": "পরের পাতায় যান"
}
}
},
"no-google-login": {
"description": "ইন্টারফেস থেকে Google লগইন বাটন এবং লিঙ্কগুলি সরান",
"name": "গুগল লগইন নয়"
},
"notifications": {
"description": "একটি গান বাজতে শুরু হলে একটি বিজ্ঞপ্তি প্রদর্শন করে (উইন্ডোজে ইন্টারঅ্যাকটিভ বিজ্ঞপ্তিগুলি উপলব্ধ)",
"menu": {
"interactive": "ইন্টারঅ্যাকটিভ বিজ্ঞপ্তিসমূহ",
"interactive-settings": {
"label": "ইন্টারঅ্যাকটিভ সেটিংস",
"submenu": {
"hide-button-text": "বাটন টেক্সট লুকান",
"refresh-on-play-pause": "প্লে/পজে রিফ্রেশ করুন",
"tray-controls": "ট্রে ক্লিকে খুলুন/বন্ধ করুন"
}
},
"priority": "বিজ্ঞপ্তি অগ্রাধিকার",
"toast-style": "টোস্ট স্টাইল",
"unpause-notification": "বিরতি থেকে ফিরলে বিজ্ঞপ্তি দেখান"
},
"name": "বিজ্ঞপ্তিসমূহ"
},
"performance-improvement": {
"description": "পরীক্ষামূলক স্ক্রিপ্টগুলি সক্ষম করে পারফরম্যান্স উন্নত করুন",
"name": "পারফরম্যান্স উন্নতিকরণ [বেটা]"
},
"picture-in-picture": {
"description": "অ্যাপকে পিকচার-ইন-পিকচার মোডে স্যুইচ করতে অনুমতি দেয়",
"menu": {
"always-on-top": "সবসময় উপরে",
"hotkey": {
"label": "হটকি",
"prompt": {
"keybind-options": {
"hotkey": "হটকি"
},
"label": "পিকচার-ইন-পিকচার টগল করার জন্য একটি হটকি নির্বাচন করুন",
"title": "পিকচার-ইন-পিকচার হটকি"
}
},
"save-window-position": "উইন্ডো অবস্থান সংরক্ষণ করুন",
"save-window-size": "উইন্ডো আকার সংরক্ষণ করুন",
"use-native-pip": "ব্রাউজারের নেটিভ PiP ব্যবহার করুন"
},
"name": "পিকচার-ইন-পিকচার",
"templates": {
"button": "পিকচার-ইন-পিকচার"
}
},
"playback-speed": {
"description": "দ্রুত শুনুন, ধীরে শুনুন! গানের গতি নিয়ন্ত্রণ করার জন্য একটি স্লাইডার যোগ করে",
"name": "প্লেব্যাক স্পিড",
"templates": {
"button": "গতি"
}
},
"precise-volume": {
"description": "মাউসহুইল/হটকি ব্যবহার করে সঠিকভাবে ভলিউম নিয়ন্ত্রণ করুন, কাস্টম HUD এবং কাস্টমাইজযোগ্য ভলিউম স্টেপ সহ",
"menu": {
"arrows-shortcuts": "লোকাল অ্যারো-কি কন্ট্রোল",
"custom-volume-steps": "কাস্টম ভলিউম স্টেপ সেট করুন",
"global-shortcuts": "গ্লোবাল হটকি"
},
"name": "নির্ভুল ভলিউম",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "ভলিউম কমান",
"increase": "ভলিউম বাড়ান"
},
"label": "গ্লোবাল ভলিউম কিবাইন্ড নির্বাচন করুন:",
"title": "গ্লোবাল ভলিউম কিবাইন্ড"
},
"volume-steps": {
"label": "ভলিউম বৃদ্ধি/হ্রাস স্টেপ নির্বাচন করুন",
"title": "ভলিউম স্টেপস"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "বর্তমান কোয়ালিটি: {{quality}}",
"message": "ভিডিও কোয়ালিটি নির্বাচন করুন:",
"title": "ভিডিও কোয়ালিটি নির্বাচন করুন"
}
}
},
"description": "ভিডিও ওভারলেতে একটি বাটনের মাধ্যমে ভিডিও কোয়ালিটি পরিবর্তন করতে দেয়",
"name": "ভিডিও কোয়ালিটি পরিবর্তক",
"renderer": {
"quality-settings-button": {
"label": "প্লেয়ারের মান পরিবর্তনের অপশন খুলুন"
}
}
},
"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-alternative-title": "বিকল্প শিরোনাম ব্যবহার করুন",
"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 চালু করে অনুসন্ধান করতে, মিডিয়া কীগুলির জন্য 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": "স্পনসরব্লক"
},
"synced-lyrics": {
"description": "LRClib এর মত প্রোভাইডার ব্যবহার করে গানের সাথে সিঙ্ক করা লিরিক্স প্রদান করে।",
"errors": {
"fetch": "⚠️\tলিরিক্স আনার সময় একটি ত্রুটি ঘটেছে।\n\tঅনুগ্রহ করে পরে আবার চেষ্টা করুন।",
"not-found": "⚠️ এই গানের জন্য কোন লিরিক্স পাওয়া যায়নি।"
},
"menu": {
"convert-chinese-character": {
"submenu": {
"disabled": {
"label": "নিষ্ক্রিয়"
}
}
},
"default-text-string": {
"label": "লিরিক্সের মাঝে ডিফল্ট অক্ষর",
"tooltip": "লিরিক্সের মধ্যে ফাঁকের জন্য ব্যবহৃত ডিফল্ট অক্ষর নির্বাচন করুন"
},
"line-effect": {
"label": "লাইন ইফেক্ট",
"submenu": {
"fancy": {
"label": "ফ্যান্সি",
"tooltip": "বর্তমান লাইনে বড়, অ্যাপের মত ইফেক্ট ব্যবহার করুন"
},
"focus": {
"label": "ফোকাস",
"tooltip": "শুধুমাত্র বর্তমান লাইনটি সাদা করুন"
},
"offset": {
"label": "অফসেট",
"tooltip": "বর্তমান লাইনকে ডানদিকে অফসেট করুন"
},
"scale": {
"label": "স্কেল",
"tooltip": "বর্তমান লাইন স্কেল করুন"
}
},
"tooltip": "বর্তমান লাইনে প্রয়োগ করার জন্য ইফেক্ট নির্বাচন করুন"
},
"precise-timing": {
"label": "লিরিক্স পুরোপুরি সিঙ্ক করুন",
"tooltip": "পরবর্তী লাইন প্রদর্শনের মিলিসেকেন্ড পর্যন্ত গণনা করুন (পারফরম্যান্সে সামান্য প্রভাব পড়তে পারে)"
},
"preferred-provider": {
"label": "পছন্দের প্রোভাইডার",
"none": {
"label": "কোনটি নয়",
"tooltip": "কোন প্রোভাইডার নির্বাচন করা হয়নি"
},
"tooltip": "সহজাত প্রোভাইডার নির্বাচন করো"
},
"romanization": {
"label": "লিরিক্স রোমানাইজ করুন",
"tooltip": "যদি লিরিক্স ভিন্ন ভাষায় হয়, তাহলে একটি ল্যাটিন সংস্করণ প্রদর্শন করার চেষ্টা করুন।"
},
"show-lyrics-even-if-inexact": {
"label": "অনির্ভুল হলেও লিরিক্স দেখান",
"tooltip": "যদি গানটি না পাওয়া যায়, প্লাগইনটি একটি ভিন্ন অনুসন্ধান কোয়েরি দিয়ে আবার চেষ্টা করে।\nদ্বিতীয় প্রচেষ্টার ফলাফল সঠিক নাও হতে পারে।"
},
"show-time-codes": {
"label": "টাইম কোড দেখান",
"tooltip": "লিরিক্সের পাশে টাইম কোড দেখান"
}
},
"name": "সিঙ্ক করা লিরিক্স",
"refetch-btn": {
"fetching": "আনছে..।",
"normal": "লিরিক্স পুনরায় আনুন"
},
"warnings": {
"duration-mismatch": "⚠️ - সময়কাল মিলে না যাওয়ার কারণে লিরিক্স সিঙ্ক হতে নাও পারে।",
"inexact": "⚠️ - এই গানের লিরিক্স সঠিক নাও হতে পারে",
"instrumental": "⚠️ - এটি একটি ইনস্ট্রুমেন্টাল গান"
}
},
"taskbar-mediacontrol": {
"description": "উইন্ডোজ টাস্কবার থেকে প্লেব্যাক নিয়ন্ত্রণ করুন",
"name": "টাস্কবার মিডিয়া কন্ট্রোল"
},
"touchbar": {
"description": "macOS ব্যবহারকারীদের জন্য একটি টাচবার উইজেট যোগ করে",
"name": "টাচবার"
},
"transparent-player": {
"description": "অ্যাপ উইন্ডো স্বচ্ছ করে",
"menu": {
"opacity": {
"label": "স্বচ্ছতা"
},
"type": {
"label": "ধরণ",
"submenu": {
"acrylic": "অ্যাক্রিলিক",
"mica": "মিকা",
"none": "কোনটি নয়",
"tabbed": "ট্যাবকৃত"
}
}
},
"name": "স্বচ্ছ প্লেয়ার"
},
"tuna-obs": {
"description": "OBS এর প্লাগইন টুনার সাথে সংযোগ",
"name": "টুনা OBS"
},
"unobtrusive-player": {
"description": "গান চালানোর সময় প্লেয়ারকে পপ আপ হওয়া থেকে বিরত রাখে",
"name": "অনাড়ম্বর প্লেয়ার"
},
"video-toggle": {
"description": "ভিডিও/গান মোডের মধ্যে স্যুইচ করার জন্য একটি বাটন যোগ করে। ঐচ্ছিকভাবে সম্পূর্ণ ভিডিও ট্যাবও সরাতে পারে",
"menu": {
"align": {
"label": "সারিবদ্ধকরণ",
"submenu": {
"left": "বাম",
"middle": "মাঝখানে",
"right": "ডান"
}
},
"force-hide": "জোর করে ভিডিও ট্যাব সরান",
"mode": {
"label": "মোড",
"submenu": {
"custom": "কাস্টম টগল",
"disabled": "নিষ্ক্রিয়",
"native": "নেটিভ টগল"
}
}
},
"name": "ভিডিও টগল",
"templates": {
"button-song": "গান",
"button-video": "ভিডিও"
}
},
"visualizer": {
"description": "প্লেয়ারে একটি ভিজ্যুয়ালাইজার যোগ করে",
"menu": {
"visualizer-type": "ভিজ্যুয়ালাইজার প্রকার"
},
"name": "ভিজ্যুয়ালাইজার"
}
}
}
================================================
FILE: src/i18n/resources/bs.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Greška u izvršavanju dodatka {{pluginName}}::{{contextName}}",
"executed-at-ms": "Dodatak {{pluginName}}::{{contextName}} se izvršio za {{ms}}ms",
"initialize-failed": "Greška prilikom inicijalizacije dodatka \"{{pluginName}}\"",
"load-all": "Učitavanje svih dodataka",
"load-failed": "Greška u učitavanju dodatka \"{{pluginName}}\"",
"loaded": "Dodatak \"{{pluginName}}\" učitan",
"unload-failed": "Greška prilikom onesposobljavanja dodatka \"{{pluginName}}\"",
"unloaded": "Dodatak \"{{pluginName}}\" ugašen"
}
}
},
"language": {
"code": "ba",
"local-name": "Bosanski",
"name": "Bosnian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Završeno učitavanje. DevTools otvoren"
},
"i18n": {
"loaded": "i18n učitan"
},
"second-instance": {
"receive-command": "Comanda primljena preko protokola \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS datoteka \"{{cssFile}}\" ne postoji, ignorišem"
},
"unresponsive": {
"details": "Greška u aplikaciji!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Čistim predmemoriju aplikacije"
},
"window": {
"tried-to-render-offscreen": "Prozor se pokušao prikazati van okvira ekrana, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Meni je sakriven, koristite 'Alt' da ga prikazete (ili 'ESC' ako koristite meni u aplikaciji)",
"message": "Sakrivanje menija je uključeno",
"title": "Meni sakriven"
},
"need-to-restart": {
"buttons": {
"later": "Kasnije",
"restart-now": "Pokreni ponovo odmah"
},
"detail": "\"{{pluginName}}\" dodatak zahtjeva ponovno pokretanje kako bi se uključio",
"message": "\"{{pluginName}}\" potrebno je resetovat",
"title": "Restart je potreban"
},
"unresponsive": {
"buttons": {
"quit": "Napusti",
"relaunch": "Ponovo otvori",
"wait": "Pricekajte"
},
"detail": "Izvinjavamo se zbog zabune! molimo vas da odaberete sta zelite uciniti",
"message": "Aplikacija ne reagira",
"title": "Prozor ne reagira"
},
"update-available": {
"buttons": {
"disable": "Ugasite Nadogradnje",
"download": "Skinuti",
"ok": "OK"
},
"detail": "Nova verzija je dostupna i može biti skinuta na {{downloadLink}}",
"message": "Nova verzija je dostupna",
"title": "Azuriranje dostupno"
}
},
"menu": {
"about": "O nama",
"navigation": {
"label": "Plejer",
"submenu": {
"copy-current-url": "Kopirajte trenutni link",
"go-back": "Idi Nazad",
"go-forward": "Idi Naprijed",
"quit": "Izadji",
"restart": "Restartujte Aplikaciju"
}
},
"options": {
"label": "Opcije",
"submenu": {
"advanced-options": {
"label": "Napredne opcije",
"submenu": {
"auto-reset-app-cache": "Resetuje kes memoriju kad se aplikacija pokrene",
"disable-hardware-acceleration": "Ugasite hardversko ubrzanje",
"edit-config-json": "Uredite config.json",
"override-user-agent": "Nadjacaj User-Agent",
"restart-on-config-changes": "Ponovno pokretanje nakon promjena konfiguracije",
"set-proxy": {
"label": "Postavi proxy",
"prompt": {
"label": "Unesite adresu proxyja: (ostavite prazno za onemogućavanje)",
"placeholder": "Primjer: SOCKS5://127.0.0.1:9999",
"title": "Postavi proxy"
}
},
"toggle-dev-tools": "Uključi/isključi DevTools"
}
},
"always-on-top": "Uvijek na vrhu",
"auto-update": "Automatski Update",
"hide-menu": {
"dialog": {
"message": "Meni će biti skriven pri sljedećem pokretanju, koristite [Alt] da ga prikažete (ili upotrijebite [`] ako koristite meni u aplikaciji)",
"title": "Sakrij meni omogućen"
},
"label": "Sakrij meni"
},
"language": {
"dialog": {
"message": "Jezik će se promijeniti nakon ponovnog pokretanja",
"title": "Jezik je uspješno promjenjen"
},
"label": "Jezik",
"submenu": {
"to-help-translate": "Želite da pomognete s prijevodom? Kliknite ovdje"
}
},
"resume-on-start": "Nastavi posljednju pjesmu pri sljedećem pokretaju",
"single-instance-lock": "Sprječavanje višestrukog pokretanja",
"start-at-login": "Pokreni čim se prijavite",
"starting-page": {
"label": "Početna stranica",
"unset": "Ukinite postavu"
},
"tray": {
"label": "Tacna",
"submenu": {
"disabled": "Onemogućeno",
"enabled-and-hide-app": "Tacna je uključena, i prozor aplikacije skrijte",
"enabled-and-show-app": "Tacna je uključena, i prozor aplikacije prikažite",
"play-pause-on-click": "Pokreni/Zaustavi na klik"
}
},
"visual-tweaks": {
"label": "Vizualne postavke",
"submenu": {
"custom-window-title": {
"label": "Prilagođeni naslov prozora",
"prompt": {
"label": "Unesite vlastiti naslov prozora: (ostavite prazno za isključenje)",
"placeholder": "Primjer: {{applicationName}}"
}
},
"like-buttons": {
"default": "Zadano",
"force-show": "Prinudno prikaži",
"hide": "Sakrij",
"label": "'Sviđa mi se' dugmadi"
},
"remove-upgrade-button": "Ukloni dugme za nadogradnju",
"theme": {
"dialog": {
"button": {
"cancel": "Otkaži",
"remove": "Ukloni"
},
"remove-theme": "Jeste li sigurni da želite ukloniti prilagođenu temu?",
"remove-theme-message": "Ovo će ukloniti prilagođenu temu"
},
"label": "Tema",
"submenu": {
"import-css-file": "Uvoz prilagođene CSS datoteke",
"no-theme": "Bez teme"
}
}
}
}
}
},
"plugins": {
"enabled": "Omogućeno",
"label": "Dodaci",
"new": "Novo"
},
"view": {
"label": "Pogled",
"submenu": {
"force-reload": "Silom Ponovo Učitaj",
"reload": "Ponovo Učitaj",
"reset-zoom": "Stvarna Veličina",
"toggle-fullscreen": "Uključi/Isključi Prikaz Cijelog Ekrana",
"zoom-in": "Uvećaj",
"zoom-out": "Umanji"
}
}
},
"tray": {
"next": "Slijedeće",
"play-pause": "Plej/Pauza",
"previous": "Prethodno",
"quit": "Izlaz",
"restart": "Ponovo Pokreni Aplikaciju",
"show": "Pokaži prozor",
"tooltip": {
"default": "{{applicationName}}"
}
}
},
"plugins": {
"ambient-mode": {
"menu": {
"quality": {
"label": "Kvalitet"
}
}
},
"discord": {
"menu": {
"set-status-display-type": {
"submenu": {
"application": "Slušate {{applicationName}}",
"artist": "Slušate {muzičar}",
"title": "Slušate {naziv pesme}"
}
}
},
"name": "Diskord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Unesi ograničenje neaktivnosti u sekundama:",
"title": "Postavi ograničenje neaktivnosti"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Ufff! Izvinite, preuzimanje nije uspelo…",
"title": "Greška u preuzimanju!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{Playlist Size}} pjesme)",
"message": "Preuzimanje Plejliste {{playlist Title}}",
"title": "Preuzimanje počelo"
}
},
"feedback": {
"conversion-progress": "Pretvaranje: {{percent}}%",
"converting": "Pretvaranje…",
"done": "Gotovo: {{file Path}}",
"download-info": "Preuzimanje {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Preuzimanje: {{percent}}%",
"downloading": "Preuzimanje…"
}
}
}
}
}
================================================
FILE: src/i18n/resources/ca.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Error en l'execució de l'extensió {{pluginName}}::{{contextName}}",
"executed-at-ms": "L'extensió {{pluginName}}::{{contextName}} s'ha executat als {{ms}}ms",
"initialize-failed": "Ha fallat la inicialització de l'extensió \"{{pluginName}}\"",
"load-all": "Carregant totes les extensions",
"load-failed": "Error en carregar l'extensió «{{pluginName}}»",
"loaded": "L'extensió «{{pluginName}}» s'ha carregat",
"unload-failed": "Error en deshabilitar l'extensió «{{pluginName}}»",
"unloaded": "Extensió «{{pluginName}}» deshabilitada"
}
}
},
"language": {
"code": "ca",
"local-name": "Català",
"name": "Catalan"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Càrrega finalitzada. S'han obert les DevTools"
},
"i18n": {
"loaded": "i18n carregat"
},
"second-instance": {
"receive-command": "Comanda rebuda a través del protocol: «{{command}}»"
},
"theme": {
"css-file-not-found": "L'arxiu CSS «{{cssFile}}» no existeix, s'ha ignorat"
},
"unresponsive": {
"details": "Error sense resposta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Netejant la memòria cau de l'aplicació"
},
"window": {
"tried-to-render-offscreen": "La finestra s'ha intentat mostrar fora de la pantalla, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "El menú es troba amagat, premi «Alt» per mostrar-lo (o «Escapament» si utilitza el menú integrat In-App)",
"message": "S'ha habilitat l'amagament del menú",
"title": "Amagament del menú habilitat"
},
"need-to-restart": {
"buttons": {
"later": "Més tard",
"restart-now": "Reinicia ara"
},
"detail": "L'extensió «{{pluginName}}» requereix reiniciar l'aplicació per fer tenir efecte",
"message": "\"{{pluginName}}\" necessita reiniciar-se",
"title": "Es requereix reiniciar"
},
"unresponsive": {
"buttons": {
"quit": "Marxar",
"relaunch": "Rellançar",
"wait": "Espera"
},
"detail": "Ho sentim per les molèsties! si us plau, tria què fer:",
"message": "L'aplicació ha deixat de respondre",
"title": "La finestra ha deixat de respondre"
},
"update-available": {
"buttons": {
"disable": "Deshabilita les actualitzacions",
"download": "Descarrega",
"ok": "D'acord"
},
"detail": "Hi ha una nova versió disponible i pot ser descarregada a {{downloadLink}}",
"message": "Hi ha una nova versió disponible",
"title": "Actualització disponible"
}
},
"menu": {
"about": "Quant a",
"navigation": {
"label": "Navegació",
"submenu": {
"copy-current-url": "Copia l'URL actual",
"go-back": "Ves enrere",
"go-forward": "Ves endavant",
"quit": "Surt",
"restart": "Reinicia l'aplicació"
}
},
"options": {
"label": "Opcions",
"submenu": {
"advanced-options": {
"label": "Opcions avançades",
"submenu": {
"auto-reset-app-cache": "Reinicialitza la memòria cau de l'aplicació quan es reiniciï",
"disable-hardware-acceleration": "Deshabilita l'acceleració per hardware",
"edit-config-json": "Edita el config.json",
"override-user-agent": "Sobreescriu l'agent d'usuari (User-Agent)",
"restart-on-config-changes": "Reinicia quan es canviï la configuració",
"set-proxy": {
"label": "Definir servidor intermediari (proxy)",
"prompt": {
"label": "Introduir l'adreça del servidor intermediari: (deixar en blanc per deshabilitar)",
"placeholder": "Exemple: SOCKS5://127.0.0.1:9999",
"title": "Definir servidor intermediari (proxy)"
}
},
"toggle-dev-tools": "Commuta les DevTools"
}
},
"always-on-top": "Mostra sempre per sobre",
"auto-update": "Actualitza automàticament",
"hide-menu": {
"dialog": {
"message": "El menú s'amagarà la següent vegada que s'iniciï l'aplicació, prem «Alt» per mostrar-lo (o accent obert « ` » si utilitza el menú integrat In-App)",
"title": "Amagament del menú habilitat"
},
"label": "Amaga el menú"
},
"language": {
"dialog": {
"message": "L'idioma es canviarà un cop es reiniciï",
"title": "Idioma canviat"
},
"label": "Idioma",
"submenu": {
"to-help-translate": "Vols ajudar a traduir? Clica aquí"
}
},
"resume-on-start": "Reprèn l'última cançó quan s'inicia l'aplicació",
"single-instance-lock": "Bloqueja en una única instància",
"start-at-login": "Obre a l'iniciar sessió",
"starting-page": {
"label": "Pàgina d'inici",
"unset": "Sense establir"
},
"tray": {
"label": "Safata d'icones",
"submenu": {
"disabled": "Deshabilitat",
"enabled-and-hide-app": "Mostra la icona i amaga l'aplicació",
"enabled-and-show-app": "Mostra la icona i mostra l'aplicació",
"play-pause-on-click": "Reprodueix / pausa en clicar"
}
},
"visual-tweaks": {
"label": "Opcions visuals",
"submenu": {
"custom-window-title": {
"label": "Títol personalitzat de la finestra",
"prompt": {
"label": "Introdueix un títol personalitzat per a la finestra (deixa-ho buit per deshabilitar-ho)",
"placeholder": "Exemple: {{applicationName}}"
}
},
"like-buttons": {
"default": "Per defecte",
"force-show": "Força que es mostri",
"hide": "Amaga",
"label": "Botons de «m'agrada»",
"swap": "Intercambiar l'ordre dels botons de m'agrada"
},
"remove-upgrade-button": "Elimina el botó «Actualitza a Music Premium»",
"theme": {
"dialog": {
"button": {
"cancel": "Cancel·la",
"remove": "Elimina"
},
"remove-theme": "De debó vols eliminar el tema personalitzat?",
"remove-theme-message": "Això eliminarà el tema personalitzat"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importa un arxiu CSS personalitzat",
"no-theme": "Cap tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Habilitat",
"label": "Extensions",
"new": "NOU"
},
"view": {
"label": "Veure",
"submenu": {
"force-reload": "Força la recàrrega",
"reload": "Recarrega",
"reset-zoom": "Mida real",
"toggle-fullscreen": "Commuta la pantalla completa",
"zoom-in": "Apropa el zoom",
"zoom-out": "Allunya el zoom"
}
}
},
"tray": {
"next": "Següent",
"play-pause": "Reprodueix/Pausa",
"previous": "Anterior",
"quit": "Tanca",
"restart": "Reinicia l'aplicació",
"show": "Mostra la finestra",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Si es reprodueix un anunci, silencia l'àudio i el reprodueix a la velocitat 16x",
"name": "Accelera els anuncis"
},
"adblocker": {
"description": "Bloqueja tots els anuncis i el seguiment",
"menu": {
"blocker": "Bloquejador"
},
"name": "Bloquejador d'anuncis"
},
"album-actions": {
"description": "Afegeix botons de «no m'agrada / retirar el no m'agrada» i «m'agrada / retirar el m'agrada» per aplicar-ho a totes les cançons en una llista de reproducció o àlbum",
"name": "Accions a l'àlbum"
},
"album-color-theme": {
"description": "Aplica un tema dinàmic i efectes visuals basats en la paleta de colors de l'àlbum",
"menu": {
"color-mix-ratio": {
"label": "Proporció de la barreja de colors",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Activar el tema en la barra de progrés"
},
"name": "Tema de color de l'àlbum"
},
"ambient-mode": {
"description": "Aplica un efecte d'il·luminació que projecta colors difusos del vídeo al fons de la pantalla",
"menu": {
"blur-amount": {
"label": "Quantitat de desenfocament",
"submenu": {
"pixels": "{{blurAmount}} píxels"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacitat",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualitat",
"submenu": {
"pixels": "{{quality}} píxels"
}
},
"size": {
"label": "Mida",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transició suau",
"submenu": {
"during": "Durant {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Utilitza en pantalla completa"
}
},
"name": "Mode ambient"
},
"amuse": {
"description": "Afegeix suport a {{applicationName}} per el widget \"now playing\" d'Amuse per 6K Labs",
"name": "Amuse",
"response": {
"query": "L'API del servidor de Amuse està funcionant. GET /query per tenir informació de la cançó."
}
},
"api-server": {
"description": "Afegeix un servidor API per controlar el reproductor",
"dialog": {
"request": {
"buttons": {
"allow": "Permet",
"deny": "Denegar"
},
"message": "Permetre que {{ID}} ({{origin}}) accedeixi a l'API?",
"title": "Petició d'autorització API"
}
},
"menu": {
"auth-strategy": {
"label": "Estratègia d'autorització",
"submenu": {
"auth-at-first": {
"label": "Autoritza a la primera petició"
},
"none": {
"label": "Sense autorització"
}
}
},
"hostname": {
"label": "Nom del host"
},
"https": {
"label": "HTTPS i Certificats",
"submenu": {
"cert": {
"dialogTitle": "Seleccionar arxiu de certificat HTTPS",
"label": "Certificar fitxer (.crt/.pem)"
},
"enable-https": {
"label": "Habilitar HTTPS"
},
"key": {
"dialogTitle": "Selecciona arxiu de clau HTTPS privada",
"label": "Arxiu de clau privada (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "Servidor API [Beta]",
"prompt": {
"hostname": {
"label": "Introdueix el nom del host (per exemple 0.0.0.0) pel servidor API:",
"title": "Nom del host"
},
"port": {
"label": "Introdueix el port pel servidor API:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Aplica compressió a l'àudio (baixa el volum de les parts més sorolloses de la senyal d'àudio i puja el volum de les parts més fluixes)",
"name": "Compressió d'àudio"
},
"auth-proxy-adapter": {
"description": "Suport per l'ús de servidors d'autenticació proxy",
"menu": {
"disable": "Desactivar adaptador Proxy",
"enable": "Activar adaptador Proxy",
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "Adaptador de proxy d'autenticació",
"prompt": {
"hostname": {
"label": "Posa hostname pel servidor del proxy local (requereix reiniciar):",
"title": "Hostname del proxy"
},
"port": {
"label": "Entra un port pel servidor local del proxy (requereix reiniciar):",
"title": "Port proxy"
}
}
},
"blur-nav-bar": {
"description": "Desenfoca i aplica transparència a la barra de navegació",
"name": "Desenfoca la barra de navegació"
},
"bypass-age-restrictions": {
"description": "Esquiva la verificació d'edat de Music Player",
"name": "Esquiva les restriccions d'edat"
},
"captions-selector": {
"description": "Selector de subtítols per les pistes d'àudio de {{applicationName}}",
"menu": {
"autoload": "Selecciona automàticament l'últim subtítol emprat",
"disable-captions": "Sense subtítols per defecte"
},
"name": "Selector de subtítols",
"prompt": {
"selector": {
"label": "Idioma actual dels subtítols: {{language}}",
"none": "Cap",
"title": "Selecciona l'idioma dels subtítols"
}
},
"templates": {
"title": "Obra el selector de subtítols"
},
"toast": {
"caption-changed": "Subtítols canviats a {{language}}",
"caption-disabled": "Subtítols desactivats",
"no-captions": "Subtítols no disponibles per aquesta cançó"
}
},
"clock": {
"description": "Afegeix un rellotge a la barra de navegació",
"menu": {
"format": {
"24-hour-format": "Format 24 Hores",
"display-seconds": "Mostra els segons",
"label": "Format"
}
},
"name": "Rellotge"
},
"compact-sidebar": {
"description": "Sempre mostrar la barra lateral en mode compacte",
"name": "Barra lateral compacta"
},
"crossfade": {
"description": "Transició creuada (crossfade) entre cançons",
"menu": {
"advanced": "Avançat"
},
"name": "Transició creuada [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Durada de la transició d'entrada (ms)",
"fade-out-duration": "Durada de la transició de sortida (ms)",
"fade-scaling": {
"label": "Escala de la transició",
"linear": "Linear",
"logarithmic": "Logarítmica"
},
"seconds-before-end": "Transiciona N segons abans del final"
},
"title": "Opcions de transició creuada"
}
}
},
"custom-output-device": {
"description": "Configura un dispositiu multimèdia de sortida personalitzat per a cançons",
"menu": {
"device-selector": "Selecciona un dispositiu"
},
"name": "Dispositiu de sortida personalitzat",
"prompt": {
"device-selector": {
"label": "Trieu el dispositiu de sortida que s'utilitzarà",
"title": "Escull el dispositiu de sortida"
}
}
},
"disable-autoplay": {
"description": "Fa que la cançó comenci en mode «pausat»",
"menu": {
"apply-once": "Tan sols s'aplica a l'inici"
},
"name": "Deshabilita la reproducció automàtica"
},
"discord": {
"backend": {
"already-connected": "S'ha intentat connectar amb una connexió activa",
"connected": "Connectat a Discord",
"disconnected": "Desconnectat de Discord"
},
"description": "Mostra als teus amics allò que escoltes a l'estat d'activitat",
"menu": {
"auto-reconnect": "Reconnecta automàticament",
"clear-activity": "Esborra l'activitat",
"clear-activity-after-timeout": "Esborra l'activitat al cap d'un temps",
"connected": "Connectat",
"disconnected": "Desconnectat",
"hide-duration-left": "Amaga la durada restant",
"hide-github-button": "Amaga el botó de l'enllaç a GitHub",
"play-on-application": "Reprodueix a {{applicationName}}",
"set-inactivity-timeout": "Estableix temps d'espera d'inactivitat",
"set-status-display-type": {
"label": "Text d'estat",
"submenu": {
"application": "Escoltant {{applicationName}}",
"artist": "Escoltant {artist}",
"title": "Escoltant {song title}"
}
}
},
"name": "Estat d'activitat de Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Introdueix el temps d'espera d'inactivitat en segons:",
"title": "Estableix el temps d'espera d'inactivitat"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "D'acord"
},
"message": "Caram! Ho sentim, ha fallat la descàrrega…",
"title": "Error a la descàrrega!"
},
"start-download-playlist": {
"buttons": {
"ok": "D'acord"
},
"detail": "({{playlistSize}} cançons)",
"message": "Descarregant llista de reproducció {{playlistTitle}}",
"title": "Descàrrega començada"
}
},
"feedback": {
"conversion-progress": "Conversió: {{percent}}%",
"converting": "Convertint…",
"done": "Fet: {{filePath}}",
"download-info": "Descarregant {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Descàrrega: {{percent}}%",
"downloading": "Descarregant…",
"downloading-counter": "Descarregant {{current}}/{{total}}…",
"downloading-playlist": "Descarregant la llista de reproducció «{{playlistTitle}}» - {{playlistSize}} cançons ({{playlistId}})",
"error-while-downloading": "Error al descarregar «{{author}} - {{title}}»: {{error}}",
"folder-already-exists": "La carpeta {{playlistFolder}} ja existeix",
"getting-playlist-info": "Obtenint la informació de la llista de reproducció…",
"loading": "Carregant…",
"playlist-has-only-one-song": "La llista de reproducció té un sol element, descarregant-lo directament",
"playlist-id-not-found": "No s'ha trobat cap ID de llista de reproducció",
"playlist-is-empty": "La llista de reproducció és buida",
"playlist-is-mix-or-private": "Error obtenint la informació de la llista de reproducció: assegura't que no és una llista de reproducció privada o de «Mixos per a tu»\n\n{{error}}",
"preparing-file": "Preparant arxiu…",
"saving": "Desant…",
"trying-to-get-playlist-id": "Intentant obtenir l'ID de la llista de reproducció: {{playlistId}}",
"video-id-not-found": "Vídeo no trobat",
"writing-id3": "Escrivint les etiquetes ID3…"
}
},
"description": "Descarrega el MP3 / àudio d'origen directament des de la interfície",
"menu": {
"choose-download-folder": "Tria la carpeta de descàrrega",
"download-finish-settings": {
"label": "Descarrega en finalitzar",
"prompt": {
"last-percent": "Desprès del x percent",
"last-seconds": "Últims x segons",
"title": "Configura quan descarregar"
},
"submenu": {
"advanced": "Avançat",
"enabled": "Habilitat",
"mode": "Mode de temps",
"percent": "Percentatge",
"seconds": "Segons"
}
},
"download-playlist": "Descarrega la llista de reproducció",
"presets": "Configuracions predefinides",
"skip-existing": "Omet els arxius existents"
},
"name": "Descàrregues",
"renderer": {
"can-not-update-progress": "No es pot actualitzar el progrés"
},
"templates": {
"button": "Descarrega"
}
},
"equalizer": {
"description": "Afegeix un equalitzador al reproductor",
"menu": {
"presets": {
"label": "Predefinits",
"list": {
"bass-booster": "Augmentar baixos"
}
}
},
"name": "Equalitzador"
},
"exponential-volume": {
"description": "Fa que el control lliscant del volum sigui exponencial per que sigui més fàcil seleccionar volums més baixos.",
"name": "Volum exponencial"
},
"in-app-menu": {
"description": "Fa que la barra de menú superior tingui un elegant aspecte fosc o basat en el color de l'àlbum",
"menu": {
"hide-dom-window-controls": "Amaga els controls de la finestra del DOM"
},
"name": "Menú integrat In-App"
},
"lumiastream": {
"description": "Afegeix suport pel Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Afegeix suport per la lletra de la majoria de cançons",
"menu": {
"romanized-lyrics": "Lletra romanitzada"
},
"name": "Lletres de Genius",
"renderer": {
"fetched-lyrics": "S'ha buscat la lletra a Genius"
}
},
"music-together": {
"description": "Comparteix una llista de reproducció amb els demés. Quan l'amfitrió reprodueix una cançó, la resta també sentiran la mateixa",
"dialog": {
"enter-host": "Introdueix l'ID de l'amfitrió"
},
"internal": {
"save": "Desa",
"track-source": "Origen de la pista",
"unknown-user": "Usuari desconegut"
},
"menu": {
"click-to-copy-id": "Copia l'ID d'amfitrió",
"close": "Tanca el Music Together",
"connected-users": "Usuaris connectats",
"disconnect": "Desconnecta el Music Together",
"empty-user": "No hi ha usuaris connectats",
"host": "Amfitrió de Music Together",
"join": "Uneix-te a Music Together",
"permission": {
"all": "Permet que els convidats controlin la llista de reproducció i el reproductor",
"host-only": "Tan sols l'amfitrió pot controlar la llista de reproducció i el reproductor",
"playlist": "Permet que els convidats controlin la llista de reproducció"
},
"set-permission": "Canvia els permisos de control",
"status": {
"disconnected": "Desconnectat",
"guest": "Connectat com a convidat",
"host": "Connectat com amfitrió"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Error al afegir la cançó",
"closed": "Music Together tancat",
"disconnected": "Music Together desconnectat",
"host-failed": "No s'ha pogut començar el Music Together",
"id-copied": "L'ID d'amfitrió s'ha copiat al porta-retalls",
"id-copy-failed": "Error al copiar l'ID d'amfitrió al porta-retalls",
"join-failed": "Error al unir-se al Music Together",
"joined": "T'has unit al Music Together",
"permission-changed": "Els permisos de Music Together han canviat a «{{permission}}»",
"remove-song-failed": "Error al eliminar la cançó",
"user-connected": "{{name}} s'ha unit al Music Together",
"user-disconnected": "{{name}} s'ha desconnectat del Music Together"
}
},
"navigation": {
"description": "Fletxes de navegació Següent / Enrere integrades directament a la interfície, com al teu navegador preferit",
"name": "Navegació",
"templates": {
"back": {
"title": "Pàgina anterior"
},
"forward": {
"title": "Pàgina següent"
}
}
},
"no-google-login": {
"description": "Elimina els botons d'inici de sessió de Google de la interfície",
"name": "Amaga l'inici de sessió de Google"
},
"notifications": {
"description": "Mostra una notificació quan una cançó es comença a reproduir (les notificacions interactives estan disponibles a Windows)",
"menu": {
"interactive": "Notificacions interactives",
"interactive-settings": {
"label": "Configuració interactiva",
"submenu": {
"hide-button-text": "Amaga text del botó",
"refresh-on-play-pause": "Recarrega al Reproduir/Pausar",
"tray-controls": "Obra/Tanca en clicar a la safata"
}
},
"priority": "Prioritat de les notificacions",
"toast-style": "Estil dels missatges emergents",
"unpause-notification": "Mostra notificació en reprendre la reproducció"
},
"name": "Notificacions"
},
"performance-improvement": {
"description": "Millora el rendiment habilitant scripts experimentals",
"name": "Millora del rendiment [Beta]"
},
"picture-in-picture": {
"description": "Permet commutar el mode d'imatge en imatge (PiP)",
"menu": {
"always-on-top": "Mostra sempre a sobre",
"hotkey": {
"label": "Drecera del teclat",
"prompt": {
"keybind-options": {
"hotkey": "Drecera del teclat"
},
"label": "Tria una drecera per commutar el mode d'imatge en imatge (PiP)",
"title": "Drecera del mode imatge en imatge (PiP)"
}
},
"save-window-position": "Desa la posició de la finestra",
"save-window-size": "Desa la mida de la finestra",
"use-native-pip": "Utilitza l'imatge en imatge (PiP) nativa del navegador"
},
"name": "Imatge en imatge (PiP)",
"templates": {
"button": "Imatge en imatge (PiP)"
}
},
"playback-speed": {
"description": "Escolta-ho ràpid, escolta-ho lent! Afegeix un control lliscant per canviar la velocitat de la cançó",
"name": "Velocitat de la reproducció",
"templates": {
"button": "Velocitat"
}
},
"precise-volume": {
"description": "Controla el volum de manera precisa a través de la rodeta del ratolí / dreceres del teclat, amb una interfície personalitzada i passos de volum personalitzats",
"menu": {
"arrows-shortcuts": "Controls locals de tecles de fletxa",
"custom-volume-steps": "Estableix passos de volum personalitzats",
"global-shortcuts": "Dreceres de teclat globals"
},
"name": "Volum precís",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Baixa el volum",
"increase": "Puja el volum"
},
"label": "Tria les dreceres globals de volum:",
"title": "Dreceres globals de volum"
},
"volume-steps": {
"label": "Tria els passos d'augment o disminució del volum",
"title": "Passos de volum"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualitat actual: {{quality}}",
"message": "Tria la qualitat del vídeo:",
"title": "Tria la qualitat del vídeo"
}
}
},
"description": "Permet canviar la qualitat del vídeo amb un botó que s'hi mostra a sobre",
"name": "Botó de qualitat del vídeo",
"renderer": {
"quality-settings-button": {
"label": "Obre les opcions de qualitat del reproductor"
}
}
},
"scrobbler": {
"description": "Afegeix suport per scrobbling (Last.fm, ListenBrainz, etc.)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Error al autenticar amb Last.fm\nAmaga la finestra emergent fins el següent reinici.",
"title": "Error d'autenticació"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Configuració de l'API de Last.fm"
},
"listenbrainz": {
"token": "Introduir token d'usuari de ListenBrainz"
},
"scrobble-alternative-artist": "Utilitza artistes alternatius",
"scrobble-alternative-title": "Useu títols alternatius",
"scrobble-other-media": "Scrobble amb altres mitjans"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Clau d'API de Last.fm",
"api-secret": "Clau secreta de l'API de Last.fm"
},
"listenbrainz": {
"token": {
"label": "Introdueix el teu token de ListenBrainz:",
"title": "Token de ListenBrainz"
}
}
}
},
"shortcuts": {
"description": "Permet l'ús de dreceres globals del teclat per la reproducció (reproduir/pausar/següent/anterior) i desactivar l'OSD dels mitjans en sobreescriure les tecles de control multimèdia, habilita el Ctrl/CMD + F per buscar, habilita el suport MPRIS a Linux per tecles de control multimèdia, i dreceres de teclat personalitzades per usuaris avançats",
"menu": {
"override-media-keys": "Sobreescriu les tecles de control multimèdia",
"set-keybinds": "Estableix controls globals de les cançons"
},
"name": "Dreceres i MPRIS",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Següent",
"play-pause": "Reproduir / Pausar",
"previous": "Anterior"
},
"label": "Tria combinacions de tecles per controlar les cançons:",
"title": "Dreceres globals"
}
}
},
"skip-disliked-songs": {
"description": "Salta les cançons amb «no m'agrada»",
"name": "Salta les cançons que no t'agraden"
},
"skip-silences": {
"description": "Omet automàticament les seccions amb silenci a les cançons",
"name": "Omet els silencis"
},
"sponsorblock": {
"description": "Omet automàticament els segments dels vídeos que no son música, com la intro o el final",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Proporciona lletres sincronitzades amb les cançons, a través de proveïdors com LRClib.",
"errors": {
"fetch": "⚠️\tS'ha produït un error en descarregar la lletra.\n\tSi us plau, intenta-ho més tard.",
"not-found": "⚠️ No s'ha trobat la lletra per aquesta cançó."
},
"menu": {
"convert-chinese-character": {
"label": "Converteix Caràcters Xinesos",
"submenu": {
"disabled": {
"label": "Deshabilitat",
"tooltip": "Deshabilitar la conversió de caràcters Xinesos"
},
"simplified-to-traditional": {
"label": "Simplificat al Tradicional",
"tooltip": "Converteix Xinés Simplificat a Xinés Tradicional"
},
"traditional-to-simplified": {
"label": "Tradicional a Simplificat",
"tooltip": "Converteix Xinès Tradicional a Xinès Simplificat"
}
},
"tooltip": "Converteix caràcter Xinès a Tradicional o Simplificat"
},
"default-text-string": {
"label": "Caràcter per defecte entre lletres",
"tooltip": "Tria el caràcter per defecte que es mostrarà a l'espai entre les lletres"
},
"line-effect": {
"label": "Efecte de la línia",
"submenu": {
"fancy": {
"label": "Caprici",
"tooltip": "Utilitza efectes grans \"app-like\" en la línia actual"
},
"focus": {
"label": "Centrar",
"tooltip": "Mostra tan sols la línia actual en blanc"
},
"offset": {
"label": "Desplaçament",
"tooltip": "Desplaçament a la dreta de la línia actual"
},
"scale": {
"label": "Escala",
"tooltip": "Redimensiona la línia actual"
}
},
"tooltip": "Tria l'efecte a aplicar a la línia actual"
},
"precise-timing": {
"label": "Fes que les lletres es sincronitzin a la perfecció",
"tooltip": "Calcula al mil·lisegon l'aparició de la següent línia (pot tenir un petit impacte en el rendiment)"
},
"preferred-provider": {
"label": "Proveïdor preferit",
"none": {
"label": "Cap",
"tooltip": "Cap proveïdor preferit"
},
"tooltip": "Trieu el proveïdor predeterminat que voleu utilitzar"
},
"romanization": {
"label": "Romanitza les lletres",
"tooltip": "Si les lletres són en un idioma diferent, intenta mostrar la versió amb alfabet llatí."
},
"show-lyrics-even-if-inexact": {
"label": "Mostra la lletra tot i que sigui inexacta",
"tooltip": "Si no es troba la cançó, el plugin torna a intentar obtenir la lletra amb una cerca diferent.\nEl resultat d'aquesta segona cerca podria no ser exacte."
},
"show-time-codes": {
"label": "Mostra els codis de temps",
"tooltip": "Mostra els codis de temps al costat de la lletra"
}
},
"name": "Lletres sincronitzades",
"refetch-btn": {
"fetching": "Obtenint...",
"normal": "Tornar a obtenir la lletra"
},
"warnings": {
"duration-mismatch": "⚠️ - La lletra podria no estar ben sincronitzada, la durada no és coincident.",
"inexact": "⚠️ - La lletra d'aquesta cançó podria no ser exacta",
"instrumental": "⚠️ - Aquesta cançó és instrumental"
}
},
"taskbar-mediacontrol": {
"description": "Controla la reproducció des de la barra de tasques del Windows",
"name": "Control multimèdia a la barra de tasques"
},
"touchbar": {
"description": "Afegeix un giny a la Touch Bar per usuaris de macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Fa la finestra de l'aplicació transparent",
"menu": {
"opacity": {
"label": "Opacitat",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipus",
"submenu": {
"acrylic": "Acrílic",
"mica": "Mica",
"none": "Cap",
"tabbed": "En pestanyes"
}
}
},
"name": "Reproductor Transparent"
},
"tuna-obs": {
"description": "Integració amb l'extensió «Tuna» del OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Impedeix que salti el reproductor mentre se sent una cançó",
"name": "Reproductor Discret"
},
"video-toggle": {
"description": "Afegeix un botó per commutar entre el mode de vídeo o de cançó. Opcionalment, es pot eliminar la pestanya de vídeo per complet",
"menu": {
"align": {
"label": "Alineament",
"submenu": {
"left": "Esquerra",
"middle": "Mig",
"right": "Dreta"
}
},
"force-hide": "Força amagar la pestanya de vídeo",
"mode": {
"label": "Mode",
"submenu": {
"custom": "Commutador personalitzat",
"disabled": "Deshabilitat",
"native": "Commutador nadiu"
}
}
},
"name": "Botó de vídeo",
"templates": {
"button-song": "Cançó",
"button-video": "Vídeo"
}
},
"visualizer": {
"description": "Afegeix un visualitzador al reproductor",
"menu": {
"visualizer-type": "Tipus de visualitzador"
},
"name": "Visualitzador"
}
}
}
================================================
FILE: src/i18n/resources/cs.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Selhalo spuštění pluginu {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} spuštěn za {{ms}}ms",
"initialize-failed": "Selhalo zapnutí \"{{pluginName}}\" pluginu",
"load-all": "Načítání všech pluginů",
"load-failed": "Selhalo načtení \"{{pluginName}}\" pluginu",
"loaded": "Plugin \"{{pluginName}}\" načten",
"unload-failed": "Selhalo vypnutí \"{{pluginName}}\" pluginu",
"unloaded": "Plugin {{pluginName}} byl odnačten"
}
}
},
"language": {
"code": "cs",
"local-name": "Čeština",
"name": "Czech"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Načítání dokončeno. Vývojářské nástroje se otevřely"
},
"i18n": {
"loaded": "i18n načteno"
},
"second-instance": {
"receive-command": "Přijmut příkaz přes protokol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS soubor \"{{cssFile}}\" neexistuje, ignorováno"
},
"unresponsive": {
"details": "Chyba - Aplikace nereaguje!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Čištění mezipaměti aplikace"
},
"window": {
"tried-to-render-offscreen": "Okno se pokusilo vykreslit na pozadí, velikost okna = {{windowSize}}, zobrazovací velikost = {{displaySize}}, pozice = {{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu je skryté, stiskněte 'Alt' k jeho zobrazení (nebo 'ESC', pokud používáte vestavěné menu)",
"message": "Skrýt menu je povoleno",
"title": "Skrýt menu Povolené"
},
"need-to-restart": {
"buttons": {
"later": "Později",
"restart-now": "Restartovat nyní"
},
"detail": "\"{{pluginName}}\" plugin vyžaduje restart, aby se projevil",
"message": "\"{{pluginName}}\" potřebuje restartovat",
"title": "Restart vyžadován"
},
"unresponsive": {
"buttons": {
"quit": "Ukončit",
"relaunch": "Spustit znovu",
"wait": "Počkat"
},
"detail": "Omlouváme se za způsobené nepříjemnosti! prosím vyberte, co dělat:",
"message": "Aplikace nereaguje",
"title": "Okno nereaguje"
},
"update-available": {
"buttons": {
"disable": "Vypnout aktualizace",
"download": "Stáhnout",
"ok": "OK"
},
"detail": "Nová verze je k dispozici a lze ji stáhnout na {{downloadLink}}",
"message": "Nová verze je dostupná",
"title": "Aktualizace je k dispozici"
}
},
"menu": {
"about": "O Aplikaci",
"navigation": {
"label": "Navigace",
"submenu": {
"copy-current-url": "Zkopírovat aktuální URL adresu",
"go-back": "Jít zpátky",
"go-forward": "Jít dopředu",
"quit": "Ukončit",
"restart": "Restartovat aplikaci"
}
},
"options": {
"label": "Možnosti",
"submenu": {
"advanced-options": {
"label": "Pokročilé možnosti",
"submenu": {
"auto-reset-app-cache": "Při spuštění aplikace se resetuje její mezipaměť",
"disable-hardware-acceleration": "Vypnout hardware zrychlení",
"edit-config-json": "Upravit config.json",
"override-user-agent": "Přepsat uživatelského agenta",
"restart-on-config-changes": "Restartovat aplikaci na změny v konfiguraci",
"set-proxy": {
"label": "Nastavit proxy",
"prompt": {
"label": "Zadejte adresu proxy: (k vypnutí nechte pole prázdné)",
"placeholder": "Příklad: SOCKS5://127.0.0.1:9999",
"title": "Nastavit proxy"
}
},
"toggle-dev-tools": "Přepínat vývojářské nástroje"
}
},
"always-on-top": "Vždy na vrchu",
"auto-update": "Automatické aktualizace",
"hide-menu": {
"dialog": {
"message": "Menu bude skryto na dalším spuštěním, použijte [Alt] k jeho zobrazení (nebo backtick [`] pokud používáte in-app-menu)",
"title": "Skrýt menu Povoleno"
},
"label": "Skrýt menu"
},
"language": {
"dialog": {
"message": "Jazyk bude změněn po restartu",
"title": "Jazyk změněn"
},
"label": "Jazyk",
"submenu": {
"to-help-translate": "Chcete pomoc s překladem? Klikněte zde"
}
},
"resume-on-start": "Při spuštění aplikace, pokračovat na poslední písničce",
"single-instance-lock": "Zámek pro jednu instanci",
"start-at-login": "Zapnutí aplikace po přihlášení",
"starting-page": {
"label": "Úvodní stránka",
"unset": "Nenastaveno"
},
"tray": {
"label": "Tray",
"submenu": {
"disabled": "Vypnuto",
"enabled-and-hide-app": "Povolit a skrýt aplikaci",
"enabled-and-show-app": "Enabled a show aplikaci",
"play-pause-on-click": "Přehrát/Pozastavit na kliknutí"
}
},
"visual-tweaks": {
"label": "Vzhledové vylepšení",
"submenu": {
"custom-window-title": {
"label": "Vlastní název okna",
"prompt": {
"label": "Zadejte vlastní název okna: (zanechejte prázdné pro zakázání)",
"placeholder": "Příklad: {{applicationName}}"
}
},
"like-buttons": {
"default": "Výchozí",
"force-show": "Vynutit zobrazení",
"hide": "Skrýt",
"label": "Like tlačítka"
},
"remove-upgrade-button": "Odebrat upgrade tlačítko",
"theme": {
"dialog": {
"button": {
"cancel": "zrušit",
"remove": "Odstranit"
},
"remove-theme": "Jste si jisti že chcete odstranit tento vlastní motiv?",
"remove-theme-message": "Tohle odstraní vlastní motiv"
},
"label": "Motiv",
"submenu": {
"import-css-file": "Vložit vlastní CSS soubor",
"no-theme": "Žádný motiv"
}
}
}
}
}
},
"plugins": {
"enabled": "Povoleno",
"label": "Pluginy",
"new": "NOVÉ"
},
"view": {
"label": "Zobrazení",
"submenu": {
"force-reload": "Vynutit znovu načtení",
"reload": "Obnovit",
"reset-zoom": "Skutečná velikost",
"toggle-fullscreen": "Přepnout režim celé obrazovky",
"zoom-in": "Přiblížit",
"zoom-out": "Oddálit"
}
}
},
"tray": {
"next": "Další",
"play-pause": "Přehrát/Pozastavit",
"previous": "Minulý",
"quit": "Ukončit",
"restart": "Restartovat aplikaci",
"show": "Zobrazit okno",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Pokud se přehraje reklama tak ztlumí zvuk a nastaví rychlost přehrávání na 16x",
"name": "Zrychlovač Reklam"
},
"adblocker": {
"description": "Blokuje všechny reklamy a sledování ihned od začátku",
"menu": {
"blocker": "Blokátor"
},
"name": "Blokátor reklam"
},
"album-actions": {
"description": "Přidává Undislike, Dislike, Like, a Unlike tlačítka k aplikování tohoto ke všem písničkám v seznamu písniček nebo albumu",
"name": "Možnosti Alba"
},
"album-color-theme": {
"description": "Používá dynamický motiv a vizuální efekty na základě palety barev alba",
"menu": {
"color-mix-ratio": {
"label": "Poměr míchání barev",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Motiv podle barvy Alba"
},
"ambient-mode": {
"description": "Aplikuje světelné efekty pomocí vrhání jemných barev z videa, do vašeho pozadí obrazovky",
"menu": {
"blur-amount": {
"label": "Množství rozmazání",
"submenu": {
"pixels": "{{blurAmount}} pixelů"
}
},
"buffer": {
"label": "Vyrovnávací paměť",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Neprůhlednost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvalita",
"submenu": {
"pixels": "{{quality}} pixelů"
}
},
"size": {
"label": "Velikost",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Plynulý přechod",
"submenu": {
"during": "Během {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Používání režimu celé obrazovky"
}
},
"name": "Ambientní režim"
},
"amuse": {
"description": "Přídá {{applicationName}} podporu pro Amuse ‚právě hraje‘ widget od 6K Labs",
"name": "Amuse",
"response": {
"query": "Server Amuse API běží. Pošli požadavek typu GET na /query, aby ses dozvěděl info o písničce."
}
},
"api-server": {
"description": "Vlož API server abys mohl ovládat přehrávač",
"dialog": {
"request": {
"buttons": {
"allow": "Povolit",
"deny": "Zakázat"
},
"message": "Povolit {{ID}} ({{origin}}) přístup k API?",
"title": "dotaz na přihlášení k API"
}
},
"menu": {
"auth-strategy": {
"label": "Možnosti přihlášení",
"submenu": {
"auth-at-first": {
"label": "Ověřit při prvním dotazu"
},
"none": {
"label": "Bez ověření"
}
}
},
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "API server [Beta]",
"prompt": {
"hostname": {
"label": "Zadej hostname API serveru (ve tvaru 0.0.0.0):",
"title": "Hostname"
},
"port": {
"label": "Zadej port API serveru:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Aplikuje kompresi k audiu (snižuje hlasitost nejhlasitěších částí signálu a zvyšuje hlasitost nejjemnějších částí)",
"name": "Audio kompresor"
},
"auth-proxy-adapter": {
"description": "Podpora pro použití ověřovacích proxy služeb",
"menu": {
"disable": "Vypnout Proxy Adaptér",
"enable": "Zapnout Proxy Adaptér",
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "Autorizační Proxy adaptér",
"prompt": {
"hostname": {
"label": "Zadejte hostname lokálního proxy serveru (vyžaduje restart):",
"title": "Proxy Hostname"
},
"port": {
"label": "Zadejte port lokálního proxy serveru (vyžaduje restart):",
"title": "Proxy Port"
}
}
},
"blur-nav-bar": {
"description": "Udělá navigační panel průhledný a rozmazaný",
"name": "Rozmazaný navigační panel"
},
"bypass-age-restrictions": {
"description": "Obejít ověření věku na Music Player",
"name": "Obejít věková omezení"
},
"captions-selector": {
"description": "Titulkový selector pro zvukové stopy v {{applicationName}}",
"menu": {
"autoload": "Automaticky vybrat naposledy použité titulky",
"disable-captions": "Žádné titulky ve vychozím nastavení"
},
"name": "Titulkový selector",
"prompt": {
"selector": {
"label": "Aktuální jazyk titulků: {{language}}",
"none": "Žádný",
"title": "Vybrat jazyk titulků"
}
},
"templates": {
"title": "Otevřít titulový selector"
},
"toast": {
"caption-changed": "Titulky změněny na {{language}}",
"caption-disabled": "Titulky vypnuty",
"no-captions": "K této skladbě nejsou titulky dostupné"
}
},
"compact-sidebar": {
"description": "Vždy nastavit postranní panel do kompaktního režimu",
"name": "Kompaktní postranní panel"
},
"crossfade": {
"description": "Prolínání mezi písničkami",
"menu": {
"advanced": "Pokročilý"
},
"name": "Prolínání [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Délka Sílení (ms)",
"fade-out-duration": "Délka Slábnutí (ms)",
"fade-scaling": {
"label": "Škálování Přechodu",
"linear": "Lineární",
"logarithmic": "Logaritmické"
},
"seconds-before-end": "Crossfade N sekund před koncem"
},
"title": "Možnosti prolínání"
}
}
},
"custom-output-device": {
"description": "Nastavte vlastní výstupní zařízení pro skladby",
"menu": {
"device-selector": "Vyberte zařízení"
},
"name": "Vlastní výstupní zařízení",
"prompt": {
"device-selector": {
"label": "Vyberte zařízení pro výstup zvuku",
"title": "Vyberte výstupní zařízení"
}
}
},
"disable-autoplay": {
"description": "Spustí písničku v režimu \"pozastaveno\"",
"menu": {
"apply-once": "Applies jenom na spuštění aplikace"
},
"name": "Vypnout automatické přehrávání"
},
"discord": {
"backend": {
"already-connected": "Pokusilo se spojit s aktivním spojením",
"connected": "Připojeno k Discordu",
"disconnected": "Odpojeno od Discordu"
},
"description": "Ukažte svým přátelům, co posloucháte pomocí Rich Persence",
"menu": {
"auto-reconnect": "Automaticky znovu připojit",
"clear-activity": "Vymazat aktivitu",
"clear-activity-after-timeout": "Vymazat aktivitu po timeout",
"connected": "Připojeno",
"disconnected": "Odpojeno",
"hide-duration-left": "Skrýt zbývající duration",
"hide-github-button": "Skrýt tlačítko s odkazem na GitHub",
"play-on-application": "Hrát na {{applicationName}}",
"set-inactivity-timeout": "Nastavit timeout pro neaktivitu",
"set-status-display-type": {
"label": "Text statusu",
"submenu": {
"artist": "Poslouchám: {artist}",
"application": "Poslouchám {{applicationName}}",
"title": "Poslouchám {song title}"
}
}
},
"name": "Discord Rich Persence",
"prompt": {
"set-inactivity-timeout": {
"label": "Zadejte timeout neaktivity v sekundách:",
"title": "Nastavit timeout pro neaktivitu"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Argh! Omlouvám se, stáhnutí selhalo…",
"title": "Chyba ve stáhování!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} písničky)",
"message": "Stahování seznamu písniček {{playlistTitle}}",
"title": "Stahování začalo"
}
},
"feedback": {
"conversion-progress": "Konverze: {{percent}}%",
"converting": "Převádím…",
"done": "Hotovo: {{filePath}}",
"download-info": "Stahování {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Stahování: {{percent}}%",
"downloading": "Stahování…",
"downloading-counter": "Stahování {{current}}/{{total}}…",
"downloading-playlist": "Stahování seznamu písniček \"{{playlistTitle}}\" - {{playlistSize}} písničky ({{playlistId}})",
"error-while-downloading": "Chyba při stahování \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Složka {{playlistFolder}} již existuje",
"getting-playlist-info": "Získávání informací o seznamu písniček…",
"loading": "Načítání…",
"playlist-has-only-one-song": "Seznam písniček má pouze jednu položku, stahuje se přímo",
"playlist-id-not-found": "Žádné ID seznamu písnček nenalezeno",
"playlist-is-empty": "Seznam písniček je prázdný",
"playlist-is-mix-or-private": "Chyba při získávání informací o seznamu písniček: ujistite se, že se nejedná o soukromý nebo \"Namíchaný pro vás\" seznam písniček\n\n{{error}}",
"preparing-file": "Připravování souboru…",
"saving": "Ukládání…",
"trying-to-get-playlist-id": "Trying se získat ID seznamu písniček: {{playlistId}}",
"video-id-not-found": "Video nebylo nalezeno",
"writing-id3": "Psaní ID3 značek…"
}
},
"description": "Stahuje MP3 / source audio přímo z rozhraní",
"menu": {
"choose-download-folder": "Vybrat složku pro stahování",
"download-finish-settings": {
"label": "Stáhnout po dokončení",
"prompt": {
"last-percent": "Po x procentech",
"last-seconds": "Posledních x vteřin",
"title": "Nastavit kdy stahovat"
},
"submenu": {
"advanced": "Pokoročile",
"enabled": "Povoleno",
"mode": "Časový režim",
"percent": "Procent",
"seconds": "Sekundy"
}
},
"download-playlist": "Stáhnout seznam písniček",
"presets": "Předvolby",
"skip-existing": "Přeskočit existující soubory"
},
"name": "Stahovač",
"renderer": {
"can-not-update-progress": "Progress nemůže být aktualizován"
},
"templates": {
"button": "Stáhnout"
}
},
"equalizer": {
"description": "Přidá do přehrávače ekvalizér",
"menu": {
"presets": {
"label": "Předvolby",
"list": {
"bass-booster": "Zesílení basů"
}
}
},
"name": "Ekvalizér"
},
"exponential-volume": {
"description": "Dělá posuvník hlasitosti exponenciální, takže je snazší vybrat nižší hlasitost.",
"name": "Exponenciální hlasitost"
},
"in-app-menu": {
"description": "Dává menu panelům fancy, tmavý nebo album-color vzhled",
"menu": {
"hide-dom-window-controls": "Skrýt DOM window controls"
},
"name": "Vestavěné Menu"
},
"lumiastream": {
"description": "Přidává Lumia Stream podporu",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Přidává lyrics podporu pro většinu písniček",
"menu": {
"romanized-lyrics": "Romanizované Lyrics"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Fetched lyrics pro Genius"
}
},
"music-together": {
"description": "Sdílejte playlist s ostatními. Když hostitel přehrává skladbu, uslyší jí i všichni ostatní",
"dialog": {
"enter-host": "Zadejte Host ID"
},
"internal": {
"save": "Uložit",
"track-source": "Zdroj Písně",
"unknown-user": "Neznámý uživatel"
},
"menu": {
"click-to-copy-id": "Zkopírovat ID Hosta",
"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 hostitel 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",
"id-copy-failed": "Kopírování ID Hosta do schránky selhalo",
"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",
"templates": {
"back": {
"title": "Přejít na předchozí stránku"
},
"forward": {
"title": "Přejít na další stránku"
}
}
},
"no-google-login": {
"description": "Odstranit tlačítka Google přihlášení a odkazy z rozhraní",
"name": "Žádné Google přihlášení"
},
"notifications": {
"description": "Zobrazit oznámení, když písnička začne hrát (interaktivní notifikace jsou dostupné na Windows)",
"menu": {
"interactive": "Interaktivní oznámení",
"interactive-settings": {
"label": "Interactive Nastavení",
"submenu": {
"hide-button-text": "Skrýt text tlačítka",
"refresh-on-play-pause": "Refresh na Přehrát/Pozastavit",
"tray-controls": "Otevřít/Zavřít aplikaci na kliknutí na tray ikonu"
}
},
"priority": "Priorita Oznámení",
"toast-style": "Toast Styl",
"unpause-notification": "Zobrazit oznámení na unpause"
},
"name": "Oznámení"
},
"performance-improvement": {
"description": "Zlepšit výkon povolením experimentálních skriptů",
"name": "Zlepšení výkonu [Beta]"
},
"picture-in-picture": {
"description": "Povoluje switch aplikaci do režimu obrázek v obrázku",
"menu": {
"always-on-top": "Vždy na vrchu",
"hotkey": {
"label": "Klávesová zkratka",
"prompt": {
"keybind-options": {
"hotkey": "Klávesová zkratka"
},
"label": "Vybrat klávesovou zkratku pro přepínání obrázek v obrázku",
"title": "klávesová zkratka pro obrázek v obrázku"
}
},
"save-window-position": "Uložit pozici okna",
"save-window-size": "Uložit velikost okna",
"use-native-pip": "Použít browser native PiP"
},
"name": "Obrázek v obrázku",
"templates": {
"button": "Obrázek v obrázku"
}
},
"playback-speed": {
"description": "Poslouchej rychle, poslouchej pomalu! Přidává slider, který kontroluje rychlost písníčky",
"name": "Rychlost přehrávání",
"templates": {
"button": "Rychlost"
}
},
"precise-volume": {
"description": "Přesná kontrola hlasitosti pomocí kolečka myši/klávesnicových zkratek, s vlastní HUD a customizable hlasitostních steps",
"menu": {
"arrows-shortcuts": "Ovádání Šipkami",
"custom-volume-steps": "Nastavit vlastní hlasitostní steps",
"global-shortcuts": "Globální klávesové zkratky"
},
"name": "Přesná hlasitost",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Snížit hlasitost",
"increase": "Zvýšit hlasitost"
},
"label": "Vybrat globální klávesnicové zkratky:",
"title": "Globální klávesnicové zkratky hlasitosti"
},
"volume-steps": {
"label": "Vybrat Zvýšení/Snížení hlasitost Steps",
"title": "Hlasitostní steps"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Aktuální kvalita: {{quality}}",
"message": "Vybrat kvalitu videa:",
"title": "Vybrat kvalitu videa"
}
}
},
"description": "Umožňuje měnit kvalitu videa pomocí tlačítka na video overlay",
"name": "Měnič kvality videa",
"renderer": {
"quality-settings-button": {
"label": "Otevřít volbu kvality přehrávače"
}
}
},
"scrobbler": {
"description": "Přidat scrobbing podporu (např .last.fm , Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Selhalo ověření s Last.fm\nSchovat vyskakovací okno do dalšího restartu.",
"title": "Ověření Selhalo"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API nastavení"
},
"listenbrainz": {
"token": "Vložte Listenbrainz user token"
},
"scrobble-alternative-artist": "Použij alternativní umělce",
"scrobble-alternative-title": "Používat alternativní názvy",
"scrobble-other-media": "Scrobble jiné média"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last,fm API klíč",
"api-secret": "Tajný klíč API Last.fm"
},
"listenbrainz": {
"token": {
"label": "Vložte svůj Listenbrainz user token:",
"title": "ListenBrainz token"
}
}
}
},
"shortcuts": {
"description": "Dovoluje nastavit globální klávesové zkratky pro playback (přehrát/pozastavit/další/předchozí) a vypínání media OSD pomocí přepisování media klíčů, zapínání Ctrl/CMD + F k vyhledávání, zapínání Linux MPRIS podporu pro media klíče, a vlastní klávesové zkratky pro pokročilé uživatele.",
"menu": {
"override-media-keys": "Přepsat media klíče",
"set-keybinds": "Nastavit globální Controls písniček"
},
"name": "Zkratky (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Další",
"play-pause": "Přehrát / Pozastavit",
"previous": "Předchozí"
},
"label": "Vybrat globální klávesnicové zkratky pro ovládání písniček:",
"title": "Globální klávesnicové zkratky"
}
}
},
"skip-disliked-songs": {
"description": "Přeskakovat disliked písničky",
"name": "Přeskočit Disliked písničky"
},
"skip-silences": {
"description": "Automaticky přeskakovat tichá místa v písničkách",
"name": "Přeskakovat Tichá místa"
},
"sponsorblock": {
"description": "Automaticky přeskakuje nehudební části jako intro/outro nebo části hudebních videí, kde nehraje písnčka",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Poskytuje synchronizaci textů do písní, pomocí poskytovatelů, jako je LRClib.",
"errors": {
"fetch": "⚠️ Při hledání textu došlo k chybě.\n \tProsím zkuste to znovu později.",
"not-found": "⚠️ Pro tuto skladbu nebyl nalezen žádný text."
},
"menu": {
"default-text-string": {
"label": "Výchozí znak mezi texty",
"tooltip": "Vyberte výchozí znak pro mezeru mezi texty"
},
"line-effect": {
"label": "Efekt řádku",
"submenu": {
"fancy": {
"label": "Luxusní",
"tooltip": "Použijte velké, aplikací inspirované efekty na aktuální řádek"
},
"focus": {
"label": "Soustředění",
"tooltip": "Nechat pouze aktuální řádek bílý"
},
"offset": {
"label": "Posun",
"tooltip": "Posunout aktuální řádek doprava"
},
"scale": {
"label": "Zvětšení",
"tooltip": "Změnit velikost aktuálního řádku"
}
},
"tooltip": "Vyberte efekt pro aktuální řádek"
},
"precise-timing": {
"label": "Dokonale synchronizovat texty",
"tooltip": "Vypočítat zobrazení dalšího řádku na milisekundu (může mít menší dopad na výkon)"
},
"preferred-provider": {
"label": "Preferovaný poskytovatel",
"none": {
"label": "Žádný",
"tooltip": "Žádný preferovaný poskytovatel"
},
"tooltip": "Zvolte výchozího poskytovatele"
},
"romanization": {
"label": "Romanizovat texty",
"tooltip": "Pokud je text v jiném jazyce, zkusit zobrazit verzi v latince."
},
"show-lyrics-even-if-inexact": {
"label": "Zobrazit i nepřesné texty",
"tooltip": "Pokud se píseň nenajde, plugin to zkusí znovu s jiným vyhledávacím výrazem.\nVýsledek druhého pokusu nemusí být přesný."
},
"show-time-codes": {
"label": "Zobrazit časové kódy",
"tooltip": "Zobrazit časové kódy vedle textu"
}
},
"name": "Synchronizované texty",
"refetch-btn": {
"fetching": "Získávání...",
"normal": "Znovu načíst texty"
},
"warnings": {
"duration-mismatch": "⚠️ - Text nemusí být synchronizován kvůli neshodě v délce trvání.",
"inexact": "⚠️ - Text pro tuto skladbu nemusí být přesný",
"instrumental": "⚠️ - Tato skladba je instrumentální"
}
},
"taskbar-mediacontrol": {
"description": "Ovládejte přehrávání z vašeho Windows hlavního panelu",
"name": "Hlavní panel Media Control"
},
"touchbar": {
"description": "Přidává Touch Bar widget pro macOS uživatele",
"name": "Touch Bar"
},
"transparent-player": {
"description": "Zprůhlední okno aplikace",
"menu": {
"opacity": {
"label": "Průhlednost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Akryl",
"mica": "Mica",
"none": "Žádné",
"tabbed": "Záložkovaný"
}
}
},
"name": "Průhledný přehrávač"
},
"tuna-obs": {
"description": "Integrace s OBS's plugin Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Zabrání tomu, aby se přehrávač objevil při hraní písně",
"name": "Nepřekážející přehrávač"
},
"video-toggle": {
"description": "Přidává tlačítko k switch mezi video/písničko režimem. Může také odstranit celou video kartu",
"menu": {
"align": {
"label": "Zarovnání",
"submenu": {
"left": "Vlevo",
"middle": "Uprostřed",
"right": "Pravo"
}
},
"force-hide": "Vynutit odstranění karty videa",
"mode": {
"label": "Režim",
"submenu": {
"custom": "Vlastní přepínač",
"disabled": "Vypnuto",
"native": "Původní přepínač"
}
}
},
"name": "Přepínač videa",
"templates": {
"button-song": "Skladba",
"button-video": "Video"
}
},
"visualizer": {
"description": "Přidá vizualizér do přehrávače",
"menu": {
"visualizer-type": "Typ vizualizéru"
},
"name": "Vizualizér"
}
}
}
================================================
FILE: src/i18n/resources/da.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Fejl ved udføring af plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} udført på {{ms}}ms",
"initialize-failed": "Fejl ved igangsætning af plugin \"{{pluginName}}\"",
"load-all": "Indlæser alle plugins",
"load-failed": "Fejl ved indlæsning af plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" indlæst",
"unload-failed": "Fejl ved aflæsning af plugin \"{{pluginNavn}}\"",
"unloaded": "Plugin \"{{pluginNavn}}\" aflæssede"
}
}
},
"language": {
"code": "dk",
"local-name": "Dansk",
"name": "Danish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Indlæsning færdig. DevTools åbnet"
},
"i18n": {
"loaded": "i18n indlæst"
},
"second-instance": {
"receive-command": "Modtog kommando over protokol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS fil \"{{cssFile}}\" eksisterer ikke, ignorere"
},
"unresponsive": {
"details": "Uresponsiv fejl!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Rydder op i appens cache"
},
"window": {
"tried-to-render-offscreen": "Windows forsøgte at indlæse uden for skærmen, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menuen er gemt, brug 'Alt' knappen for at vise den igen (eller 'Escape' hvis In-App menuen bruges)",
"message": "Skjul menuen er aktiveret",
"title": "Skjult menu aktiveret"
},
"need-to-restart": {
"buttons": {
"later": "Senere",
"restart-now": "Genstart nu"
},
"detail": "\"{{pluginName}}\" plugin kræver en genstart for at have en effekt",
"message": "\"{{pluginName}}\" skal genstarte",
"title": "Genstart krævet"
},
"unresponsive": {
"buttons": {
"quit": "Afslut",
"relaunch": "Genåben",
"wait": "Vent"
},
"detail": "Vi undskylder for ubelejligheden! Vælg næste handling:",
"message": "Appen svarer ikke",
"title": "Vindue svarer ikke"
},
"update-available": {
"buttons": {
"disable": "Slå opdateringer fra",
"download": "Hent",
"ok": "OK"
},
"detail": "En ny version er tilgængelig og kan downloades her: {{downloadLink}}",
"message": "En ny version er tilgængelig",
"title": "Opdatering tilgængelig"
}
},
"menu": {
"about": "Om",
"navigation": {
"label": "Navigering",
"submenu": {
"copy-current-url": "Kopier nuværende URL",
"go-back": "Tilbage",
"go-forward": "Frem",
"quit": "Afslut",
"restart": "Genstart Appen"
}
},
"options": {
"label": "Indstillinger",
"submenu": {
"advanced-options": {
"label": "Avancerede indstillinger",
"submenu": {
"auto-reset-app-cache": "Nulstil app cache når appen starter",
"disable-hardware-acceleration": "Deaktiver hardware acceleration",
"edit-config-json": "Rediger config.json",
"override-user-agent": "Erstat Bruger-Agent",
"restart-on-config-changes": "Genstart ved config ændringer",
"set-proxy": {
"label": "Indstil proxy",
"prompt": {
"label": "Skriv proxy adresse: (Efterlad tom for at deaktivere)",
"placeholder": "Eksempel: SOCKS5://127.0.0.1:9999",
"title": "Sæt proxy"
}
},
"toggle-dev-tools": "Skift DevTools"
}
},
"always-on-top": "Altid øverst",
"auto-update": "Automatisk opdatering",
"hide-menu": {
"dialog": {
"message": "Menuen vil være lukket næste gang appen starter. Brug [Alt] for at vise den (Eller backtick [`] hvis in-app-menu bruges)",
"title": "Gemt menu aktiveret"
},
"label": "Skjul menu"
},
"language": {
"dialog": {
"message": "Sproget vil blive ændret efter genstart",
"title": "Sprog ændret"
},
"label": "Sprog",
"submenu": {
"to-help-translate": "Vil du hjælpe med at oversætte? Klik her"
}
},
"resume-on-start": "Genoptag sidste sang når appen starter",
"single-instance-lock": "Enkeltinstans lås",
"start-at-login": "Start ved login",
"starting-page": {
"label": "Startside",
"unset": "Ikke valgt"
},
"tray": {
"label": "Bakke",
"submenu": {
"disabled": "Deaktiveret",
"enabled-and-hide-app": "Bakke aktiveret, og skjul programvindue",
"enabled-and-show-app": "Aktiver og vis app",
"play-pause-on-click": "Start/Stop ved klik"
}
},
"visual-tweaks": {
"label": "Visuelle Justeringer",
"submenu": {
"custom-window-title": {
"label": "Tilpasset vindues titel",
"prompt": {
"label": "Indtast tilpasset vindues titel: (lad være top for deaktiveret)",
"placeholder": "Eksempel: {{applicationName}}"
}
},
"like-buttons": {
"default": "Standard",
"force-show": "Tving visning",
"hide": "Skjul",
"label": "Like knapper"
},
"remove-upgrade-button": "Fjern opgrader knappen",
"theme": {
"dialog": {
"button": {
"cancel": "Annuller",
"remove": "Fjern"
},
"remove-theme": "Er du sikker på at du til fjerne det brugerdefinerede tema?",
"remove-theme-message": "Dette vil fjerne det brugerdefinerede tema"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importer brugerdefinerede CSS fil",
"no-theme": "Intet tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Aktiveret",
"label": "Plugins",
"new": "NY"
},
"view": {
"label": "Vis",
"submenu": {
"force-reload": "Tving Genindlæs",
"reload": "Genindlæs",
"zoom-in": "Zoom ind",
"zoom-out": "Zoom ud"
}
}
},
"tray": {
"next": "Næste",
"play-pause": "Afspil",
"previous": "Sidste",
"quit": "Luk",
"restart": "Genstart app",
"show": "Vis vindue",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Hvis en reklame afspilles, slår den lyden fra og sætter hastigheden til 16x",
"name": "Spol igennem reklamen"
},
"adblocker": {
"description": "Bloker alle reklamer og sporing fra starten af",
"menu": {
"blocker": "Bloker"
},
"name": "Bloker reklamer"
},
"album-color-theme": {
"menu": {
"color-mix-ratio": {
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Albummets farve tema"
},
"ambient-mode": {
"menu": {
"blur-amount": {
"label": "Sløringsmængde",
"submenu": {
"pixels": "{{blurAmount}} pixel"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Gennemsigtighed",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvalitet",
"submenu": {
"pixels": "{{quality}} pixel"
}
},
"size": {
"label": "Størrelse",
"submenu": {
"percent": "{{size}}%"
}
},
"use-fullscreen": {
"label": "Bruger fuldskærm"
}
}
},
"api-server": {
"dialog": {
"request": {
"buttons": {
"allow": "Tillad",
"deny": "Afvis"
},
"message": "Tillad at {{ID}} ({{origin}}) får adgang til API'en?"
}
},
"menu": {
"auth-strategy": {
"label": "Godkendelsesstrategi"
},
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Skriv API serverens hostname (f. eks. 0.0.0.0):",
"title": "Hostname"
},
"port": {
"label": "Skriv API serverens port:",
"title": "Port"
}
}
},
"audio-compressor": {
"name": "Lyd kompressor"
},
"blur-nav-bar": {
"description": "Gør navigationsbaren gennemsigtig og sløret",
"name": "Slør navigationsbar"
},
"captions-selector": {
"menu": {
"disable-captions": "Ingen undertekster som standard"
},
"name": "Vælg undertekster",
"prompt": {
"selector": {
"label": "Nuværende sprog på undertekster: {{language}}",
"none": "Ingen",
"title": "Vælg underteksternes sprog"
}
}
},
"crossfade": {
"description": "Fade imellem sange",
"menu": {
"advanced": "Avanceret"
},
"name": "Fade [Beta]"
}
}
}
================================================
FILE: src/i18n/resources/de.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Erweiterung {{pluginName}}::{{contextName}} konnte nicht ausgeführt werden",
"executed-at-ms": "Erweiterung {{pluginName}}::{{contextName}} in {{ms}}ms ausgeführt",
"initialize-failed": "Initialisierung der Erweiterung \"{{pluginName}}\" fehlgeschlagen",
"load-all": "Lade alle Erweiterungen",
"load-failed": "Laden der Erweiterung \"{{pluginName}}\" fehlgeschlagen",
"loaded": "Erweiterung \"{{pluginName}}\" geladen",
"unload-failed": "Entladen der Erweiterung \"{{pluginName}}\" fehlgeschlagen",
"unloaded": "Erweiterung \"{{pluginName}}\" entladen"
}
}
},
"language": {
"code": "de",
"local-name": "Deutsch",
"name": "German"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Laden fertiggestellt. Entwicklerwerkzeuge geöffnet"
},
"i18n": {
"loaded": "i18n geladen"
},
"second-instance": {
"receive-command": "Befehl über Protokoll empfangen: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS-Datei \"{{cssFile}}\" existiert nicht, ignoriere"
},
"unresponsive": {
"details": "Nicht reagierender Fehler!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Leere Anwendungscache"
},
"window": {
"tried-to-render-offscreen": "Fenster vesucht außerhalb des Bildschirms zu rendern, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"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"
},
"need-to-restart": {
"buttons": {
"later": "Später",
"restart-now": "Jetzt neustarten"
},
"detail": "\"{{pluginName}}\"-Erweiterung erfordert einen Neustart, um in Kraft zu treten",
"message": "\"{{pluginName}}\" muss neugestartet werden",
"title": "Neustart erforderlich"
},
"unresponsive": {
"buttons": {
"quit": "Verlassen",
"relaunch": "Neustarten",
"wait": "Warten"
},
"detail": "Wir entschuldigen uns für die Unannehmlichkeiten! Bitte entscheide, was du tun möchtest:",
"message": "Die Anwendung reagiert nicht",
"title": "Fenster reagiert nicht"
},
"update-available": {
"buttons": {
"disable": "Aktualisierungen deaktivieren",
"download": "Herunterladen",
"ok": "OK"
},
"detail": "Eine neue Version ist verfügbar und kann unter {{downloadLink}} heruntergeladen werden",
"message": "Eine neue Version ist verfügbar",
"title": "Aktualisierung verfügbar"
}
},
"menu": {
"about": "Über",
"navigation": {
"label": "Navigation",
"submenu": {
"copy-current-url": "Aktuelle URL kopieren",
"go-back": "Zurück gehen",
"go-forward": "Vorwärts gehen",
"quit": "Beenden",
"restart": "Anwendung neustarten"
}
},
"options": {
"label": "Einstellungen",
"submenu": {
"advanced-options": {
"label": "Erweiterte Einstellungen",
"submenu": {
"auto-reset-app-cache": "Anwendungscache beim Start der Anwendung zurücksetzen",
"disable-hardware-acceleration": "Hardware-Beschleunigung deaktivieren",
"edit-config-json": "config.json ändern",
"override-user-agent": "User-Agent außer Kraft setzen",
"restart-on-config-changes": "Neustarten bei Änderungen der Konfiguration",
"set-proxy": {
"label": "Proxy setzen",
"prompt": {
"label": "Proxy-Adresse eingeben: (leer lassen zum Ausschalten)",
"placeholder": "Beispiel: SOCKS5://127.0.0.1:9999",
"title": "Proxy setzen"
}
},
"toggle-dev-tools": "Entwicklerwerkzeuge umschalten"
}
},
"always-on-top": "Immer im Vordergrund",
"auto-update": "Automatisch Aktualisieren",
"hide-menu": {
"dialog": {
"message": "Menü wird beim nächsten Start versteckt, verwende [Alt], um es zu zeigen (oder Backtick [`], wenn du das In-App-Menü benutzt)",
"title": "Menü Verstecken Aktiviert"
},
"label": "Menü Verstecken"
},
"language": {
"dialog": {
"message": "Sprache wird nach Neustart geändert",
"title": "Sprache geändert"
},
"label": "Sprache",
"submenu": {
"to-help-translate": "Willst du beim Übersetzen helfen? Klicke hier"
}
},
"resume-on-start": "Letztes Lied weiter abspielen, wenn Anwendung startet",
"single-instance-lock": "Sperren einer einzelnen Instanz",
"start-at-login": "Start beim Einschalten",
"starting-page": {
"label": "Startseite",
"unset": "Ungesetzt"
},
"tray": {
"label": "Tray",
"submenu": {
"disabled": "Deaktiviert",
"enabled-and-hide-app": "Aktiviert und verstecke Anwendung",
"enabled-and-show-app": "Aktiviert und zeige Anwendung",
"play-pause-on-click": "Abspielen/Pausieren durch Klick"
}
},
"visual-tweaks": {
"label": "Visuelle Optimierungen",
"submenu": {
"custom-window-title": {
"label": "Benutzerdefinierter Fenstertitel",
"prompt": {
"label": "Benutzerdefinierten Fenstertitel eingeben: (zum Deaktivieren leer lassen)",
"placeholder": "Beispiel: {{applicationName}}"
}
},
"like-buttons": {
"default": "Standard",
"force-show": "Zeigen erzwungen",
"hide": "Versteckt",
"label": "Gefällt mir-Knopf",
"swap": "Gefällt mir-Knopf Reihenfolge ändern"
},
"remove-upgrade-button": "Upgrade-Schaltfläche entfernen",
"theme": {
"dialog": {
"button": {
"cancel": "Abbrechen",
"remove": "Entfernen"
},
"remove-theme": "Sind Sie sich sicher, dass Sie das benutzerdefinierte Aussehen ändern wollen?",
"remove-theme-message": "Dies wird das benutzerdefinierte Aussehen löschen"
},
"label": "Thema",
"submenu": {
"import-css-file": "Importiere eigene CSS-Datei",
"no-theme": "Kein Thema"
}
}
}
}
}
},
"plugins": {
"enabled": "Aktiviert",
"label": "Erweiterungen",
"new": "NEU"
},
"view": {
"label": "Ansicht",
"submenu": {
"force-reload": "Neuladen erzwingen",
"reload": "Neu laden",
"reset-zoom": "Tatsächliche Größe",
"toggle-fullscreen": "Vollbild umschalten",
"zoom-in": "Vergrößern",
"zoom-out": "Verkleinern"
}
}
},
"tray": {
"next": "Nächstes",
"play-pause": "Weiter/Pause",
"previous": "Vorheriges",
"quit": "Beenden",
"restart": "Anwendung neu starten",
"show": "Fenster anzeigen",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Wenn eine Werbung spielt, stummt es das Audio und setzt die Wiedergabegeschwindigkeit auf 16x",
"name": "Werbungsbeschleunigung"
},
"adblocker": {
"description": "Blockiere jegliche Werbung und Tracker",
"menu": {
"blocker": "Abfangmethode"
},
"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}}%"
}
},
"enable-seekbar": "Suchleisten-Design aktivieren"
},
"name": "Thema aus Albumfarbe"
},
"ambient-mode": {
"description": "Fügt einen Lichteffekt durch sanftes Abstreifen der Farben des Videos in deinen Bildschirmhintergrund hinzu",
"menu": {
"blur-amount": {
"label": "Unschärfemenge",
"submenu": {
"pixels": "{{blurAmount}} Pixel"
}
},
"buffer": {
"label": "Puffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Transparenz",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualität",
"submenu": {
"pixels": "{{quality}} Pixel"
}
},
"size": {
"label": "Größe",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Glatter Übergang",
"submenu": {
"during": "Während {{interpolationTime}}s"
}
},
"use-fullscreen": {
"label": "Vollbild nutzen"
}
},
"name": "Ambiente-Modus"
},
"amuse": {
"description": "Fügt {{applicationName}} Unterstützung für das Amuse \"Spielt gerade\"-Widget von 6K Labs hinzu",
"name": "Amuse",
"response": {
"query": "Amuse API-Server läuft. /query für Liedinformationen."
}
},
"api-server": {
"description": "Fügt einen API-Server hinzu, um die Wiedergabe zu steuern",
"dialog": {
"request": {
"buttons": {
"allow": "Erlauben",
"deny": "Ablehnen"
},
"message": "{{ID}} ({{origin}}) den Zugriff zur API erlauben?",
"title": "API-Autorisierungs-Anfrage"
}
},
"menu": {
"auth-strategy": {
"label": "Autorisations-Methode",
"submenu": {
"auth-at-first": {
"label": "Beim ersten Zugriff autorisieren"
},
"none": {
"label": "Keine Autorisierung"
}
}
},
"hostname": {
"label": "Hostname"
},
"https": {
"label": "HTTPS & Zertifikate",
"submenu": {
"cert": {
"dialogTitle": "HTTPS Zertifikat Datei auswählen",
"label": "Zertifikate Datei (.crt/.pem)"
},
"enable-https": {
"label": "HTTPS aktivieren"
},
"key": {
"dialogTitle": "HTTPS privaten Schlüssel Datei auswählen",
"label": "Privater Schlüssel Datei (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "API-Server [Beta]",
"prompt": {
"hostname": {
"label": "Hostname des API-Servers vergeben (z. B. 0.0.0.0):",
"title": "Hostname"
},
"port": {
"label": "Port des API-Server:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Kompressor auf Audio anwenden (senkt die Lautstärke der lautesten Teile des Signals und hebt die Lautstärke der leisesten Teile an)",
"name": "Audio-Komprimierer"
},
"auth-proxy-adapter": {
"description": "Unterstützung für Proxy-Authentifizierungsdienste",
"menu": {
"disable": "Proxy-Adapter deaktivieren",
"enable": "Proxy-Adapter aktivieren",
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "Authentifizierungs-Proxyadapter",
"prompt": {
"hostname": {
"label": "Hostnamen eingeben für lokalen Proxy-Server (Neustart erforderlich):",
"title": "Proxy Hostname"
},
"port": {
"label": "Geben Sie den Port für den lokalen Proxyserver ein (Neustart erforderlich):",
"title": "Proxy Port"
}
}
},
"blur-nav-bar": {
"description": "Macht Navigationsleiste durchsichtig und unscharf",
"name": "Verschwommene Navigationsleiste"
},
"bypass-age-restrictions": {
"description": "Music Player Altersbestätigung umgehen",
"name": "Altersbeschränkungen umgehen"
},
"captions-selector": {
"description": "Untertitelwähler für {{applicationName}}-Audio-Lieder",
"menu": {
"autoload": "Wähle automatisch den zuletzt verwendeten Untertitel",
"disable-captions": "Standardmäßig keine Untertitel"
},
"name": "Untertitelwähler",
"prompt": {
"selector": {
"label": "Aktuelle Untertitelsprache: {{language}}",
"none": "Keine",
"title": "Wähle Untertitelsprache"
}
},
"templates": {
"title": "Untertitelwähler öffnen"
},
"toast": {
"caption-changed": "Untertitel gewechselt zu {{language}}",
"caption-disabled": "Untertitel deaktiviert",
"no-captions": "Keine Untertitel für dieses Lied verfügbar"
}
},
"clock": {
"description": "Füge eine Uhr der Navigationsleiste hinzu",
"menu": {
"format": {
"24-hour-format": "24-Stunden Format",
"display-seconds": "Sekunden anzeigen",
"label": "Format"
}
},
"name": "Uhr"
},
"compact-sidebar": {
"description": "Seitenleiste immer in den kompakten Modus setzen",
"name": "Kompakte Seitenleiste"
},
"crossfade": {
"description": "Übergang zwischen Liedern",
"menu": {
"advanced": "Erweitert"
},
"name": "Übergang [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Einblendezeit (Millisekunden)",
"fade-out-duration": "Ausblendezeit (Millisekunden)",
"fade-scaling": {
"label": "Übergangsskalierung",
"linear": "Linear",
"logarithmic": "Logarithmisch"
},
"seconds-before-end": "Übergang N Sekunden vor dem Ende starten"
},
"title": "Übergangseinstellungen"
}
}
},
"custom-output-device": {
"description": "Einen maßgeschneiderten Ausgabemedienträger für Lieder einrichten",
"menu": {
"device-selector": "Gerät auswählen"
},
"name": "Benutzerdefiniertes Ausgabegerät",
"prompt": {
"device-selector": {
"label": "Wähle das Ausgabegerät, welches benutzt werden soll",
"title": "Wähle ein Ausgabegerät"
}
}
},
"disable-autoplay": {
"description": "Startet Lied im pausierten Modus",
"menu": {
"apply-once": "Nur beim Start der Anwendung anwenden"
},
"name": "Deaktiviere automatisches Abspielen"
},
"discord": {
"backend": {
"already-connected": "Verbindungsaufbau bei aktiver Verbindung versucht",
"connected": "Mit Discord verbunden",
"disconnected": "Verbindung zu Discord getrennt"
},
"description": "Zeige deinen Freunden, was du hörst mit Discords Aktivitätsstatus",
"menu": {
"auto-reconnect": "Automatisch erneut verbinden",
"clear-activity": "Aktivität leeren",
"clear-activity-after-timeout": "Aktivität nach Timeout leeren",
"connected": "Verbunden",
"disconnected": "Getrennt",
"hide-duration-left": "Verbleibende Zeit verstecken",
"hide-github-button": "Knopf mit Link zu GitHub ausblenden",
"play-on-application": "Auf {{applicationName}} abspielen",
"set-inactivity-timeout": "Inaktivitätstimeout setzen",
"set-status-display-type": {
"label": "Status Text",
"submenu": {
"application": "Hört {{applicationName}}",
"artist": "Hört {artist} zu",
"title": "Du hörst {song title}"
}
}
},
"name": "Discords Aktivitätsstatus",
"prompt": {
"set-inactivity-timeout": {
"label": "Inaktivitätstimeout in Sekunden eingeben:",
"title": "Inaktivitätstimeout setzen"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Argh! Entschuldigung, herunterladen fehlgeschlagen…",
"title": "Fehler beim Herunterladen!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} Lieder)",
"message": "Lade Playlist {{playlistTitle}} herunter",
"title": "Download begonnen"
}
},
"feedback": {
"conversion-progress": "Konvertieren: {{percent}}%",
"converting": "Konvertiere…",
"done": "Abgeschlossen: {{filePath}}",
"download-info": "Lade {{artist}} - {{title}} [{{videoId}} herunter",
"download-progress": "Herunterladen: {{percent}}%",
"downloading": "Lade herunter…",
"downloading-counter": "Lade herunter {{current}}/{{total}}…",
"downloading-playlist": "Lade Playlist \"{{playlistTitle}}\" herunter - {{playlistSize}} Lieder ({{playlistId}})",
"error-while-downloading": "Fehler beim Herunterladen \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Der Ordner {{playlistFolder}} existiert bereits",
"getting-playlist-info": "Hole Playlist-Informationen…",
"loading": "Lade…",
"playlist-has-only-one-song": "Playlist hat nur ein Element, wird direkt heruntergeladen",
"playlist-id-not-found": "Keine Playlist-ID gefunden",
"playlist-is-empty": "Playlist ist leer",
"playlist-is-mix-or-private": "Fehler beim Sammeln der Playlist-Informationen: stelle sicher, dass es keine private oder \"Mixed for you\"-Playlist ist\n\n{{error}}",
"preparing-file": "Bereite Datei vor…",
"saving": "Speichere…",
"trying-to-get-playlist-id": "Versuche Playlist-ID zu bekommen: {{playlistId}}",
"video-id-not-found": "Video nicht gefunden",
"writing-id3": "Schreibe ID3 tags…"
}
},
"description": "Lädt MP3-/Original-Audio direkt von der Schnittstelle herunter",
"menu": {
"choose-download-folder": "Downloadordner wählen",
"download-finish-settings": {
"label": "Song am Ende runterladen",
"prompt": {
"last-percent": "Nach x Prozent",
"last-seconds": "Letzten x Sekunden",
"title": "Konfiguriere wann runtergeladen werden soll"
},
"submenu": {
"advanced": "Erweitert",
"enabled": "Aktiviert",
"mode": "Zeitmodus",
"percent": "Prozent",
"seconds": "Sekunden"
}
},
"download-playlist": "Wiedergabeliste herunterladen",
"presets": "Voreinstellungen",
"skip-existing": "Vorhandene Dateien überspringen"
},
"name": "Downloader",
"renderer": {
"can-not-update-progress": "Fortschritt kann nicht aktualisiert werden"
},
"templates": {
"button": "Herunterladen"
}
},
"equalizer": {
"description": "Fügt einen Equalizer zum Player hinzu",
"menu": {
"presets": {
"label": "Vorgaben",
"list": {
"bass-booster": "Bass-Verstärker"
}
}
},
"name": "Equalizer"
},
"exponential-volume": {
"description": "Macht den Lautstärkeregler exponentiell, damit es einfacher ist leise Lautstärken zu wählen.",
"name": "Exponentielle Lautstärke"
},
"in-app-menu": {
"description": "Verleiht den Menüleisten ein schickes, dunkles oder albumfarbenes Aussehen",
"menu": {
"hide-dom-window-controls": "DOM-Fenster-Steuerelemente ausblenden"
},
"name": "In-App Menü"
},
"lumiastream": {
"description": "Fügt Unterstützung für Lumia Stream hinzu",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Für Songtextunterstützung für die meisten Lieder hinzu",
"menu": {
"romanized-lyrics": "Romanisierte Songtexte"
},
"name": "Songtexte von Genius",
"renderer": {
"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ästen 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",
"templates": {
"back": {
"title": "Zur vorherigen Seite gehen"
},
"forward": {
"title": "Zur nächsten Seite gehen"
}
}
},
"no-google-login": {
"description": "Googles Anmelden-Knöpfe und -Links von der Oberfläche entfernen",
"name": "Keine Google-Anmeldung"
},
"notifications": {
"description": "Zeige eine Benachrichtigung, wenn ein Lied beginnt zu spielen (interaktive Benachrichtigungen sind unter Windows verfügbar)",
"menu": {
"interactive": "Interaktive Benachrichtigungen",
"interactive-settings": {
"label": "Interaktivitätseinstellungen",
"submenu": {
"hide-button-text": "Text der Knöpfe verstecken",
"refresh-on-play-pause": "Aktualisieren bei Wiedergabe/Pause",
"tray-controls": "Öffnen/Schließen beim Klicken des Tray-Icons"
}
},
"priority": "Benachrichtigungspriorität",
"toast-style": "Toast-Stil",
"unpause-notification": "Benachrichtigungen beim Pausieren anzeigen"
},
"name": "Benachrichtigungen"
},
"performance-improvement": {
"description": "Leistung durch Aktivieren experimenteller Skripte verbessern",
"name": "Leistungs Verbesserung [Beta]"
},
"picture-in-picture": {
"description": "Erlaubt die App in den Bild-im-Bild-Modus zu wechseln",
"menu": {
"always-on-top": "Immer im Vordergrund",
"hotkey": {
"label": "Tastenkürzel",
"prompt": {
"keybind-options": {
"hotkey": "Tastenkürzel"
},
"label": "Tastenkürzel für Bild-im-Bild wählen",
"title": "Bild-im-Bild Tastenkürzel"
}
},
"save-window-position": "Fensterposition speichern",
"save-window-size": "Fenstergröße speichern",
"use-native-pip": "Browsereigenes PiP verwenden"
},
"name": "Bild-im-Bild",
"templates": {
"button": "Bild-im-Bild"
}
},
"playback-speed": {
"description": "Schnell hören, langsam hören! Fügt einen Schieberegler zur Steuerung der Songgeschwindigkeit hinzu",
"name": "Wiedergabegeschwindigkeit",
"templates": {
"button": "Geschwindigkeit"
}
},
"precise-volume": {
"description": "Präzise Steuerung der Lautstärke mit dem Mausrad/Numpad mit einem benutzerdefinierten HUD und benutzerdefinierten Lautstärkestufen",
"menu": {
"arrows-shortcuts": "Lokale Pfeiltasten als Steuerung",
"custom-volume-steps": "Eigene Lautstärkestufen setzen",
"global-shortcuts": "Globale Tastenkürzel"
},
"name": "Genaue Lautstärke",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Lautstärke senken",
"increase": "Lautstärke erhöhen"
},
"label": "Wähle globale Tastenkombinationen für Lautstärke:",
"title": "Globale Lautstärketastenbelegungen"
},
"volume-steps": {
"label": "Wähle Schritte zur Lautstärkehebung/-senkung",
"title": "Lautstärkestufen"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Aktuelle Videoqualität: {{quality}}",
"message": "Wähle Videoqualität:",
"title": "Videoqualität wählen"
}
}
},
"description": "Erlaubt die Videoqualität über einen Knopf auf dem Video",
"name": "Videoqualitätsänderer",
"renderer": {
"quality-settings-button": {
"label": "Videoqualität ändern"
}
}
},
"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-alternative-artist": "Benutze Alternative Künstler",
"scrobble-alternative-title": "Nutze alternative Titel",
"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",
"menu": {
"override-media-keys": "Medientasten überschreiben",
"set-keybinds": "Globale Liedsteuerung setzen"
},
"name": "Abkürzungen (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Nächstes",
"play-pause": "Weiter / Pause",
"previous": "Vorheriges"
},
"label": "Wähle globale Tastenkombinationen für die Liedsteuerung:",
"title": "Globale Tastenkombinationen"
}
}
},
"skip-disliked-songs": {
"description": "Überspringt Lieder, die ihnen nicht gefallen",
"name": "Überspring Lieder, die ihnen nicht gefallen"
},
"skip-silences": {
"description": "Automatisch stille Abschnitte in Liedern überspringen",
"name": "Stille überspringen"
},
"sponsorblock": {
"description": "Überspringt automatisch nicht-musikalische Teile wie Intro/Outro oder Teile von Musikvideos, in denen der Song nicht gespielt wird",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Bietet synchronisierte Liedtexte zu Songs, verwendet Anbieter wie LRClib.",
"errors": {
"fetch": "⚠️ - \tBeim Abrufen des Liedtexts ist ein Fehler aufgetreten. \n\tBitte versuchen Sie es später nochmal.",
"not-found": "⚠️ Kein Text für diesen Song gefunden."
},
"menu": {
"convert-chinese-character": {
"label": "Chinesische Zeichen Umwandeln",
"submenu": {
"disabled": {
"label": "Deaktiviert",
"tooltip": "Chinesische Zeichenkonvertierung deaktivieren"
},
"simplified-to-traditional": {
"label": "Vereinfacht zu Traditionell",
"tooltip": "Vereinfachtes Chinesisch in traditionelles Chinesisch umwandeln"
},
"traditional-to-simplified": {
"label": "Traditionell zu Vereinfacht",
"tooltip": "Traditionelles Chinesisch in vereinfachtes Chinesisch umwandeln"
}
},
"tooltip": "Chinesische Zeichen in traditionell oder vereinfacht umwandeln"
},
"default-text-string": {
"label": "Standardzeichen zwischen Texten",
"tooltip": "Standardzeichen für die Lücke zwischen Songtexten auswählen"
},
"line-effect": {
"label": "Zeileneffekt",
"submenu": {
"fancy": {
"label": "schick",
"tooltip": "Verwende große, app-ähnliche Effekte in der aktuellen Zeile"
},
"focus": {
"label": "Fokussieren",
"tooltip": "Nur aktive Zeile weiß darstellen"
},
"offset": {
"label": "Versatz",
"tooltip": "Verschiebe die aktuelle Zeile nach rechts"
},
"scale": {
"label": "Skalieren",
"tooltip": "Aktuelle Zeile skalieren"
}
},
"tooltip": "Effekt für aktive Zeile auswählen"
},
"precise-timing": {
"label": "Den Songtext perfekt synchronisieren",
"tooltip": "Auf die Millisekunde genau berechnen, wann die nächste Zeile angezeigt werden soll (Kann Einfluss auf die Leistung haben)"
},
"preferred-provider": {
"label": "bevorzugter Anbieter",
"none": {
"label": "Nichts",
"tooltip": "Kein bevorzugter Anbieter"
},
"tooltip": "Standardanbieter auswählen"
},
"romanization": {
"label": "Lateinische Umschrift anzeigen",
"tooltip": "Wenn der Liedtext in einer anderen Schrift ist, zeige nach Möglichkeit eine Version in lateinischer Schrift an."
},
"show-lyrics-even-if-inexact": {
"label": "Songtext anzeigen, auch wenn er ungenau ist",
"tooltip": "Die Erweiterung sucht mit anderen Suchparameter nochmals, wenn der Song nicht gefunden wurde.\nEs kann sein, dass das Ergebnis von der zweiten Anfrage nicht genau ist."
},
"show-time-codes": {
"label": "Zeitkodierungen anzeigen",
"tooltip": "Zeitkodierungen neben Songtext anzeigen"
}
},
"name": "Synchronisierte Texte",
"refetch-btn": {
"fetching": "Laden...",
"normal": "Songtext neu laden"
},
"warnings": {
"duration-mismatch": "⚠️ - Es kann sein, dass die Synchronization nicht stimmt, da die Songdauer nicht übereinstimmt.",
"inexact": "⚠️ - Es ist möglich, dass der Songtext für diesen Song nicht übereinstimmt.",
"instrumental": "⚠️ - Das ist ein instrumentales Lied"
}
},
"taskbar-mediacontrol": {
"description": "Wiedergabe aus der Windows Taskleiste kontrollieren",
"name": "Mediensteuerung in der Taskleiste"
},
"touchbar": {
"description": "Fügt ein TouchBar-Widget für macOS-Benutzer hinzu",
"name": "TouchBar"
},
"transparent-player": {
"description": "Macht das Player-Fenster transparent",
"menu": {
"opacity": {
"label": "Hintergrund-Sichtbarkeit",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Acryl",
"mica": "Mica",
"none": "Nichts",
"tabbed": "Mit Registerkarten"
}
}
},
"name": "Transparenter Player"
},
"tuna-obs": {
"description": "Integration mit dem OBS-Plugin Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Verhindert das Aufpoppen des Spielers während ein Song gespielt wird",
"name": "Unauffälliger Player"
},
"video-toggle": {
"description": "Fügt einen Knopf hinzu, um zwischen Video-/Liedmodus zu wechseln. kann auch genutzt werden, um den ganzen Videoabschnitt zu entfernen",
"menu": {
"align": {
"label": "Ausrichtung",
"submenu": {
"left": "Links",
"middle": "Mitte",
"right": "Rechts"
}
},
"force-hide": "Entfernen des Videoabschnitts erzwingen",
"mode": {
"label": "Modus",
"submenu": {
"custom": "Angepasster Schalter",
"disabled": "Deaktiviert",
"native": "Eingebauter Schalter"
}
}
},
"name": "Videoumschalter",
"templates": {
"button-song": "Lied",
"button-video": "Video"
}
},
"visualizer": {
"description": "Fügt einen Visualisierer zum Player hinzu",
"menu": {
"visualizer-type": "Visualisierertyp"
},
"name": "Visualisierer"
}
}
}
================================================
FILE: src/i18n/resources/el.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Απέτυχε η εκτέλεση του plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Το plugin {{pluginName}}::{{contextName}} εκτελέστηκε σε {{ms}}ms",
"initialize-failed": "Απέτυχε η εκκίνηση του plugin \"{{pluginName}}\"",
"load-all": "Φόρτωση όλων των plugin",
"load-failed": "Απέτυχε η φόρτωση του plugin \"{{pluginName}}\"",
"loaded": "Το plugin \"{{pluginName}}\" φορτώθηκε",
"unload-failed": "Απέτυχε η εκφόρτωση του plugin \"{{pluginName}}\"",
"unloaded": "Το plugin \"{{pluginName}}\" εκφορτώθηκε"
}
}
},
"language": {
"code": "el",
"local-name": "Ελληνικά",
"name": "Greek"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Ολοκληρώθηκε η φόρτωση. Τα DevTools άνοιξαν"
},
"i18n": {
"loaded": "Το i18n φορτώθηκε"
},
"second-instance": {
"receive-command": "Λήφθηκε εντολή μέσω πρωτοκόλλου: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Το αρχείο CSS \"{{cssFile}}\" δεν υπάρχει, θα αγνοηθεί"
},
"unresponsive": {
"details": "Σφάλμα απόκρισης!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Εκκαθάριση μνήμης cache εφαρμογής"
},
"window": {
"tried-to-render-offscreen": "Το παράθυρο προσπάθησε να απεικονίσει εκτός οθόνης, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Το μενού είναι κρυμμένο, χρησιμοποιήστε το 'Alt' για να το εμφανίσετε (ή το 'Escape' αν χρησιμοποιείτε το μενού εντός εφαρμογής)",
"message": "Η απόκρυψη μενού είναι ενεργοποιημένη",
"title": "Η απόκρυψη μενού ενεργοποιήθηκε"
},
"need-to-restart": {
"buttons": {
"later": "Αργότερα",
"restart-now": "Επανεκκίνηση τώρα"
},
"detail": "Το plugin \"{{pluginName}}\" απαιτεί επανεκκίνηση για να ενεργοποιηθεί",
"message": "Το plugin \"{{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": "Επαναφορά μνήμης cache εφαρμογής όταν η εφαρμογή ξεκινά",
"disable-hardware-acceleration": "Απενεργοποίηση επιτάχυνσης από υπολογιστή",
"edit-config-json": "Επεξεργασία του config.json",
"override-user-agent": "Παράκαμψη του User-Agent",
"restart-on-config-changes": "Επανεκκίνηση σε αλλαγές του config",
"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": {
"custom-window-title": {
"label": "Προσαρμοσμένος τίτλος παραθύρου",
"prompt": {
"label": "Εισαγωγή προσαρμοσμένου τίτλου παραθύρου: (κενό για απενεργοποίηση)",
"placeholder": "Παράδειγμα: {{applicationName}}"
}
},
"like-buttons": {
"default": "Προεπιλογή",
"force-show": "Επιβολή εμφάνισης",
"hide": "Απόκρυψη",
"label": "Κουμπιά like",
"swap": "Εναλλαγή της σειράς των κουμπιών like"
},
"remove-upgrade-button": "Αφαίρεση κουμπιού αναβάθμισης",
"theme": {
"dialog": {
"button": {
"cancel": "Άκυρο",
"remove": "Αφαίρεση"
},
"remove-theme": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε το προσαρμοσμένο θέμα;",
"remove-theme-message": "Αυτό θα αφαιρέσει το προσαρμοσμένο θέμα"
},
"label": "Θέμα",
"submenu": {
"import-css-file": "Εισαγωγή προσαρμοσμένου αρχείου CSS",
"no-theme": "Χωρίς θέμα"
}
}
}
}
}
},
"plugins": {
"enabled": "Ενεργοποιημένο",
"label": "Πρόσθετα",
"new": "ΝΕΟ"
},
"view": {
"label": "Προβολή",
"submenu": {
"force-reload": "Επιβολή επαναφόρτωσης",
"reload": "Επαναφόρτωση",
"reset-zoom": "Πραγματικό μέγεθος",
"toggle-fullscreen": "Εναλλαγή πλήρους οθόνης",
"zoom-in": "Μεγέθυνση",
"zoom-out": "Σμίκρυνση"
}
}
},
"tray": {
"next": "Επόμενο",
"play-pause": "Αναπαραγωγή/Παύση",
"previous": "Προηγούμενο",
"quit": "Έξοδος",
"restart": "Επανεκκίνηση εφαρμογής",
"show": "Εμφάνιση παραθύρου",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Εάν ξεκινήσει διαφήμιση, ο ήχος απενεργοποιείται και η ταχύτητα αναπαραγωγής ορίζεται σε 16x",
"name": "Επιτάχυνση διαφημίσεων"
},
"adblocker": {
"description": "Αποκλεισμός όλων των διαφημίσεων και της παρακολούθησης από προεπιλογή",
"menu": {
"blocker": "Πρόγραμμα αποκλεισμού"
},
"name": "Πρόγραμμα αποκλεισμού διαφημίσεων"
},
"album-actions": {
"description": "Προσθέτει κουμπιά Like/Unlike και Dislike/Undislike που δρουν συνολικά σε όλα τα κομμάτια μιας playlist ή ενός άλμπουμ",
"name": "Ενέργειες σε Άλμπουμ"
},
"album-color-theme": {
"description": "Εφαρμόζει ένα δυναμικό θέμα και οπτικά εφέ βάσει της παλέτας χρωμάτων του άλμπουμ",
"menu": {
"color-mix-ratio": {
"label": "Αναλογία ανάμειξης χρωμάτων",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Θέμα χρωμάτων άλμπουμ"
},
"ambient-mode": {
"description": "Εφαρμόζει ένα εφέ φωτισμού ρίχνοντας απαλά χρώματα από το βίντεο στο φόντο της οθόνης σας",
"menu": {
"blur-amount": {
"label": "Ποσότητα θολώματος",
"submenu": {
"pixels": "{{blurAmount}} pixels"
}
},
"buffer": {
"label": "Ενδιάμεση μνήμη",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Αδιαφάνεια",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Ποιότητα",
"submenu": {
"pixels": "{{quality}} pixels"
}
},
"size": {
"label": "Μέγεθος",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Ομαλή μετάβαση",
"submenu": {
"during": "Για {{interpolationTime}} δευτερόλεπτα"
}
},
"use-fullscreen": {
"label": "Σε πλήρη οθόνη"
}
},
"name": "Λειτουργία περιβάλλοντος"
},
"amuse": {
"description": "Προσθέτει υποστήριξη {{applicationName}} στο widget Amuse now playing από την 6K Labs",
"name": "Amuse",
"response": {
"query": "Ο διακομιστής Amuse API εκτελείται. GET /query για να λάβετε πληροφορίες για το τραγούδι."
}
},
"api-server": {
"description": "Προσθέτει έναν διακομιστή API για τον έλεγχο του παίκτη",
"dialog": {
"request": {
"buttons": {
"allow": "Αποδοχή",
"deny": "Άρνηση"
},
"message": "Επιτρέψτε {{ID}} ({{origin}}) να έχει πρόσβαση στο API;",
"title": "Αίτημα εξουσιοδότησης API"
}
},
"menu": {
"auth-strategy": {
"label": "Στρατηγική εξουσιοδότησης",
"submenu": {
"auth-at-first": {
"label": "Εξουσιοδότηση στο πρώτο αίτημα"
},
"none": {
"label": "Χωρίς εξουσιοδότηση"
}
}
},
"hostname": {
"label": "Όνομα κεντρικού υπολογιστή"
},
"port": {
"label": "Θύρα"
}
},
"name": "Διακομιστής API [Beta]",
"prompt": {
"hostname": {
"label": "Εισάγετε το όνομα κεντρικού υπολογιστή (όπως 0.0.0.0.0) για τον διακομιστή API:",
"title": "Όνομα κεντρικού υπολογιστή"
},
"port": {
"label": "Εισάγετε τη θύρα για το διακομιστή API:",
"title": "Θύρα"
}
}
},
"audio-compressor": {
"description": "Συμπίεση ήχου (μειώνει την ένταση των πιο δυνατών τμημάτων του κύματος και αυξάνει την ένταση των πιο μαλακών τμημάτων)",
"name": "Συμπιεστής ήχου"
},
"auth-proxy-adapter": {
"description": "Υποστήριξη για τη χρήση υπηρεσιών μεσολάβησης αυθεντικοποίησης",
"menu": {
"disable": "Απενεργοποίηση προσαρμογέα μεσολάβησης",
"enable": "Ενεργοποίηση προσαρμογέα μεσολάβησης",
"hostname": {
"label": "Όνομα οικοδεσπότη"
},
"port": {
"label": "Θύρα"
}
},
"name": "Προσαρμογέας μεσολάβησης Auth",
"prompt": {
"hostname": {
"label": "Εισάγετε το όνομα κεντρικού υπολογιστή για τον τοπικό διακομιστή μεσολάβησης (απαιτείται επανεκκίνηση):",
"title": "Όνομα κεντρικού υπολογιστή μεσολάβησης"
},
"port": {
"label": "Εισάγετε τη θύρα για τον τοπικό διακομιστή μεσολάβησης (απαιτεί επανεκκίνηση):",
"title": "Θύρα διακομιστή μεσολάβησης"
}
}
},
"blur-nav-bar": {
"description": "θέτει τη γραμμή πλοήγησης διαφανή και θολή",
"name": "Θόλωμα γραμμής πλοήγησης"
},
"bypass-age-restrictions": {
"description": "Παράκαμψη επαλήθευσης ηλικίας στο Music Player",
"name": "Παράκαμψη ηλικιακών περιορισμών"
},
"captions-selector": {
"description": "Επιλογέας λεζάντας για μουσικά κομμάτια ήχου του {{applicationName}}",
"menu": {
"autoload": "Αυτόματη επιλογή της τελευταίας χρησιμοποιούμενης λεζάντας",
"disable-captions": "Χωρίς λεζάντες από προεπιλογή"
},
"name": "Επιλογέας λεζάντες",
"prompt": {
"selector": {
"label": "Τρέχουσα γλώσσα λεζάντας: {{language}}",
"none": "None",
"title": "Επιλογή γλώσσας λεζάντας"
}
},
"templates": {
"title": "Ανοίξτε τον επιλογέα λεζάντας"
},
"toast": {
"caption-changed": "Λεζάντα άλλαξε σε {{language}}",
"caption-disabled": "Λεζάντες απενεργοποιήθηκαν",
"no-captions": "Λεζάντες μη διαθέσιμες για αυτό το τραγούδι"
}
},
"compact-sidebar": {
"description": "Να είναι πάντα συμπαγές το sidebar",
"name": "Συμπαγής πλευρική μπάρα"
},
"crossfade": {
"description": "Crossfade μεταξύ τραγουδιών",
"menu": {
"advanced": "Για προχωρημένους"
},
"name": "Crossfade [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Διάρκεια εξασθένισης (ms)",
"fade-out-duration": "Διάρκεια σβήσιμου (ms)",
"fade-scaling": {
"label": "Κλιμάκωση εξασθένισης",
"linear": "Γραμμική",
"logarithmic": "Λογαριθμική"
},
"seconds-before-end": "Crossfade N δευτερόλεπτα πριν το τέλος"
},
"title": "Επιλογές Crossfade"
}
}
},
"disable-autoplay": {
"description": "Κάνει τα τραγούδια να είναι αυτόματα σε παύση",
"menu": {
"apply-once": "Εφαρμόζεται μόνο στο πρώτο τραγούδι"
},
"name": "Απενεργοποίηση αυτόματης αναπαραγωγής"
},
"discord": {
"backend": {
"already-connected": "Προσπάθεια σύνδεσης με ενεργή σύνδεση",
"connected": "Συνδεδεμένος με το Discord",
"disconnected": "Αποσυνδεδεμένος από το Discord"
},
"description": "Δείξτε στους φίλους σας τι ακούτε με το Rich Presence",
"menu": {
"auto-reconnect": "Αυτόματη επανασύνδεση",
"clear-activity": "Εκκαθάριση δραστηριότητας",
"clear-activity-after-timeout": "Εκκαθάριση δραστηριότητας μετά από χρονικό όριο",
"connected": "Συνδεδεμένο",
"disconnected": "Αποσυνδεδεμένο",
"hide-duration-left": "Απόκρυψη της διάρκειας που απομένει",
"hide-github-button": "Απόκρυψη κουμπιού συνδέσμου GitHub",
"play-on-application": "Αναπαραγωγή στο {{applicationName}}",
"set-inactivity-timeout": "Ορισμός χρονικού ορίου αδράνειας"
},
"name": "Discord Πλούσια παρουσία",
"prompt": {
"set-inactivity-timeout": {
"label": "Εισαγωγή χρονικού ορίου αδράνειας σε δευτερόλεπτα:",
"title": "Ορισμός χρονικού ορίου αδράνειας"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Ωχ! Λυπούμαστε, η λήψη απέτυχε…",
"title": "Σφάλμα στη λήψη!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "{{playlistSize}} τραγούδια)",
"message": "Λήψη της λίστας αναπαραγωγής {{playlistTitle}}",
"title": "Η λήψη ξεκίνησε"
}
},
"feedback": {
"conversion-progress": "Μετατροπή: {{percent}}%",
"converting": "Μετατροπή…",
"done": "Τέλος: {{filePath}}",
"download-info": "Λήψη του {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Λήψη: {{percent}}%",
"downloading": "Λήψη…",
"downloading-counter": "Λήψη {{current}}/{{total}}…",
"downloading-playlist": "Λήψη της λίστας αναπαραγωγής \"{{playlistTitle}}\" - {{playlistSize}} τραγούδια ({{playlistId}})",
"error-while-downloading": "Σφάλμα λήψης \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Ο φάκελος {{playlistFolder}} υπάρχει ήδη",
"getting-playlist-info": "Λήψη πληροφοριών λίστας αναπαραγωγής…",
"loading": "Φόρτωση…",
"playlist-has-only-one-song": "Η λίστα αναπαραγωγής έχει μόνο ένα στοιχείο, κατεβάζοντάς το απευθείας",
"playlist-id-not-found": "Δεν βρέθηκε ID λίστας αναπαραγωγής",
"playlist-is-empty": "Η λίστα αναπραγωγής είναι άδεια",
"playlist-is-mix-or-private": "Σφάλμα λήψης πληροφοριών λίστας αναπαραγωγής: βεβαιωθείτε ότι δεν είναι ιδιωτική ή «Μικτή για εσάς» λίστα αναπαραγωγής\n\n{{error}}",
"preparing-file": "Προετοιμασία αρχείου…",
"saving": "Αποθήκευση…",
"trying-to-get-playlist-id": "Προσπαθώ να πάρω το αναγνωριστικό της λίστας αναπαραγωγής: {{playlistId}}",
"video-id-not-found": "Το βίντεο δεν βρέθηκε",
"writing-id3": "Εγγραφή ετικετών ID3…"
}
},
"description": "Λήψεις MP3 / ήχου πηγής απευθείας από τη διεπαφή",
"menu": {
"choose-download-folder": "Επιλογή φακέλου λήψης",
"download-finish-settings": {
"label": "Λήψη στο τέλος",
"prompt": {
"last-percent": "Μετά από x ποσοστό",
"last-seconds": "Τελευταία x δευτερόλεπτα",
"title": "Ρύθμιση του πότε θα γίνεται λήψη"
},
"submenu": {
"advanced": "Για προχωρημένους",
"enabled": "Ενεργοποιημένο",
"mode": "Λειτουργία χρόνου",
"percent": "Ποσοστό",
"seconds": "Δευτερόλεπτα"
}
},
"download-playlist": "Λήψη λίστας αναπαραγωγής",
"presets": "Προεπιλογές",
"skip-existing": "Παράλειψη υπάρχοντων αρχείων"
},
"name": "Κατεβαστής",
"renderer": {
"can-not-update-progress": "Δεν μπορεί να ενημερωθεί η πρόοδος"
},
"templates": {
"button": "Λήψη"
}
},
"equalizer": {
"description": "Προσθέτει έναν ισοσταθμιστή στο πρόγραμμα αναπαραγωγής",
"menu": {
"presets": {
"label": "Προεπιλογές",
"list": {
"bass-booster": "Ενίσχυση μπάσου"
}
}
},
"name": "Ισοσταθμιστής"
},
"exponential-volume": {
"description": "Κάνει το ρυθμιστικό έντασης εκθετικό, ώστε να είναι ευκολότερη η επιλογή χαμηλότερων εντάσεων.",
"name": "Εκθετικός όγκος"
},
"in-app-menu": {
"description": "Δίνει στις γραμμές μενού μια φανταχτερή, σκοτεινή ή άλμπουμ-χρωματική εμφάνιση",
"menu": {
"hide-dom-window-controls": "Απόκρυψη στοιχείων ελέγχου παραθύρου DOM"
},
"name": "Μενού εντός της εφαρμογής"
},
"lumiastream": {
"description": "Προσθέτει υποστήριξη Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Προσθέτει υποστήριξη στίχων για τα περισσότερα τραγούδια",
"menu": {
"romanized-lyrics": "Ρομαντικοποιημένοι στίχοι"
},
"name": "Στίχοι 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": "Γίνετε μέλος της Μουσικής Μαζί",
"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": "Απέτυχε να φιλοξενήσει το Μουσική Μαζί",
"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": "Πλοήγηση",
"templates": {
"back": {
"title": "Μετάβαση στην προηγούμενη σελίδα"
},
"forward": {
"title": "Μετάβαση στην επόμενη σελίδα"
}
}
},
"no-google-login": {
"description": "Αφαίρεση των κουμπιών και των συνδέσμων σύνδεσης Google από το περιβάλλον εργασίας",
"name": "No Google Login"
},
"notifications": {
"description": "Εμφάνιση ειδοποίησης όταν ξεκινάει η αναπαραγωγή ενός τραγουδιού (οι διαδραστικές ειδοποιήσεις είναι διαθέσιμες στα Windows)",
"menu": {
"interactive": "Διαδραστικές ειδοποιήσεις",
"interactive-settings": {
"label": "Διαδραστικές ρυθμίσεις",
"submenu": {
"hide-button-text": "Απόκρυψη κειμένου κουμπιού",
"refresh-on-play-pause": "Ανανέωση σε Αναπαραγωγή/Παύση",
"tray-controls": "Άνοιγμα/κλείσιμο με κλικ στο δίσκο"
}
},
"priority": "Προτεραιότητα κοινοποίησης",
"toast-style": "Στυλ τοστ",
"unpause-notification": "Εμφάνιση ειδοποίησης κατά την κατάργηση της παύσης"
},
"name": "Ειδοποιήσεις"
},
"performance-improvement": {
"description": "Βελτιώστε την απόδοση ενεργοποιώντας πειραματικές δέσμες ενεργειών",
"name": "Βελτίωση της απόδοσης με την ενεργοποίηση επικίνδυνων σεναρίωνΒελτίωση της απόδοσης [Beta]"
},
"picture-in-picture": {
"description": "Επιτρέπει την εναλλαγή της εφαρμογής σε λειτουργία εικόνας σε εικόνα",
"menu": {
"always-on-top": "Πάντα σε πρώτο πλάνο",
"hotkey": {
"label": "Πλήκτρο πρόσβασης",
"prompt": {
"keybind-options": {
"hotkey": "Πλήκτρο πρόσβασης"
},
"label": "Επιλέξτε ένα πλήκτρο συντόμευσης για να ενεργοποιήσετε την εικόνα στην εικόνα",
"title": "Πλήκτρο Hotkey Εικόνα-σε-Εικόνα"
}
},
"save-window-position": "Αποθήκευση θέσης παραθύρου",
"save-window-size": "Αποθήκευση μεγέθους παραθύρου",
"use-native-pip": "Χρήση εγγενούς PiP του προγράμματος περιήγησης"
},
"name": "Εικόνα-στην-εικόνα",
"templates": {
"button": "Εικόνα-στην-εικόνα"
}
},
"playback-speed": {
"description": "Ακούστε γρήγορα, ακούστε αργά! Προσθέτει ένα ρυθμιστικό που ελέγχει την ταχύτητα του τραγουδιού",
"name": "Ταχύτητα αναπαραγωγής",
"templates": {
"button": "Ταχύτητα"
}
},
"precise-volume": {
"description": "Ελέγξτε την ένταση του ήχου με ακρίβεια χρησιμοποιώντας τον τροχό του ποντικιού/τα πλήκτρα, με ένα προσαρμοσμένο HUD και προσαρμόσιμα βήματα έντασης",
"menu": {
"arrows-shortcuts": "Τοπικά πλήκτρα βέλους Έλεγχοι",
"custom-volume-steps": "Ορισμός προσαρμοσμένων βημάτων έντασης ήχου",
"global-shortcuts": "Παγκόσμια πλήκτρα συντόμευσης"
},
"name": "Ακριβής Ήχος",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Μείωση έντασης",
"increase": "Αύξηση έντασης"
},
"label": "Επιλέξτε Παγκόσμια δέσμευση πλήκτρων έντασης ήχου:",
"title": "Επιλέξτε Παγκόσμια δέσμευση πλήκτρων έντασης ήχου"
},
"volume-steps": {
"label": "Επιλέξτε Βήματα αύξησης/μείωσης έντασης ήχου",
"title": "Βήματα έντασης"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Τρέχουσα ποιότητα: {{quality}}",
"message": "Επιλογή ποιότητας βίντεο:",
"title": "Επιλογή ποιότητας βίντεο"
}
}
},
"description": "Επιτρέπει την αλλαγή της ποιότητας βίντεο με ένα κουμπί στην επικάλυψη βίντεο",
"name": "Αλλαγή ποιότητας βίντεο",
"renderer": {
"quality-settings-button": {
"label": "Άνοιγμα ρυθμίσεων ποιότητας αναπαραγωγέα"
}
}
},
"scrobbler": {
"description": "Προσθήκη υποστήριξης scrobbling (κ.λπ. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Απέτυχε η πιστοποίηση ταυτότητας στο Last.fm\nΚρύψτε το αναδυόμενο παράθυρο μέχρι την επόμενη επανεκκίνηση.",
"title": "Αποτυχία ελέγχου ταυτότητας"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Ρυθμίσεις API Last.fm"
},
"listenbrainz": {
"token": "Εισάγετε το διακριτικό χρήστη ListenBrainz"
},
"scrobble-alternative-title": "Χρήση εναλλακτικών τίτλων",
"scrobble-other-media": "Scrobble άλλα μέσα ενημέρωσης"
},
"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": "Επιλέξτε Global Keybinds για το τραγούδι Έλεγχος:",
"title": "Παγκόσμια δέσμευση πλήκτρων"
}
}
},
"skip-disliked-songs": {
"description": "Παραλείπει τα αρεστά τραγούδια",
"name": "Παραλείψτε τα τραγούδια που δεν άρεσαν"
},
"skip-silences": {
"description": "Αυτόματη παράλειψη τμημάτων σιωπής σε τραγούδια",
"name": "Παραλείψτε τις σιωπές"
},
"sponsorblock": {
"description": "Παραλείπει αυτόματα μέρη που δεν είναι μουσικά, όπως intro/outro ή μέρη μουσικών βίντεο όπου δεν παίζεται το τραγούδι",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Παρέχει συγχρονισμένους στίχους σε τραγούδια, χρησιμοποιώντας παρόχους όπως η LRClib.",
"errors": {
"fetch": "⚠️ Προέκυψε σφάλμα κατά την ανάκτηση των στίχων.\n\tΠροσπαθήστε ξανά αργότερα.",
"not-found": "⚠️ Δεν βρέθηκαν στίχοι για αυτό το τραγούδι."
},
"menu": {
"default-text-string": {
"label": "Προεπιλεγμένος χαρακτήρας μεταξύ στίχων",
"tooltip": "Επιλέξτε τον προεπιλεγμένο χαρακτήρα που θα χρησιμοποιηθεί για το κενό μεταξύ των στίχων"
},
"line-effect": {
"label": "Επίδραση γραμμής",
"submenu": {
"fancy": {
"label": "Φανταχτερό",
"tooltip": "Χρήση μεγάλων εφέ που μοιάζουν με εφαρμογές στην τρέχουσα γραμμή"
},
"focus": {
"label": "Εστίαση",
"tooltip": "Κάντε μόνο την τρέχουσα γραμμή λευκή"
},
"offset": {
"label": "Μετατόπιση",
"tooltip": "Μετατόπιση προς τα δεξιά της τρέχουσας γραμμής"
},
"scale": {
"label": "Κλίμακα",
"tooltip": "Κλιμάκωση της τρέχουσας γραμμής"
}
},
"tooltip": "Επιλέξτε το εφέ που θα εφαρμοστεί στην τρέχουσα γραμμή"
},
"precise-timing": {
"label": "Κάντε τους στίχους τέλεια συγχρονισμένους",
"tooltip": "Υπολογίζει με ακρίβεια χιλιοστού του δευτερολέπτου την εμφάνιση της επόμενης γραμμής (μπορεί να έχει μικρή επίπτωση στην απόδοση)"
},
"romanization": {
"label": "Στίχοι Ρομαντικοποίηση",
"tooltip": "Αν οι στίχοι είναι σε διαφορετική γλώσσα, προσπαθήστε να εμφανίσετε μια λατινική έκδοση."
},
"show-lyrics-even-if-inexact": {
"label": "Εμφάνιση στίχων ακόμα και αν είναι ανακριβείς",
"tooltip": "Εάν το τραγούδι δεν βρεθεί, το πρόσθετο προσπαθεί ξανά με διαφορετικό ερώτημα αναζήτησης.\nΤο αποτέλεσμα της δεύτερης προσπάθειας μπορεί να μην είναι ακριβές."
},
"show-time-codes": {
"label": "Εμφάνιση κωδικών ώρας",
"tooltip": "Εμφάνιση των κωδικών ώρας δίπλα στους στίχους"
}
},
"name": "Συγχρονισμένοι στίχοι",
"refetch-btn": {
"fetching": "Φέρνοντας...",
"normal": "Στίχοι Refetch"
},
"warnings": {
"duration-mismatch": "⚠️ - Οι στίχοι ενδέχεται να μην είναι συγχρονισμένοι λόγω αναντιστοιχίας διάρκειας.",
"inexact": "⚠️ - Οι στίχοι για αυτό το τραγούδι μπορεί να μην είναι ακριβείς",
"instrumental": "⚠️ - Αυτό είναι ένα ορχηστρικό τραγούδι"
}
},
"taskbar-mediacontrol": {
"description": "Έλεγχος αναπαραγωγής από τη γραμμή εργασιών των Windows",
"name": "Έλεγχος μέσων γραμμής εργασιών"
},
"touchbar": {
"description": "Προσθέτει ένα γραφικό στοιχείο TouchBar για χρήστες macOS",
"name": "TouchBar"
},
"tuna-obs": {
"description": "Ενσωμάτωση με το plugin Tuna του OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Αποτρέπει την εμφάνιση του προγράμματος αναπαραγωγής κατά την αναπαραγωγή ενός τραγουδιού",
"name": "Ανεπαίσθητος παίκτης"
},
"video-toggle": {
"description": "Προσθέτει ένα κουμπί για εναλλαγή μεταξύ της λειτουργίας βίντεο/τραγουδιού. μπορεί επίσης προαιρετικά να αφαιρέσει ολόκληρη την καρτέλα βίντεο",
"menu": {
"align": {
"label": "Στοίχιση",
"submenu": {
"left": "Αριστερά",
"middle": "Middle",
"right": "Δεξιά"
}
},
"force-hide": "Αναγκαστική αφαίρεση καρτέλας βίντεο",
"mode": {
"label": "Mode",
"submenu": {
"custom": "Προσαρμοσμένη εναλλαγή",
"disabled": "Απενεργοποιημένο",
"native": "Γηγενής εναλλαγή"
}
}
},
"name": "Εναλλαγή βίντεο",
"templates": {
"button-song": "Τραγούδι",
"button-video": "Βίντεο"
}
},
"visualizer": {
"description": "Προσθέτει έναν απεικονιστή στο πρόγραμμα αναπαραγωγής",
"menu": {
"visualizer-type": "Τύπος απεικονιστή"
},
"name": "Απεικονιστής"
}
}
}
================================================
FILE: src/i18n/resources/en.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Failed to execute plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} executed at {{ms}}ms",
"initialize-failed": "Failed to initialize plugin \"{{pluginName}}\"",
"load-all": "Loading all plugins",
"load-failed": "Failed to load plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" loaded",
"unload-failed": "Failed to unload plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" unloaded"
}
}
},
"language": {
"code": "en",
"local-name": "English",
"name": "English"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Finished loading. DevTools opened"
},
"i18n": {
"loaded": "i18n loaded"
},
"second-instance": {
"receive-command": "Received command over protocol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS file \"{{cssFile}}\" does not exist, ignoring"
},
"unresponsive": {
"details": "Unresponsive Error!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Clearing app cache"
},
"window": {
"tried-to-render-offscreen": "Window tried to render offscreen, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu is hidden, use 'Alt' to show it (or 'Escape' if using In-App Menu)",
"message": "Hide Menu is enabled",
"title": "Hide Menu Enabled"
},
"need-to-restart": {
"buttons": {
"later": "Later",
"restart-now": "Restart Now"
},
"detail": "\"{{pluginName}}\" plugin requires a restart to take effect",
"message": "\"{{pluginName}}\" needs to restart",
"title": "Restart Required"
},
"unresponsive": {
"buttons": {
"quit": "Quit",
"relaunch": "Relaunch",
"wait": "Wait"
},
"detail": "We are sorry for the inconvenience! please choose what to do:",
"message": "The Application is Unresponsive",
"title": "Window Unresponsive"
},
"update-available": {
"buttons": {
"disable": "Disable Updates",
"download": "Download",
"ok": "OK"
},
"detail": "A new version is available and can be downloaded at {{downloadLink}}",
"message": "A new version is available",
"title": "Update Available"
}
},
"menu": {
"about": "About",
"navigation": {
"label": "Navigation",
"submenu": {
"copy-current-url": "Copy current URL",
"go-back": "Go back",
"go-forward": "Go forward",
"quit": "Exit",
"restart": "Restart App"
}
},
"options": {
"label": "Options",
"submenu": {
"advanced-options": {
"label": "Advanced options",
"submenu": {
"auto-reset-app-cache": "Reset app cache when app starts",
"disable-hardware-acceleration": "Disable hardware acceleration",
"edit-config-json": "Edit config.json",
"override-user-agent": "Override User-Agent",
"restart-on-config-changes": "Restart on config changes",
"set-proxy": {
"label": "Set proxy",
"prompt": {
"label": "Enter Proxy Address: (leave empty to disable)",
"placeholder": "Example: SOCKS5://127.0.0.1:9999",
"title": "Set proxy"
}
},
"toggle-dev-tools": "Toggle DevTools"
}
},
"always-on-top": "Always on top",
"auto-update": "Auto Update",
"hide-menu": {
"dialog": {
"message": "Menu will be hidden on next launch, use [Alt] to show it (or backtick [`] if using in-app-menu)",
"title": "Hide Menu Enabled"
},
"label": "Hide Menu"
},
"language": {
"dialog": {
"message": "Language will be changed after restart",
"title": "Language Changed"
},
"label": "Language",
"submenu": {
"to-help-translate": "Want to help translate? Click here"
}
},
"resume-on-start": "Resume last song when app starts",
"single-instance-lock": "Single Instance Lock",
"start-at-login": "Start at login",
"starting-page": {
"label": "Starting page",
"unset": "Unset"
},
"tray": {
"label": "Tray",
"submenu": {
"disabled": "Disabled",
"enabled-and-hide-app": "Enabled and hide app",
"enabled-and-show-app": "Enabled and show app",
"play-pause-on-click": "Play/Pause on click"
}
},
"visual-tweaks": {
"label": "Visual Tweaks",
"submenu": {
"like-buttons": {
"default": "Default",
"force-show": "Force show",
"hide": "Hide",
"swap": "Swap like buttons order",
"label": "Like buttons"
},
"custom-window-title": {
"label": "Custom window title",
"prompt": {
"label": "Enter custom window title: (leave empty to disable)",
"placeholder": "Example: {{applicationName}}"
}
},
"remove-upgrade-button": "Remove upgrade button",
"theme": {
"dialog": {
"button": {
"cancel": "Cancel",
"remove": "Remove"
},
"remove-theme": "Are you sure you want to remove the custom theme?",
"remove-theme-message": "This will remove the custom theme"
},
"label": "Theme",
"submenu": {
"import-css-file": "Import custom CSS file",
"no-theme": "No theme"
}
}
}
}
}
},
"plugins": {
"enabled": "Enabled",
"label": "Plugins",
"new": "NEW"
},
"view": {
"label": "View",
"submenu": {
"force-reload": "Force Reload",
"reload": "Reload",
"reset-zoom": "Actual Size",
"toggle-fullscreen": "Toggle Full Screen",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
}
}
},
"tray": {
"next": "Next",
"play-pause": "Play/Pause",
"previous": "Previous",
"quit": "Exit",
"restart": "Restart App",
"show": "Show window",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "If an ad play it mutes the audio and sets playback speed to 16x",
"name": "Ad Speedup"
},
"adblocker": {
"description": "Block all ads and tracking out of the box",
"menu": {
"blocker": "Blocker"
},
"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",
"menu": {
"color-mix-ratio": {
"label": "Color mix ratio",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Enable seekbar theming"
},
"name": "Album Color Theme"
},
"ambient-mode": {
"description": "Applies a lighting effect by casting gentle colors from the video, into your screen’s background",
"menu": {
"blur-amount": {
"label": "Blur amount",
"submenu": {
"pixels": "{{blurAmount}} pixels"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacity",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Quality",
"submenu": {
"pixels": "{{quality}} pixels"
}
},
"size": {
"label": "Size",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Smoothness transition",
"submenu": {
"during": "During {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Using fullscreen"
}
},
"name": "Ambient Mode"
},
"amuse": {
"description": "Adds {{applicationName}} support for the Amuse now playing widget by 6K Labs",
"name": "Amuse",
"response": {
"query": "Amuse API server is running. GET /query to get song info."
}
},
"api-server": {
"description": "Adds an API server to control the player",
"dialog": {
"request": {
"buttons": {
"allow": "Allow",
"deny": "Deny"
},
"message": "Allow {{ID}} ({{origin}}) to access the API?",
"title": "API authorization request"
}
},
"menu": {
"auth-strategy": {
"label": "Authorization strategy",
"submenu": {
"auth-at-first": {
"label": "Authorize at first request"
},
"none": {
"label": "No authorization"
}
}
},
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
},
"https": {
"label": "HTTPS & Certificates",
"submenu": {
"enable-https": {
"label": "Enable HTTPS"
},
"cert": {
"label": "Certificate file (.crt/.pem)",
"dialogTitle": "Select HTTPS certificate file"
},
"key": {
"label": "Private key file (.key/.pem)",
"dialogTitle": "Select HTTPS private key file"
}
}
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Enter the hostname (like 0.0.0.0) for the API server:",
"title": "Hostname"
},
"port": {
"label": "Enter the port for the API server:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the volume of the softest parts)",
"name": "Audio Compressor"
},
"auth-proxy-adapter": {
"description": "Support for the use of authentication proxy services",
"menu": {
"disable": "Disable Proxy Adapter",
"enable": "Enable Proxy Adapter",
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "Auth Proxy Adapter",
"prompt": {
"hostname": {
"title": "Proxy Hostname",
"label": "Enter hostname for local proxy server (requires restart):"
},
"port": {
"title": "Proxy Port",
"label": "Enter port for local proxy server (requires restart):"
}
}
},
"blur-nav-bar": {
"description": "Makes navigation bar transparent and blurry",
"name": "Blur Navigation Bar"
},
"bypass-age-restrictions": {
"description": "Bypass Music Player's age verification",
"name": "Bypass Age Restrictions"
},
"captions-selector": {
"description": "Caption selector for {{applicationName}} audio tracks",
"menu": {
"autoload": "Automatically select last used caption",
"disable-captions": "No captions by default"
},
"name": "Captions Selector",
"prompt": {
"selector": {
"label": "Current caption language: {{language}}",
"none": "None",
"title": "Select caption language"
}
},
"templates": {
"title": "Open captions selector"
},
"toast": {
"caption-changed": "Caption changed to {{language}}",
"caption-disabled": "Captions disabled",
"no-captions": "No captions available for this song"
}
},
"clock": {
"description": "Add a clock to the navigation bar",
"name": "Clock",
"menu": {
"format": {
"label": "Format",
"display-seconds": "Display Seconds",
"24-hour-format": "24-Hour Format"
}
}
},
"compact-sidebar": {
"description": "Always set the sidebar in compact mode",
"name": "Compact Sidebar"
},
"crossfade": {
"description": "Crossfade between songs",
"menu": {
"advanced": "Advanced"
},
"name": "Crossfade [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Fade in duration (ms)",
"fade-out-duration": "Fade out duration (ms)",
"fade-scaling": {
"label": "Fade scaling",
"linear": "Linear",
"logarithmic": "Logarithmic"
},
"seconds-before-end": "Crossfade N seconds before end"
},
"title": "Crossfade options"
}
}
},
"custom-output-device": {
"description": "Configure a custom output media device for songs",
"menu": {
"device-selector": "Select Device"
},
"name": "Custom Output Device",
"prompt": {
"device-selector": {
"label": "Choose the output media device to be used",
"title": "Select Output Device"
}
}
},
"disable-autoplay": {
"description": "Makes song start in \"paused\" mode",
"menu": {
"apply-once": "Applies only on startup"
},
"name": "Disable Autoplay"
},
"discord": {
"backend": {
"already-connected": "Attempted to connect with active connection",
"connected": "Connected to Discord",
"disconnected": "Disconnected from Discord"
},
"description": "Show your friends what you listen to with Rich Presence",
"menu": {
"auto-reconnect": "Auto reconnect",
"clear-activity": "Clear activity",
"clear-activity-after-timeout": "Clear activity after timeout",
"connected": "Connected",
"disconnected": "Disconnected",
"hide-duration-left": "Hide duration left",
"hide-github-button": "Hide GitHub link Button",
"play-on-application": "Play on {{applicationName}}",
"set-inactivity-timeout": "Set inactivity timeout",
"set-status-display-type": {
"label": "Status text",
"submenu": {
"application": "Listening to {{applicationName}}",
"artist": "Listening to {artist}",
"title": "Listening to {song title}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Enter inactivity timeout in seconds:",
"title": "Set inactivity timeout"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Argh! Apologies, download failed…",
"title": "Error in download!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} songs)",
"message": "Downloading Playlist {{playlistTitle}}",
"title": "Download started"
}
},
"feedback": {
"conversion-progress": "Conversion: {{percent}}%",
"converting": "Converting…",
"done": "Done: {{filePath}}",
"download-info": "Downloading {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Download: {{percent}}%",
"downloading": "Downloading…",
"downloading-counter": "Downloading {{current}}/{{total}}…",
"downloading-playlist": "Downloading playlist \"{{playlistTitle}}\" - {{playlistSize}} songs ({{playlistId}})",
"error-while-downloading": "Error downloading \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "The folder {{playlistFolder}} already exists",
"getting-playlist-info": "Getting playlist info…",
"loading": "Loading…",
"playlist-has-only-one-song": "Playlist has only one item, downloading it directly",
"playlist-id-not-found": "No playlist ID found",
"playlist-is-empty": "Playlist is empty",
"playlist-is-mix-or-private": "Error getting playlist info: make sure it isn't a private or \"Mixed for you\" playlist\n\n{{error}}",
"preparing-file": "Preparing file…",
"saving": "Saving…",
"trying-to-get-playlist-id": "Trying to get playlist ID: {{playlistId}}",
"video-id-not-found": "Video not found",
"writing-id3": "Writing ID3 tags…"
}
},
"description": "Downloads MP3 / source audio directly from the interface",
"menu": {
"choose-download-folder": "Choose download folder",
"download-finish-settings": {
"label": "Download on finish",
"prompt": {
"last-percent": "After x percent",
"last-seconds": "Last x seconds",
"title": "Configure when to download"
},
"submenu": {
"advanced": "Advanced",
"enabled": "Enabled",
"mode": "Time mode",
"percent": "Percent",
"seconds": "Seconds"
}
},
"download-playlist": "Download playlist",
"presets": "Presets",
"skip-existing": "Skip existing files"
},
"name": "Downloader",
"renderer": {
"can-not-update-progress": "Cannot update progress"
},
"templates": {
"button": "Download"
}
},
"equalizer": {
"description": "Adds an equalizer to the player",
"menu": {
"presets": {
"label": "Presets",
"list": {
"bass-booster": "Bass booster"
}
}
},
"name": "Equalizer"
},
"exponential-volume": {
"description": "Makes the volume slider exponential so it's easier to select lower volumes.",
"name": "Exponential Volume"
},
"in-app-menu": {
"description": "Gives menu-bars a fancy, dark or album-color look",
"menu": {
"hide-dom-window-controls": "Hide DOM window controls"
},
"name": "In-App Menu"
},
"lumiastream": {
"description": "Adds Lumia Stream support",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Adds lyrics support for most songs",
"menu": {
"romanized-lyrics": "Romanized Lyrics"
},
"name": "Lyrics Genius",
"renderer": {
"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",
"templates": {
"back": {
"title": "Go to previous page"
},
"forward": {
"title": "Go to next page"
}
}
},
"no-google-login": {
"description": "Remove Google login buttons and links from the interface",
"name": "No Google Login"
},
"notifications": {
"description": "Display a notification when a song starts playing (interactive notifications are available on Windows)",
"menu": {
"interactive": "Interactive Notifications",
"interactive-settings": {
"label": "Interactive Settings",
"submenu": {
"hide-button-text": "Hide button text",
"refresh-on-play-pause": "Refresh on Play/Pause",
"tray-controls": "Open/Close on tray click"
}
},
"priority": "Notification Priority",
"toast-style": "Toast style",
"unpause-notification": "Show notification on unpause"
},
"name": "Notifications"
},
"performance-improvement": {
"description": "Improve performance by enabling experimental scripts",
"name": "Performance improvement [Beta]"
},
"picture-in-picture": {
"description": "Allows to switch the app to picture-in-picture mode",
"menu": {
"always-on-top": "Always on top",
"hotkey": {
"label": "Hotkey",
"prompt": {
"keybind-options": {
"hotkey": "Hotkey"
},
"label": "Choose a hotkey to toggle picture-in-picture",
"title": "Picture-in-picture Hotkey"
}
},
"save-window-position": "Save window position",
"save-window-size": "Save window size",
"use-native-pip": "Use browser native PiP"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Listen fast, listen slow! Adds a slider that controls song speed",
"name": "Playback Speed",
"templates": {
"button": "Speed"
}
},
"precise-volume": {
"description": "Control the volume precisely using mousewheel/hotkeys, with a custom HUD and customizable volume steps",
"menu": {
"arrows-shortcuts": "Local Arrow-keys Controls",
"custom-volume-steps": "Set Custom Volume Steps",
"global-shortcuts": "Global Hotkeys"
},
"name": "Precise Volume",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Decrease Volume",
"increase": "Increase Volume"
},
"label": "Choose Global Volume Keybinds:",
"title": "Global Volume Keybinds"
},
"volume-steps": {
"label": "Choose Volume Increase/Decrease Steps",
"title": "Volume Steps"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Current Quality: {{quality}}",
"message": "Choose Video Quality:",
"title": "Choose Video Quality"
}
}
},
"description": "Allows changing the video quality with a button on the video overlay",
"name": "Video Quality Changer",
"renderer": {
"quality-settings-button": {
"label": "Open player quality changer"
}
}
},
"scrobbler": {
"description": "Add scrobbling support (etc. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Failed to authenticate with Last.fm\nHide the popup until the next restart.",
"title": "Authentication Failed"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API Settings"
},
"listenbrainz": {
"token": "Enter ListenBrainz user token"
},
"scrobble-alternative-title": "Use alternative titles",
"scrobble-alternative-artist": "Use alternative artists",
"scrobble-other-media": "Scrobble other media"
},
"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",
"menu": {
"override-media-keys": "Override Media Keys",
"set-keybinds": "Set Global Song Controls"
},
"name": "Shortcuts (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Next",
"play-pause": "Play / Pause",
"previous": "Previous"
},
"label": "Choose Global Keybinds for Songs Control:",
"title": "Global Keybinds"
}
}
},
"skip-disliked-songs": {
"description": "Skips disliked songs",
"name": "Skip Disliked Songs"
},
"skip-silences": {
"description": "Automatically skip silences sections in songs",
"name": "Skip Silences"
},
"sponsorblock": {
"description": "Automatically Skips non-music parts like intro/outro or parts of music videos where the song isn't playing",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Provides synced lyrics to songs, using providers like LRClib.",
"errors": {
"fetch": "⚠️\tAn error occurred while fetching the lyrics.\n\tPlease try again later.",
"not-found": "⚠️ No lyrics found for this song."
},
"menu": {
"preferred-provider": {
"label": "Preferred Provider",
"tooltip": "Choose the default provider to use",
"none": {
"label": "None",
"tooltip": "No preferred provider"
}
},
"default-text-string": {
"label": "Default character between lyrics",
"tooltip": "Choose the default character to use for the gap between lyrics"
},
"line-effect": {
"label": "Line effect",
"submenu": {
"fancy": {
"label": "Fancy",
"tooltip": "Use large, app-like effects on the current line"
},
"focus": {
"label": "Focus",
"tooltip": "Make only the current line white"
},
"offset": {
"label": "Offset",
"tooltip": "Offset on the right the current line"
},
"scale": {
"label": "Scale",
"tooltip": "Scale the current line"
}
},
"tooltip": "Choose the effect to apply to the current line"
},
"precise-timing": {
"label": "Make the lyrics perfectly synced",
"tooltip": "Calculate to the milisecond the display of the next line (can have a small impact on performance)"
},
"romanization": {
"label": "Romanize lyrics",
"tooltip": "If the lyrics are in a different language, try to display a latin version."
},
"show-lyrics-even-if-inexact": {
"label": "Show lyrics even if inexact",
"tooltip": "If the song is not found, the plugin tries again with a different search query.\nThe result from the second attempt may not be exact."
},
"show-time-codes": {
"label": "Show time codes",
"tooltip": "Show the time codes next to the lyrics"
},
"convert-chinese-character": {
"label": "Convert Chinese character",
"submenu": {
"disabled": {
"label": "Disabled",
"tooltip": "Disable Chinese character conversion"
},
"simplified-to-traditional": {
"label": "Simplified to Traditional",
"tooltip": "Convert Simplified Chinese to Traditional Chinese"
},
"traditional-to-simplified": {
"label": "Traditional to Simplified",
"tooltip": "Convert Traditional Chinese to Simplified Chinese"
}
},
"tooltip": "Convert Chinese character to Traditional or Simplified"
}
},
"name": "Synced Lyrics",
"refetch-btn": {
"fetching": "Fetching...",
"normal": "Refetch lyrics"
},
"warnings": {
"duration-mismatch": "⚠️ - The lyrics may be out of sync due to a duration mismatch.",
"inexact": "⚠️ - The lyrics for this song may not be exact",
"instrumental": "⚠️ - This is an instrumental song"
}
},
"taskbar-mediacontrol": {
"description": "Control playback from your Windows taskbar",
"name": "Taskbar Media Control"
},
"touchbar": {
"description": "Adds a TouchBar widget for macOS users",
"name": "TouchBar"
},
"transparent-player": {
"description": "Makes the app window transparent",
"name": "Transparent Player",
"menu": {
"opacity": {
"label": "Opacity",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Type",
"submenu": {
"acrylic": "Acrylic",
"mica": "Mica",
"tabbed": "Tabbed",
"none": "None"
}
}
}
},
"tuna-obs": {
"description": "Integration with OBS's plugin Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Prevents the player from popping up when playing a song",
"name": "Unobtrusive Player"
},
"video-toggle": {
"description": "Adds a button to switch between Video/Song mode. can also optionally remove the whole video tab",
"menu": {
"align": {
"label": "Alignment",
"submenu": {
"left": "Left",
"middle": "Middle",
"right": "Right"
}
},
"force-hide": "Force remove video tab",
"mode": {
"label": "Mode",
"submenu": {
"custom": "Custom toggle",
"disabled": "Disabled",
"native": "Native toggle"
}
}
},
"name": "Video Toggle",
"templates": {
"button-song": "Song",
"button-video": "Video"
}
},
"visualizer": {
"description": "Adds a visualizer to the player",
"menu": {
"visualizer-type": "Visualizer Type"
},
"name": "Visualizer"
}
}
}
================================================
FILE: src/i18n/resources/es.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Error al ejecutar el complemento {{pluginName}}::{{contextName}}",
"executed-at-ms": "Complemento {{pluginName}}::{{contextName}} se ejecutó en {{ms}}ms",
"initialize-failed": "Error al inicializar el complemento \"{{pluginName}}\"",
"load-all": "Cargando todos los complementos",
"load-failed": "Error al cargar el complemento \"{{pluginName}}\"",
"loaded": "Complementos \"{{pluginName}}\" cargados",
"unload-failed": "No se ha podido descargar el complemento \"{{pluginName}}\"",
"unloaded": "Complemento \"{{pluginName}}\" descargado"
}
}
},
"language": {
"code": "es",
"local-name": "Español",
"name": "Spanish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Carga finalizada. DevTools abierto"
},
"i18n": {
"loaded": "i18n cargado"
},
"second-instance": {
"receive-command": "Comando recibido sobre el protocolo: \"{{command}}\""
},
"theme": {
"css-file-not-found": "El archivo CSS \"{{cssFile}}\" no existe, ignorando"
},
"unresponsive": {
"details": "¡Error sin repuesta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Borrando caché de la aplicación"
},
"window": {
"tried-to-render-offscreen": "La ventana intentó mostrarse fuera de la pantalla, windowSize={{windowSize}}, displaySize={{displaySize}}, posicion={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "El menú está oculto, utiliza \"Alt\" para mostrarlo (o \"Escape\" si utilizas el menú integrado en la aplicación)",
"message": "El \"Menú Oculto\" está habilitado",
"title": "\"Menú oculto\" habilitado"
},
"need-to-restart": {
"buttons": {
"later": "Más tarde",
"restart-now": "Reiniciar ahora"
},
"detail": "El complemento \"{{pluginName}}\" requiere reiniciar para tomar efecto",
"message": "\"{{pluginName}}\" necesita reiniciar",
"title": "Se requiere reinicio"
},
"unresponsive": {
"buttons": {
"quit": "Salir",
"relaunch": "Volver a abrir",
"wait": "Espera"
},
"detail": "Sentimos las molestias. Por favor, elija qué hacer:",
"message": "La aplicación no responde",
"title": "La ventana no responde"
},
"update-available": {
"buttons": {
"disable": "Desactivar actualizaciones",
"download": "Descargar",
"ok": "OK"
},
"detail": "Una nueva versión está disponible y puede descargarse en {{downloadLink}}",
"message": "Hay una nueva versión disponible",
"title": "Actualización disponible"
}
},
"menu": {
"about": "Acerca de",
"navigation": {
"label": "Navegación",
"submenu": {
"copy-current-url": "Copiar la URL actual",
"go-back": "Atrás",
"go-forward": "Adelante",
"quit": "Salir",
"restart": "Reiniciar la aplicación"
}
},
"options": {
"label": "Opciones",
"submenu": {
"advanced-options": {
"label": "Opciones avanzadas",
"submenu": {
"auto-reset-app-cache": "Restablecer la caché de la aplicación al iniciarla",
"disable-hardware-acceleration": "Desactivar la aceleración por hardware",
"edit-config-json": "Editar config.json",
"override-user-agent": "Sobrescribir User-Agent",
"restart-on-config-changes": "Reiniciar al modificar la configuración",
"set-proxy": {
"label": "Establecer proxy",
"prompt": {
"label": "Introduzca la dirección del proxy: (déjela vacía para desactivarla)",
"placeholder": "Ejemplo: SOCKS5://127.0.0.1:9999",
"title": "Establecer proxy"
}
},
"toggle-dev-tools": "Activar DevTools"
}
},
"always-on-top": "Siempre al frente",
"auto-update": "Actualización automática",
"hide-menu": {
"dialog": {
"message": "El menú se ocultará la próxima vez que inicies la aplicación, usa [Alt] para mostrarlo (o pulsa [`] si usas el menú dentro de la aplicación)",
"title": "Menú oculto habilitado"
},
"label": "Ocultar menú"
},
"language": {
"dialog": {
"message": "El idioma se cambiará después de reiniciar",
"title": "Se cambió el idioma"
},
"label": "Idioma",
"submenu": {
"to-help-translate": "¿Quieres ayudar a traducir? Haz clic aquí"
}
},
"resume-on-start": "Reanudar la última canción reproducida al iniciar la aplicación",
"single-instance-lock": "Limitar a una única instancia",
"start-at-login": "Iniciar al iniciar sesión",
"starting-page": {
"label": "Página de inicio",
"unset": "Sin configurar"
},
"tray": {
"label": "Bandeja",
"submenu": {
"disabled": "Desactivado",
"enabled-and-hide-app": "Habilitado y ocultar la aplicación",
"enabled-and-show-app": "Habilitado y mostrar aplicación",
"play-pause-on-click": "Reproducir/Pausar al hacer clic"
}
},
"visual-tweaks": {
"label": "Ajustes visuales",
"submenu": {
"custom-window-title": {
"label": "Título de ventana personalizado",
"prompt": {
"label": "Ingresa un título de ventana personalizado: (déjalo vacío para desactivar)",
"placeholder": "Ejemplo: {{applicationName}}"
}
},
"like-buttons": {
"default": "Predeterminado",
"force-show": "Forzar la visualización",
"hide": "Ocultar",
"label": "Botones de \"Me Gusta\"",
"swap": "Intercambiar el orden de los botones de \"Me Gusta\""
},
"remove-upgrade-button": "Eliminar el botón de Actualización",
"theme": {
"dialog": {
"button": {
"cancel": "Cancelar",
"remove": "Quitar"
},
"remove-theme": "¿Estás seguro de que quieres eliminar el tema personalizado?",
"remove-theme-message": "Esto eliminará el tema personalizado"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importar archivo CSS personalizado",
"no-theme": "Sin temas"
}
}
}
}
}
},
"plugins": {
"enabled": "Habilitado",
"label": "Complementos",
"new": "NUEVO"
},
"view": {
"label": "Ver",
"submenu": {
"force-reload": "Forzar la recarga",
"reload": "Recargar",
"reset-zoom": "Tamaño actual",
"toggle-fullscreen": "Alternar pantalla completa",
"zoom-in": "Acercar",
"zoom-out": "Alejar"
}
}
},
"tray": {
"next": "Siguiente",
"play-pause": "Reproducir/Pausar",
"previous": "Anterior",
"quit": "Salir",
"restart": "Reiniciar la aplicación",
"show": "Mostrar ventana",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Si se reproduce un anuncio, silencia el audio y fija la velocidad de reproducción en 16x",
"name": "Aumento de la velocidad de anuncios"
},
"adblocker": {
"description": "Bloquear todos los anuncios y el rastreo por defecto",
"menu": {
"blocker": "Bloqueador"
},
"name": "Bloqueador de anuncios"
},
"album-actions": {
"description": "Añade los botones \"Quitar no me gusta\", \"No me gusta\", \"Me gusta\" y \"Quitar 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}}%"
}
},
"enable-seekbar": "Habilitar temas a la barra de búsqueda"
},
"name": "Tema de color del álbum"
},
"ambient-mode": {
"description": "Aplica un efecto de iluminación mediante la proyección de colores suaves extraídos del vídeo sobre el fondo de pantalla",
"menu": {
"blur-amount": {
"label": "Cantidad de desenfoque",
"submenu": {
"pixels": "{{blurAmount}} píxeles"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacidad",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Calidad",
"submenu": {
"pixels": "{{quality}} píxeles"
}
},
"size": {
"label": "Tamaño",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transición suave",
"submenu": {
"during": "Durante {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Usando Pantalla Completa"
}
},
"name": "Modo ambiente"
},
"amuse": {
"description": "Agrega soporte a {{applicationName}} para el widget \"reproduciendo\" de Amuse por 6K Labs",
"name": "Amuse",
"response": {
"query": "El servidor API de Amuse se está ejecutando. Usa GET /query para obtener información de la canción."
}
},
"api-server": {
"description": "Añade un servidor API para controlar el reproductor",
"dialog": {
"request": {
"buttons": {
"allow": "Permitir",
"deny": "Denegar"
},
"message": "¿Permitir que {{ID}} ({{origin}}) acceda a la API?",
"title": "Petición de autorización API"
}
},
"menu": {
"auth-strategy": {
"label": "Estrategia de autorización",
"submenu": {
"auth-at-first": {
"label": "Autorizar a la primera solicitud"
},
"none": {
"label": "Sin autorización"
}
}
},
"hostname": {
"label": "Nombre del host"
},
"https": {
"label": "HTTPS y Certificados",
"submenu": {
"cert": {
"dialogTitle": "Seleccione el archivo de certificado HTTPS",
"label": "Archivo de certificado (.crt/.pem)"
},
"enable-https": {
"label": "Habilitar HTTPS"
},
"key": {
"dialogTitle": "Selecciona el archivo de clave privada HTTPS",
"label": "Archivo de clave privada (.key/.pem)"
}
}
},
"port": {
"label": "Puerto"
}
},
"name": "Servidor API [Beta]",
"prompt": {
"hostname": {
"label": "Introduzca el nombre de host (como 0.0.0.0) para el servidor API:",
"title": "Nombre de host"
},
"port": {
"label": "Introduzca el puerto para el servidor API:",
"title": "Puerto"
}
}
},
"audio-compressor": {
"description": "Aplicar compresión al audio (reduce la diferencia entre las partes más fuertes y más suaves de una pista para que tenga un nivel más consistente)",
"name": "Compresor de audio"
},
"auth-proxy-adapter": {
"description": "Soporte para el uso de servicios de proxy de autenticación",
"menu": {
"disable": "Deshabilitar el adaptador proxy",
"enable": "Habilitar el adaptador proxy",
"hostname": {
"label": "Nombre de host"
},
"port": {
"label": "Puerto"
}
},
"name": "Adaptador de proxy de autenticación",
"prompt": {
"hostname": {
"label": "Ingrese el nombre de host del servidor proxy local (requiere reinicio):",
"title": "Nombre de host del proxy"
},
"port": {
"label": "Ingrese el puerto para el servidor de proxy local (requiere reinicio):",
"title": "Puerto de proxy"
}
}
},
"blur-nav-bar": {
"description": "Hace que la barra de navegación se vea transparente y borrosa",
"name": "Desenfocar barra de navegación"
},
"bypass-age-restrictions": {
"description": "Saltarse la verificación de edad de Music Player",
"name": "Saltarse las restricciones de edad"
},
"captions-selector": {
"description": "Selector de subtítulos para pistas de audio de {{applicationName}}",
"menu": {
"autoload": "Seleccionar automáticamente el último subtítulo utilizado",
"disable-captions": "Sin subtítulos por defecto"
},
"name": "Selector de subtítulos",
"prompt": {
"selector": {
"label": "Idioma actual de los subtítulos: {{language}}",
"none": "Ninguno",
"title": "Seleccionar idioma de los subtítulos"
}
},
"templates": {
"title": "Abrir el selector de subtítulos"
},
"toast": {
"caption-changed": "Subtítulos cambiados a {{language}}",
"caption-disabled": "Subtítulos desactivados",
"no-captions": "Sin subtítulos para ésta canción"
}
},
"clock": {
"description": "Añade un reloj a la barra de navegación",
"menu": {
"format": {
"24-hour-format": "Formato 24 horas",
"display-seconds": "Mostrar segundos",
"label": "Formato"
}
},
"name": "Reloj"
},
"compact-sidebar": {
"description": "Establecer siempre la barra lateral en modo compacto",
"name": "Barra lateral compacta"
},
"crossfade": {
"description": "Crossfade entre canciones",
"menu": {
"advanced": "Avanzado"
},
"name": "Crossfade [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Duración del fundido de entrada (ms)",
"fade-out-duration": "Duración del fundido de salida (ms)",
"fade-scaling": {
"label": "Escala del fundido",
"linear": "Lineal",
"logarithmic": "Logarítmico"
},
"seconds-before-end": "Activar Crossfade N segundos antes del final"
},
"title": "Opciones de Crossfade"
}
}
},
"custom-output-device": {
"description": "Configura un dispositivo de salida de audio personalizado para las canciones",
"menu": {
"device-selector": "Seleccionar un dispositivo"
},
"name": "Dispositivo de audio personalizado",
"prompt": {
"device-selector": {
"label": "Escoge el dispositivo de salida de audio que se va a usar",
"title": "Seleccionar un dispositivo de audio"
}
}
},
"disable-autoplay": {
"description": "Hace que la canción comience en modo \"pausado\"",
"menu": {
"apply-once": "Sólo se aplica al inicio"
},
"name": "Desactivar reproducción automática"
},
"discord": {
"backend": {
"already-connected": "Se intentó conectar con una conexión activa",
"connected": "Conectado a Discord",
"disconnected": "Desconectado de Discord"
},
"description": "Muestra a tus amigos lo que escuchas con Rich Presence",
"menu": {
"auto-reconnect": "Reconectar automáticamente",
"clear-activity": "Borrar actividad",
"clear-activity-after-timeout": "Borrar actividad después de un tiempo",
"connected": "Conectado",
"disconnected": "Desconectado",
"hide-duration-left": "Ocultar la duración restante",
"hide-github-button": "Ocultar el botón de enlace a GitHub",
"play-on-application": "Reproducir en {{applicationName}}",
"set-inactivity-timeout": "Establecer tiempo de inactividad",
"set-status-display-type": {
"label": "Texto de estado",
"submenu": {
"application": "Escuchando {{applicationName}}",
"artist": "Escuchando a {artist}",
"title": "Escuchando {song title}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Introduzca el tiempo de inactividad en segundos:",
"title": "Establecer tiempo de inactividad"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "¡Argh! Lo siento, la descarga falló…",
"title": "¡Error en la descarga!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} canciones)",
"message": "Descargando lista de reproducción {{playlistTitle}}",
"title": "Descarga iniciada"
}
},
"feedback": {
"conversion-progress": "Conversión: {{percent}}%",
"converting": "Convirtiendo…",
"done": "Listo: {{filePath}}",
"download-info": "Descargando {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Descarga: {{percent}}%",
"downloading": "Descargando…",
"downloading-counter": "Descargando {{current}}/{{total}}…",
"downloading-playlist": "Descargando lista de reproducción \"{{playlistTitle}}\" - {{playlistSize}} canciones ({{playlistId}})",
"error-while-downloading": "Error al descargar \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "La carpeta {{playlistFolder}} ya existe",
"getting-playlist-info": "Obteniendo información de la lista de reproducción…",
"loading": "Cargando…",
"playlist-has-only-one-song": "La lista de reproducción sólo tiene un elemento, descargándolo directamente",
"playlist-id-not-found": "No se ha encontrado la ID de la lista de reproducción",
"playlist-is-empty": "La lista de reproducción está vacía",
"playlist-is-mix-or-private": "Error obteniendo la información de la lista de reproducción: asegúrese de que no es una lista privada o \"Mixed for you\"\n\n{{error}}",
"preparing-file": "Preparando archivo…",
"saving": "Guardando…",
"trying-to-get-playlist-id": "Intentando obtener la ID de la lista de reproducción: {{playlistId}}",
"video-id-not-found": "Video no encontrado",
"writing-id3": "Escribiendo las etiquetas ID3…"
}
},
"description": "Descarga audio MP3 / fuente directamente desde la interfaz",
"menu": {
"choose-download-folder": "Elija la carpeta de descarga",
"download-finish-settings": {
"label": "Descargar al finalizar",
"prompt": {
"last-percent": "Después de x porcentaje",
"last-seconds": "Últimos x segundos",
"title": "Configurar cuándo descargar"
},
"submenu": {
"advanced": "Avanzado",
"enabled": "Habilitado",
"mode": "Modo de tiempo",
"percent": "Porcentaje",
"seconds": "Segundos"
}
},
"download-playlist": "Descargar lista de reproducción",
"presets": "Ajustes preestablecidos",
"skip-existing": "Saltar archivos existentes"
},
"name": "Gestor de descargas",
"renderer": {
"can-not-update-progress": "No se puede actualizar el progreso"
},
"templates": {
"button": "Descargar"
}
},
"equalizer": {
"description": "Añade un ecualizador al reproductor",
"menu": {
"presets": {
"label": "Ajustes preestablecidos",
"list": {
"bass-booster": "Amplificador de graves"
}
}
},
"name": "Ecualizador"
},
"exponential-volume": {
"description": "Hace que la barra de volumen sea exponencial para que sea más fácil seleccionar volúmenes más bajos.",
"name": "Volumen exponencial"
},
"in-app-menu": {
"description": "Da a las barras de menú un aspecto elegante, oscuro o del color de un álbum",
"menu": {
"hide-dom-window-controls": "Ocultar controles de ventana DOM"
},
"name": "Menú de aplicación"
},
"lumiastream": {
"description": "Agrega soporte para Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Añade soporte para letras para la mayoría de las canciones",
"menu": {
"romanized-lyrics": "Letras Romanizadas"
},
"name": "Letras Genius",
"renderer": {
"fetched-lyrics": "Letras obtenidas 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 la ID del host"
},
"internal": {
"save": "Guardar",
"track-source": "Fuente de la pista",
"unknown-user": "Usuario desconocido"
},
"menu": {
"click-to-copy-id": "Copiar la 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": "Permitir 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 desconectado",
"host-failed": "Fallo al hostear Music Together",
"id-copied": "ID del host copiada al portapapeles",
"id-copy-failed": "No se ha podido copiar la ID del host al portapapeles",
"join-failed": "Fallo al unirse 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",
"templates": {
"back": {
"title": "Volver a la página anterior"
},
"forward": {
"title": "Ir a la siguiente página"
}
}
},
"no-google-login": {
"description": "Eliminar los botones y enlaces de inicio de sesión de Google de la interfaz",
"name": "Sin inicio de sesión de Google"
},
"notifications": {
"description": "Mostrar una notificación cuando empiece a sonar una canción (las notificaciones interactivas están disponibles en Windows)",
"menu": {
"interactive": "Notificaciones interactivas",
"interactive-settings": {
"label": "Ajustes interactivos",
"submenu": {
"hide-button-text": "Ocultar el texto del botón",
"refresh-on-play-pause": "Actualizar al reproducir/pausar",
"tray-controls": "Abrir/Cerrar al hacer clic en la bandeja"
}
},
"priority": "Prioridad de notificación",
"toast-style": "Estilo de mensaje emergente",
"unpause-notification": "Mostrar notificación al reanudar"
},
"name": "Notificaciones"
},
"performance-improvement": {
"description": "Mejore el rendimiento habilitando scripts experimentales",
"name": "Mejora del rendimiento [Beta]"
},
"picture-in-picture": {
"description": "Permite cambiar la aplicación al modo picture-in-picture",
"menu": {
"always-on-top": "Siempre encima",
"hotkey": {
"label": "Tecla de acceso rápido",
"prompt": {
"keybind-options": {
"hotkey": "Tecla de acceso rápido"
},
"label": "Elige una tecla de acceso rápido para activar la función picture-in-picture",
"title": "Tecla de acceso directo a picture-in-picture"
}
},
"save-window-position": "Guardar la posición de la ventana",
"save-window-size": "Guardar tamaño de la ventana",
"use-native-pip": "Utilizar PiP nativo del navegador"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Escucha rápido, escucha despacio! Añade un control deslizante que ajusta la velocidad de la canción",
"name": "Velocidad de reproducción",
"templates": {
"button": "Velocidad"
}
},
"precise-volume": {
"description": "Controla el volumen de manera precisa utilizando la rueda del ratón/teclas de acceso rápido, con una interfaz personalizada y niveles de volumen personalizables",
"menu": {
"arrows-shortcuts": "Controles de teclas de flechas locales",
"custom-volume-steps": "Establecer niveles de volumen personalizados",
"global-shortcuts": "Teclas de acceso rápido globales"
},
"name": "Volumen preciso",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Disminuir el volumen",
"increase": "Aumentar el volumen"
},
"label": "Elija combinaciones de teclas para el volumen:",
"title": "Combinaciones de teclas para el volumen"
},
"volume-steps": {
"label": "Escoge los niveles de aumento o disminución del volumen",
"title": "Niveles de volumen"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Calidad actual: {{quality}}",
"message": "Elija la calidad de vídeo:",
"title": "Elija la calidad de vídeo"
}
}
},
"description": "Permite cambiar la calidad del vídeo con un botón sobre puesto en el vídeo",
"name": "Ajustador de calidad de vídeo",
"renderer": {
"quality-settings-button": {
"label": "Abrir selector de calidad del reproductor"
}
}
},
"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-alternative-artist": "Usar artistas alternativos",
"scrobble-alternative-title": "Usar títulos alternativos",
"scrobble-other-media": "Hacer Scrobble sobre 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",
"menu": {
"override-media-keys": "Anular teclas de medios",
"set-keybinds": "Configurar controles globales de canciones"
},
"name": "Atajos (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Siguiente",
"play-pause": "Reproducir / Pausa",
"previous": "Anterior"
},
"label": "Elija combinaciones de teclas para el control de las canciones:",
"title": "Atajos de teclado globales"
}
}
},
"skip-disliked-songs": {
"description": "Omite las canciones que no le gustan",
"name": "Saltar canciones que no me gustan"
},
"skip-silences": {
"description": "Salta automáticamente las secciones silenciosas de las canciones",
"name": "Saltar silencios"
},
"sponsorblock": {
"description": "Salta automáticamente las partes no musicales como la introducción/final o secciones de videos musicales donde la canción no está sonando",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Proporciona letras de canciones sincronizadas, utilizando proveedores como LRClib.",
"errors": {
"fetch": "⚠️\tSe produjo un error al obtener la letra.\n\tPor favor, inténtelo de nuevo más tarde.",
"not-found": "⚠️ No se han encontrado letras para esta canción."
},
"menu": {
"convert-chinese-character": {
"label": "Convertir carácter Chino",
"submenu": {
"disabled": {
"label": "Deshabilitado",
"tooltip": "Deshabilitar conversión de caracteres Chinos"
},
"simplified-to-traditional": {
"label": "Simplificar a Tradicional",
"tooltip": "Convertir Chino Simplifcado en Chino Tradicional"
},
"traditional-to-simplified": {
"label": "Tradicional a Simplificado",
"tooltip": "Convertir Chino Tradicional a Chino Simplificado"
}
},
"tooltip": "Convertir carácter Chino a Tradicional o Simplificado"
},
"default-text-string": {
"label": "Carácter predeterminado entre letras",
"tooltip": "Elige el carácter predeterminado que se utilizará para el espacio entre letras"
},
"line-effect": {
"label": "Efecto de la línea",
"submenu": {
"fancy": {
"label": "Elegante",
"tooltip": "Usar efectos grandes, similares a los de una aplicación, en la línea actual"
},
"focus": {
"label": "Enfoque",
"tooltip": "Mostrar solo la línea actual en blanco"
},
"offset": {
"label": "Desplazamiento",
"tooltip": "Desplazamiento a la derecha de la línea actual"
},
"scale": {
"label": "Escala",
"tooltip": "Escalar la línea actual"
}
},
"tooltip": "Elige el efecto que deseas aplicar a la línea actual"
},
"precise-timing": {
"label": "Haz que la letra esté perfectamente sincronizada",
"tooltip": "Calcular al milisegundo la visualización de la siguiente línea (puede tener un pequeño impacto en el rendimiento)"
},
"preferred-provider": {
"label": "Proveedor preferido",
"none": {
"label": "Ninguno",
"tooltip": "Ningún proveedor preferido"
},
"tooltip": "Elige el proveedor predeterminado que deseas usar"
},
"romanization": {
"label": "Romanizar letras",
"tooltip": "Si la letra está en un idioma diferente, intenta mostrar una versión en latín."
},
"show-lyrics-even-if-inexact": {
"label": "Mostrar la letra aunque sea inexacta",
"tooltip": "Si no se encuentra la canción, el plugin vuelve a intentarlo con una búsqueda diferente.\nEl resultado del segundo intento puede no ser exacto."
},
"show-time-codes": {
"label": "Visualización del código de tiempo",
"tooltip": "Mostrar los códigos de tiempo junto a la letra"
}
},
"name": "Letras sincronizadas",
"refetch-btn": {
"fetching": "Obteniendo...",
"normal": "Volver a buscar letras"
},
"warnings": {
"duration-mismatch": "⚠️ - La letra puede estar desincronizada debido a un desajuste en la duración.",
"inexact": "⚠️ - La letra de esta canción puede no ser exacta",
"instrumental": "⚠️ - Esta es una canción instrumental"
}
},
"taskbar-mediacontrol": {
"description": "Controla la reproducción desde la barra de tareas de Windows",
"name": "Control de medios desde la barra de tareas"
},
"touchbar": {
"description": "Añade un widget TouchBar para los usuarios de macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Hace que la ventana de la aplicación sea transparente",
"menu": {
"opacity": {
"label": "Opacidad",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrílico",
"mica": "Mica",
"none": "Ninguno",
"tabbed": "Con pestañas"
}
}
},
"name": "Reproductor transparente"
},
"tuna-obs": {
"description": "Integración con el complemento Tuna de OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Evita que el reproductor aparezca al reproducir una canción",
"name": "Reproductor discreto"
},
"video-toggle": {
"description": "Añade un botón para cambiar entre el modo Vídeo/Canción. También puede eliminar opcionalmente toda la pestaña de vídeo",
"menu": {
"align": {
"label": "Alineación",
"submenu": {
"left": "Izquierda",
"middle": "Centro",
"right": "Derecha"
}
},
"force-hide": "Forzar eliminación de la pestaña de vídeo",
"mode": {
"label": "Modo",
"submenu": {
"custom": "Alternador personalizado",
"disabled": "Desactivado",
"native": "Alternador nativo"
}
}
},
"name": "Alternador de vídeo",
"templates": {
"button-song": "Canción",
"button-video": "Vídeo"
}
},
"visualizer": {
"description": "Añadir un visualizador al reproductor",
"menu": {
"visualizer-type": "Tipo de visualizador"
},
"name": "Visualizador"
}
}
}
================================================
FILE: src/i18n/resources/et.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "{{pluginName}}::{{contextName}} lisamooduli käivitamine ei õnnestunud",
"executed-at-ms": "{{pluginName}}::{{contextName}} lisamoodul käivitus {{ms}} millisekundiga",
"initialize-failed": "„{{pluginName}}“ lisamooduli töö alustamine ei õnnestunud",
"load-all": "Laadime kõiki lisamooduleid",
"load-failed": "„{{pluginName}}“ lisamooduli laadimine ei õnnestunud",
"loaded": "„{{pluginName}}“ lisamoodul on laaditud",
"unload-failed": "„{{pluginName}}“ lisamooduli mälust eemaldamine ei õnnestunud",
"unloaded": "„{{pluginName}}“ lisamoodul on mälust eemaldatud"
}
}
},
"language": {
"code": "et",
"local-name": "Eesti",
"name": "Estonian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Laadimine lõppes, arendaja tarvikud on avatud"
},
"i18n": {
"loaded": "i18n on laaditud"
},
"second-instance": {
"receive-command": "„{{command}}“ käsk on vastu võetud"
},
"theme": {
"css-file-not-found": "CSS faili „{{cssFile}}“ pole olemas, seega eirame eelistust"
},
"unresponsive": {
"details": "Tõrge ei vasta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Programmi vahemälu kustutamine"
},
"window": {
"tried-to-render-offscreen": "Akent prooviti renderdada väljaspool ekraani, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menüü on peidetud ja „Alt“ klahviga saad ta nähtavaks (rakenduse-siseses menüüs „Esc“ klahviga)",
"message": "Menüü peitmine on sisselülitatud",
"title": "Menüü peitmine on sisselülitatud"
},
"need-to-restart": {
"buttons": {
"later": "Hiljem",
"restart-now": "Taaskäivita kohe"
},
"detail": "„{{pluginName}}“ lisamooduli sisselülitamine eeldab rakenduse taaskäivitamist",
"message": "„{{pluginName}}“ lisamoodul eeldab rakenduse taaskäivitamist",
"title": "Palun käivita rakendus uuesti"
},
"unresponsive": {
"buttons": {
"quit": "Välju",
"relaunch": "Käivita uuesti",
"wait": "Oota"
},
"detail": "Vabandame ebamugavuste pärast! Palun vali kuidas jätkata:",
"message": "Rakendus ei vasta ega reageeri",
"title": "Aken ei vasta ega reageeri"
},
"update-available": {
"buttons": {
"disable": "Lülita uuendused välja",
"download": "Laadi alla",
"ok": "Sobib"
},
"detail": "Saadaval on uus versioon, ning seda saad alla laadida siit {{downloadLink}}",
"message": "Uus versioon on saadaval",
"title": "Rakenduse uuendus on saadaval"
}
},
"menu": {
"about": "Rakenduse teave",
"navigation": {
"label": "Liikumine",
"submenu": {
"copy-current-url": "Kopeeri esitamisel oleva pala URL",
"go-back": "Mine tagasi",
"go-forward": "Mine edasi",
"quit": "Välju",
"restart": "Käivita rakendus uuesti"
}
},
"options": {
"label": "Seadistused",
"submenu": {
"advanced-options": {
"label": "Lisaseadistused",
"submenu": {
"auto-reset-app-cache": "Rakenduse käivitamisel lähtesta puhverdatud andmed",
"disable-hardware-acceleration": "Lülita raudvaraline kiirendamine välja",
"edit-config-json": "Muuda config.json faili",
"override-user-agent": "Jõudlusta User-Agent",
"restart-on-config-changes": "Taaskäivita pärast konfiguratsiooni muutmist",
"set-proxy": {
"label": "Määra proxy",
"prompt": {
"label": "Sisesta proxy aadress: (jäta täitmata, et välja lülitada)",
"placeholder": "Näide: SOCKS5://127.0.0.1:9999",
"title": "Määra proxy"
}
},
"toggle-dev-tools": "Lülita sisse arendaja tööriistad"
}
},
"always-on-top": "Alati esiplaanil",
"auto-update": "Automaatsed uuendused",
"hide-menu": {
"dialog": {
"message": "Järgmisel käivitamisel jääb menüü peidetuks, kasutage [Alt] klahvi, et näidata (või [`], kui kasutate rakendusesisest menüüd)",
"title": "Menüü peitmine on sisse lülitatud"
},
"label": "Peida menüü"
},
"language": {
"dialog": {
"message": "Keele muutmine jõustub peale uuesti käivitamist",
"title": "Keel on muutunud"
},
"label": "Keel",
"submenu": {
"to-help-translate": "Soovid aidata tõlkimisel? Klõpsa siin"
}
},
"resume-on-start": "Rakenduse käivitamisel jätka viimatiesitatud loo esitamist",
"single-instance-lock": "Ühe instantsi lukk",
"start-at-login": "Käivita sisselogimisel",
"starting-page": {
"label": "Avaleht",
"unset": "Määramata"
},
"tray": {
"label": "Tasku",
"submenu": {
"disabled": "Välja lülitatud",
"enabled-and-hide-app": "Sisse lülitatud ja rakendus peidetud",
"enabled-and-show-app": "Sisse lülitatud ja rakendus nähtav",
"play-pause-on-click": "Mängi/Peata klõpsates"
}
},
"visual-tweaks": {
"label": "Visuaalsed muudatused",
"submenu": {
"custom-window-title": {
"label": "Kohandatud akna tiitel",
"prompt": {
"label": "Sisesta kohandatud akna tiitel: (jäta täitmata, et välja lülitada)",
"placeholder": "Näide: {{applicationName}}"
}
},
"like-buttons": {
"default": "Vaikimisi",
"force-show": "Sunni näitama",
"hide": "Peida",
"label": "Meeldib nupud"
},
"remove-upgrade-button": "Eemalda upgrade nupp",
"theme": {
"dialog": {
"button": {
"cancel": "Katkesta",
"remove": "Eemalda"
},
"remove-theme": "Kas oled kindel, et soovid enda loodud kujunduse eemaldada?",
"remove-theme-message": "Sellega saab sinu loodud kujundus eemdladatud"
},
"label": "Kujundus",
"submenu": {
"import-css-file": "Impordi kohandatud CSS fail",
"no-theme": "Ilma kujunduseta"
}
}
}
}
}
},
"plugins": {
"enabled": "Kasutusel",
"label": "Lisamoodulid",
"new": "UUS"
},
"view": {
"label": "Vaata",
"submenu": {
"force-reload": "Laadi sundkorras uuesti",
"reload": "Laadi uuesti",
"reset-zoom": "Tegelik suurus",
"toggle-fullscreen": "Lülita täisekraanivaade sisse/välja",
"zoom-in": "Suumi sisse",
"zoom-out": "Suumi välja"
}
}
},
"tray": {
"next": "Edasi",
"play-pause": "Esita/Peata esitus",
"previous": "Eelmine",
"quit": "Välju",
"restart": "Käivita rakendus uuesti",
"show": "Näita akent",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Reklaami esitamisel summutatakse heli ja keritakse edasi 16-kordse kiirusega",
"name": "Reklaamikiirendaja"
},
"adblocker": {
"description": "Blokeeri kõik reklaamid ja jälitajad",
"menu": {
"blocker": "Blokeerijad"
},
"name": "Reklaamiblokeerija"
},
"album-actions": {
"description": "Lisab Undislike, Ebameeldiv, Meeldiv ja Unlike nupud selle rakendamiseks kõikidele loendisse või albumisse kuuluvatele lauludele.",
"name": "Albumi toimingud"
},
"album-color-theme": {
"description": "Rakendab dünaamilist teemat ja visuaalseid efekte, mis põhinevad albumi värvipalettil",
"menu": {
"color-mix-ratio": {
"label": "Värvide segamissuhe",
"submenu": {
"percent": "{{suhe}}%"
}
},
"enable-seekbar": "Luba kerimisriba kujundamine"
},
"name": "Albumi värviteema"
},
"ambient-mode": {
"description": "Rakendab valgusefekti, projitseerides videost õrnad värvid ekraani taustale",
"menu": {
"blur-amount": {
"label": "Hägusus",
"submenu": {
"pixels": "{{blurAmount}} pikslit"
}
},
"buffer": {
"label": "Puhver",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Läbipaistmatus",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvaliteet",
"submenu": {
"pixels": "{{quality}} pikslit"
}
},
"size": {
"label": "Suurus",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Sujuv üleminek"
},
"use-fullscreen": {
"label": "Kasutamas täisekraani"
}
},
"name": "Ümbritsev režiim"
},
"blur-nav-bar": {
"description": "Muudab navigatsiooniriba läbipaistavaks ja hägusaks",
"name": "Hägus navigatsiooniriba"
},
"lyrics-genius": {
"description": "Lisa enamustele lugudele laulusõnad",
"menu": {
"romanized-lyrics": "Latiniseeritud laulusõnad"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Leidsime Geeniuse jaoks ühed laulusõnad"
}
},
"navigation": {
"name": "Liikumine"
},
"no-google-login": {
"description": "Eemalda kasutajaliidesest Google'i sisselogimisnupud",
"name": "Elu ilma Google'i sisselogimiseta"
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Praegune kvaliteet: {{quality}}",
"message": "Vali video kvaliteet:",
"title": "Videokvaliteedi valik"
}
}
},
"description": "Võimaldab muuta video kvaliteeti nupust, mis asub video ülekattes",
"name": "Videokvaliteedi muutja"
},
"scrobbler": {
"description": "Lisa kraasimise tugi (last.fm, Listenbrainz, jne)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm'i autentimine ei õnnestunud\nPeida hüpikaken järgmise taaskäivituseni.",
"title": "Autentimine ei õnnestunud"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API seadistused"
},
"listenbrainz": {
"token": "Sisesta ListenBrainz'i kasutaja tunnusluba"
},
"scrobble-other-media": "Kraasi muud meediat"
},
"name": "Kraasija",
"prompt": {
"lastfm": {
"api-key": "Last.fm API võti",
"api-secret": "Last.fm API saladus"
},
"listenbrainz": {
"token": {
"label": "Sisesta oma ListenBrainz'i tunnusluba:",
"title": "ListenBrainz'i tunnusluba"
}
}
}
},
"synced-lyrics": {
"menu": {
"show-lyrics-even-if-inexact": {
"tooltip": "Kui lugu ei leidu, siis lisamoodul üritab uut otsingut teistsuguse päringuga.\nTeise katse puhul tulemused ei pruugi olla väga täpsed."
}
}
},
"tuna-obs": {
"description": "Lõimimine OBSi Tuna lisamooduliga"
}
}
}
================================================
FILE: src/i18n/resources/eu.json
================================================
{
"language": {
"code": "eu",
"local-name": "Euskara",
"name": "Basque"
}
}
================================================
FILE: src/i18n/resources/fa.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "اجراى {{اسمزمىنه}}::{{اسمپلاگىن}} با خطا مواجه شد",
"executed-at-ms": "افزونه {{pluginName}}::{{contextName}} در {{ms}} میلیثانیه اجرا شد",
"initialize-failed": "افزونه \"{{pluginName}}\" با خطا در حین مقداردهی اولیه مواجه شد",
"load-all": "در حال بارگذاری تمامی افزونهها",
"load-failed": "افزونه \"{{pluginName}}\" بارگیری نشد",
"loaded": "افزونه \"{{pluginName}}\" بارگیری شد",
"unload-failed": "افزونه \"{{pluginName}}\" بارگذاری نشد",
"unloaded": "افزونه \"{{pluginName}}\" بارگذاری شد"
}
}
},
"language": {
"code": "fa",
"local-name": "فارسی",
"name": "Persian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "بارگذاری کامل شد. DevTools باز شد"
},
"i18n": {
"loaded": "i18n بارگذاری شد"
},
"second-instance": {
"receive-command": "دریافت فرمان از طریق پروتکل: \"{{command}}\""
},
"theme": {
"css-file-not-found": "فایل CSS \"{{cssFile}}\" وجود ندارد، نادیده گرفته شد"
},
"unresponsive": {
"details": "خطای عدم پاسخگویی!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "پاکسازی حافظه کش برنامه"
},
"window": {
"tried-to-render-offscreen": "پنجره تلاش کرد خارج از صفحه نمایش داده شود، اندازه پنجره={{windowSize}}، اندازه نمایشگر={{displaySize}}، موقعیت={{position}}"
}
},
"dialog": {
"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": "کپی کردن لینک صفحه فعلی",
"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": {
"custom-window-title": {
"label": "عنوان پنجره سفارشى",
"prompt": {
"label": "عنوان پنجره سفارشى را وارد کنىد: (خالى بزارىد تا غىرفعال شود)",
"placeholder": "مثال :{{applicationName}}"
}
},
"like-buttons": {
"default": "پیشفرض",
"force-show": "اجبار به نمایش",
"hide": "پنهان کردن",
"label": "دکمههای پسندیدن"
},
"remove-upgrade-button": "حذف دکمه ارتقا",
"theme": {
"dialog": {
"button": {
"cancel": "لغو",
"remove": "حذف"
},
"remove-theme": "آیا مطمئن هستید که میخواهید تم سفارشی را حذف کنید؟",
"remove-theme-message": "این کار تم سفارشی را حذف خواهد کرد"
},
"label": "تم",
"submenu": {
"import-css-file": "سفارشی CSS وارد کردن فایل",
"no-theme": "بدون تم"
}
}
}
}
}
},
"plugins": {
"enabled": "فعال/غیرفعال کردن",
"label": "افزونهها",
"new": "جدید"
},
"view": {
"label": "مشاهده",
"submenu": {
"force-reload": "اجبار به بارگذاری مجدد",
"reload": "بارگذاری مجدد",
"reset-zoom": "اندازه واقعی",
"toggle-fullscreen": "تغییر به تمام صفحه",
"zoom-in": "بزرگنمایی",
"zoom-out": "کوچکنمایی"
}
}
},
"tray": {
"next": "بعدی",
"play-pause": "پخش/توقف",
"previous": "قبلی",
"quit": "خروج",
"restart": "راهاندازی مجدد برنامه",
"show": "نمایش پنجره",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "اگر تبلیغ پخش شود، صدا را بیصدا کرده و سرعت پخش را به 16 برابر افزایش میدهد",
"name": "سرعتدهی به تبلیغ"
},
"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": "حالت محیطی"
},
"amuse": {
"description": "حالا ویجت Amuse از {{applicationName}} هم پشتیبانی میکنه! (توسط 6K Labs)",
"name": "Amuse",
"response": {
"query": "سرور Amuse فعال است. برای دریافت اطلاعات آهنگ، از آدرس /query استفاده کنید."
}
},
"api-server": {
"description": "برای کنترل پخشکننده API افزودن یک سرور",
"dialog": {
"request": {
"buttons": {
"allow": "اجازه",
"deny": "رد کردن"
},
"message": "اجازه دادن به {{ID}} ({{origin}}) برای دسترسی به API؟",
"title": "درخواست مجوز API"
}
},
"menu": {
"auth-strategy": {
"label": "استراتژی مجوز",
"submenu": {
"auth-at-first": {
"label": "مجوز در اولین درخواست"
},
"none": {
"label": "بدون نیاز به مجوز"
}
}
},
"hostname": {
"label": "نام میزبان"
},
"https": {
"label": "HTTPS و گواهینامهها",
"submenu": {
"cert": {
"dialogTitle": "پرونده گواهینامه HTTPS را انتخاب کنید",
"label": "پرونده گواهینامه (crt/.pem.)"
},
"enable-https": {
"label": "فعال کردن HTTPS"
},
"key": {
"dialogTitle": "پرونده کلید خصوصی HTTPS را انتخاب کنید",
"label": "پرونده کلید خصوصی (key/.pem)"
}
}
},
"port": {
"label": "پورت"
}
},
"name": "[بتا]API سرور",
"prompt": {
"hostname": {
"label": "وارد کنید (مثل 0.0.0.0): API نام میزبان را برای سرور",
"title": "نام میزبان"
},
"port": {
"label": "وارد کنید: API پورت را برای سرور",
"title": "پورت"
}
}
},
"audio-compressor": {
"description": "اعمال فشردهسازی به صدا (کاهش حجم بلندترین بخشهای سیگنال و افزایش حجم بخشهای نرمتر)",
"name": "فشردهساز صدا"
},
"auth-proxy-adapter": {
"description": "پشتیبانی برای استفاده از سرویسهای پروکسی احراز هویت",
"menu": {
"disable": "غیرفعال کردن آداپتور پروکسی",
"enable": "فعال کردن آداپتور پروکسی",
"hostname": {
"label": "نام میزبان"
},
"port": {
"label": "پورت"
}
},
"name": "آداپتور پروکسی احراز هویت",
"prompt": {
"hostname": {
"label": "نام میزبان را برای سرور پروکسی محلی وارد کنید (نیاز به راه اندازی مجدد دارد):",
"title": "پروکسی نام میزبان"
},
"port": {
"label": "پورت مربوط به پروکسی سرور محلی را وارد کنید(نیاز به راه اندازی مجدد دارد):",
"title": "پورت پروکسی"
}
}
},
"blur-nav-bar": {
"description": "شفاف و محو کردن نوار کنترل",
"name": "محو کردن نوار کنترل"
},
"bypass-age-restrictions": {
"description": "دور زدن تأیید سن مىوزىک پلىر",
"name": "دور زدن محدودیتهای سنی"
},
"captions-selector": {
"description": "انتخاب زیرنویس برای آهنگهای پىر دسکتاپ",
"menu": {
"autoload": "به طور خودکار انتخاب آخرین زیرنویس استفاده شده",
"disable-captions": "بدون زیرنویس به صورت پیشفرض"
},
"name": "انتخابکننده زیرنویس",
"prompt": {
"selector": {
"label": "زبان زیرنویس فعلی: {{language}}",
"none": "هیچکدام",
"title": "انتخاب زبان زیرنویس"
}
},
"templates": {
"title": "باز کردن انتخابکننده زیرنویس"
},
"toast": {
"caption-changed": "زیرنویس تغییر کرد به {{language}}",
"caption-disabled": "زیرنویس غیرفعال شده",
"no-captions": "برای این آهنگ زیرنویسی موجود نیست"
}
},
"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": "گزینههای تداخل"
}
}
},
"custom-output-device": {
"description": "ىک اسپىکر براى پخش آهنگها انتخاب کنىد",
"menu": {
"device-selector": "دستگاه را انتخاب کنىد"
},
"name": "اسپىکر دلخواه",
"prompt": {
"device-selector": {
"label": "اسپىکر دلخواه را انتخاب کنىد",
"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": "مخفی کردن دکمه لینک گیت هاب",
"play-on-application": "پخش در یوتیوب موزیک",
"set-inactivity-timeout": "تنظیم زمان عدم فعالیت",
"set-status-display-type": {
"label": "متن وضعىت",
"submenu": {
"application": "به پىر دسکتاپ گوش مىکند",
"artist": "به {artist} گوش مىکند",
"title": "به {song title} گوش مىکند"
}
}
},
"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": "شناسه فهرست پخش یافت نشد",
"playlist-is-empty": "فهرست پخش خالی است",
"playlist-is-mix-or-private": "خطا در دریافت اطلاعات فهرست پخش: اطمینان حاصل کنید که فهرست پخش خصوصی یا \"مختص شما\" نباشد\n\n{{error}}",
"preparing-file": "در حال آمادهسازی فایل…",
"saving": "در حال ذخیرهسازی…",
"trying-to-get-playlist-id": "تلاش برای دریافت شناسه فهرست پخش: {{playlistId}}",
"video-id-not-found": "ویدئو یافت نشد",
"writing-id3": "در حال نوشتن تگهای ID3…"
}
},
"description": "دانلود MP3 / صدای منبع به طور مستقیم از رابط",
"menu": {
"choose-download-folder": "انتخاب پوشه دانلود",
"download-finish-settings": {
"label": "دانلود پس از پایان",
"prompt": {
"last-percent": "پس از x درصد",
"last-seconds": "آخرین x ثانیه",
"title": "پیکربندی زمان دانلود"
},
"submenu": {
"advanced": "پیشرفته",
"enabled": "فعال",
"mode": "حالت زمان",
"percent": "درصد",
"seconds": "ثانیه"
}
},
"download-playlist": "دانلود فهرست پخش",
"presets": "پیشتنظیمها",
"skip-existing": "رد کردن فایلهای موجود"
},
"name": "دانلودر",
"renderer": {
"can-not-update-progress": "امکان بهروزرسانی پیشرفت نیست"
},
"templates": {
"button": "دانلود"
}
},
"equalizer": {
"description": "اضافه کردن یک اکولایزر به پخشکننده",
"menu": {
"presets": {
"label": "تنظیمات از پیش تعیین شده",
"list": {
"bass-booster": "تقویتکننده باس صدا"
}
}
},
"name": "اکولایزر"
},
"exponential-volume": {
"description": "نوار لغزنده حجم را به صورت نمایی میسازد تا انتخاب حجمهای پایینتر آسانتر شود.",
"name": "حجم نمایی"
},
"in-app-menu": {
"description": "منوها را به صورت جذاب، تاریک یا با رنگ آلبوم نمایش میدهد",
"menu": {
"hide-dom-window-controls": "کنترلهای پنجره DOM را مخفی کن"
},
"name": "منوی داخل برنامه"
},
"lumiastream": {
"description": "Lumia Stream افزودن پشتیبانی از",
"name": "Lumia Stream [بتا]"
},
"lyrics-genius": {
"description": "افزودن متن ترانه پشتیبان برای اکثر ترانه ها",
"menu": {
"romanized-lyrics": "الفبای لاتین برای آهنگهایی با الفبای شرقی (فینگلیش)"
},
"name": "Genius متن آهنگ",
"renderer": {
"fetched-lyrics": "بازیابی شد Genius متن ترانه توسط"
}
},
"music-together": {
"description": "اشتراکگذاری فهرست پخش با دیگران. وقتی میزبان آهنگی را پخش میکند، همه بقیه همان آهنگ را میشنوند",
"dialog": {
"enter-host": "شناسه میزبان را وارد کنید"
},
"internal": {
"save": "ذخیره",
"track-source": "منبع آهنگ",
"unknown-user": "کاربر ناشناس"
},
"menu": {
"click-to-copy-id": "کپی کردن شناسه میزبان",
"close": "بستن Music Together",
"connected-users": "کاربران متصل",
"disconnect": "قطع اتصال Music Together",
"empty-user": "هیچ کاربر متصلی وجود ندارد",
"host": "میزبان Music Together",
"join": "پیوستن به Music Together",
"permission": {
"all": "اجازه دادن به مهمانان برای کنترل فهرست پخش و پخشکننده",
"host-only": "فقط میزبان میتواند فهرست پخش و پخشکننده را کنترل کند",
"playlist": "اجازه دادن به مهمانان برای کنترل فهرست پخش"
},
"set-permission": "تغییر مجوز کنترل",
"status": {
"disconnected": "قطع اتصال",
"guest": "متصل به عنوان مهمان",
"host": "متصل به عنوان میزبان"
}
},
"name": "Music Together [بتا]",
"toast": {
"add-song-failed": "افزودن آهنگ با شکست مواجه شد",
"closed": "بسته شد Music Together",
"disconnected": "Music Together قطع اتصال",
"host-failed": "با شکست مواجه شد Music Together میزبانی",
"id-copied": "شناسه میزبان به کلیپبورد کپی شد",
"id-copy-failed": "کپی شناسه میزبان به کلیپبورد با شکست مواجه شد",
"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": "کنترل های رابط",
"templates": {
"back": {
"title": "برو به صفحه قبل"
},
"forward": {
"title": "برو به صفحه بعد"
}
}
},
"no-google-login": {
"description": "حذف دکمهها و لینکهای ورود به گوگل از رابط کاربری",
"name": "بدون ورود به گوگل"
},
"notifications": {
"description": "نمایش اعلان هنگامی که آهنگی شروع به پخش میکند (اعلانهای تعاملی در ویندوز در دسترس هستند)",
"menu": {
"interactive": "اعلانهای تعاملی",
"interactive-settings": {
"label": "تنظیمات تعاملی",
"submenu": {
"hide-button-text": "مخفی کردن متن دکمه",
"refresh-on-play-pause": "تازهسازی در پخش/توقف",
"tray-controls": "باز/بسته شدن با کلیک روی آیکون در نوار وظیفه"
}
},
"priority": "اولویت اعلان",
"toast-style": "Toast سبک",
"unpause-notification": "نمایش اعلان هنگام از سرگیری پخش"
},
"name": "اعلانها"
},
"performance-improvement": {
"description": "بهبود عملکرد با فعال کردن اسکریپتهای آزمایشی",
"name": "بهبود عملکرد [بتا]"
},
"picture-in-picture": {
"description": "اجازه میدهد تا برنامه به حالت تصویر در تصویر تغییر کند",
"menu": {
"always-on-top": "همیشه در بالا",
"hotkey": {
"label": "کلید میانبر",
"prompt": {
"keybind-options": {
"hotkey": "کلید میانبر"
},
"label": "یک کلید میانبر انتخاب کنید برای فعال/غیرفعال کردن حالت تصویر در تصویر",
"title": "کلید میانبر برای حالت تصویر در تصویر"
}
},
"save-window-position": "ذخیره موقعیت پنجره",
"save-window-size": "ذخیره اندازه پنجره",
"use-native-pip": "استفاده از حالت تصویر در تصویر اصلی مرورگر"
},
"name": "تصویر در تصویر",
"templates": {
"button": "تصویر در تصویر"
}
},
"playback-speed": {
"description": "به سرعت گوش بده، به آرامی گوش بده! یک دکمه کشویی برای تنظیم سرعت آهنگ اضافه شد",
"name": "سرعت پخش",
"templates": {
"button": "سرعت"
}
},
"precise-volume": {
"description": "کنترل دقیق صدا با استفاده از چرخ موس/میانبرها، همراه با HUD سفارشی و مراحل تنظیم حجم قابل تنظیم",
"menu": {
"arrows-shortcuts": "میانبرهای کلیدهای فلشی",
"custom-volume-steps": "مراحل تنظیم صدای دلخواه",
"global-shortcuts": "کلید های میانبر جهانی"
},
"name": "صدای دقیق",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "کاهش صدا",
"increase": "افزایش صدا"
},
"label": "انتخاب کلیدهای میانبر سراسری صدا:",
"title": "میانبرهای کلید سراسری صدا"
},
"volume-steps": {
"label": "مراحل انتخاب افزایش/کاهش صدا",
"title": "سطح صدا"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "کیفیت کنونی: {{quality}}",
"message": "انتخاب کیفیت ویدیو:",
"title": "انتخاب کیفیت ویدیو"
}
}
},
"description": "امکان تغییر کیفیت ویدیو با استفاده از دکمه در رابط پخش ویدیو",
"name": "تغییر دهنده کیفیت ویدیو",
"renderer": {
"quality-settings-button": {
"label": "باز کردن تغییر دهنده کیفیت پخش کننده"
}
}
},
"scrobbler": {
"description": "اضافه کردن پشتیبانی از اسکرابلینگ (etc. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "احراز هویت با Last.fm ناموفق بود\nپنجره شناور را تا راهاندازی مجدد بعدی مخفی کن.",
"title": "احراز هویت ناموفق بود"
}
}
},
"menu": {
"lastfm": {
"api-settings": "تنظیمات \"Last.fm \"API"
},
"listenbrainz": {
"token": "توکن کاربری ListenBrainz را وارد کنید"
},
"scrobble-alternative-artist": "از هنرمند دىگرى استفاده کنىد",
"scrobble-alternative-title": "از عناوین جایگزین استفاده کنید",
"scrobble-other-media": "ردیابی رسانههای دیگر"
},
"name": "ابزار ثبتکنندهی آهنگ",
"prompt": {
"lastfm": {
"api-key": "کلید Last.fm API",
"api-secret": "API مخفی Last.fm"
},
"listenbrainz": {
"token": {
"label": "توکن کاربری ListenBrainz خود را وارد کنید:",
"title": "توکن ListenBrainz"
}
}
}
},
"shortcuts": {
"description": "امکان تنظیم میانبرهای سراسری برای کنترل (پخش/توقف/بعدی/قبلی) و خاموش کردن OSD رسانه با بازنویسی کلیدهای رسانهای، فعالسازی Ctrl/CMD + F برای جستجو، فعالسازی پشتیبانی MPRIS در لینوکس برای کلیدهای رسانهای، و میانبرهای سفارشی برای کاربران پیشرفته",
"menu": {
"override-media-keys": "تغییر عملکرد کلیدهای رسانه",
"set-keybinds": "تنظیم کنترلهای سراسری آهنگ"
},
"name": "میانبرها (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "بعدی",
"play-pause": "پخش / توقف",
"previous": "قبلی"
},
"label": "انتخاب میانبرهای سراسری برای کنترل آهنگها:",
"title": "میانبرهای کلیدی سراسری"
}
}
},
"skip-disliked-songs": {
"description": "خودکار آهنگ های غیر موردعلاقه رد میشن",
"name": "رد آهنگهای غیر مورد علاقه"
},
"skip-silences": {
"description": "رد خودکار بخشهای بی صدا آهنگ ها",
"name": "رد بخشهای بیصدا"
},
"sponsorblock": {
"description": "بهطور خودکار بخشهای غیرموسیقی مانند مقدمه/پایان یا قسمتهایی از ویدیوهای موسیقی که آهنگ در آن پخش نمیشود را رد میکند",
"name": "مسدودکننده اسپانسر"
},
"synced-lyrics": {
"description": "ارائه متن ترانهها به صورت هماهنگ با آهنگها، با استفاده از ارائهدهندگانی مانند LRClib.",
"errors": {
"fetch": "⚠️هنگام بارگیری متن ترانه خطایی رخ داده است.\n\tلطفاً بعداً دوباره تلاش کنید.",
"not-found": "⚠️ متنی برای این ترانه پیدا نشد."
},
"menu": {
"default-text-string": {
"label": "حرف/کاراکتر پیشفرض بین متنهای ترانه",
"tooltip": "حرف/کاراکتر پیشفرض را برای فاصله بین متنهای ترانه انتخاب کنید"
},
"line-effect": {
"label": "افکت خط متن",
"submenu": {
"fancy": {
"label": "شیک",
"tooltip": "استفاده از افکتهای بزرگ و شبیه به اپلیکیشنها برای خط فعلی"
},
"focus": {
"label": "تمرکز",
"tooltip": "فقط خط فعلی رو سفید کن"
},
"offset": {
"label": "جابجایی",
"tooltip": "جابجایی خط فعلی به سمت راست"
},
"scale": {
"label": "مقیاس",
"tooltip": "تغییر اندازه خط فعلی"
}
},
"tooltip": "افکت مورد نظر را برای خط فعلی انتخاب کنید"
},
"precise-timing": {
"label": "هماهنگسازی کامل متن ترانه",
"tooltip": "محاسبه دقیق نمایش خط بعدی تا میلیثانیه (ممکن است تاثیر کمی بر عملکرد داشته باشد)"
},
"preferred-provider": {
"label": "منبع دلخواه",
"none": {
"label": "هىچکدام",
"tooltip": "منبح دلخواهى انتخاب نشده"
},
"tooltip": "منبع دلخواهتان را انتخاب کنىد"
},
"romanization": {
"label": "اشعار رومی شده",
"tooltip": "اگر اشعار به زبانی متفاوت هستند، سعی کنید نسخه لاتین را نمایش دهید."
},
"show-lyrics-even-if-inexact": {
"label": "نمایش متن ترانه ها حتی اگر دقیق نباشد",
"tooltip": "اگر آهنگ پیدا نشد، افزونه دوباره با یک جستجوی متفاوت امتحان میکند.\nنتیجهی این تلاش ممکن است دقیق نباشد."
},
"show-time-codes": {
"label": "نمایش زمانبندیها",
"tooltip": "نمایش زمانبندیها کنار متن ترانه"
}
},
"name": "متن ترانه هماهنگ شد",
"refetch-btn": {
"fetching": "در حال بارگذاری...",
"normal": "دریافت مجدد متن ترانه"
},
"warnings": {
"duration-mismatch": "⚠️ - ممکن است متن ترانه به دلیل عدم تطابق زمان با مشکل هماهنگی مواجه شود.",
"inexact": "⚠️ - ممکن است متن ترانه برای این آهنگ دقیق نباشد",
"instrumental": "⚠️ - این آهنگ بی کلام است"
}
},
"taskbar-mediacontrol": {
"description": "کنترل پخش از نوار وظیفه ویندوز(taskbar)",
"name": "کنترل رسانه از نوار وظیفه (taskbar)"
},
"touchbar": {
"description": "افزودن ویجت TouchBar برای کاربران macOS",
"name": "نوار لمسی"
},
"transparent-player": {
"description": "پنجره برنامه را شفاف مىکند",
"menu": {
"opacity": {
"label": "تارى",
"submenu": {
"percent": "%{{تارى}}"
}
},
"type": {
"label": "نوع",
"submenu": {
"acrylic": "اکرىلىک",
"mica": "مىکا",
"none": "هىچکدام",
"tabbed": "نوار دار"
}
}
},
"name": "پلىر شفاف"
},
"tuna-obs": {
"description": "ادغام با پلاگین Tuna در OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "هنگام پخش یک آهنگ از پخش کننده جلوگیری می کند",
"name": "پخشکننده بی نظیر"
},
"video-toggle": {
"description": "دکمهای اضافه میکند برای جابجایی بین حالت ویدیو/آهنگ. همچنین به صورت اختیاری میتواند تب ویدیو را حذف کند",
"menu": {
"align": {
"label": "چینش",
"submenu": {
"left": "چپ",
"middle": "میانه",
"right": "راست"
}
},
"force-hide": "حذف اجباری تب ویدیو",
"mode": {
"label": "حالت",
"submenu": {
"custom": "حالت شخصیسازی شده",
"disabled": "غیرفعال",
"native": "حالت پیشفرض"
}
}
},
"name": "ویدیو به آهنگ",
"templates": {
"button-song": "ترانه",
"button-video": "ویدیو"
}
},
"visualizer": {
"description": "اضافه کردن نمایشدهنده تصویری به پخشکننده",
"menu": {
"visualizer-type": "نوع نمایشدهنده تصویری"
},
"name": "نمایشدهنده تصویری"
}
}
}
================================================
FILE: src/i18n/resources/fi.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Virhe pluginin lataamisessa: {{pluginName}}, koska {{contextName}}",
"executed-at-ms": "Lisäosa: {{pluginName}} ja {{contextName}} on ladattu/liitetty {{ms}}",
"initialize-failed": "Laajennuksen alustaminen epäonnistui kohteelle \"{{pluginName}}\"",
"load-all": "Ladataan kaikkia lisäosia",
"load-failed": "Virhe lisäosan lataamisessa kohteelle: {{pluginName}}",
"loaded": "Lisäosa {{pluginName}} on ladattu",
"unload-failed": "Laajennuksen purkaminen epäonnistui kohtelle: {{pluginName}}",
"unloaded": "Lisäosa {{pluginName}} on purettu"
}
}
},
"language": {
"code": "fi",
"local-name": "Suomi",
"name": "Finnish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Onnistuneesti ladattu. Devtools avautuu"
},
"i18n": {
"loaded": "i18n ladattu"
},
"second-instance": {
"receive-command": "Komento \"{{command}}\" on vastaanotettu"
},
"theme": {
"css-file-not-found": "{{cssFile}} on jätetty väliin, koska tiedosto on virheellinen"
},
"unresponsive": {
"details": "Reagoimaton virhe\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Puhdista välimuisti"
},
"window": {
"tried-to-render-offscreen": "Näyttö yritti renderöidä näyttöäsi asetuksilla: {{windowSize}}, {{displaySize}} sekä {{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Valikko on piilotettu, avaa valikko uudestaan painamalla \"Alt\" näppäintä tai \"Escape\" näppäintä",
"message": "Valikon piilotus on nyt päällä",
"title": "Piilota valikko päällä"
},
"need-to-restart": {
"buttons": {
"later": "Myöhemmin",
"restart-now": "Uudelleen käynnistä NYT"
},
"detail": "{{pluginName}} lisäosa vaatii uudelleen käynnistyksen YT musicille",
"message": "{{pluginName}} vaatii uudelleen käynnistyksen YT musicille",
"title": "Uudelleen käynnistä sovellus"
},
"unresponsive": {
"buttons": {
"quit": "Poistu",
"relaunch": "Uudelleen käynnistä",
"wait": "Odotas vähän"
},
"detail": "Pahoittelemme häiriötä! ole hyvä ja valitse mitä teet:",
"message": "Sovellus ei ole saataville eli tapahtui virhe",
"title": "Ikkuna ei vastaa"
},
"update-available": {
"buttons": {
"disable": "Poista päivitykset käytöstä",
"download": "Lataa",
"ok": "Selvä"
},
"detail": "Uusin versio sovelluksesta on nyt saatavilla, lataa se tästä {{downloadLink}}",
"message": "Uusin versio on nyt saatavilla",
"title": "Päivitys saatavilla"
}
},
"menu": {
"about": "Tietoa",
"navigation": {
"label": "Selaa",
"submenu": {
"copy-current-url": "Kopio URL osoite",
"go-back": "Takaisin",
"go-forward": "Eteenpäin",
"quit": "Poistu alustalta",
"restart": "Uudelleen käynnistä aplikaatio"
}
},
"options": {
"label": "Asetukset",
"submenu": {
"advanced-options": {
"label": "Lisäasetukset",
"submenu": {
"auto-reset-app-cache": "Puhdista sovelluksen välimuisti aina sovelluksen käynnistyksen aikana",
"disable-hardware-acceleration": "Poista laitteistokiihdytys käytöstä",
"edit-config-json": "Muokkaa \"config.json\" tiedostoa",
"override-user-agent": "Ohita käyttäjäagentti",
"restart-on-config-changes": "Käynnistä uudelleen asetusten muuton jälkeen",
"set-proxy": {
"label": "Aseta välityspalvelin (proxy)",
"prompt": {
"label": "Aseta välityspalvelimen IP-osoite: (jos jätät tyhjäksi, palvelin ei käynnisty)",
"placeholder": "Esimerkki osoite: penapertti://127.0.0.0:6969",
"title": "Aseta välityspalvelin (proxy)"
}
},
"toggle-dev-tools": "Ota DevTools käyttöön"
}
},
"always-on-top": "Aina päällä",
"auto-update": "Automaattisest päivitykset",
"hide-menu": {
"dialog": {
"message": "Valikko piilotetaan seuraavan käynnistyksen yhteydessä. Saat sen päälle painamalla [Alt] näppäintä (tai merkitse takaisin [`], jos käytät sovelluksen sisäistä valikkoa)",
"title": "Piilota valikko (päällä)"
},
"label": "Piilota valikko"
},
"language": {
"dialog": {
"message": "Kieli vaihtuu uudelleen käynnistyksen jälkeen (Language will be changed after restart)",
"title": "Kieli vaihdettu (Language Changed)"
},
"label": "Kieli (languages)",
"submenu": {
"to-help-translate": "Haluatko kääntää puuttuvan kielen? Klkkaa tästä! (Want to help translate? Click here)"
}
},
"resume-on-start": "Jatka kappaleesta, johon jäin aikaisemmin",
"single-instance-lock": "Yhden instanssin lukko",
"start-at-login": "Aloita kirjautuminen",
"starting-page": {
"label": "Etusivu",
"unset": "Valitsematta"
},
"tray": {
"label": "Suositukset",
"submenu": {
"disabled": "Pois päältä",
"enabled-and-hide-app": "Suositukset ovat käytössä ja piilota valikko",
"enabled-and-show-app": "Päällä ja sovellus näkyvissä",
"play-pause-on-click": "Soita/pysäytä klikkaamalla"
}
},
"visual-tweaks": {
"label": "Visuaalisia tehosteita",
"submenu": {
"custom-window-title": {
"label": "Mukautettu ikkunan otsikko",
"prompt": {
"label": "Syötä mukautettu ikkunan otsikko: (jätä tyhjäksi poistaaksesi päältä)",
"placeholder": "Esimerkki: {{applicationName}}"
}
},
"like-buttons": {
"default": "Vakio",
"force-show": "Pakota näyttämään",
"hide": "Piilota",
"label": "Tykkäys nappula"
},
"remove-upgrade-button": "Poista päivitys nappula",
"theme": {
"dialog": {
"button": {
"cancel": "Peruuta",
"remove": "Poista"
},
"remove-theme": "Oletko aivan varma, että haluat poistaa kustomoidun teeman?",
"remove-theme-message": "Tämä poistaa kustomoidun teeman"
},
"label": "Teema",
"submenu": {
"import-css-file": "Liitä kustomoitu CSS tiedosto",
"no-theme": "Ei teemaa"
}
}
}
}
}
},
"plugins": {
"enabled": "Päällä",
"label": "Lisäosat",
"new": "UUSI"
},
"view": {
"label": "Katso",
"submenu": {
"force-reload": "pakota uudelleen lataamaan",
"reload": "Uudelleen lataa",
"reset-zoom": "Todellinen koko",
"toggle-fullscreen": "Koko näyttö päälle/pois",
"zoom-in": "Zoomaa lähemmäksi",
"zoom-out": "Zoomaa kauemmaksi"
}
}
},
"tray": {
"next": "Seuraava",
"play-pause": "Soita/pysäytä",
"previous": "Edellinen",
"quit": "Lähde pois",
"restart": "Uudelleen käynnistä appi",
"show": "Näytä ikkuna",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Jos mainos toistuu, mykistä ääni ja aseta toistonopeus 16x:een",
"name": "Mainoksen nopeutus"
},
"adblocker": {
"description": "Estä kaikki mainokset ja seuranta",
"menu": {
"blocker": "Estäjät (blockerit)"
},
"name": "Mainos estäjä"
},
"album-actions": {
"description": "Lisää tykkäysnappulat, joilla voit lisätä tai poistaa tykkäyksiä kerralla kaikille soittolistan tai albumin kappaleille",
"name": "Albumin Toiminnot"
},
"album-color-theme": {
"description": "Käyttää dynaamista teemaa ja visuaalisia tehosteita albumin väripaletin perusteella",
"menu": {
"color-mix-ratio": {
"label": "Värien sekoitussuhde",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Albumin Värinen Teema"
},
"ambient-mode": {
"description": "Antaa valaistustehosteen heittämällä videosta lempeitä värejä näytön taustalle",
"menu": {
"blur-amount": {
"label": "Sumennuksen voimakkuus",
"submenu": {
"pixels": "{{blurAmount}} pikseliä"
}
},
"buffer": {
"label": "Puskurointi",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Läpinäkyvyys",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Laatu",
"submenu": {
"pixels": "{{quality}} pikseliä"
}
},
"size": {
"label": "Koko",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Siirtymän sujuvuus",
"submenu": {
"during": "Kesto {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Käytetään koko näytön tilaa"
}
},
"name": "Tunnelmallinen Tila"
},
"amuse": {
"description": "Lisää {{applicationName}} tuen Amusen nyt soitetaan -widgetille, kehittäjänä 6K Labs",
"name": "Amuse",
"response": {
"query": "Amuse API-palvelin on päällä. Käytä GET /query-rajapintaa saadaksesi kappaleen tiedot."
}
},
"api-server": {
"description": "Lisää API-palvelimen hallitsemaan soitinta",
"dialog": {
"request": {
"buttons": {
"allow": "Hyväksy",
"deny": "Kiellä"
},
"message": "Sallitaanko {{ID}} ({{origin}}) pääsy API:in?",
"title": "API vahvistuspyyntö"
}
},
"menu": {
"auth-strategy": {
"label": "Valtuutus-strategia",
"submenu": {
"auth-at-first": {
"label": "Valtuuta ensimmäisellä kyselyllä"
},
"none": {
"label": "Ei valtuuksia"
}
}
},
"hostname": {
"label": "Isäntänimi"
},
"port": {
"label": "Portti"
}
},
"name": "API Serveri [Beta]",
"prompt": {
"hostname": {
"label": "Syötä isäntänimi (esimerkiksi 0.0.0.0) API-palvelimelle:",
"title": "Isäntänimi"
},
"port": {
"label": "Syötä API-palvelimen portti:",
"title": "Portti"
}
}
},
"audio-compressor": {
"description": "Lisää äänen kompressointia (hiljentää voimakkaimpien äänien voimakkuutta ja tehostaa pehmeämpien äänien voimakkuutta)",
"name": "Äänen Kompressoija"
},
"auth-proxy-adapter": {
"description": "Tukee todennusvälipalvelinten käyttöä",
"menu": {
"disable": "Poista välipalvelimen adapteri pois käytöstä",
"enable": "Aseta välipalvelimen adapteri käyttöön",
"hostname": {
"label": "Isäntänimi"
},
"port": {
"label": "Portti"
}
},
"name": "Todennusvälipalvelinadapteri",
"prompt": {
"hostname": {
"label": "Syötä paikallisen välipalvelimen isäntänimi (vaatii uudelleenkäynnistyksen):",
"title": "Välipalvelimen isäntänimi"
},
"port": {
"label": "Syötä paikallisen välipalvelimen portti (vaatii uudelleenkäynnistyksen):",
"title": "Välipalvelimen portti"
}
}
},
"blur-nav-bar": {
"description": "Tekee siirtymäpalkista läpikuultavan ja sumean",
"name": "Sumenna Siirtymäpalkki"
},
"bypass-age-restrictions": {
"description": "Ohita Music Player iän vahvistus",
"name": "Ohita Ikään Perustuvat Rajoitukset"
},
"captions-selector": {
"description": "{{applicationName}} ääniraitojen tekstitysten valitsin",
"menu": {
"autoload": "Valitse automaattisesti viimeksi käytetty tekstitys",
"disable-captions": "Tekstitys ei oletusarvoisesti käytössä"
},
"name": "Tekstitysten valinta",
"prompt": {
"selector": {
"label": "Tekstitysten nykyinen kieli: {{language}}",
"none": "Ei mitään",
"title": "Valitse tekstitysten kieli"
}
},
"templates": {
"title": "Avaa tekstitysten valitsin"
},
"toast": {
"caption-changed": "Tekstitys vaihdettu kieleksi {{language}}",
"caption-disabled": "Tekstitykset pois päältä",
"no-captions": "Tekstityksiä ei ole saatavilla tälle kappaleelle"
}
},
"compact-sidebar": {
"description": "Asettaa sivupalkin aina kompaktiin tilaan",
"name": "Kompakti sivupalkki"
},
"crossfade": {
"description": "Ristihäivytä kappaleet",
"menu": {
"advanced": "Edistynyt"
},
"name": "Ristihäivytys [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Sisään häivytyksen kesto (ms)",
"fade-out-duration": "Ulos häivytyksen kesto (ms)",
"fade-scaling": {
"label": "Häivytyksen skaalaus",
"linear": "Lineaarinen",
"logarithmic": "Logaritminen"
},
"seconds-before-end": "Ristihäivytä N sekuntia ennen loppua"
},
"title": "Ristihäivytyksen asetukset"
}
}
},
"disable-autoplay": {
"description": "Kappaleet alkavat \"pysäytetty\" tilassa",
"menu": {
"apply-once": "Käytetään vain käynnistäessä"
},
"name": "Poista automaattinen toisto käytöstä"
},
"discord": {
"backend": {
"already-connected": "Yritettiin yhdistää vaikka yhteys on jo aktiivinen",
"connected": "Yhdistetty Discordiin",
"disconnected": "Katkaistu yhteys Discordiin"
},
"description": "Näytä ystävillesi mitä kuuntelet \"Rich Presence\":n avulla",
"menu": {
"auto-reconnect": "Automaatinen uudelleenyhdistys",
"clear-activity": "Nollaa toiminta",
"clear-activity-after-timeout": "Nollaa toiminta aikakatkaisun jälkeen",
"connected": "Yhdistetty",
"disconnected": "Yhteys katkaistu",
"hide-duration-left": "Piilota kappaleen jäljellä oleva kesto",
"hide-github-button": "Piilota \"linkki GitHubiin\" -nappi",
"play-on-application": "Kuuntele palvelussa {{applicationName}}",
"set-inactivity-timeout": "Aseta toimettomuuden aikakatkaisu"
},
"name": "Discord Aktiviteetti (Rich Presence)",
"prompt": {
"set-inactivity-timeout": {
"label": "Anna toimettomuuden aikakatkaisun aika sekunteina:",
"title": "Aseta toimettomuuden aikakatkaisu"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Äh! Pahoittelut, lataus epäonnistui…",
"title": "Virhe latauksessa!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} kappaletta)",
"message": "Lataa Soittolista {{playlistTitle}}",
"title": "Lataus aloitettu"
}
},
"feedback": {
"conversion-progress": "Muunnetaan: {{percent}}%",
"converting": "Muuntaa…",
"done": "Valmis: {{filePath}}",
"download-info": "Ladataan {{artist}} -{{title}} [{{videoId}}",
"download-progress": "Latauksen edistyminen: {{percent}}%",
"downloading": "Ladataan…",
"downloading-counter": "Ladataan {{current}}/{{total}}…",
"downloading-playlist": "Ladataan soittolistaa \"{{playlistTitle}}\" {{playlistSize}} kappaletta ({{playlistId}})",
"error-while-downloading": "Virhe ladattaessa \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Kansio {{playlistFolder}} on jo olemassa",
"getting-playlist-info": "Haetaan soittolistan tietoja…",
"loading": "Ladataan…",
"playlist-has-only-one-song": "Soittolistalla on vain yksi kappale, se ladataan suoraan",
"playlist-id-not-found": "Soittolistan tunnistetta ei löytynyt",
"playlist-is-empty": "Soittolista on tyhjä",
"playlist-is-mix-or-private": "Virhe haettaessa soittolista tietoja: varmista ettei soittolista ole yksityinen tai \"Miksattu sinulle\" soittolista\n\n{{error}}",
"preparing-file": "Valmistellaan tiedostoa…",
"saving": "Tallennetaan…",
"trying-to-get-playlist-id": "Yritetään hakea soittolistan tunnistetta: {{playlistId}}",
"video-id-not-found": "Videota ei löytynyt",
"writing-id3": "Kirjoitetaan ID3-tunnisteita…"
}
},
"description": "Lataa MP3- tai lähdetiedoston suoraan käyttöliittymästä",
"menu": {
"choose-download-folder": "Valitse latauskansio",
"download-finish-settings": {
"label": "Lataa toiston päätyttyä",
"prompt": {
"last-percent": "x prosentin jälkeen",
"last-seconds": "Viimeiset x sekuntia",
"title": "Määritä milloin ladata"
},
"submenu": {
"advanced": "Edistynyt",
"enabled": "Päällä",
"mode": "Aikatila",
"percent": "Prosentti",
"seconds": "Sekuntia"
}
},
"download-playlist": "Lataa soittolista",
"presets": "Esiasetukset",
"skip-existing": "Ohita olemassa olevat tiedostot"
},
"name": "Lataaja",
"renderer": {
"can-not-update-progress": "Edistystä ei voida päivittää"
},
"templates": {
"button": "Lataa"
}
},
"equalizer": {
"description": "Lisää taajuuskorjaimen toistimeen",
"menu": {
"presets": {
"label": "Pohjat",
"list": {
"bass-booster": "Bassonlisääjä"
}
}
},
"name": "Taajuuskorjain"
},
"exponential-volume": {
"description": "Tekee äänenvoimakkuuden säätimestä eksponentiaalisen, jotta matalampien äänenvoimakkuuksien valinta on helpompaa.",
"name": "Eksponentiaalinen Äänenvoimakkuus"
},
"in-app-menu": {
"description": "Antaa valikkopalkeille hienon tumman tai albumin värisen ulkonäön",
"menu": {
"hide-dom-window-controls": "Piilota ikkunan DOM ohjaimet"
},
"name": "Sovelluksen sisäinen valikko"
},
"lumiastream": {
"description": "Lisää tuen Lumia Stream -palvelulle",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Lisää tuen useimpien kappaleiden sanoituksille",
"menu": {
"romanized-lyrics": "Latinaistetut sanoitukset"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Sanoitukset haettu Geniukselle"
}
},
"music-together": {
"description": "Jaa soittolista muiden kanssa. Kun isäntä soittaa kappaleen, kaikki muut kuulevat saman kappaleen",
"dialog": {
"enter-host": "Anna Istunnon tunniste"
},
"internal": {
"save": "Tallenna",
"track-source": "Kappaleen lähde",
"unknown-user": "Tuntematon käyttäjä"
},
"menu": {
"click-to-copy-id": "Kopioi Istunnon tunniste",
"close": "Sulje \"Music Together\"",
"connected-users": "Yhdistetyt käyttäjät",
"disconnect": "Katkaise yhteys \"Music Together\" -istuntoon",
"empty-user": "Ei yhdistyneitä käyttäjiä",
"host": "\"Music Together\" -istunnon isäntä",
"join": "Yhdistä \"Music Together\" -istuntoon",
"permission": {
"all": "Salli vieraiden hallita soittolistaa ja soitinta",
"host-only": "Vain isäntä voi hallita soittolistaa ja soitinta",
"playlist": "Salli vieraiden hallita soittolistaa"
},
"set-permission": "Muuta hallintaoikeuksia",
"status": {
"disconnected": "Yhteys katkaistu",
"guest": "Yhdistetty vieraana",
"host": "Yhdistetty isäntänä"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Kappaleen lisääminen epäonnistui",
"closed": "Music Together suljettu",
"disconnected": "\"Music Together\" yhteys katkaistu",
"host-failed": "Music Together -istunnon isännöinti epäonnistui",
"id-copied": "Istunnon tunnus kopioitu leikepöydälle",
"id-copy-failed": "Istunnon tunnisteen kopioiminen epäonnistui",
"join-failed": "Music Together -istuntoon liittyminen epäonnistui",
"joined": "Liityttiin Music Together -istuntoon",
"permission-changed": "Music Together -istunnon oikeuksia muutettiin \"{{permission}}\"",
"remove-song-failed": "Kappaleen poistaminen epäonnistui",
"user-connected": "{{name}} liittyi Music Together -istuntoon",
"user-disconnected": "{{name}} poistui Music Together -istunnosta"
}
},
"navigation": {
"description": "Eteen- ja taaksepäin vievät nuolet suoraan integroituna käyttöliittymään. Juuri niin kuin lempiselaimessasi",
"name": "Siirtyminen",
"templates": {
"back": {
"title": "Palaa edelliselle sivulle"
},
"forward": {
"title": "Siirry seuraavalle sivulle"
}
}
},
"no-google-login": {
"description": "Poista Googlen kirjautumispainikkeet ja linkit käyttöliittymästä",
"name": "Ei Google kirjautumista"
},
"notifications": {
"description": "Näytä ilmoitus, kun kappale alkaa soida (interaktiiviset ilmoitukset ovat käytettävissä Windowsilla)",
"menu": {
"interactive": "Interaktiiviset Ilmoitukset",
"interactive-settings": {
"label": "Interaktiiviset Asetukset",
"submenu": {
"hide-button-text": "Piilota painikkeen teksti",
"refresh-on-play-pause": "Päivitä Toistamisen/Tauottamisen yhteydessä",
"tray-controls": "Avaa/Sulje tehtäväpalkista"
}
},
"priority": "Ilmoitusten tärkeys",
"toast-style": "Ponnahdusilmoitusten tyyli",
"unpause-notification": "Näytä ilmoitus toistamisen yhteydessä"
},
"name": "Ilmoitukset"
},
"performance-improvement": {
"description": "Paranna suorituskykyä käyttämällä kokeellisia skriptejä",
"name": "Suorituskykyparannus [Beta]"
},
"picture-in-picture": {
"description": "Sallii sovelluksen vaihtamisen \"kuva kuvassa\" tilaan",
"menu": {
"always-on-top": "Aina päällimmäisenä",
"hotkey": {
"label": "Pikanäppäin",
"prompt": {
"keybind-options": {
"hotkey": "Pikanäppäin"
},
"label": "Valitse pikanäppäin \"kuva kuvassa\" -tilan kytkemiseksi",
"title": "\"Kuva kuvassa\" -tilan pikanäppäin"
}
},
"save-window-position": "Tallenna ikkunan sijainti",
"save-window-size": "Tallenna ikkunan koko",
"use-native-pip": "Käytä selaimen natiivia \"Kuva kuvassa\" -tilaa"
},
"name": "Kuva kuvassa",
"templates": {
"button": "Kuva kuvassa"
}
},
"playback-speed": {
"description": "Kuuntele nopeasti, kuuntele hitaasti! Lisää säätimen, jolla voit säätää kappaleen toistonopeutta",
"name": "Toistonopeus",
"templates": {
"button": "Nopeus"
}
},
"precise-volume": {
"description": "Säädä äänenvoimakkuutta tarkasti hiiren rullaa tai pikanäppäimiä käyttäen. Kustomoidulla käyttöliittymällä ja säädettävällä äänenvoimakkuuden porrastuksella",
"menu": {
"arrows-shortcuts": "Paikallinen nuolinäppäinohjaus",
"custom-volume-steps": "Aseta mukautettu äänenvoimakkuuden porrastus",
"global-shortcuts": "Yleiset pikanäppäimet"
},
"name": "Tarkka äänenvoimakkuus",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Vähennä äänenvoimakkuutta",
"increase": "Lisää äänenvoimakkuutta"
},
"label": "Valitse yleiset äänenvoimakkuuden pikanäppäimet:",
"title": "Globaalit äänenvoimakkuusnäppäimet"
},
"volume-steps": {
"label": "Valitse äänenvoimakkuuden suurennus-/pienennysaskeleet",
"title": "Äänenvoimakkuusaskeleet"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Nykyinen laatu: {{quality}}",
"message": "Valitse videon laatu:",
"title": "Valitse videon laatu"
}
}
},
"description": "Salli videon laadun muuttaminen videon päällä näkyvällä painikkeella",
"name": "Videonlaadunmuuttaja",
"renderer": {
"quality-settings-button": {
"label": "Avaa toistimen laadun muuttaja"
}
}
},
"scrobbler": {
"description": "Lisää jakamistuki (esim. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm-varmennus epäonnistui\nPiilota ponnahdusikkuna kunnes käynnistät ohjelman uudelleen.",
"title": "Todennus epäonnistui"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API:n asetukset"
},
"listenbrainz": {
"token": "Syötä ListenBrainz-käyttötunnus (token)"
},
"scrobble-alternative-title": "Käytä vaihtoehtoisia otsikoita",
"scrobble-other-media": "Jaa muuta mediaa"
},
"name": "Jakaja",
"prompt": {
"lastfm": {
"api-key": "Last.fm:n API-avain",
"api-secret": "Last.fm:n API-salaisuus"
},
"listenbrainz": {
"token": {
"label": "Syötä ListenBrainz käyttötunnuksesi (token):",
"title": "ListenBrainz-käyttötunnus"
}
}
}
},
"shortcuts": {
"prompt": {
"keybind": {
"keybind-options": {
"previous": "Edellinen"
}
}
}
},
"tuna-obs": {
"name": "Tuna OBS"
},
"video-toggle": {
"menu": {
"align": {
"submenu": {
"left": "Vasen",
"right": "Oikea"
}
}
}
}
}
}
================================================
FILE: src/i18n/resources/fil.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Nabigong patakbuin ang plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Ang plugin na {{pluginName}}::{{contextName}} ay pinatakbo sa loob ng {{ms}}ms",
"initialize-failed": "Nabigo ang pagsimula ng plugin na \"{{pluginName}}\"",
"load-all": "Nilo-load lahat ng mga plugin",
"load-failed": "Nabigong i-load ang plugin na \"{{pluginName}}\"",
"loaded": "Na-load ang \"{{pluginName}}\" na plugin",
"unload-failed": "Nabigong i-unload ang plugin na \"{{pluginName}}\"",
"unloaded": "Na-unload ang \"{{pluginName}}\" na plugin"
}
}
},
"language": {
"code": "fil",
"local-name": "Tagalog",
"name": "Filipino"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Natapos ang pag-load. Nabuksan ang DevTools"
},
"i18n": {
"loaded": "na-load ang i18n"
},
"second-instance": {
"receive-command": "Natanggap ang command sa pamamagitan ng protocol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Ang CSS file na \"{{cssFile}}\" ay hindi umiiral, hindi papansin"
},
"unresponsive": {
"details": "Hindi tumutugon na Error!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Naglilinis ng app cache"
},
"window": {
"tried-to-render-offscreen": "Nasubukan ng window na mag-render sa labas ng screen, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Nakatago ang menu, gamitin ang 'Alt' para makita ito (o 'Escape' kung gagamitin ang In-App na Menu)",
"message": "Ang Pagtatago ng Menu ay napagana na",
"title": "Napagana ang Pagtatago ng Menu"
},
"need-to-restart": {
"buttons": {
"later": "Mamaya",
"restart-now": "Mag-restart na"
},
"detail": "Ang plugin na \"{{pluginName}}\" ay kinakailangan ng restart para gumana ito",
"message": "Kinakailangan ng \"{{pluginName}}\" na mag-restart",
"title": "Kinakailangan ng Restart"
},
"unresponsive": {
"buttons": {
"quit": "Umalis",
"relaunch": "Muling patakbuhin",
"wait": "Maghintay"
},
"detail": "Ikinalulungkot namin ang abala! piliin kung ano ang gagawin:",
"message": "Ang Application ay Hindi Tumutugon",
"title": "Di tumutugon ang Window"
},
"update-available": {
"buttons": {
"disable": "Di-paganahin ang mga Update",
"download": "I-download",
"ok": "OK"
},
"detail": "Ang isang bagong bersyon ay available at maaaring i-download sa {{downloadLink}}",
"message": "Mayroong bagong version ay available",
"title": "Available ang Update"
}
},
"menu": {
"about": "Patungkol",
"navigation": {
"label": "Nabigasyon",
"submenu": {
"copy-current-url": "Kopyahin ang kasalukuyang URL",
"go-back": "Bumalik",
"go-forward": "Pasulong",
"quit": "Lumabas",
"restart": "I-restart ang App"
}
},
"options": {
"label": "Mga Opsyon",
"submenu": {
"advanced-options": {
"label": "Mga advance na opsyon",
"submenu": {
"auto-reset-app-cache": "I-reset ang app cache kapag nagsisimula ang app",
"disable-hardware-acceleration": "Di-paganahin ang pagpapabilis ng hardware",
"edit-config-json": "I-edit ang config.json",
"override-user-agent": "I-override ang User-Agent",
"restart-on-config-changes": "I-restart kada may pagbabago sa config",
"set-proxy": {
"label": "I-set ang proxy",
"prompt": {
"label": "Ilagay ang Proxy Address: (iwanang walang laman para di-paganahin)",
"placeholder": "Halimbawa: SOCKS5://127.0.0.1:9999",
"title": "I-set ang proxy"
}
},
"toggle-dev-tools": "I-toggle ang DevTools"
}
},
"always-on-top": "Laging nasa ibabaw",
"auto-update": "Awto Update",
"hide-menu": {
"dialog": {
"message": "Ang menu ay itatago sa susunod na pag-launch, gamitin ang [Alt] upang ipakita ito (o backtick [`] kung gumagamit ng in-app-menu)",
"title": "Pinagana ang Pagtatago ng Menu"
},
"label": "Pagtatago ng Menu"
},
"language": {
"dialog": {
"message": "Ang wika ay mababago pagkatapos mag-restart",
"title": "Napalitan ang Wika"
},
"label": "Wika",
"submenu": {
"to-help-translate": "Gusto mong tumulong sa pagsasalin? Mag-click dito"
}
},
"resume-on-start": "Ipagpatuloy ang huling kanta kapag nagsisimula ang app",
"single-instance-lock": "I-lock sa isang Instance",
"start-at-login": "Magsimula sa pag-login",
"starting-page": {
"label": "Simulang page",
"unset": "I-unset"
},
"tray": {
"label": "Tray",
"submenu": {
"disabled": "Di-napagana",
"enabled-and-hide-app": "Napagana at natago ang app",
"enabled-and-show-app": "Napagana at napakita ang app",
"play-pause-on-click": "Mag play/pause kada click"
}
},
"visual-tweaks": {
"label": "Mga Biswal na Tweak",
"submenu": {
"custom-window-title": {
"label": "Custom na window title",
"prompt": {
"label": "I-enter ang custom na window tile: (iwanang blanko para di-mapagana)",
"placeholder": "Halimbawa: {{applicationName}}"
}
},
"like-buttons": {
"default": "Default",
"force-show": "Pilitang ipakita",
"hide": "Itago",
"label": "Mga Like na button"
},
"remove-upgrade-button": "Tanggalin ang upgrade na button",
"theme": {
"dialog": {
"button": {
"cancel": "Kanselahin",
"remove": "Tanggalin"
},
"remove-theme": "Sigurado ka bang gusto mong alisin ang custom na tema?",
"remove-theme-message": "Aalisin nito ang custom na tema"
},
"label": "Tema",
"submenu": {
"import-css-file": "Mag-import ng custom na CSS file",
"no-theme": "Walang tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Napagana",
"label": "Mga Plugin",
"new": "BAGO"
},
"view": {
"label": "View",
"submenu": {
"force-reload": "Pilitang I-reload",
"reload": "I-reload",
"reset-zoom": "Aktuwal na Size",
"toggle-fullscreen": "I-toggle ang Full Screen",
"zoom-in": "Mag-zoom in",
"zoom-out": "Mag-zoom out"
}
}
},
"tray": {
"next": "Susunod",
"play-pause": "Mag-play/Mag-pause",
"previous": "Nakaraan",
"quit": "Lumabas",
"restart": "I-restart ang App",
"show": "Ipakita ang window",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Pag mag-play ng ad, I-mute ang audio at i-set ang bilis ng playback ng 16x",
"name": "Pagbilis ng Ad"
},
"adblocker": {
"description": "I-block ang lahat ng ad at tracking",
"menu": {
"blocker": "Blocker"
},
"name": "Pag-block ng Ad"
},
"album-actions": {
"description": "Idadagdag ang Undislike, Dislike, Like, at Unlike na button para ilapat ito sa lahat ng kanta sa isang playlist o album",
"name": "Mga aksyon sa Album"
},
"album-color-theme": {
"description": "Naglalapat ng dynamic na tema at visual effect batay sa color palette ng album",
"menu": {
"color-mix-ratio": {
"label": "Ratio ng paghahalo ng kulay",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Tema ng Kulay ng Album"
},
"ambient-mode": {
"description": "Naglalapat ng lighting effect sa pamamagitan ng pag-cast ng mga magiliw na kulay mula sa video, sa background ng iyong screen",
"menu": {
"blur-amount": {
"label": "Dami ng blur",
"submenu": {
"pixels": "{{blurAmount}} na pixel"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Kalabuan (Opacity)",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kalidad",
"submenu": {
"pixels": "{{quality}} na pixel"
}
},
"size": {
"label": "Laki",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Ayos ng Transisyon",
"submenu": {
"during": "Habang {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Gumamit ng fullscreen"
}
},
"name": "Ambient Mode"
},
"amuse": {
"description": "Nagdaragdag ng suporta sa {{applicationName}} para sa Amuse now playing widget ng 6K Labs",
"response": {
"query": "Tumatakbo ang Amuse API server. Gamitin ang GET /query para makuha ang impo ng kanta."
}
},
"api-server": {
"description": "Nagdadagdag ng API Server upang kontrolin ang player",
"dialog": {
"request": {
"buttons": {
"allow": "Payagan",
"deny": "Tanggihan"
},
"message": "Payagan ang {{ID}} ({{origin}}) upang ma-access ang API?",
"title": "Awtorisasyon ng API request"
}
},
"menu": {
"auth-strategy": {
"label": "Estratehiya ng awtorisasyon",
"submenu": {
"auth-at-first": {
"label": "Mag-autorisa sa unang request"
},
"none": {
"label": "Walang awtorisasyon"
}
}
},
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Itala ang hostname (tulad ng 0.0.0.0) para sa API server:",
"title": "Hostname"
},
"port": {
"label": "Itala ang port para sa API server:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Ilapat ang compression sa audio (pinababa ang volume ng pinakamalakas na bahagi ng signal at pinapataas ang volume ng pinakamalambot na bahagi)",
"name": "Compressor ng Audio"
},
"auth-proxy-adapter": {
"description": "Suporta para sa paggamit ng authentication proxy services",
"menu": {
"disable": "Huwag paganahin ang Proxy Adapter",
"enable": "Paganahin ang Proxy Adapter",
"port": {
"label": "Port"
}
},
"name": "Auth Proxy Adapter",
"prompt": {
"hostname": {
"label": "Ilagay ang pangalan ng host para sa local proxy server (kinailangang mag-restart):",
"title": "Hostname ng Proxy"
},
"port": {
"label": "Ilagay ang port para sa local proxy server (kinailangang mag-restart):",
"title": "Port ng Proxy"
}
}
},
"blur-nav-bar": {
"description": "Gawing transparent at malabo ang bar ng nabigasyon",
"name": "Palabuin ang Bar ng Nabigasyon"
},
"bypass-age-restrictions": {
"description": "I-bypass ang pag-verify ng edad ng Music Player",
"name": "I-bypass ang Restriksyon sa Edad"
},
"captions-selector": {
"description": "Tagapili ng caption para sa mga audio track ng {{applicationName}}",
"menu": {
"autoload": "Awtomatikong piliin ang huling ginamit na caption",
"disable-captions": "Walang mga caption bilang default"
},
"name": "Tagapili ng Caption",
"prompt": {
"selector": {
"label": "Kasalukuyang wika ng caption:{{language}}",
"none": "Wala",
"title": "Pumili ng wika ng caption"
}
},
"templates": {
"title": "Bumukas ng pagpilian ng caption"
},
"toast": {
"caption-changed": "Binago ang caption sa {{language}}",
"caption-disabled": "Di-napagana ang mga caption",
"no-captions": "Walang captions ay available para sa kantang ito"
}
},
"compact-sidebar": {
"description": "Laging i-set ang sidebar sa compact mode",
"name": "Pinaliit na Sidebar"
},
"crossfade": {
"description": "I-crossfade kada kanta",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Tagal ng pag-fade in (ms)",
"fade-out-duration": "Tagal ng pag-fade out (ms)",
"fade-scaling": {
"label": "Scaling ng pag-fade"
},
"seconds-before-end": "I-crossfade sa loob ng N segundo bago ang katapusan"
},
"title": "Pagpipilian sa crossfade"
}
}
},
"custom-output-device": {
"description": "I-configure ang custom na output media device para sa mga kanta",
"menu": {
"device-selector": "Pumili ng Device"
},
"name": "Custom na Output Device",
"prompt": {
"device-selector": {
"label": "Pumili ng output media device na gagamitin",
"title": "Pumili ng Output Device"
}
}
},
"disable-autoplay": {
"description": "Gawing simulan ang kanta sa \"naka-pause\" na mode",
"menu": {
"apply-once": "Nalalapat lamang sa startup"
},
"name": "Di-paganahin ang Autoplay"
},
"discord": {
"backend": {
"already-connected": "Sinubukang kumonekta sa aktibong koneksyon",
"connected": "Nakakonekta sa Discord",
"disconnected": "Nadiskonekta sa Discord"
},
"description": "Ipakita sa iyong mga kaibigan kung ano ang pinapakinggan mo gamit ang Rich Presence",
"menu": {
"auto-reconnect": "Awtomatikong kumonekta muli",
"clear-activity": "I-clear ang aktibidad",
"clear-activity-after-timeout": "I-clear ang aktibidad pagkatapos ng timeout",
"connected": "Nakakonekta",
"disconnected": "Nadiskonekta",
"hide-duration-left": "Itago ang natitirang oras",
"hide-github-button": "Itago ang button na GitHub link",
"play-on-application": "Patugtugin sa {{applicationName}}",
"set-inactivity-timeout": "I-set ang inactivity timeout",
"set-status-display-type": {
"submenu": {
"artist": "Nakikinig sa {artist}",
"title": "Nakikinig sa {song title}",
"application": "Kumikinig sa {{applicationName}}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Ilagay ang inactivity timeout sa ilang segundo:",
"title": "I-set ang inactivity timeout"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Kainis! Paumanhin, nabigo ang pag-download…",
"title": "Nagkaroon ng error sa pag-download!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} na mga kanta)",
"message": "Dina-download ang Playlist na {{playlistTitle}}",
"title": "Nasimulan na ang pag-download"
}
},
"feedback": {
"conversion-progress": "Pag-convert: {{percent}}%",
"converting": "Kino-convert…",
"done": "Natapos na: {{filePath}}",
"download-info": "Dina-download ang {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Dina-download: {{percent}}%",
"downloading": "Dina-download…",
"downloading-counter": "Dina-download {{current}}/{{total}}…",
"downloading-playlist": "Dina-download ang playlist \"{{playlistTitle}}\" - {{playlistSize}} na mga kanta ({{playlistId}})",
"error-while-downloading": "Error sa pag-download \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Ang folder na {{playlistFolder}} ay umiiral na",
"getting-playlist-info": "Kinukuha ang impo ng playlist…",
"loading": "Naglo-load…",
"playlist-has-only-one-song": "May isang aytem lang ang playlist, direktang dina-download na",
"playlist-id-not-found": "Walang playlist ID na nahanap",
"playlist-is-empty": "Walang laman ang playlist",
"playlist-is-mix-or-private": "Error sa pagkuha ng impo ng playlist: tiyaking hindi ito pribado o \"Mixed para sa iyo\" na playlist\n\n{{error}}",
"preparing-file": "Inihahanda ang file…",
"saving": "Sine-save…",
"trying-to-get-playlist-id": "Sinusubukang makuha ang playlist ID: {{playlistId}}",
"video-id-not-found": "Hindi nahanap ang video",
"writing-id3": "Sinusulat ang mga ID3 na tag…"
}
},
"description": "Dina-download ang mga MP3 / source audio direkta mula sa interface",
"menu": {
"choose-download-folder": "Pumili ng download folder",
"download-finish-settings": {
"label": "Kung natapos ang download",
"prompt": {
"last-percent": "Tapos ng x na porsyento",
"last-seconds": "Huling x na segundo",
"title": "I-configure kung kailan magda-download"
},
"submenu": {
"enabled": "Napagana na",
"mode": "Sukatan ng oras",
"percent": "Porsyento",
"seconds": "Segundo"
}
},
"download-playlist": "Dina-download ang playlist",
"presets": "Mga preset",
"skip-existing": "Laktawan ang mga kasalukuyang file"
},
"name": "Taga-download",
"renderer": {
"can-not-update-progress": "Hindi ma-update ang progress"
},
"templates": {
"button": "I-download"
}
},
"equalizer": {
"description": "Nagdaragdag ng equalizer sa player",
"menu": {
"presets": {
"label": "Mga Preset",
"list": {
"bass-booster": "Taga-boost ng Bass"
}
}
},
"name": "Equalizer"
},
"exponential-volume": {
"description": "Ginagawang exponential ang volume slider para mas madaling pumili ng mas mababang volume.",
"name": "Exponential na Volume"
},
"in-app-menu": {
"description": "Nagbibigay sa mga menu-bar ng magarbo, madilim o kulay ng album",
"menu": {
"hide-dom-window-controls": "Itago ang mga DOM window control"
},
"name": "In-App na Menu"
},
"lumiastream": {
"description": "Nabibigay suporta sa Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Nagdaragdag ng suporta sa lyrics para sa karamihan ng kanta",
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Kinuha ang lyrics para sa Genius"
}
},
"music-together": {
"description": "Magbahagi ng playlist sa iba. Kapag nagpatugtog ang host ng isang kanta, maririnig ng lahat ang parehong kanta",
"dialog": {
"enter-host": "Ilagay ang Host ID"
},
"internal": {
"save": "I-save",
"track-source": "Source ng Track",
"unknown-user": "Di-kilalang User"
},
"menu": {
"click-to-copy-id": "Kopyahin ang Host ID",
"close": "Isara ang Music Together",
"connected-users": "Nakakonektang (mga) User",
"disconnect": "Mag-diskonekta sa Music Together",
"empty-user": "Walang naka-konektang user",
"host": "Host ng Music Together",
"join": "Sumali sa Music Together",
"permission": {
"all": "Payagan ang mga guest na kontrolin ang playlist at player",
"host-only": "Ang host lamang ang maka-kontrol ng playlist at player",
"playlist": "Payagan ang mga guest na kontrolin ang playlist"
},
"set-permission": "Palitan ng permiso ng pag-control",
"status": {
"disconnected": "Nadiskonekta",
"guest": "Nakakonekta bilang Guest",
"host": "Nakakonekta bilang Host"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Nabigong magdagdag ng kanta",
"closed": "Nakasara ang Music Together",
"disconnected": "Nakadiskonekta ang Music Together",
"host-failed": "Nabigong mag-host ng Music Together",
"id-copied": "Nakopya na ang Host ID sa clipboard",
"id-copy-failed": "Nabigong nakopya ang Host ID sa clipboard",
"join-failed": "Nabigong sumali sa Music Together",
"joined": "Nakasali sa Music Together",
"permission-changed": "Ang permiso ng Music Together ay nabago sa \"{{permission}}\"",
"remove-song-failed": "Nabigong natanggal ang kanta",
"user-connected": "{{name}} ay sumali sa Music Together",
"user-disconnected": "{{name}} ay umalis sa Music Together"
}
},
"navigation": {
"description": "Ang Next/Back navigation na arrow ay direktang magamit sa interface, katulad sa iyong paboritong browser",
"name": "Nabigasyon",
"templates": {
"back": {
"title": "Pumunta sa nakaraang page"
},
"forward": {
"title": "Pumunta sa susunod na page"
}
}
},
"no-google-login": {
"description": "Tanggalin ang mga Google login na button at mga link mula sa interface",
"name": "Walang Google na Login"
},
"notifications": {
"description": "Magpakita ng notification kapag nagsimulang tumugtog ang kanta (magagamit ang mga interactive na notification sa Windows)",
"menu": {
"interactive": "Interactive na Notification",
"interactive-settings": {
"label": "Mga Interactive na Setting",
"submenu": {
"hide-button-text": "Itago ang button na texto",
"refresh-on-play-pause": "I-refresh sa Pag-play/Pag-pause",
"tray-controls": "Buksan/Isara sa pag-click sa tray"
}
},
"priority": "Prioridad ng Notification",
"toast-style": "Estilo ng toast",
"unpause-notification": "Ipakita ang notification sa pag-unpause"
},
"name": "Mga Abiso"
},
"performance-improvement": {
"description": "Pagbutihin ang performance sa pamamagitan ng pagpapagana ng mga mapanganib na script",
"name": "Pagpapabuti ng performance [Beta]"
},
"picture-in-picture": {
"description": "Payagan ang pag-palit ng app sa picture-in-picture mode",
"menu": {
"always-on-top": "Laging sa itaas",
"hotkey": {
"prompt": {
"label": "Pumili ng hotkey sa pag-toggle ng picture-in-picture",
"title": "Hotkey ng Picture-in-picture"
}
},
"save-window-position": "I-save ang posisyon ng window",
"save-window-size": "I-save ang laki ng window",
"use-native-pip": "Gamitin ang browser native na PiP"
},
"name": "Picture-na-picture",
"templates": {
"button": "Picture-na-picture"
}
},
"playback-speed": {
"description": "Makinig na mabilisan, makinig na mabagalan! Nagdaragdag ito ng slider upang makontrol ang bilis ng kanta",
"name": "Bilis ng Playback",
"templates": {
"button": "Bilis"
}
},
"precise-volume": {
"description": "Kontrolin nang wasto ang volume gamit ang mousewheel/mga hotkey, na may custom HUD at customizable na volume step",
"menu": {
"arrows-shortcuts": "Lokal na Arrow-key na Kontrol",
"custom-volume-steps": "I-set ang custom na Volume Step",
"global-shortcuts": "Global na mga Hotkey"
},
"name": "Eksaktong Volume",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Bawasan ang Volume",
"increase": "Dagdagan ang Volume"
},
"label": "Pumili ng Keybind para sa Global Volume:"
},
"volume-steps": {
"label": "Pumili ng Dagdagan/Bawasan ang volume step"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Kasalukuyang Kalidad: {{quality}}",
"message": "Pumili ng Kalidad ng Video:",
"title": "Pumili ng Kalidad ng Video"
}
}
},
"description": "Payagang mapapalitan ang kalidad ng video na may button sa video overlay",
"name": "Taga-palit sa quality ng video",
"renderer": {
"quality-settings-button": {
"label": "Buksan ang taga-palit ng quality"
}
}
},
"scrobbler": {
"description": "Idagdag ang scrobbling support (last.fm, Listenbrains, atbp.)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Nabigong mag-authenticate sa Last.fm\nItago ang popup hanggang sa susunod na pag-restart.",
"title": "Nabigo ang Authentication"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Mga setting ng API para sa Last.fm"
},
"listenbrainz": {
"token": "Ilagay ang user token ng ListenBrainz"
},
"scrobble-alternative-artist": "Gumamit ng mga alternatibong artist",
"scrobble-alternative-title": "Gumamit ng alternatibong mga title",
"scrobble-other-media": "Mag-Scrobble ng ibang media"
},
"prompt": {
"lastfm": {
"api-key": "API key ng Last.fm",
"api-secret": "API secret ng Last.fm"
},
"listenbrainz": {
"token": {
"label": "Ilagay ang ListenBrainz user token:",
"title": "Token ng ListenBrainz"
}
}
}
},
"shortcuts": {
"description": "Nagbibigay-daan sa pagtatakda ng mga global hotkey para sa playback (play/pause/susunod/nakaraan) at pag-off ng media OSD sa pamamagitan ng pag-override sa mga media key, pag-on sa Ctrl/CMD + F para maghanap, pag-on sa suporta ng Linux MPRIS para sa mga media key, at mga custom na hotkey para sa mga advanced na user",
"menu": {
"override-media-keys": "I-override ang mga Media Key",
"set-keybinds": "I-set ang Global Song Control"
},
"name": "Mga shortcut (at MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Susunod",
"play-pause": "Mag-play / Mag-pause",
"previous": "Nakaraan"
},
"label": "Pumili ng Global na Keybind para sa Songs Control:",
"title": "Global na mga Keybind"
}
}
},
"skip-disliked-songs": {
"description": "Laktawan ang na-dislike na kanta",
"name": "I-skip ang mga Na-dislike na Kanta"
},
"skip-silences": {
"description": "Automatikong laktawan ang mga tahimik na mga seksyon sa kanta",
"name": "I-skip ang mga Katahimikan"
},
"sponsorblock": {
"description": "Automatikong Laktawan ang di part ng kanta tulad ng intro/outro o part ng mga music video na ang kanta ay di nagple-play"
},
"synced-lyrics": {
"description": "Nagbibigay ng naka-sync na lyrics sa mga kanta, gamit ang mga provider tulad ng LRClib.",
"errors": {
"fetch": "⚠️\t Nagkaroon ng error habang kinukuha ang lyrics.\n\t Subukang muli mamaya.",
"not-found": "⚠️ Walang nakitang lyrics para sa kantang ito."
},
"menu": {
"default-text-string": {
"label": "Default na character sa pagitan ng lyrics",
"tooltip": "Pumili ng default na character na gagamitin sa pagitan ng lyrics"
},
"line-effect": {
"label": "Effect ng Linya",
"submenu": {
"fancy": {
"label": "Magarbo",
"tooltip": "Gumamit ng malaki, mala-app na effect sa kasalukuyang linya"
},
"focus": {
"label": "Focus",
"tooltip": "Gawing puti lamang ang kasalukuyang linya"
},
"offset": {
"label": "Offset",
"tooltip": "I-offset sa kanan ang kasalukuyang linya"
},
"scale": {
"label": "Scale",
"tooltip": "I-scale ang kasalukuyang linya"
}
},
"tooltip": "Pumili ng effect na ilalapat sa kasalukuyang linya"
},
"precise-timing": {
"label": "Gawing perpektong naka-sync ang lyrics",
"tooltip": "Kalkulahin sa millisecond ang pagpapakita ng susunod na linya (maaaring magkaroon ng maliit na epekto sa performance)"
},
"preferred-provider": {
"label": "Napiling Provider",
"none": {
"label": "Wala",
"tooltip": "Walang napiling provider"
},
"tooltip": "Pumili ng default na provider para gagamitin"
},
"romanization": {
"label": "I-romanize ang lyrics",
"tooltip": "Kung ang lyrics ay nasa ibang wika, subukang magpakita ng latin na bersyon."
},
"show-lyrics-even-if-inexact": {
"label": "Ipakita ang lyrics kahit di-eksakto",
"tooltip": "Kung hindi matagpuan ang kanta, susubukan muli ng plugin gamit ang ibang query sa paghahanap.\nAng resulta mula sa pangalawang pagsubok ay maaaring hindi eksakto."
},
"show-time-codes": {
"label": "Ipakita ang mga time code",
"tooltip": "Ipakita ang mga time code kasunod sa lyrics"
}
},
"name": "Pag-sync ng Lyrics",
"refetch-btn": {
"fetching": "Nag-fe-fetch...",
"normal": "I-fetch muli ang lyrics"
},
"warnings": {
"duration-mismatch": "⚠️ - Maaaring hindi naka-sync ang lyrics dahil sa hindi pagkakatugma ng duration.",
"inexact": "⚠️ - Maaaring hindi eksakto ang lyrics para sa kantang ito",
"instrumental": "⚠️ - Ito ay isang instrumental na kanta"
}
},
"taskbar-mediacontrol": {
"description": "Kontrolin ang pag-play mula sa iyong taskbar ng Windows"
},
"touchbar": {
"description": "Idaragdag ang TouchBar na widget para sa mga user ng macOS"
},
"transparent-player": {
"description": "Gawing transparent ang app window",
"menu": {
"type": {
"label": "Uri",
"submenu": {
"none": "Wala",
"tabbed": "Naka-tab"
}
}
},
"name": "Transparent na Player"
},
"tuna-obs": {
"description": "Integrasyon kasama ang Tuna na OBS plugin"
},
"unobtrusive-player": {
"description": "Pinipigilan ang player na mag-pop up kapag nagpe-play ng kanta",
"name": "Hindi mapanghimasok na Player"
},
"video-toggle": {
"description": "Idaragdag ng button na magpalit sa Video/Kanta na mode. maaari ding opsyonal na alisin ang tab ng video",
"menu": {
"align": {
"label": "Pag-align",
"submenu": {
"left": "Kaliwa",
"middle": "Gitna",
"right": "Kanan"
}
},
"force-hide": "Piliting tanggalin ang video tab",
"mode": {
"submenu": {
"disabled": "Di-napagana"
}
}
},
"name": "Pag-toggle ng Video",
"templates": {
"button-song": "Kanta"
}
},
"visualizer": {
"description": "Idaragdag ng visualizer sa player",
"menu": {
"visualizer-type": "Uri ng Visualizer"
},
"name": "Taga-visualize"
}
}
}
================================================
FILE: src/i18n/resources/fr.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Échec de l'exécution du plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} exécuté en {{ms}}ms",
"initialize-failed": "Échec de l'initialisation du plugin \"{{pluginName}}\"",
"load-all": "Chargement de tous les plugins",
"load-failed": "Échec du chargement du plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" chargé",
"unload-failed": "Échec du déchargement du plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" déchargé"
}
}
},
"language": {
"code": "fr",
"local-name": "Français",
"name": "French"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Chargement terminé. DevTools ouvert"
},
"i18n": {
"loaded": "i18n chargé"
},
"second-instance": {
"receive-command": "Received command over protocol : \"{{command}}\""
},
"theme": {
"css-file-not-found": "Le fichier de CSS \"{{cssFile}}\" n'existe pas, ignorer"
},
"unresponsive": {
"details": "Erreur : Aucune réponse!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Effacement du cache de l'application"
},
"window": {
"tried-to-render-offscreen": "La fenêtre a essayé d'effectuer un rendu hors écran, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Le menu est masqué, utilisez 'Alt' pour l'afficher (ou 'Échap' si vous utilisez le menu de l'application)",
"message": "Le masquage du menu est activé",
"title": "Masquer le menu activé"
},
"need-to-restart": {
"buttons": {
"later": "Plus tard",
"restart-now": "Redémarrer maintenant"
},
"detail": "\"{{pluginName}}\" plugin nécessite un redémarrage pour qu'il soit pris en compte",
"message": "\"{{pluginName}}\" a besoin d'un redémarrage",
"title": "Redémarrage requis"
},
"unresponsive": {
"buttons": {
"quit": "Quitter",
"relaunch": "Relancer",
"wait": "Attendre"
},
"detail": "Nous sommes désolés du dérangement ! veuillez choisir quoi faire :",
"message": "L'application ne répond pas",
"title": "La fenêtre ne répond pas"
},
"update-available": {
"buttons": {
"disable": "Désactiver les mises à jour",
"download": "Télécharger",
"ok": "Ok"
},
"detail": "Une nouvelle version est disponible et peut-être télécharger sur {{downloadLink}}",
"message": "Une nouvelle version est disponible",
"title": "Mise à jour disponible"
}
},
"menu": {
"about": "À propos",
"navigation": {
"label": "Navigation",
"submenu": {
"copy-current-url": "Copier l'URL actuelle",
"go-back": "Retour",
"go-forward": "Avancer",
"quit": "Quitter",
"restart": "Redémarrer l'application"
}
},
"options": {
"label": "Paramètres",
"submenu": {
"advanced-options": {
"label": "Options avancées",
"submenu": {
"auto-reset-app-cache": "Réinitialiser le cache de l'application au démarrage",
"disable-hardware-acceleration": "Désactiver les accélérations matérielles",
"edit-config-json": "Modifier config.json",
"override-user-agent": "Remplacer le User-Agent",
"restart-on-config-changes": "Redémarrer quand la configuration change",
"set-proxy": {
"label": "Définir un proxy",
"prompt": {
"label": "Entrez l'adresse proxy : (laissez vide pour désactiver)",
"placeholder": "Exemple : SOCKS5://127.0.0.1:9999",
"title": "Définir un proxy"
}
},
"toggle-dev-tools": "Ouvrir/fermer les outils de développement"
}
},
"always-on-top": "Toujours au dessus",
"auto-update": "Mise à jour automatique",
"hide-menu": {
"dialog": {
"message": "Le menu sera masqué au prochain lancement, utilisez [Alt] pour l'afficher (ou backtick [`] si vous utilisez le menu intégré à l'application)",
"title": "Masquer le menu activé"
},
"label": "Cacher le menu"
},
"language": {
"dialog": {
"message": "La langue sera changée après le redémarrage",
"title": "Langue modifiée"
},
"label": "Langue",
"submenu": {
"to-help-translate": "Envie d'aider à la traduction ? Cliquer ici"
}
},
"resume-on-start": "Reprendre la dernière chanson quand l'application démarre",
"single-instance-lock": "Verrouillage d'instance unique",
"start-at-login": "Démarrer à la connexion",
"starting-page": {
"label": "Page de démarrage",
"unset": "Définir à vide"
},
"tray": {
"label": "Plateau",
"submenu": {
"disabled": "Désactivé",
"enabled-and-hide-app": "Activé et cacher l'app",
"enabled-and-show-app": "Activé et afficher l'application",
"play-pause-on-click": "Lecture/Pause au clic"
}
},
"visual-tweaks": {
"label": "Ajustements visuels",
"submenu": {
"custom-window-title": {
"label": "Titre de fenêtre personnalisé",
"prompt": {
"label": "Entrer un titre de fenêtre : (Laissé vide pour désactiver)",
"placeholder": "Exemple : {{applicationName}}"
}
},
"like-buttons": {
"default": "Par défaut",
"force-show": "Forcer à apparaître",
"hide": "Cacher",
"label": "Boutons « J'aime »",
"swap": "Inverser l'order des boutons like"
},
"remove-upgrade-button": "Supprimer le bouton de mise à niveau",
"theme": {
"dialog": {
"button": {
"cancel": "Annuler",
"remove": "Supprimer"
},
"remove-theme": "Êtes-vous sûr de supprimer le thème personnalisé ?",
"remove-theme-message": "Cela va supprimer le thème personnalisé"
},
"label": "Thème",
"submenu": {
"import-css-file": "Importer fichier CSS personnalisé",
"no-theme": "Pas de thème"
}
}
}
}
}
},
"plugins": {
"enabled": "Activé",
"label": "Extensions",
"new": "NOUVEAU"
},
"view": {
"label": "Fenêtre",
"submenu": {
"force-reload": "Forcer l'actualisation",
"reload": "Actualiser",
"reset-zoom": "Taille réelle",
"toggle-fullscreen": "Basculer en plein écran",
"zoom-in": "Zoom avant",
"zoom-out": "Zoom arrière"
}
}
},
"tray": {
"next": "Suivant",
"play-pause": "Lecture/Pause",
"previous": "Précédent",
"quit": "Quitter",
"restart": "Redémarrer l'application",
"show": "Afficher la fenêtre",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}} : {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Si une publicité apparaît, le son est coupé et la vitesse de lecture est réglée sur 16x",
"name": "Accélérer les publicités"
},
"adblocker": {
"description": "Bloquer toutes les annonces et le suivi par défaut",
"menu": {
"blocker": "Bloqueur"
},
"name": "Bloqueur de publicités"
},
"album-actions": {
"description": "Ajoute les boutons Dislike, Undislike, Like, et Unlike à appliquer sur toutes les chansons dans un playlist ou un album.",
"name": "Actions d'Album"
},
"album-color-theme": {
"description": "Applique un thème dynamique et des effets visuels basés sur la palette des couleurs de l'album",
"menu": {
"color-mix-ratio": {
"label": "Ratio de mélange des couleurs",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Activer le thème sur la barre de progression"
},
"name": "Thème de couleur d'album"
},
"ambient-mode": {
"description": "Applique un effet d'éclairage en jetant des couleurs douces de la vidéo, dans le fond de votre écran",
"menu": {
"blur-amount": {
"label": "Quantité de flou",
"submenu": {
"pixels": "{{blurAmount}} pixels"
}
},
"buffer": {
"label": "Tampon",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacité",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualité",
"submenu": {
"pixels": "{{quality}} pixels"
}
},
"size": {
"label": "Taille",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transition en douceur",
"submenu": {
"during": "Pendant {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Utilisation du mode plein écran"
}
},
"name": "Mode ambiant"
},
"amuse": {
"description": "Ajout de la prise en charge de {{applicationName}} pour le widget Amuse now playing de 6K Labs",
"name": "Amuse",
"response": {
"query": "Le serveur API d'Amuse est en cours d'exécution. GET /query pour obtenir des informations sur les chansons."
}
},
"api-server": {
"description": "Ajouter un serveur API pour contrôler le lecteur",
"dialog": {
"request": {
"buttons": {
"allow": "Autoriser",
"deny": "Interdire"
},
"message": "Autoriser {{ID}} ({{origin}}) à accéder à l'API ?",
"title": "Demande d'autorisation à l'API"
}
},
"menu": {
"auth-strategy": {
"label": "Plan d'autorisation",
"submenu": {
"auth-at-first": {
"label": "Autoriser lors de la première requête"
},
"none": {
"label": "Pas d'autorisation"
}
}
},
"hostname": {
"label": "Nom de l'hôte"
},
"https": {
"label": "HTTPS & Certificats",
"submenu": {
"cert": {
"dialogTitle": "Sélectionner le fichier de certificat HTTPS",
"label": "Fichier de certificat (.crt/.pem)"
},
"enable-https": {
"label": "Activer HTTPS"
},
"key": {
"dialogTitle": "Sélectionner le fichier de clé privée HTTPS",
"label": "Fichier de clé privée (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "Serveur API [Beta]",
"prompt": {
"hostname": {
"label": "Entrer le nom de l'hôte (par exemple : 0.0.0.0) pour le serveur API :",
"title": "Nom d'hôte"
},
"port": {
"label": "Entrez le port du serveur API :",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Appliquer une compression à l'audio (diminue le volume des parties les plus fortes du signal et augmente le volume des parties les plus faibles)",
"name": "Compresseur audio"
},
"auth-proxy-adapter": {
"description": "Prise en charge de l'utilisation des services de proxy d'authentification",
"menu": {
"disable": "Désactiver l'Adaptateur Proxy",
"enable": "Activer l'Adaptateur Proxy",
"hostname": {
"label": "Nom d'hôte"
},
"port": {
"label": "Port"
}
},
"name": "Adaptateur de Proxy d'Authentification",
"prompt": {
"hostname": {
"label": "Saisir le nom d'hôte pour le serveur proxy local (nécessite un redémarrage) :",
"title": "Nom d'hôte du proxy"
},
"port": {
"label": "Saisir le port pour le serveur proxy local (nécessite un redémarrage) :",
"title": "Port du proxy"
}
}
},
"blur-nav-bar": {
"description": "Rend la barre de navigation transparente et floue",
"name": "Barre de navigation floue"
},
"bypass-age-restrictions": {
"description": "Contourner la vérification de l'âge de Music Player",
"name": "Contourner les restrictions d’âge"
},
"captions-selector": {
"description": "Sélecteur de sous-titres pour les pistes audio {{applicationName}}",
"menu": {
"autoload": "Sélectionner automatiquement la dernière légende utilisée",
"disable-captions": "Pas de sous-titres par défaut"
},
"name": "Sélecteur de sous-titres",
"prompt": {
"selector": {
"label": "Langue de sous-titrage actuelle : {{language}}",
"none": "Aucun",
"title": "Sélectionnez la langue des sous-titres"
}
},
"templates": {
"title": "Ouvrir le sélecteur de sous-titres"
},
"toast": {
"caption-changed": "Sous-titres changés en {{language}}",
"caption-disabled": "Sous-titres désactivés",
"no-captions": "Aucun sous-titres disponibles pour cette chanson"
}
},
"clock": {
"description": "Ajoute une horloge a la barre de navigation",
"menu": {
"format": {
"24-hour-format": "Format 24 heures",
"display-seconds": "Afficher les secondes",
"label": "Format"
}
},
"name": "Horloge"
},
"compact-sidebar": {
"description": "Toujours définir la barre latérale en mode compact",
"name": "Barre latérale compacte"
},
"crossfade": {
"description": "Fondu enchaîné entre les chansons",
"menu": {
"advanced": "Avancé"
},
"name": "Fondu enchaîné [Bêta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Durée du 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",
"logarithmic": "Logarithmique"
},
"seconds-before-end": "Fondu enchaîné N secondes avant la fin"
},
"title": "Options de fondu enchaîné"
}
}
},
"custom-output-device": {
"description": "Configurer un périphérique de sortie personnalisé pour les morceaux",
"menu": {
"device-selector": "Sélectionner un appareil"
},
"name": "Périphérique de sortie personnalisé",
"prompt": {
"device-selector": {
"label": "Choisissez le périphérique de sortie à utiliser",
"title": "Sélectionner le périphérique de sortie"
}
}
},
"disable-autoplay": {
"description": "Fait démarrer la chanson en mode \"pause\"",
"menu": {
"apply-once": "S'applique seulement au démarrage"
},
"name": "Désactiver la lecture automatique"
},
"discord": {
"backend": {
"already-connected": "Tentative de connexion avec une connexion active",
"connected": "Connecté à Discord",
"disconnected": "Déconnecté de Discord"
},
"description": "Montrez à vos amis ce que vous écoutez avec Rich Presence",
"menu": {
"auto-reconnect": "Reconnexion automatique",
"clear-activity": "Effacer l'activité",
"clear-activity-after-timeout": "Effacer l’activité après un délai d’attente",
"connected": "Connecté",
"disconnected": "Déconnecté",
"hide-duration-left": "Masquer la durée restante",
"hide-github-button": "Masquer le bouton du lien GitHub",
"play-on-application": "Jouer sur {{applicationName}}",
"set-inactivity-timeout": "Définir le délai d'inactivité",
"set-status-display-type": {
"label": "Texte d'état",
"submenu": {
"application": "Écoute {{applicationName}}",
"artist": "Écoute {artiste}",
"title": "Écoute {titre de la chanson}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Entrez le délai d'inactivité en secondes :",
"title": "Définir le délai d'inactivité"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Ok"
},
"message": "Argh ! Désolé, le téléchargement a échoué…",
"title": "Erreur de téléchargement !"
},
"start-download-playlist": {
"buttons": {
"ok": "Ok"
},
"detail": "({{playlistSize}} chansons)",
"message": "Téléchargement de la playlist {{playlistTitle}}",
"title": "Téléchargement commencé"
}
},
"feedback": {
"conversion-progress": "Conversion : {{percent}}%",
"converting": "Conversion…",
"done": "Terminé : {{filePath}}",
"download-info": "Téléchargement {{artist}} - {{title}} [{{videoId}}",
"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}})",
"error-while-downloading": "Erreur lors du téléchargement de \"{{author}} - {{title}}\" : {{error}}",
"folder-already-exists": "Le dossier {{playlistFolder}} existe déjà",
"getting-playlist-info": "Obtention des données de la playlist…",
"loading": "Chargement…",
"playlist-has-only-one-song": "La playlist ne contient qu'un seul élément, téléchargement du morceau seul",
"playlist-id-not-found": "Aucun ID de playlist trouvé",
"playlist-is-empty": "La playlist est vide",
"playlist-is-mix-or-private": "Erreur lors de l'obtention des informations sur la playlist : assurez-vous qu'il ne s'agit pas d'une playlist privée ou \"Mixée pour vous\"\n\n{{error}}",
"preparing-file": "Préparation des fichier…",
"saving": "Sauvegarde…",
"trying-to-get-playlist-id": "Obtention de l'ID de la playlist : {{playlistId}}",
"video-id-not-found": "Vidéo introuvable",
"writing-id3": "Écriture des balises ID3…"
}
},
"description": "Télécharge les fichiers MP3/source audio directement depuis l'interface",
"menu": {
"choose-download-folder": "Choisissez le dossier de téléchargement",
"download-finish-settings": {
"label": "Télécharger une fois terminé",
"prompt": {
"last-percent": "Après x pourcents",
"last-seconds": "Dernières x secondes",
"title": "Configurer quand télécharger"
},
"submenu": {
"advanced": "Avancé",
"enabled": "Activé",
"mode": "Unité de temps",
"percent": "Pourcent",
"seconds": "Secondes"
}
},
"download-playlist": "Télécharger la playlist",
"presets": "Préconfigurations",
"skip-existing": "Passer les fichiers existants"
},
"name": "Téléchargeur",
"renderer": {
"can-not-update-progress": "Impossible de mettre à jour la progression"
},
"templates": {
"button": "Télécharger"
}
},
"equalizer": {
"description": "Ajoute un égaliseur au lecteur",
"menu": {
"presets": {
"label": "Préréglages",
"list": {
"bass-booster": "Amplificateur de basses"
}
}
},
"name": "Égaliseur"
},
"exponential-volume": {
"description": "Rend le curseur de volume exponentiel afin qu'il soit plus facile de sélectionner des volumes plus faibles.",
"name": "Volume exponentiel"
},
"in-app-menu": {
"description": "Donne aux barres de menus un aspect élégant, sombre ou aux couleurs de l'album",
"menu": {
"hide-dom-window-controls": "Masquer les contrôles de la fenêtre DOM"
},
"name": "Menu intégré à l'application"
},
"lumiastream": {
"description": "Ajoute la prise en charge de Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Ajoute la prise en charge des paroles pour la plupart des chansons",
"menu": {
"romanized-lyrics": "Paroles romanisées"
},
"name": "Paroles Genius",
"renderer": {
"fetched-lyrics": "Paroles récupérées pour Genius"
}
},
"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 playlist et le lecteur",
"host-only": "Seulement l'hôte peut contrôler la playlist et le lecteur",
"playlist": "Autoriser les invités à contrôler la playlist"
},
"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": "Music Together rejoint",
"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",
"templates": {
"back": {
"title": "Aller à la page précédente"
},
"forward": {
"title": "Aller à la page suivante"
}
}
},
"no-google-login": {
"description": "Supprimer les boutons et liens de connexion Google de l'interface",
"name": "Pas de connexion Google"
},
"notifications": {
"description": "Afficher une notification quand une chanson commence à jouer (les notifications interactives sont disponibles sur Windows)",
"menu": {
"interactive": "Notifications interactives",
"interactive-settings": {
"label": "Paramètres interactifs",
"submenu": {
"hide-button-text": "Masquer le texte du bouton",
"refresh-on-play-pause": "Actualiser lors de la lecture/pause",
"tray-controls": "Ouvrir/Fermer au clic sur l’icône de la barre des tâches"
}
},
"priority": "Priorité des notifications",
"toast-style": "Style des notifications \"Toast\"",
"unpause-notification": "Afficher la notification lors de la reprise"
},
"name": "Notifications"
},
"performance-improvement": {
"description": "Améliorer les performances en activant les scripts expérimentaux",
"name": "Amélioration des performances [Beta]"
},
"picture-in-picture": {
"description": "Permet de basculer l’application en mode picture-in-picture",
"menu": {
"always-on-top": "Toujours au dessus",
"hotkey": {
"label": "Raccourci clavier",
"prompt": {
"keybind-options": {
"hotkey": "Raccourci clavier"
},
"label": "Choisissez un raccourci clavier pour activer le mode picture-in-picture",
"title": "Touche de raccourci picture-in-picture"
}
},
"save-window-position": "Enregistrer la position de la fenêtre",
"save-window-size": "Enregistrer la taille de la fenêtre",
"use-native-pip": "Utiliser le mode PiP natif du navigateur"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Écoutez vite, écoutez lentement ! Ajoute un curseur qui contrôle la vitesse de la chanson",
"name": "Vitesse de lecture",
"templates": {
"button": "Vitesse"
}
},
"precise-volume": {
"description": "Contrôlez le volume avec précision à l'aide de la molette de la souris/des raccourcis clavier, avec une interface personnalisée et des étapes de volume personnalisables",
"menu": {
"arrows-shortcuts": "Contrôles avec les touches fléchées",
"custom-volume-steps": "Définir des étapes de volume personnalisées",
"global-shortcuts": "Raccourcis clavier globaux"
},
"name": "Volume précis",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Diminuer le volume",
"increase": "Augmenter le volume"
},
"label": "Choisissez les raccourcis clavier du volume global :",
"title": "Raccourcis clavier de volume global"
},
"volume-steps": {
"label": "Choisissez les étapes d'augmentation/diminution du volume",
"title": "Étapes de volume"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualité actuelle : {{quality}}",
"message": "Choisissez la qualité vidéo :",
"title": "Choisissez la qualité vidéo"
}
}
},
"description": "Permet de changer la qualité vidéo avec un bouton sur l'overlay vidéo",
"name": "Sélecteur de qualité vidéo",
"renderer": {
"quality-settings-button": {
"label": "Ouvrir le sélecteur de qualité du lecteur"
}
}
},
"scrobbler": {
"description": "Ajouter le support de scrobbling (ex. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Erreur lors de l'authetification avec Last.fm\nCacher 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-alternative-artist": "Utilisez d'autres artistes",
"scrobble-alternative-title": "Utiliser des titres alternatifs",
"scrobble-other-media": "Scrobbler d'autres médias"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Clé API de Last.fm",
"api-secret": "Secret de l'API 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.",
"menu": {
"override-media-keys": "Remplacer les touches multimédias",
"set-keybinds": "Définir les contrôles globaux des morceaux"
},
"name": "Raccourcis (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Suivant",
"play-pause": "Lecture / Pause",
"previous": "Précédent"
},
"label": "Choisissez les raccourcis clavier globaux pour le contrôle des morceaux :",
"title": "Raccourcis clavier globaux"
}
}
},
"skip-disliked-songs": {
"description": "Passe les titres \"Je n'aime pas\"",
"name": "Passer les titres \"Je n'aime pas\""
},
"skip-silences": {
"description": "Ignorer automatiquement les sections de silence dans les chansons",
"name": "Passer les silences"
},
"sponsorblock": {
"description": "Saute automatiquement les parties non musicales comme l'intro/outro ou les parties de clips vidéo où la chanson n'est pas lue",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Ajoute des paroles synchronisées aux chansons, grâce à LRClib par exemple.",
"errors": {
"fetch": "⚠️\tUne erreur s'est produite en allant chercher les paroles.\n\tMerci de réessayer plus tard.",
"not-found": "⚠️ Aucune paroles trouvées pour ce titre."
},
"menu": {
"convert-chinese-character": {
"label": "Convertir les caractères Chinois",
"submenu": {
"disabled": {
"label": "Désactivé",
"tooltip": "Désactiver la conversion des caractères Chinois"
},
"simplified-to-traditional": {
"label": "Simplifié a Traditionnel",
"tooltip": "Convertir le Chinois Simplifié au Chinois Traditionnel"
},
"traditional-to-simplified": {
"label": "Traditionnel a Simplifié",
"tooltip": "Convertir le Chinois Traditionnel au Chinois Simplifié"
}
},
"tooltip": "Convertir les caractères Chinois en Traditionnel ou Simplifié"
},
"default-text-string": {
"label": "Caractère par défaut entre les paroles",
"tooltip": "Choisi le caractère par défaut à utiliser pour les blancs entre les paroles"
},
"line-effect": {
"label": "Effet de ligne",
"submenu": {
"fancy": {
"label": "Raffiné",
"tooltip": "Utilise de grands effets de type application sur la ligne actuelle"
},
"focus": {
"label": "Focus",
"tooltip": "Rend blanche seulement la ligne actuelle"
},
"offset": {
"label": "Décalage",
"tooltip": "Décale sur la droite la ligne actuelle"
},
"scale": {
"label": "Grossissement",
"tooltip": "Agrandis la ligne actuelle"
}
},
"tooltip": "Choisi l'effet à appliquer sur la ligne actuelle"
},
"precise-timing": {
"label": "Rend les paroles parfaitement synchronisées",
"tooltip": "Calcul à la milliseconde près l'affichage de la ligne suivante (peut avoir un faible impact sur les performances)"
},
"preferred-provider": {
"label": "Fournisseur privilégié",
"none": {
"label": "Aucun",
"tooltip": "Aucun fournisseur privilégié"
},
"tooltip": "Choisissez le fournisseur à utiliser par défaut"
},
"romanization": {
"label": "Romaniser les paroles",
"tooltip": "Si les paroles sont dans une autre langue, essayez de les afficher dans une version latine."
},
"show-lyrics-even-if-inexact": {
"label": "Afficher les paroles même si inexactes",
"tooltip": "Si la musique n'est pas trouvé, le plugin essaye à nouveau avec une différence requête.\nLe résultat du deuxième essais peut ne pas être exacte."
},
"show-time-codes": {
"label": "Afficher les timecodes",
"tooltip": "Affiche le timecode à côté de chaque paroles"
}
},
"name": "Paroles Synchronisées",
"refetch-btn": {
"fetching": "Chargement...",
"normal": "Rafraîchir les paroles"
},
"warnings": {
"duration-mismatch": "⚠️ - Les paroles peuvent ne pas être synchronisées à cause d'une différence de durée.",
"inexact": "⚠️ - Les paroles de cette chanson peuvent ne pas être exactes",
"instrumental": "⚠️ - C'est un titre instrumental"
}
},
"taskbar-mediacontrol": {
"description": "Contrôlez la lecture depuis votre barre des tâches Windows",
"name": "Contrôle multimédia de la barre des tâches"
},
"touchbar": {
"description": "Ajoute un widget TouchBar pour les utilisateurs de macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Rend la fenêtre de l'application transparente",
"menu": {
"opacity": {
"label": "Opacité",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Type",
"submenu": {
"acrylic": "Acrylique",
"mica": "Mica",
"none": "Aucun",
"tabbed": "À onglets"
}
}
},
"name": "Lecteur transparent"
},
"tuna-obs": {
"description": "Intégration avec le plugin OBS Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Empêche le lecteur de s'afficher quand un titre est en cours de lecture",
"name": "Lecteur Non-Intrusif"
},
"video-toggle": {
"description": "Ajoute un bouton pour basculer entre le mode Vidéo/Chanson. peut également supprimer tout l'onglet vidéo",
"menu": {
"align": {
"label": "Alignement",
"submenu": {
"left": "Gauche",
"middle": "Milieu",
"right": "Droite"
}
},
"force-hide": "Forcer la suppression de l'onglet vidéo",
"mode": {
"label": "Mode",
"submenu": {
"custom": "Bascule personnalisée",
"disabled": "Désactivé",
"native": "Bascule native"
}
}
},
"name": "Bouton de bascule vidéo",
"templates": {
"button-song": "Musique",
"button-video": "Vidéo"
}
},
"visualizer": {
"description": "Ajoute un visualiseur au lecteur",
"menu": {
"visualizer-type": "Type de visualiseur"
},
"name": "Visualiseur"
}
}
}
================================================
FILE: src/i18n/resources/gl.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Error ao executar o plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "O plugin {{pluginName}}::{{contextName}} foi executado a {{ms}}milisegundos",
"initialize-failed": "Erro ao iniciar o plugin \"{{pluginName}}\"",
"load-all": "Cargando todos os plugins",
"load-failed": "Erro ao cargar o plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" cargado",
"unload-failed": "Erro descargando o plugin {{pluginName}}",
"unloaded": "Plugin {{pluginName}} decargado"
}
}
},
"language": {
"code": "gl",
"local-name": "Galego",
"name": "Galego"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Carga completada. DevTools aberto"
},
"i18n": {
"loaded": "i18n cargado"
},
"second-instance": {
"receive-command": "Recibido comando sobre protocolo \"{{command}}\""
},
"theme": {
"css-file-not-found": "O arquivo CSS \"{{cssFile}}\" non existe, ignorando"
},
"unresponsive": {
"details": "Error irresponsivo!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Limpando a caché da app"
},
"window": {
"tried-to-render-offscreen": "A ventana tentou de renderizarse fora da pantalla, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "O menú está agochado, use 'Alt' para mostralo (ou 'Escape' se usa o menú dentro da app)",
"message": "Esconder Menú está deshabilitado",
"title": "Esconder Menú Habilitado"
},
"need-to-restart": {
"buttons": {
"later": "Despois",
"restart-now": "Reiniciar Agora"
},
"detail": "O plugin \"{{pluginName}}\" precisa dun reinicio para tomar efecto",
"message": "\"{{pluginName}}\" precisa reiniciar",
"title": "Requírese reinicio"
},
"unresponsive": {
"buttons": {
"quit": "Saír",
"relaunch": "Lanzar de novo",
"wait": "Agardar"
},
"detail": "Desculpa o inconveniente! Por favor escolle que facer:",
"message": "A aplicación non responde",
"title": "A xanela non responde"
},
"update-available": {
"buttons": {
"disable": "Desactivar actualizacións",
"download": "Descarregar",
"ok": "OK"
},
"detail": "Está dispoñíbel unha nova versión que se pode descarregar de {{downloadLink}}",
"message": "Hai una nova versión dispoñíbel",
"title": "Actualización dispoñíbel"
}
},
"menu": {
"about": "Sobre",
"navigation": {
"label": "Navegación",
"submenu": {
"copy-current-url": "Copiar o URL actual",
"go-back": "Atrás",
"go-forward": "Adiante",
"quit": "Saír",
"restart": "Reiniciar a aplicación"
}
},
"options": {
"label": "Opcións",
"submenu": {
"advanced-options": {
"label": "Opcións avanzadas",
"submenu": {
"auto-reset-app-cache": "Reiniciar a caché cando a aplicación arrinque",
"disable-hardware-acceleration": "Desactivar a aceleración hardware",
"edit-config-json": "Editar config.json",
"override-user-agent": "Substituír o User-Agent",
"restart-on-config-changes": "Reiniciar ao alterar a configuración",
"set-proxy": {
"label": "Configurar o proxy",
"prompt": {
"label": "Introducir o enderezo do proxy (deixar baleiro para desactivalo)",
"placeholder": "Exemplo: SOCKS5://127.0.0.1:9999",
"title": "Configurar o proxy"
}
},
"toggle-dev-tools": "Activar ou desactivar as DevTools"
}
},
"always-on-top": "Sempre en primeiro plano",
"auto-update": "Actualización automática",
"hide-menu": {
"dialog": {
"message": "O menú ocultarase no próximo inicio; use [Alt] para mostralo (ou a tecla [`] se emprega o menú integrado)",
"title": "Ocultar Menú activado"
},
"label": "Ocultar Menú"
},
"language": {
"dialog": {
"message": "A lingua hase mudar despois do reinicio",
"title": "Mudouse a lingua"
},
"label": "Lingua",
"submenu": {
"to-help-translate": "Quere axudar a traducir? Prema aquí"
}
},
"resume-on-start": "Retomar a última canción ao iniciar a aplicación",
"single-instance-lock": "Bloqueo de instancia única",
"start-at-login": "Iniciar co inicio de sesión",
"starting-page": {
"label": "Páxina de inicio",
"unset": "Sen definir"
},
"tray": {
"label": "Bandexa",
"submenu": {
"disabled": "Desactivado",
"enabled-and-hide-app": "Activado e ocultar a aplicación",
"enabled-and-show-app": "Activado e mostrar a aplicación",
"play-pause-on-click": "Reproducir/Pausar ao premer"
}
},
"visual-tweaks": {
"label": "Axustes visuais",
"submenu": {
"custom-window-title": {
"label": "Título de xanela personalizado",
"prompt": {
"label": "Introduza o título personalizado da xanela (deixe baleiro para desactivala)",
"placeholder": "Exemplo: {{applicationName}}"
}
},
"like-buttons": {
"default": "Predeterminado",
"force-show": "Forzar a visualización",
"hide": "Agochar",
"label": "Botóns de Gústame"
},
"remove-upgrade-button": "Retirar o botón de anovación",
"theme": {
"dialog": {
"button": {
"cancel": "Cancelar",
"remove": "Retirar"
},
"remove-theme": "Estás certo que queres retirar o tema personalizado?",
"remove-theme-message": "Isto ha retirar o tema personalizado"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importar arquivo CSS personalizado",
"no-theme": "Sen tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Activado",
"label": "Complementos",
"new": "NOVO"
},
"view": {
"label": "Vista",
"submenu": {
"force-reload": "Forzar recarga",
"reload": "Recargar",
"reset-zoom": "Tamaño real",
"toggle-fullscreen": "Alternar Pantalla Completa",
"zoom-in": "Achegarse",
"zoom-out": "Afastarse"
}
}
},
"tray": {
"next": "Seguinte",
"play-pause": "Reproducir/Pausar",
"previous": "Anterior",
"quit": "Saír"
}
}
}
================================================
FILE: src/i18n/resources/he.json
================================================
{
"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": "he",
"local-name": "עברית",
"name": "Hebrew"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "הטעינה הסתיימה. הכלים לפמתחים נפתחו"
},
"i18n": {
"loaded": "i18n נטען"
},
"second-instance": {
"receive-command": "התקבלה פקודה מעבר פרוטוקל: {{command}}"
},
"theme": {
"css-file-not-found": "קובץ ה-CSS \"{{cssFile}}\" לא קיים. מדלג"
},
"unresponsive": {
"details": "שגיאה ללא תגובה\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "מוחק קבצי מתמון"
},
"window": {
"tried-to-render-offscreen": "ווינדוס ניסה להציג תוכן מחוץ למסך, גודל חלון={{windowSize}}, גודל מסך={{displaySize}}, מיקום={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "התפריט מוחבא, השתמש \"Alt\" על להציג אותו (או \"Esacpe\" אם משתמשים בתפריט בתוך האפליקציה)",
"message": "הסתרת התפריט מופעלת",
"title": "הסתרת התפריט הופעלה"
},
"need-to-restart": {
"buttons": {
"later": "אחר כך",
"restart-now": "מתחיל את התוכנה מחדש עכשיו"
},
"detail": "\"{{pluginName}}\" מצריך אתחול",
"message": "\"{{pluginName}}\" דורש אתחול",
"title": "נדרשת הפעלה מחדש"
},
"unresponsive": {
"buttons": {
"quit": "יציאה",
"relaunch": "הפעל מחדש",
"wait": "המתן"
},
"detail": "אנו מצטערים על אי הנוחות! אנא בחר מה לעשות:",
"message": "האפליקציה אינה מגיבה",
"title": "החלון אינו מגיב"
},
"update-available": {
"buttons": {
"disable": "בטל עדכונים",
"download": "הורדה",
"ok": "אוקיי"
},
"detail": "גרסה חדשה זמינה, ניתן להוריד אותה ב-{{downloadLink}}",
"message": "גירסה חדשה זמינה כעת",
"title": "קיים עדכון חדש"
}
},
"menu": {
"about": "אודות",
"navigation": {
"label": "ניווט",
"submenu": {
"copy-current-url": "העתק את כתובת ה-URL",
"go-back": "חזור אחורה",
"go-forward": "לך קדימה",
"quit": "יציאה",
"restart": "הפעל מחדש את היישום"
}
},
"options": {
"label": "אפשרויות",
"submenu": {
"advanced-options": {
"label": "אפשרויות מתקדמות",
"submenu": {
"auto-reset-app-cache": "אפס את מטמון האפליקציה כאשר האפליקציה מתחילה",
"disable-hardware-acceleration": "השבת האצת החומרה",
"edit-config-json": "ערוך את config.json",
"override-user-agent": "עוקף את סוכן המשתמש",
"restart-on-config-changes": "הפעל מחדש בשינויי תצורה",
"set-proxy": {
"label": "הגדר שרת proxy",
"prompt": {
"label": "הזן כתובת פרוקסי: (להשאיר ריק כדי להשבית)",
"placeholder": "דוגמה: SOCKS5://127.0.0.1:9999",
"title": "הגדר שרת proxy"
}
},
"toggle-dev-tools": "שנה את מצב כלי המפתחים"
}
},
"always-on-top": "השאר מקדימה",
"auto-update": "עדכון אוטומטי",
"hide-menu": {
"dialog": {
"message": "התפריט יוסתר בהפעלה הבאה, השתמש ב-[Alt] כדי להציג אותו (או סמן את [`] אם אתה משתמש בתפריט בתוך האפליקציה)",
"title": "הסתר תפריט מופעל"
},
"label": "הסתר את התפריט"
},
"language": {
"dialog": {
"message": "השפה תשתנה לאחר הפעלת היישום מחדש",
"title": "השפה שונתה"
},
"label": "שפה",
"submenu": {
"to-help-translate": "רוצים לעזור לתרגם? לחץ כאן"
}
},
"resume-on-start": "המשך את השיר האחרון עם הפעלת האפליקציה",
"single-instance-lock": "נעילת מופע יחיד",
"start-at-login": "התחל בכניסה",
"starting-page": {
"label": "דף פתיחה",
"unset": "בטל"
},
"tray": {
"label": "מגש",
"submenu": {
"disabled": "מושבת",
"enabled-and-hide-app": "מופעל והסתר אפליקציה",
"enabled-and-show-app": "מופעל והמציג את האפליקציה",
"play-pause-on-click": "הפעל/השהה בלחיצה"
}
},
"visual-tweaks": {
"label": "תיקונים חזותיים",
"submenu": {
"custom-window-title": {
"prompt": {
"placeholder": "לדוגמה: שולחן כתיבה אגסי"
}
},
"like-buttons": {
"default": "ברירת מחדל",
"force-show": "הפעל בכוח",
"hide": "הסתר",
"label": "כפתורי לייק"
},
"remove-upgrade-button": "הסר לחצן שדרוג",
"theme": {
"dialog": {
"button": {
"cancel": "ביטול",
"remove": "הסר"
},
"remove-theme": "האם אתה בטוח שברצונך להסיר את העיצוב המותאם אישית?",
"remove-theme-message": "פעולה זו תסיר את ערכת הנושא המותאמת אישית"
},
"label": "ערכת נושא",
"submenu": {
"import-css-file": "ייבא קובץ CSS מותאם אישית",
"no-theme": "ללא ערכת נושא"
}
}
}
}
}
},
"plugins": {
"enabled": "מופעל",
"label": "פלאגינים",
"new": "חדש"
},
"view": {
"label": "צפה",
"submenu": {
"force-reload": "התחל מחדש בכוח",
"reload": "רענון",
"reset-zoom": "גודל אמיתי",
"toggle-fullscreen": "מסך מלא",
"zoom-in": "התקרב",
"zoom-out": "התרחק"
}
}
},
"tray": {
"next": "הבא",
"play-pause": "נגן/הפסק",
"previous": "הקודם",
"quit": "יציאה",
"restart": "הפעל מחדש",
"show": "הראה חלון",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "במקרה של פרסומת, הסאונד מושתק ומהירות הוידאו מוכפלת ב-16",
"name": "הגבר מהירות פרסומת"
},
"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": "מצב אווירה"
},
"amuse": {
"description": "מוסיף תמיכה ב-{{applicationName}} עבור הווידג'ט של Amuse המתנגן כעת על ידי 6K Labs",
"name": "משעשע",
"response": {
"query": "שרת ה-API של Amuse פועל. קבל מידע על השיר באמצעות GET /query."
}
},
"api-server": {
"description": "הוסף כתובת שירות כדי לשלוט בנגן",
"dialog": {
"request": {
"buttons": {
"allow": "מותר",
"deny": "לדחות"
},
"message": "אפשר ל {{ID}}{{origin}} לגשת לשירות?",
"title": "בקשת לאימות השירות"
}
},
"menu": {
"auth-strategy": {
"label": "שיטת אימות",
"submenu": {
"auth-at-first": {
"label": "לאמת בבקשה הראשונה"
},
"none": {
"label": "ללא אימות"
}
}
},
"hostname": {
"label": "שם שרת אחסון"
},
"port": {
"label": "פורט"
}
},
"name": "כתובת שירות (בטא)",
"prompt": {
"hostname": {
"label": "הכנסת את כתובת IP של השרת (לדוגמה 0.0.0.0) לשירות:",
"title": "שם שרת אחסון"
},
"port": {
"label": "הכנסת מספר פורט של השירות:",
"title": "פורט"
}
}
},
"audio-compressor": {
"description": "החל דחיסה על אודיו (מורידה את עוצמת הקול של החלקים הרועשים ביותר של האות ומעלה את עוצמת הקול של החלקים החלשים ביותר)",
"name": "דוחס ומצפין קול"
},
"auth-proxy-adapter": {
"description": "תמיכה בשימוש בשירותי פרוקסי אימות",
"menu": {
"disable": "השבת מתאם פרוקסי",
"enable": "הפעל מתאם פרוקסי",
"hostname": {
"label": "שם שרת מאחסן"
},
"port": {
"label": "פורט"
}
},
"name": "מתאם זיהוי פרוקסי",
"prompt": {
"hostname": {
"label": "הזן שם מארח עבור שרת הפרוקסי המקומי (דורש הפעלה מחדש):",
"title": "שם פרוקסי של שרת מאחסן"
},
"port": {
"label": "הזן פורט עבור שרת הפרוקסי המקומי (דורש הפעלה מחדש):",
"title": "יציאת פרוקסי"
}
}
},
"blur-nav-bar": {
"description": "הפוך את סרגל הניווט לשקוף ומטושטש",
"name": "טשטש את סרגל הניווט"
},
"bypass-age-restrictions": {
"description": "עקוף את אימות גיל המשתמש של יוטיוב",
"name": "עקוף את ההחמרות של הגיל"
},
"captions-selector": {
"description": "בורר כתוביות עבור רצועות אודיו של {{applicationName}}",
"menu": {
"autoload": "בחר אוטומטי את הכתובית האחרונה שנבחרה",
"disable-captions": "ברירת מחד ללא כתוביות"
},
"name": "בוחר כתוביות",
"prompt": {
"selector": {
"label": "שפת כתוביות נוכחית: {{language}}",
"none": "ללא",
"title": "בחר שפת כתוביות"
}
},
"templates": {
"title": "פתח בחירת כתוביות"
},
"toast": {
"caption-changed": "תרגום שונה ל {{שפה}}",
"caption-disabled": "תרגום בוטל",
"no-captions": "אין תרגום זמין לשיר הזה"
}
},
"compact-sidebar": {
"description": "הגדר תמיד את סרגל הצד למצב קומפקטי",
"name": "סרגל צד קומפקטי"
},
"crossfade": {
"description": "עמעם בין השירים",
"menu": {
"advanced": "מתקדם"
},
"name": "התפיידות צלב[בית]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "תתפייד בזמן[מילישניות]",
"fade-scaling": {
"linear": "לינארי",
"logarithmic": "לוגריתמי"
}
}
}
}
},
"disable-autoplay": {
"menu": {
"apply-once": "חל רק בהפעלה"
},
"name": "השבתת הפעלה אוטומטית"
},
"discord": {
"backend": {
"connected": "התחבר לדיסקורד",
"disconnected": "התנתק לדיסקורד"
},
"menu": {
"auto-reconnect": "חיבור מחדש אוטומטי",
"clear-activity": "נקה פעילות",
"connected": "מחובר",
"disconnected": "מנותק",
"hide-github-button": "הסתר את לחצן הקישור של GitHub",
"play-on-application": "הפעל ביוטיוב מיוזיק",
"set-inactivity-timeout": "הגדר פסק זמן לחוסר פעילות"
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "אוקיי"
},
"message": "אה ! מתנצלים, הורדה נכשלה",
"title": "שגיאה בהורדה!"
},
"start-download-playlist": {
"buttons": {
"ok": "אוקיי"
},
"message": "מוריד פלייליסט {{playlistTitle}}",
"title": "הורדה התחילה"
}
},
"feedback": {
"downloading": "מוריד…",
"loading": "בטְעִינָה…",
"playlist-has-only-one-song": "לפלייליסט יש רק פריט אחד, מוריד אותו ישירות",
"playlist-id-not-found": "לא נמצא מזהה ID פלייליסט",
"preparing-file": "מכין קובץ…",
"saving": "שומר…",
"trying-to-get-playlist-id": "מנסה להשיג מזהה פלייליסט: {{playlistId}}",
"video-id-not-found": "הסרטון לא נמצא"
}
},
"description": "מוריד MP3 / אודיו מקור ישירות מהממשק",
"menu": {
"choose-download-folder": "בחר תיקיית הורדה",
"download-finish-settings": {
"label": "הורדה בסיום",
"prompt": {
"last-percent": "אחרי x אחוזים",
"last-seconds": "נשארו x שניות",
"title": "הגדר מתי להוריד"
},
"submenu": {
"advanced": "מִתקַדֵם",
"enabled": "מופעל",
"percent": "אָחוּז",
"seconds": "שניות"
}
},
"presets": "הגדרות קבועות מראש",
"skip-existing": "דלג על קבצים קיימים"
}
}
}
}
================================================
FILE: src/i18n/resources/hi.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "{{pluginName}}::{{contextName}} प्लगइन निष्पादित करने में विफल",
"executed-at-ms": "{{pluginName}}::{{contextName}} प्लगिन {{ms}} में निष्पाशित हुआ",
"initialize-failed": "\"{{pluginName}}\" प्लगिन इनिशियलाइज़ होने में असफल रहा",
"load-all": "सारे प्लगिन लोड हो चुके हैं",
"load-failed": "\"{{pluginName}}\" प्लगिन लोड होने में असफल रहा",
"loaded": "प्लगिन \"{{pluginName}}\" लोड हो चुका है",
"unload-failed": "\"{{pluginName}}\" अनलोड होने में असफल रहा",
"unloaded": "प्लगिन \"{{pluginName}}\" अनलोड हो गया है"
}
}
},
"language": {
"code": "hi",
"local-name": "हिंदी",
"name": "Hindi"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "लोडिंग समाप्त हुई । डेवटूल्स खोले गए हैं"
},
"i18n": {
"loaded": "i18n लोड हो गया है"
},
"second-instance": {
"receive-command": "प्रोटोकॉल पर आदेश प्राप्त हुआ \"{{command}}\""
},
"theme": {
"css-file-not-found": "सीएसएस फाइल \"{{cssFile}}\" मौजूद नही है, अनदेखा किया जा रहा है"
},
"unresponsive": {
"details": "अनरेस्पॉन्सिव एरर\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "एप कैश साफ़ किया जा रहा है"
},
"window": {
"tried-to-render-offscreen": "विंडो ने ऑफस्क्रीन रेंडर करने का प्रयास किया, विंडो साइज={{windowSize}}, डिस्प्ले साइज={{displaySize}}, पोजिशन={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "मेनू छिपा हुआ है, देखने के लिए 'Alt' का इस्तेमाल करें (या '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": "मौजूदा यूआरएल कापी करें",
"go-back": "पीछे जाएं",
"go-forward": "आगे जाएं",
"quit": "निकास",
"restart": "एप को पुनः शुरू करें"
}
},
"options": {
"label": "विकल्प",
"submenu": {
"advanced-options": {
"label": "उन्नत विकल्प",
"submenu": {
"auto-reset-app-cache": "एप शुरू होते समय कैश रीसेट करें",
"disable-hardware-acceleration": "हार्डवेयर एक्सीलरेशन बंद करें",
"edit-config-json": "config.json को एडिट करें",
"override-user-agent": "यूजर-एजेंट को रद्द करें",
"restart-on-config-changes": "कनफिग बदलने पे पुनः शुरू करें",
"set-proxy": {
"label": "प्रॉक्सी तय करें",
"prompt": {
"label": "प्प्रॉक्सी पता डालें: (बंद करने के लिए खाली छोड़ें)",
"placeholder": "उदाहरण: SOCKS5://127.0.0.1:9999",
"title": "प्रॉक्सी तय करें"
}
},
"toggle-dev-tools": "डेवटूल्स को टॉगल करें"
}
},
"always-on-top": "हमेशा ऊपर",
"auto-update": "ऑटो अपडेट",
"hide-menu": {
"dialog": {
"message": "अगले लॉन्च पे मेनू छुपा दिया जायेगा, देखने के लिए [Alt] का प्रयोग करें (या बैकटिक [`] अगर आप इन एप मेनू का प्रयोग कर रहे हैं)",
"title": "मेनू छुपाना सक्रिय है"
},
"label": "मेनू छुपाएं"
},
"language": {
"dialog": {
"message": "पुनः शुरू करने के बाद भाषा बदल दी जाएगी",
"title": "भाषा बदल दी गई है"
},
"label": "भाषा",
"submenu": {
"to-help-translate": "अनुवाद करने में सहायता करना चाहते हैं? यहां दबाएं"
}
},
"resume-on-start": "एप शुरू होने पर आखरी गाना फिर शुरू करें",
"single-instance-lock": "सिंगल इंस्टेंस लॉक",
"start-at-login": "शुरू होने पे लॉगिन करें",
"starting-page": {
"label": "स्टार्टिंग पेज",
"unset": "अनसेट"
},
"tray": {
"label": "ट्रे",
"submenu": {
"disabled": "बंद किया गया है",
"enabled-and-hide-app": "सक्रिय है और एप छुपाएं",
"enabled-and-show-app": "सक्रिय है और एप दिखाएं",
"play-pause-on-click": "क्लिक पर प्ले/पोज"
}
},
"visual-tweaks": {
"label": "दृश्य परिवर्तन",
"submenu": {
"custom-window-title": {
"label": "कस्टम विंडो टाइटल",
"prompt": {
"label": "कस्टम विंडो टाइटल डालें: (डिसएबल करने के लिए खाली छोड़ें)",
"placeholder": "उदाहरण: पियर डेस्कटॉप"
}
},
"like-buttons": {
"default": "डिफॉल्ट",
"force-show": "बल पूर्वक दिखाएं",
"hide": "छुपाएं",
"label": "लाइक बटंस",
"swap": "लाइक बटन का क्रम बदलें"
},
"remove-upgrade-button": "अपग्रेड बटन हटाएं",
"theme": {
"dialog": {
"button": {
"cancel": "रद्द करें",
"remove": "हटाएं"
},
"remove-theme": "क्या आप निश्चित है आपको कस्टम थीम हटानी है?",
"remove-theme-message": "यह कस्टम थीम को हटा देगा"
},
"label": "थीम",
"submenu": {
"import-css-file": "कस्टम सीएसएस फाइल को आयात करें",
"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": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "यदि कोई विज्ञापन चलता है तो यह ऑडियो को म्यूट कर देता है और प्लेबैक गति 16x पर सेट कर देता है",
"name": "विज्ञापन की गति बढ़ाना"
},
"adblocker": {
"description": "डिफ़ॉल्ट रूप से सभी विज्ञापनों और ट्रैकिंग को ब्लॉक करें",
"menu": {
"blocker": "ब्लॉकर"
},
"name": "विज्ञापन अवरोधक"
},
"album-actions": {
"description": "प्लेलिस्ट या एल्बम के सभी गानों पर लागू करने के लिए \"अंडिसलाइक,\" \"डिसलाइक,\" \"लाइक,\" और \"अनलाइक\" बटन जोड़ता है",
"name": "एल्बम एक्शन"
},
"album-color-theme": {
"description": "एल्बम रंग पैलेट के आधार पर एक गतिशील थीम और दृश्य प्रभाव लागू करता है",
"menu": {
"color-mix-ratio": {
"label": "रंग मिश्रण अनुपात",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "सीकबार थीमिंग सक्षम करें"
},
"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": "अम्बिएन्ट मोड्"
},
"amuse": {
"description": "6K लैब्स द्वारा Amuse now playing विजेट के लिए Music Player म्यूजिक समर्थन जोड़ा गया",
"name": "मन बहलाना",
"response": {
"query": "अमयूस ए.पि.ऐ. चल रहा है। गाने की जान्कारि होने के लिये GET /query कीजिये।"
}
},
"api-server": {
"description": "प्लेयर को नियंत्रित करने के लिए एक API सर्वर जोड़ता है",
"dialog": {
"request": {
"buttons": {
"allow": "अनुमति दें",
"deny": "मना करना"
},
"message": "{{ID}} ({{origin}}) को ए.पि.ऐ. ऐकसेस करने की अनुमति दे?",
"title": "एपीआई अनुमोदन अनुरोध"
}
},
"menu": {
"auth-strategy": {
"label": "अनुमोदन रणनीति",
"submenu": {
"auth-at-first": {
"label": "अधिकार प्रदान प्रारंभिक अनुरोध पर"
},
"none": {
"label": "कोई प्राधिकरण नहीं"
}
}
},
"hostname": {
"label": "होस्टनेम"
},
"https": {
"label": "HTTPS और प्रमाणपत्र",
"submenu": {
"cert": {
"dialogTitle": "HTTPS प्रमाणपत्र फ़ाइल चुनें",
"label": "प्रमाणपत्र फ़ाइल (.crt/.pem)"
},
"enable-https": {
"label": "HTTPS सक्षम करें"
},
"key": {
"dialogTitle": "HTTPS निजी कुंजी फ़ाइल चुनें",
"label": "निजी कुंजी फ़ाइल (.key/.pem)"
}
}
},
"port": {
"label": "पोर्ट"
}
},
"name": "एपीआई सर्वर [बीटा]",
"prompt": {
"hostname": {
"label": "एपीआई सर्वर के लिए होस्ट नाम (जैसे 0.0.0.0) दर्ज करें:",
"title": "होस्टनेम"
},
"port": {
"label": "एपीआई सर्वर के लिए पोर्ट दर्ज करें:",
"title": "पोर्ट"
}
}
},
"audio-compressor": {
"description": "ऑडियो पर कम्प्रेशन लागू करें (सिग्नल के सबसे ऊँचे हिस्सों की आवाज़ को कम करता है और सबसे नर्म हिस्सों की आवाज़ को बढ़ाता है)",
"name": "ऑडियो कंप्रेसर"
},
"auth-proxy-adapter": {
"description": "ऑथेंटिकेशन प्रॉक्सी सेवाओं के उपयोग के लिए सपोर्ट",
"menu": {
"disable": "प्रॉक्सी एडाप्टर बंद करें",
"enable": "प्रॉक्सी एडाप्टर शुरू करें",
"hostname": {
"label": "होस्ट का नाम"
},
"port": {
"label": "पोर्ट"
}
},
"name": "ऑथ प्रॉक्सी एडाप्टर",
"prompt": {
"hostname": {
"label": "स्थानीय प्रॉक्सी सर्वर के लिए होस्ट का नाम लिखें (पुनः आरंभ ज़रूरी है):",
"title": "प्रॉक्सी होस्ट का नाम"
},
"port": {
"label": "स्थानीय प्रॉक्सी सर्वर के लिए पोर्ट लिखें (पुनः प्रारंभ जरूरी है):",
"title": "प्रॉक्सी का पोर्ट"
}
}
},
"blur-nav-bar": {
"description": "नेविगेशन बार को पारदर्शी और धुंधला बनाता है",
"name": "नेविगेशन बार को ब्लर करें"
},
"bypass-age-restrictions": {
"description": "Music Player आयु की जांच को बायपास करें",
"name": "आयु प्रतिबंध को बायपास करें"
},
"captions-selector": {
"description": "{{applicationName}} म्यूज़िक ऑडियो ट्रैक के लिए कैप्शन चयनकर्ता",
"menu": {
"autoload": "अंतिम बार उपयोग किए गए कैप्शन का ऑटोमैटिक रूप से चयन करें",
"disable-captions": "डिफ़ॉल्ट रूप में कोई कैप्शन नहीं"
},
"name": "कैप्शन चयनकर्ता",
"prompt": {
"selector": {
"label": "वर्तमान कैप्शन भाषा: {{language}}",
"none": "कुछ नहीं",
"title": "कैप्शन भाषा चुनें"
}
},
"templates": {
"title": "कैप्शन चयनकर्ता खोलें"
},
"toast": {
"caption-changed": "कैप्शन {{language}} में बदल दिया गया है",
"caption-disabled": "कैप्शन बंद कर दिए गए हैं",
"no-captions": "इस गाने के लिए कोई कैप्शन उपलब्ध नहीं हैं"
}
},
"clock": {
"description": "नेविगेशन बार में घड़ी जोड़ें",
"menu": {
"format": {
"24-hour-format": "24-घंटे का प्रारूप",
"display-seconds": "सेकंड दिखाएं",
"label": "प्रारूप"
}
},
"name": "घड़ी"
},
"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": "क्रॉसफ़ेड विकल्प"
}
}
},
"custom-output-device": {
"description": "गानों के लिए एक कस्टम आउटपुट मीडिया डिवाइस कॉन्फ़िगर करें",
"menu": {
"device-selector": "डिवाइस चुनें"
},
"name": "अपनी पसंद का आउटपुट डिवाइस",
"prompt": {
"device-selector": {
"label": "उपयोग किए जाने वाला आउटपुट मीडिया चुने",
"title": "आउटपुट डिवाइस चुनें"
}
}
},
"disable-autoplay": {
"description": "गीत को \"रुके हुए \" मोड में शुरू करता है",
"menu": {
"apply-once": "केवल प्रारम्भ पर लागू होता है"
},
"name": "ऑटोप्ले अयोग्य करें"
},
"discord": {
"backend": {
"already-connected": "सक्रिय कनेक्शन से जुड़ने का प्रयास किया गया",
"connected": "डिस्कॉर्ड से कनेक्टेड है",
"disconnected": "डिस्कॉर्ड से कनेक्टेड नहीं है"
},
"description": "Rich Presence के साथ अपने दोस्तों के साथ बाटें कि आप क्या सुनते हैं",
"menu": {
"auto-reconnect": "स्वतः पुनः कनेक्ट करें",
"clear-activity": "Activity साफ़ करें",
"clear-activity-after-timeout": "समय समाप्त होने के बाद एक्टिविटी साफ़ करें",
"connected": "स्थापित",
"disconnected": "डिस्कनेक्ट किया गया",
"hide-duration-left": "शेष अवधि छिपाएँ",
"hide-github-button": "GitHub लिंक के बटन को छिपाएँ",
"play-on-application": "{{applicationName}} म्यूज़िक पर चलाएँ",
"set-inactivity-timeout": "निष्क्रियता समय समाप्ति सेट करें",
"set-status-display-type": {
"label": "स्टेटस टेक्स्ट",
"submenu": {
"application": "{{applicationName}} सुन रहे है",
"artist": "{artist} को सुन रहे है",
"title": "{song title} सुन रहे है"
}
}
},
"name": "डिस्कॉर्ड रिच प्रेजेंस",
"prompt": {
"set-inactivity-timeout": {
"label": "निष्क्रियता समय समाप्ति सेकंड में दर्ज करें:",
"title": "निष्क्रियता समय समाप्ति सेट करें"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "ठीक है"
},
"message": "आह! क्षमा करें, डाउनलोड विफल हो गया…",
"title": "डाउनलोड में दिक्कत है !"
},
"start-download-playlist": {
"buttons": {
"ok": "ठीक है"
},
"detail": "({{playlistSize}} गाने)",
"message": "प्लेलिस्ट {{playlistTitle}} डाउनलोड हो रही है",
"title": "डाउनलोड शुरू"
}
},
"feedback": {
"conversion-progress": "कन्वर्ज़न: {{percent}}%",
"converting": "परिवर्तित हो रहा है…",
"done": "हो गया: {{filePath}}",
"download-info": "{{artist}} - {{title}} डाउनलोड हो रहा है [{{videoId}}",
"download-progress": "डाउनलोड करें: {{percent}}%",
"downloading": "डाउनलोड हो रहा है…",
"downloading-counter": "{{current}}/{{total}} डाउनलोड हो रहा है…",
"downloading-playlist": "प्लेलिस्ट \"{{playlistTitle}}\" - {{playlistSize}} गाने ({{playlistId}}) डाउनलोड हो रहे हैं",
"error-while-downloading": "\"{{author}} - {{title}}\" डाउनलोड करते समय दिक्कत: {{error}}",
"folder-already-exists": "फ़ोल्डर {{playlistFolder}} पहले से मौजूद है",
"getting-playlist-info": "प्लेलिस्ट की जानकारी प्राप्त हो रही है…",
"loading": "लोड हो रहा है…",
"playlist-has-only-one-song": "प्लेलिस्ट में केवल एक आइटम है, इसे सीधे डाउनलोड करें",
"playlist-id-not-found": "कोई प्लेलिस्ट आईडी नहीं मिली",
"playlist-is-empty": "प्लेलिस्ट ख़ाली है",
"playlist-is-mix-or-private": "प्लेलिस्ट जानकारी प्राप्त करने में दिक्कत: सुनिश्चित करें कि यह निजी या \"आपके लिए मिश्रित\" प्लेलिस्ट नहीं है\n\n{{error}}",
"preparing-file": "फ़ाइल तैयार की जा रही है…",
"saving": "सेव जा रहा है…",
"trying-to-get-playlist-id": "प्लेलिस्ट आईडी प्राप्त करने का प्रयास किया जा रहा है: {{playlistId}}",
"video-id-not-found": "वीडियो नहीं मिला",
"writing-id3": "ID3 टैग लिख रहे हैं…"
}
},
"description": "इंटरफ़ेस से सीधे MP3 / स्रोत ऑडियो डाउनलोड करता है",
"menu": {
"choose-download-folder": "डाउनलोड फ़ोल्डर चुनें",
"download-finish-settings": {
"label": "समाप्त होने पर डाउनलोड करें",
"prompt": {
"last-percent": "x प्रतिशत के बाद",
"last-seconds": "अंतिम x सेकंड",
"title": "डाउनलोड करने का समय कॉन्फ़िगर करें"
},
"submenu": {
"advanced": "विकसित",
"enabled": "सक्रिय",
"mode": "टाइम मोड",
"percent": "प्रतिशत",
"seconds": "सेकंड"
}
},
"download-playlist": "प्लेलिस्ट डाउनलोड करें",
"presets": "प्रीसेट",
"skip-existing": "मौजूदा फ़ाइलें छोड़ें"
},
"name": "डाउनलोडर",
"renderer": {
"can-not-update-progress": "प्रगति अपडेट नहीं की जा सकती"
},
"templates": {
"button": "डाउनलोड"
}
},
"equalizer": {
"description": "प्लेयर में एक एक्विलाइज़र जोड़ता है",
"menu": {
"presets": {
"label": "प्रीसेट",
"list": {
"bass-booster": "bass वर्धक"
}
}
},
"name": "एक्विलाइज़र"
},
"exponential-volume": {
"description": "वॉल्यूम स्लाइडर को घातांकीय बनाता है ताकि कम वॉल्यूम का चयन करना आसान हो।",
"name": "तीव्र वॉल्यूम"
},
"in-app-menu": {
"description": "मेनू-बार को फैंसी, गहरा या एल्बम-रंग का रूप दें",
"menu": {
"hide-dom-window-controls": "DOM विंडो कंट्रोल को छिपाएँ"
},
"name": "इन-ऐप मेनू"
},
"lumiastream": {
"description": "लूमिया स्ट्रीम सपोर्ट जोड़ा गया",
"name": "लूमिया स्ट्रीम [बीटा]"
},
"lyrics-genius": {
"description": "अधिकांश गानों के लिए गीत के लिरिक्स को जोड़ता है",
"menu": {
"romanized-lyrics": "रोमनकृत लिरिक्स"
},
"name": "लिरिक्स जीनियस",
"renderer": {
"fetched-lyrics": "जीनियस के लिए प्राप्त किये गए लिरिक्स"
}
},
"music-together": {
"description": "दूसरों के साथ प्लेलिस्ट साझा करें। जब होस्ट कोई गाना बजाता है, तो बाकी सभी लोग वही गाना सुनेंगे",
"dialog": {
"enter-host": "होस्ट आईडी दर्ज करें"
},
"internal": {
"save": "सेव",
"track-source": "ट्रैक स्रोत",
"unknown-user": "अज्ञात उपयोगकर्ता"
},
"menu": {
"click-to-copy-id": "होस्ट आईडी कॉपी करें",
"close": "संगीत को एक साथ बंद करें",
"connected-users": "जुड़े हुए उपयोगकर्ता",
"disconnect": "संगीत को एक साथ डिस्कनेक्ट करें",
"empty-user": "कोई जुड़े हुए उपयोगकर्ता नहीं",
"host": "म्यूजिक टुगेदर होस्ट",
"join": "संगीत से साथ में जुड़ें",
"permission": {
"all": "मेहमानों को प्लेलिस्ट और प्लेयर को नियंत्रित करने की अनुमति दें",
"host-only": "केवल होस्ट ही प्लेलिस्ट और प्लेयर को नियंत्रित कर सकता है",
"playlist": "मेहमानों को प्लेलिस्ट नियंत्रित करने की अनुमति दें"
},
"set-permission": "नियंत्रण अनुमति बदलें",
"status": {
"disconnected": "डिस्कनेक्ट किया गया",
"guest": "अतिथि के रूप में जुड़े हुए",
"host": "मेज़बान के रूप में जुड़े हुए"
}
},
"name": "संगीत टुगेदर [बीटा]",
"toast": {
"add-song-failed": "गाना जोड़ने में असफलता",
"closed": "म्यूजिक टुगेदर बंद हुआ",
"disconnected": "म्यूजिक टुगेदर डिस्कनेक्ट हुआ",
"host-failed": "म्यूज़िक टुगेदर होस्ट करने में असफल",
"id-copied": "होस्ट आईडी क्लिपबोर्ड पर कॉपी की गई",
"id-copy-failed": "होस्ट आईडी को क्लिपबोर्ड पर कॉपी करने में असफल",
"join-failed": "म्यूजिक टुगेदर में शामिल होने में विफल",
"joined": "म्यूजिक टुगेदर में शामिल होने में सफल",
"permission-changed": "म्यूजिक टुगेदर की अनुमति बदलकर \"{{permission}}\" कर दी गई है",
"remove-song-failed": "गाना हटाने में विफल",
"user-connected": "{{name}} म्यूजिक टुगेदर में शामिल हुए",
"user-disconnected": "{{name}} ने म्यूजिक टुगेदर छोड़ा"
}
},
"navigation": {
"description": "आगे /पीछे नेविगेशन अर्रोस सीधे इंटरफ़ेस में एकीकृत, जैसे आपके पसंदीदा ब्राउज़र में",
"name": "नेविगेशन",
"templates": {
"back": {
"title": "पिछले पेज पर जाएं"
},
"forward": {
"title": "अगले पेज पर जाएं"
}
}
},
"no-google-login": {
"description": "इंटरफ़ेस से गूगल लॉगिन बटन और लिंक हटाएँ",
"name": "कोई गूगल लॉगिन नहीं"
},
"notifications": {
"description": "जब कोई गाना बजना शुरू हो जाए तो नोटिफ़िकेशन दें (विंडोज़ पर इंटरैक्टिव नोटिफ़िकेशन्स उपलब्ध हैं)",
"menu": {
"interactive": "इंटरैक्टिव नोटिफ़िकेशन्स",
"interactive-settings": {
"label": "इंटरैक्टिव सेटिंग्स",
"submenu": {
"hide-button-text": "बटन टेक्स्ट को छिपाएँ",
"refresh-on-play-pause": "प्ले/पॉज़ पर रिफ्रेश करें",
"tray-controls": "ट्रे क्लिक पर खोलें/बंद करें"
}
},
"priority": "नोटिफ़िकेशन प्राथमिकता",
"toast-style": "टोस्ट स्टाइल",
"unpause-notification": "पॉज हटने पर नोटिफ़िकेशन दिखाएं"
},
"name": "नोटिफ़िकेशन्स"
},
"performance-improvement": {
"description": "प्रयोगात्मक स्क्रिप्ट सक्षम करके प्रदर्शन में सुधार करें",
"name": "प्रदर्शन सुधार [Beta]"
},
"picture-in-picture": {
"description": "ऐप को पिक्चर-इन-पिक्चर मोड में बदलने की अनुमति दें",
"menu": {
"always-on-top": "हमेशा ऊपर",
"hotkey": {
"label": "हॉट की",
"prompt": {
"keybind-options": {
"hotkey": "हॉट की"
},
"label": "पिक्चर-इन-पिक्चर टॉगल करने के लिए हॉट की चुनें",
"title": "पिक्चर-इन-पिक्चर हॉट की"
}
},
"save-window-position": "विंडो पोज़ीशन सेव करें",
"save-window-size": "विंडो के आकार को सेव करें",
"use-native-pip": "ब्राउज़र के नेटिव PiP का उपयोग करें"
},
"name": "पिक्चर-इन-पिक्चर",
"templates": {
"button": "पिक्चर-इन-पिक्चर"
}
},
"playback-speed": {
"description": "तेज़ सुनो, धीरे सुनो! गाने की गति को नियंत्रित करने वाला स्लाइडर जोडें",
"name": "प्लेबैक गति",
"templates": {
"button": "गति"
}
},
"precise-volume": {
"description": "कस्टम HUD और कस्टोमिज़ाबले वॉल्यूम चरणों के साथ, माउसव्हील/हॉट कीज़ का उपयोग करके वॉल्यूम को सटीक रूप से नियंत्रित करें",
"menu": {
"arrows-shortcuts": "लोकल एरो-की नियंत्रण",
"custom-volume-steps": "कस्टम वॉल्यूम चरण सेट करें",
"global-shortcuts": "वैश्विक हॉट कीज़"
},
"name": "सटीक वॉल्यूम",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "वॉल्यूम घटाएँ",
"increase": "वॉल्यूम बढ़ाएँ"
},
"label": "ग्लोबल वॉल्यूम कीबाइंड्स चुनें:",
"title": "ग्लोबल वॉल्यूम कीबाइंड्स"
},
"volume-steps": {
"label": "वॉल्यूम बढ़ाने/घटाने के चरण चुनें",
"title": "वॉल्यूम चरण"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "वर्तमान गुणवत्ता: {{quality}}",
"message": "वीडियो गुणवत्ता चुनें:",
"title": "वीडियो गुणवत्ता चुनें"
}
}
},
"description": "वीडियो ओवरले पर एक बटन के साथ वीडियो की गुणवत्ता बदलने की अनुमति देता है",
"name": "वीडियो गुणवत्ता परिवर्तक",
"renderer": {
"quality-settings-button": {
"label": "प्लेयर क्वालिटी सेटिंग खोलें"
}
}
},
"scrobbler": {
"description": "स्क्रोब्लिंग सपोर्ट जोड़ें (etc. last.fm, listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm के साथ प्रमाणीकरण विफल\nअगले पुनरारंभ तक पॉपअप छिपाएँ।",
"title": "प्रमाणीकरण विफल"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API सेटिंग्स"
},
"listenbrainz": {
"token": "listenbrainz उपयोगकर्ता टोकन दर्ज करें"
},
"scrobble-alternative-artist": "वैकल्पिक आर्टिस्ट का उपयोग करें",
"scrobble-alternative-title": "वैकल्पिक शीर्षक का उपयोग करें",
"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 से खोज चालू करता है, 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"
},
"synced-lyrics": {
"description": "LRClib जैसे सोर्सेज के उपयोग से, गानों के लिए सिंक किए गए लिरिक्स देता है।"
},
"video-toggle": {
"menu": {
"align": {
"submenu": {
"left": "बाएं",
"middle": "मध्य",
"right": "दाहिने"
}
},
"force-hide": "वीडियो टैब को बलपूर्वक हटाएं",
"mode": {
"label": "तरीका"
}
}
}
}
}
================================================
FILE: src/i18n/resources/hr.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Neuspješno izvršavanje plugina {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} je izvršen za {{ms}}ms",
"initialize-failed": "Neuspješno inicijaliziranje plugina \"{{pluginName}}\"",
"load-all": "Učitavanje svih plugina",
"load-failed": "Neuspješno učitavanje plugina \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" je učitan",
"unload-failed": "Neuspješna deaktivacija plugina \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" deaktiviran"
}
}
},
"language": {
"code": "hr",
"local-name": "Hrvatski",
"name": "Croatian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Završeno učitavanje. DevTools je otvoren"
},
"i18n": {
"loaded": "i18n je učitan"
},
"second-instance": {
"receive-command": "Zaprimljena naredba preko protokola: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS datoteka \"{{cssFile}}\" ne postoji, zanemarujem"
},
"unresponsive": {
"details": "Neresponzivna Pogreška!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Brisanje predmemorije aplikacije"
},
"window": {
"tried-to-render-offscreen": "Prozor se pokušao prikazat van ekrana, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Izbornik je sakriven, pritisnite 'Alt' da bi se prikazao (ili 'Escape' ako koristite unutar aplikacijski izbornik)",
"message": "Sakrij Izbornik je uključen",
"title": "Sakrij Izbornik Ukljućen"
},
"need-to-restart": {
"buttons": {
"later": "Kasnije",
"restart-now": "Ponovo Pokreni Sada"
},
"detail": "\"{{pluginName}}\" plugin zahtjeva ponovno pokretanje da bi postao aktivan",
"message": "\"{{pluginName}}\" se treba ponovo pokreniti",
"title": "Potrebno Ponovno Pokretanje"
},
"unresponsive": {
"buttons": {
"quit": "Izađi",
"relaunch": "Ponovno Pokretanje",
"wait": "Čekaj"
},
"detail": "Ispričavamo se zbog neugodnosti! izaberite sljedeću radnju:",
"message": "Aplikacija je Neresponzivna",
"title": "Prozor je Neresponzivan"
},
"update-available": {
"buttons": {
"disable": "Isključi Ažuriranja",
"download": "Preuzmi",
"ok": "OK"
},
"detail": "Nova verzija je dostupna i može se preuzeti preko {{downloadLink}}",
"message": "Nova verzija je dostupna",
"title": "Dostupno Ažuriranje"
}
},
"menu": {
"about": "O programu",
"navigation": {
"label": "Navigacija",
"submenu": {
"copy-current-url": "Kopiraj trenutni URL",
"go-back": "Idi natrag",
"go-forward": "Idi naprijed",
"quit": "Izađi",
"restart": "Ponovno Pokreni Aplikaciju"
}
},
"options": {
"label": "Opcije",
"submenu": {
"advanced-options": {
"label": "Napredne opcije",
"submenu": {
"auto-reset-app-cache": "Resetiraj predmemoriju aplikacije pri pokretanju",
"disable-hardware-acceleration": "Isključi hardversku akceleraciju",
"edit-config-json": "Uredi config.json",
"override-user-agent": "Promijeni User-Agent",
"restart-on-config-changes": "Ponovno pokreni na promjene konfiguracije",
"set-proxy": {
"label": "Postavi proxy",
"prompt": {
"label": "Unesi Adresu za Proxy: (ostavite prazno ako želite onemogućiti)",
"placeholder": "Primjer: SOCKS5://127.0.0.1:9999",
"title": "Postavi proxy"
}
},
"toggle-dev-tools": "Uključi/isključi DevTools"
}
},
"always-on-top": "Uvijek na vrhu",
"auto-update": "Automatsko Ažuriranje",
"hide-menu": {
"dialog": {
"message": "Izbornik će se sakriti pri sljedećem pokretanju, stisnite [Alt] da se prikaže (ili backtick [`] ako koristite meni unutar aplikacije)",
"title": "Sakrij Izbornik Uključen"
},
"label": "Sakrij Izbornik"
},
"language": {
"dialog": {
"message": "Jezik će se promijeniti nakon ponovnog pokretanja",
"title": "Jezik promijenjen"
},
"label": "Jezik",
"submenu": {
"to-help-translate": "Želite pomoći sa prijevodom? Kliknite ovdje"
}
},
"resume-on-start": "Nastavi zadnju pjesmu kad se aplikacija pokrene",
"single-instance-lock": "Sprječavanje višestrukog pokretanja",
"start-at-login": "Počni od prijave",
"starting-page": {
"label": "Početna stranica",
"unset": "Nepostavljeno"
},
"tray": {
"label": "Traka",
"submenu": {
"disabled": "Isključeno",
"enabled-and-hide-app": "Uključena i skrivena aplikacija",
"enabled-and-show-app": "Uključena i prikaži aplikaciju",
"play-pause-on-click": "Reproduciraj/Pauziraj na klik"
}
},
"visual-tweaks": {
"label": "Vizualna podešavanja",
"submenu": {
"custom-window-title": {
"label": "Prilagođeni naslov prozora",
"prompt": {
"label": "Unesi prilagođeni naslov prozora: (ostavi prazno za onemogućiti)",
"placeholder": "Primjer: {{applicationName}}"
}
},
"like-buttons": {
"default": "Zadano",
"force-show": "Prisilno prikaži",
"hide": "Sakrij",
"label": "\"Sviđa mi se\" gumbi"
},
"remove-upgrade-button": "Ukloni gumb za nadogradnju",
"theme": {
"dialog": {
"button": {
"cancel": "Odustani",
"remove": "Ukloni"
},
"remove-theme": "Jeste li sigurni da želite ukloniti prilagođenu temu?",
"remove-theme-message": "Ovo će ukloniti prilagođenu temu"
},
"label": "Tema",
"submenu": {
"import-css-file": "Uvezi prilagođenu CSS datoteku",
"no-theme": "Bez teme"
}
}
}
}
}
},
"plugins": {
"enabled": "Uključeno",
"label": "Plugini",
"new": "NOVO"
},
"view": {
"label": "Pogled",
"submenu": {
"force-reload": "Prisilno Ponovo Učitaj",
"reload": "Ponovno učitaj",
"reset-zoom": "Prava Veličina",
"toggle-fullscreen": "Uključi/Isključi Prikaz Preko Cijelog Ekrana",
"zoom-in": "Povećaj",
"zoom-out": "Smanji"
}
}
},
"tray": {
"next": "Sljedeće",
"play-pause": "Reproduciraj/Pauziraj",
"previous": "Prethodni",
"quit": "Izađi",
"restart": "Ponovo Pokreni Aplikaciju",
"show": "Prikaži prozor",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Ako se pokrene oglas, zvuk se isključi i brzina reprodukcije se postavi na 16x",
"name": "Ubrzanje Oglasa"
},
"adblocker": {
"description": "Blokiraj sve oglase i praćenje odmah po pokretanju",
"menu": {
"blocker": "Blokator"
},
"name": "Blokator Oglasa"
},
"album-actions": {
"description": "Dodaje tipke za 'Ne sviđa mi se', 'Sviđa mi se' i 'Dislike'/'Like' za primjenu na sve pjesme u playlisti ili albumu",
"name": "Radnje Albuma"
},
"album-color-theme": {
"description": "Primjenjuje dinamičnu temu i vizualne efekte prema paleti boje albuma",
"menu": {
"color-mix-ratio": {
"label": "Omjer miješanja boja",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Omogući postavljanje teme \"seekbar\"-a"
},
"name": "Boja teme albuma"
},
"ambient-mode": {
"description": "Primjenjuje efekt osvjetljenja prikazivajući nježne boje iz videa na pozadinu vašeg ekrana",
"menu": {
"blur-amount": {
"label": "Količina zamućenja",
"submenu": {
"pixels": "{{blurAmount}} pikseli"
}
},
"buffer": {
"label": "Predmemorija (Bufer)",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Prozirnost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvaliteta",
"submenu": {
"pixels": "{{quality}} pikseli"
}
},
"size": {
"label": "Veličina",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Glatakoća prijelaza",
"submenu": {
"during": "Tijekom {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Koristi se prikaz preko cijelog ekrana"
}
},
"name": "Ambijentalni Način"
},
"amuse": {
"description": "Dodaje podršku za {{applicationName}} za widget \"sada reproducira\" od Amuse od strane 6K Labs",
"name": "Zabavljati",
"response": {
"query": "Amuse API poslužitelj je pokrenut. Koristi GET /query za dohvat informacija o pjesmi."
}
},
"api-server": {
"description": "Dodaje API poslužitelj za kontrolu medija",
"dialog": {
"request": {
"buttons": {
"allow": "Dozvoli",
"deny": "Odbij"
},
"message": "Dozvoli {{ID}} ({{origin}}) pristup API-ju?",
"title": "Zahtjev za autorizaciju API-ja"
}
},
"menu": {
"auth-strategy": {
"label": "Strategija autorizacije",
"submenu": {
"auth-at-first": {
"label": "Autorizacija pri prvom zahjtevu"
},
"none": {
"label": "Bez autorizacije"
}
}
},
"hostname": {
"label": "Naziv hosta"
},
"https": {
"label": "HTTPS i Sertifikati",
"submenu": {
"cert": {
"dialogTitle": "Biraj HTTPS sertifikat datoteku",
"label": "Sertifikat datoteka (.crt/.pem)"
},
"enable-https": {
"label": "Omogući HTTPS"
},
"key": {
"dialogTitle": "Biraj privatni ključ datoteku za HTTPS",
"label": "Privatni ključ datoteka (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "API Poslužitelj [Beta]",
"prompt": {
"hostname": {
"label": "Unesite ime hosta (npr. 0.0.0.0) od API poslužitelja:",
"title": "Naziv hosta"
},
"port": {
"label": "Unesite port od API poslužitelja:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Primijeni kompresiju na zvuk (smanjuje glasnoću najglasnijih dijelova signala i povećava glasnoću najslabijih dijelova)",
"name": "Kompresor Zvuka"
},
"auth-proxy-adapter": {
"description": "Podrška za korištenje usluga autentifikacijskog proxyja",
"menu": {
"disable": "Onemogući proxy adapter",
"enable": "Omogući proxy adapter",
"hostname": {
"label": "Naziv hosta"
},
"port": {
"label": "Port"
}
},
"name": "Proxy adapter za autentifikaciju",
"prompt": {
"hostname": {
"label": "Unesite naziv hosta za lokalnog proxy poslužitelja (zahtijeva ponovno pokretanje):",
"title": "Naziv hosta proxyja"
},
"port": {
"label": "Unesite port za lokalni proxy poslužitelj (zahtijeva ponovno pokretanje):",
"title": "Port proxyja"
}
}
},
"blur-nav-bar": {
"description": "Čini navigacijsku traku prozirnom i zamagljenom",
"name": "Zamagli Navigacijsku Traku"
},
"bypass-age-restrictions": {
"description": "Zaobiđi Music Player provjeru dobi",
"name": "Zaobiđi dobna ograničenja"
},
"captions-selector": {
"description": "Izbornik titlova za audiozapise od {{applicationName}}a",
"menu": {
"autoload": "Automatski izaberi posljednje korištene titlove",
"disable-captions": "Bez titlova"
},
"name": "Izbornik za titlove",
"prompt": {
"selector": {
"label": "Trenutni jezik za titlove: {{language}}",
"none": "Ništa",
"title": "Izaberi jezik za titlove"
}
},
"templates": {
"title": "Otvori izbornik za titlove"
},
"toast": {
"caption-changed": "Titlovi su promijenjeni u {{language}}",
"caption-disabled": "Titlovi su isključeni",
"no-captions": "Za ovu pjesmu nisu dostupni titlovi"
}
},
"compact-sidebar": {
"description": "Uvijek postavi bočnu traku na kompaktni način rada",
"name": "Kompaktna bočna traka"
},
"crossfade": {
"description": "Pretapanje (cross-fade) između pjesama",
"menu": {
"advanced": "Napredno"
},
"name": "Pretapanje (cross-fade) [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Trajanje postepenog pojačavanja zvuka (Fade in) (ms)",
"fade-out-duration": "Trajanje postepenog smanjenje zvuka (Fade out) (ms)",
"fade-scaling": {
"label": "Skaliranje postepenog prijelaza (Fade scaling)",
"linear": "Linearno",
"logarithmic": "Logaritamsko"
},
"seconds-before-end": "Pretapanje (cross-fade) N sekundi prije kraja"
},
"title": "Opcije za pretapanje (cross-fade)"
}
}
},
"custom-output-device": {
"description": "Konfiguriraj prilagođeni izlazni medijski uređaj za pjesme",
"menu": {
"device-selector": "Odaberi Uređaj"
},
"name": "Prilagođeni uređaj za izlaz",
"prompt": {
"device-selector": {
"label": "Odaberi izlazni medijski uređaj za korištenje",
"title": "Odaberi izlazni uređaj"
}
}
},
"disable-autoplay": {
"description": "Pokreće pjesmu u pauziranom načinu rada",
"menu": {
"apply-once": "Primjenjuje se samo pri pokretanju aplikacije"
},
"name": "Isključi automatsko reprodukciju"
},
"discord": {
"backend": {
"already-connected": "Pokušano je povezivanje s aktivnom vezom",
"connected": "Spojen na Discord",
"disconnected": "Odspojen od Discorda"
},
"description": "Pokaži svojim prijateljima što slušate sa Rich Presence",
"menu": {
"auto-reconnect": "Automatski se ponovo spoji",
"clear-activity": "Očisti aktivnosti",
"clear-activity-after-timeout": "Očisti aktivnosti nakon isteka vremena",
"connected": "Spojen",
"disconnected": "Odspojen",
"hide-duration-left": "Sakrij preostalo vrijeme",
"hide-github-button": "Sakrij gumb sa GitHub poveznicom",
"play-on-application": "Reproduciraj na {{applicationName}}u",
"set-inactivity-timeout": "Postavi vremensko ograničenje neaktivnosti (inactivity timeout)",
"set-status-display-type": {
"label": "Tekst statusa",
"submenu": {
"application": "Slušate {{applicationName}}",
"artist": "Slušate {glazbenika}",
"title": "Slušate {naslov pjesme}"
}
}
},
"name": "Bogata prisutnost Discorda",
"prompt": {
"set-inactivity-timeout": {
"label": "Postavi vremensko ograničenje neaktivnosti u sekundama:",
"title": "Postavi vremensko ograničenje neaktivnosti"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Jao! Oprostite, preuzimanje je bilo neuspješno…",
"title": "Pogreška pri preuzimanju!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} pjesma)",
"message": "Preuzimanje Playliste {{playlistTitle}}",
"title": "Preuzimanje započeto"
}
},
"feedback": {
"conversion-progress": "Konverzija: {{percent}}%",
"converting": "Konvertiranje…",
"done": "Gotov: {{filePath}}",
"download-info": "Preuzimanje {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Preuzimanje: {{percent}}%",
"downloading": "Preuzimanje…",
"downloading-counter": "Preuzimanje {{current}}/{{total}}…",
"downloading-playlist": "Preuzimanje playliste \"{{playlistTitle}}\" - {{playlistSize}} pjesama ({{playlistId}})",
"error-while-downloading": "Pogreška pri preuzimanju \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Mapa {{playlistFolder}} već postoji",
"getting-playlist-info": "Dobavljaju se informacije o playlisti…",
"loading": "Učitavanje…",
"playlist-has-only-one-song": "Playlista ima samo jednu stavku, preuzeti će se direktno",
"playlist-id-not-found": "Nije pronađen ID playliste",
"playlist-is-empty": "Playlista je prazna",
"playlist-is-mix-or-private": "Pogreška pri dobavljanju informacija o playlisti: provjerite da nije privatna ili \"Za vas\" playlista\n\n{{error}}",
"preparing-file": "Priprema se datoteka…",
"saving": "Spremanje…",
"trying-to-get-playlist-id": "Pokušavam dobaviti playlist ID: {{playlistId}}",
"video-id-not-found": "Videozapis nije pronađen",
"writing-id3": "Zapisujem ID3 tagove…"
}
},
"description": "Preuzima MP3 / izvorni audiozapis izravno iz sučelja",
"menu": {
"choose-download-folder": "Odaberite mapu za preuzimanje",
"download-finish-settings": {
"label": "Preuzmi pri završetku",
"prompt": {
"last-percent": "Nakon x posto",
"last-seconds": "Zadnjih x sekundi",
"title": "Podesi kada preuzeti"
},
"submenu": {
"advanced": "Napredno",
"enabled": "Uključeno",
"mode": "Tip vremena",
"percent": "Postotak",
"seconds": "Sekunde"
}
},
"download-playlist": "Preuzmi playlistu",
"presets": "Unaprijed postavljeno",
"skip-existing": "Preskoči datoteke koje već postoje"
},
"name": "Preuzimatelj",
"renderer": {
"can-not-update-progress": "Nemoguće ažuriranje napredka"
},
"templates": {
"button": "Preuzmi"
}
},
"equalizer": {
"description": "Dodaje equalizer reprodukciji",
"menu": {
"presets": {
"label": "Zadane postavke",
"list": {
"bass-booster": "Pojačivač basa"
}
}
},
"name": "Ekvalizator"
},
"exponential-volume": {
"description": "Čini klizač glasnoće (volume slider) eksponencijalnim za lakši odabir niže glasnoće.",
"name": "Eksponencijalna Glasnoća"
},
"in-app-menu": {
"description": "Daje izbornicima fensi, tamni ili prema boji albuma izgled",
"menu": {
"hide-dom-window-controls": "Sakrij kontrole prozora DOM-a"
},
"name": "Izbornik unutar aplikacije"
},
"lumiastream": {
"description": "Dodaje podršku za Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Dodaje podršku za tekstove pjesama za većinu pjesama",
"menu": {
"romanized-lyrics": "Romanizirani tekstovi pjesme"
},
"name": "Genius tekstovi",
"renderer": {
"fetched-lyrics": "Dobavljen tekst pjesme s Genius-a"
}
},
"music-together": {
"description": "Podijeli playlistu s drugima. Kada domaćin (host) pusti pjesmu, svi ostali će čuti istu pjesmu",
"dialog": {
"enter-host": "Unesi ID hosta"
},
"internal": {
"save": "Spremi",
"track-source": "Izvor Pjesme",
"unknown-user": "Nepoznati Korisnik"
},
"menu": {
"click-to-copy-id": "Kopiraj ID Hosta",
"close": "Zatvori Glazbu Zajedno",
"connected-users": "Spojeni Korisnici",
"disconnect": "Odspoji Glazbu Zajedno",
"empty-user": "Nema spojenih korisnika",
"host": "Host Glazbe Zajedno",
"join": "Pridruži se Glazbi Zajedno",
"permission": {
"all": "Dopusti gostima da kontroliraju playlistu i reprodukciju",
"host-only": "Samo host može kontrolirati playlistu i reprodukciju",
"playlist": "Dozvoli gostima da kontroliraju playlistu"
},
"set-permission": "Promijeni dozvolu za upravljanje",
"status": {
"disconnected": "Odspojen",
"guest": "Spojen kao Gost",
"host": "Spojen kao Host"
}
},
"name": "Glazba Zajedno [Beta]",
"toast": {
"add-song-failed": "Neuspješno dodavanje pjesme",
"closed": "Glazba Zajedno je zatvorena",
"disconnected": "Glazba Zajedno je odspojena",
"host-failed": "Neuspješno hostanje Glazbe Zajedno",
"id-copied": "ID hosta kopiran u međuspremnik",
"id-copy-failed": "Neuspješno kopiranje ID-a hosta u međuspremnik",
"join-failed": "Neuspješno pridruživanje Glazbi Zajedno",
"joined": "Pridružen Glazbi Zajedno",
"permission-changed": "Dozvola Glazbe Zajedno promijenjena na '{{permission}}'",
"remove-song-failed": "Neuspješno uklanjanje pjesme",
"user-connected": "{{name}} se pridružio Glazbi Zajedno",
"user-disconnected": "{{name}} je napustio Glazbu Zajedno"
}
},
"navigation": {
"description": "Naprijed/Nazad navigacijske strelice su izravno integrirane u sučelje, kao i u vašem omiljenom pregledniku",
"name": "Navigacija",
"templates": {
"back": {
"title": "Vrati se na prijethodnu stranicu"
},
"forward": {
"title": "Idi na sljedeću stranicu"
}
}
},
"no-google-login": {
"description": "Ukloni Google prijavne gumbe i linkove iz sučelja",
"name": "Nema Google Prijave"
},
"notifications": {
"description": "Prikažite obavijest kada pjesma počne svirati (interaktivne obavijesti dostupne su na Windowsu)",
"menu": {
"interactive": "Interaktivne Obavijesti",
"interactive-settings": {
"label": "Interaktivne Postavke",
"submenu": {
"hide-button-text": "Sakrij tekst gumba",
"refresh-on-play-pause": "Osvježi pri Reprodukciji/Pauzi",
"tray-controls": "Otvori/Zatvori klikom na traku"
}
},
"priority": "Prioritet Obavijesti",
"toast-style": "Tipa Toast (kao Android obavjest)",
"unpause-notification": "Prikaži notifikaciju pri nastavku"
},
"name": "Obavijesti"
},
"performance-improvement": {
"description": "Poboljšati performanse uključivanjem eksperimentalnih skripti",
"name": "Poboljšanje performansa (Beta)"
},
"picture-in-picture": {
"description": "Dozvoljava aplikaciji da se prebaci u režim slike-u-slici",
"menu": {
"always-on-top": "Uvijek na vrhu",
"hotkey": {
"label": "Prečac",
"prompt": {
"keybind-options": {
"hotkey": "Prečac"
},
"label": "Odaberi prečac za prebacivanje u režim slike-u-slici",
"title": "Prečac za režim slike-u-slici"
}
},
"save-window-position": "Sačuvaj mjesto prozora",
"save-window-size": "Sačuvaj veličinu prozora",
"use-native-pip": "Koristi izvorni režim slike-u-slici za pretraživače"
},
"name": "Slika-u-slici",
"templates": {
"button": "Slika-u-slici"
}
},
"playback-speed": {
"description": "Slušajte brzo, slušajte sporo! Ovo će dodat klizač koji kontrolira brzinu pjesme",
"name": "Brzina pokretanja",
"templates": {
"button": "Brzina"
}
},
"precise-volume": {
"description": "Precizno kontrolirajte jačinu zvuka korištenjem točkih na mišu/prečaca, sa prilagođenim sučeljem i prilagodivim stupnjevima jačine",
"menu": {
"arrows-shortcuts": "Lokalne kontrole tipkih sa strelicama",
"custom-volume-steps": "Postavi prilagođene stope za promjenu jačine",
"global-shortcuts": "Globalni prečaci"
},
"name": "Precizna jačina zvuka",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Snizi jačinu zvuka",
"increase": "Pojačaj jačinu zvuka"
},
"label": "Odaberi globalne prečace na tipkovnici za jačinu zvuka:",
"title": "Globalni prečaci na tipkovnici za jačinu zvuka"
},
"volume-steps": {
"label": "Odaberi stope za povišenje/sniženje jačine zvuka",
"title": "Stope za promjenu jačine zvuka"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Trenutni kvalitet: {{quality}}",
"message": "Odaberi kvalitet videa:",
"title": "Odaberi kvalitet videa"
}
}
},
"description": "Dozvoljava promjenu kvaliteta videa pomoću gumba na video preklopu",
"name": "Promjena kvalitete videa",
"renderer": {
"quality-settings-button": {
"label": "Otvori izbornik za promjenu kvalitete pokretača"
}
}
},
"scrobbler": {
"description": "Dodaj podršku za 'četkanje' (poput last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Autentifikacija sa Last.fm nije uspjela.\nZatvori skočni prozor do sljedećeg ponovnog pokretanja.",
"title": "Autentifikacija je neuspješna"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Postavke za Last.fm API"
},
"listenbrainz": {
"token": "Unesi korisnički žeton za ListenBrainz"
},
"scrobble-alternative-artist": "Izaberite druge glazbenike",
"scrobble-alternative-title": "Koristi alternativne naslove",
"scrobble-other-media": "Učetkaj druge medije"
},
"name": "Četkarnik",
"prompt": {
"lastfm": {
"api-key": "Last.fm API ključ",
"api-secret": "Last.fm API tajna"
},
"listenbrainz": {
"token": {
"label": "Unesi svoj ListenBrainz korisnički žeton:",
"title": "ListenBrainz žeton"
}
}
}
},
"shortcuts": {
"description": "Dozvoljava postavljanje globalnih prečaca na tipkovnici za reproduciranje (pokreni/zaustavi/sljedeće/prijethodno) i isključivanje OSD-a za medije tako što će prebrisati tipke za medije, uključiti Ctrl/CMD + F za pretragu, isključiti MPRIS podršku za medijske tipke na Linux-u, i prilagođene prečace za napredne korisnike",
"menu": {
"override-media-keys": "Prebriši medijske tipke",
"set-keybinds": "Postavi globalne kontrole za pjesme"
},
"name": "Prečaci (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Sljedeće",
"play-pause": "Pokreni / Zaustavi",
"previous": "Prijethodno"
},
"label": "Odaberi globalne prečace za upravljanje pjesmama:",
"title": "Globalni prečaci na tipkovnici"
}
}
},
"skip-disliked-songs": {
"description": "Preskače pjesme koje vam se ne sviđaju",
"name": "Preskočite pjesme koje vam se ne sviđaju"
},
"skip-silences": {
"description": "Automatski preskočite dijelove pjesama gdje nema zvuka",
"name": "Preskoči tišine"
},
"sponsorblock": {
"description": "Automatski preskače dijelove pjesama koji nisu glazba poput uvoda/odjave ili dijelove spotova u kojima nema muzike",
"name": "Bloker sponzora"
},
"synced-lyrics": {
"description": "Obezbjeđava sinkronizirane lirike pjesama, korištenjem dobavljača poput LRClib.",
"errors": {
"fetch": "⚠️\tDošlo je do greške prilikom dobavljanja stihova pjesme.\n\tMolimo vas da probate ponovno kasnije.",
"not-found": "⚠️ Tekst ove pjesme nije pronađen."
},
"menu": {
"default-text-string": {
"label": "Zadani karakteri između stihova pjesama",
"tooltip": "Odaberi zadane karaktere koji će biti korišteni za razmake između stihova pjesama"
},
"line-effect": {
"label": "Efekt crte",
"submenu": {
"fancy": {
"label": "Kitnjast",
"tooltip": "Koristi velike (kao iz aplikacije) efekte na trenutnu crtu"
},
"focus": {
"label": "Fokus",
"tooltip": "Pretvorite samo trenutnu crtu bijelu"
},
"offset": {
"label": "Izmak",
"tooltip": "Izmak na trenutnoj crti"
},
"scale": {
"label": "Razmjera",
"tooltip": "Promjeni razmjeru trenutne crte"
}
},
"tooltip": "Odaberi efekt koji će biti primijenjen na trenutnoj crti"
},
"precise-timing": {
"label": "Napravi da tekst pjesme bude izvrsno usklađen",
"tooltip": "Izračunaj do milisekunde prikaz sljedeće crteteksta (može malo utjecati na učinak)"
},
"preferred-provider": {
"label": "Željeni davatelj usluga",
"none": {
"label": "Nema / prazno",
"tooltip": "Nema željenog pružatelja usluga"
},
"tooltip": "Odaberite zadanog pružatelja usluga kojeg ćete koristiti"
},
"romanization": {
"label": "Romanizuj stihove pjesama",
"tooltip": "Ako je tekst pjesme na drugom jeziku, probajte ga prikazati na latinici."
},
"show-lyrics-even-if-inexact": {
"label": "Prikaži tekst pjesme čak i ako je netačan",
"tooltip": "Ako pjesma nije pronađena, produžetak će probati ponovno sa novim upitom za pretragu.\nRezultat iz drugog pokušaja možda neće biti tačan."
},
"show-time-codes": {
"label": "Prikaži vremenske oznake",
"tooltip": "Prikaži vremenske oznake pored teksta pjesme"
}
},
"name": "Sinkronizirani stihovi pjesama",
"refetch-btn": {
"fetching": "Dobavljanje...",
"normal": "Ponovo dobavite tekst pjesme"
},
"warnings": {
"duration-mismatch": "⚠️ - Tekst pjesme možda nije usklađen zbog neuklapanja u daljini trajanja.",
"inexact": "⚠️ - Tekst ove pjesme možda nije točan",
"instrumental": "⚠️ - Ovo je instrumentalna glazba"
}
},
"taskbar-mediacontrol": {
"description": "Upravljajte reprodukcijom iz Windows radne trake",
"name": "Upravljanje medijima iz radne trake"
},
"touchbar": {
"description": "Dodaje dodatak dodirne trake za macOS korisnike",
"name": "Dodirna Traka"
},
"transparent-player": {
"description": "Učinit će prozor aplikacije prozirnim",
"menu": {
"opacity": {
"label": "Neprozirnost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Vrsta",
"submenu": {
"acrylic": "Akrilni",
"mica": "Tinjac",
"none": "Nema / prazno",
"tabbed": "U karticama"
}
}
},
"name": "Prozirni Svirač"
},
"tuna-obs": {
"description": "Integracija sa OBS-ovim Tuna dodatkom",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Onemogućava pokretaču da iskoči u toku pokretanja pjesme",
"name": "Nenametljivi pokretač"
},
"video-toggle": {
"description": "Dodaje gumb za šaltanje između režima za video/numeru. Dodatno, može da ukloni cijelu karticu sa videom",
"menu": {
"align": {
"label": "Izravnanje",
"submenu": {
"left": "Lijevo",
"middle": "Sredina",
"right": "Desno"
}
},
"force-hide": "Nasilno uklonite karticu sa videom",
"mode": {
"label": "Režim",
"submenu": {
"custom": "Prilagođeno šaltanje",
"disabled": "Isključeno",
"native": "Izvorno šaltanje"
}
}
},
"name": "Video šaltanje",
"templates": {
"button-song": "Pjesma",
"button-video": "Video"
}
},
"visualizer": {
"description": "Dodaje vizualizator u plejer",
"menu": {
"visualizer-type": "Tip vizualizacije"
},
"name": "Vizualizacija"
}
}
}
================================================
FILE: src/i18n/resources/hu.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Nem sikerült futtatni a bővítményt {{pluginName}}::{{contextName}}",
"executed-at-ms": "A {{pluginName}}::{{contextName}} bővítmény végrehajtva {{ms}} ms alatt",
"initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" bővítményt",
"load-all": "Összes bővítmény betöltése",
"load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" bővítményt",
"loaded": "\"{{pluginName}}\" bővítmény betöltve",
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt kikapcsolni",
"unloaded": "A \"{{pluginName}}\" bővítmény kikapcsolva"
}
}
},
"language": {
"code": "hu",
"local-name": "Magyar",
"name": "Hungarian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Betöltés befejezve. DevTools megnyitva"
},
"i18n": {
"loaded": "i18n betöltve"
},
"second-instance": {
"receive-command": "Fogadott parancs a protokollon keresztül: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS fájl \"{{cssFile}}\" nem létezik, figyelmen kívül hagyva"
},
"unresponsive": {
"details": "Nem válaszol!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Alkalmazás gyorsítótárának törlése"
},
"window": {
"tried-to-render-offscreen": "Az ablak a képernyőn kívül próbált betölteni, ablakMéret={{windowSize}}, kijelzőMéret={{displaySize}}, pozíció={{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}}\" bővítmény bekapcsolása az alkalmazás újraindítását igényli",
"message": "\"{{pluginName}}\" nevű bővítményt újra kell indítani",
"title": "Újraindítás szükséges"
},
"unresponsive": {
"buttons": {
"quit": "Kilépés",
"relaunch": "Újraindítás",
"wait": "Várj"
},
"detail": "Elnézést a kellemetlenségért! Válaszdd ki mi történjen:",
"message": "Az alkalmazás nem válaszol",
"title": "Az ablak nem válaszol"
},
"update-available": {
"buttons": {
"disable": "Frissítések kikapcsolása",
"download": "Letöltés",
"ok": "OK"
},
"detail": "Új verzió elérhető, amely letölthető az alábbi linken {{downloadLink}}",
"message": "Új verzió áll rendelkezésre",
"title": "Frissítés elérhető"
}
},
"menu": {
"about": "Névjegy",
"navigation": {
"label": "Navigáció",
"submenu": {
"copy-current-url": "Jelenlegi URL másolása",
"go-back": "Vissza",
"go-forward": "Előre",
"quit": "Kilépés",
"restart": "Alkalmazás újraindítása"
}
},
"options": {
"label": "Beállítások",
"submenu": {
"advanced-options": {
"label": "Speciális beállítások",
"submenu": {
"auto-reset-app-cache": "Alkalmazás gyorsítótárának törlése indításkor",
"disable-hardware-acceleration": "Hardveres gyorsítás kikapcsolása",
"edit-config-json": "config.json szerkesztése",
"override-user-agent": "Kliens felülírása",
"restart-on-config-changes": "Újraindítás a konfigurációs változtatás után",
"set-proxy": {
"label": "Proxy beállítása",
"prompt": {
"label": "Proxy cím megadása: (Hagyja üresen a kikapcsoláshoz)",
"placeholder": "Példa: SOCKS5://127.0.0.1:9999",
"title": "Proxy beállítása"
}
},
"toggle-dev-tools": "Fejlesztőeszközök BE/KI"
}
},
"always-on-top": "Mindig látható",
"auto-update": "Automatikus frissítés",
"hide-menu": {
"dialog": {
"message": "A menü a következő indításnál rejtve lesz, használja az [Alt] billentyűt a megjelenítéséhez (vagy a backtick [`] billentyűt, ha az alkalmazás belső menüjét használja)",
"title": "Menü elrejtés engedélyezve"
},
"label": "Menü elrejtése"
},
"language": {
"dialog": {
"message": "A nyelv az allkalmazás újraindítása után megváltozik",
"title": "Nyelv megváltoztatva"
},
"label": "Nyelv",
"submenu": {
"to-help-translate": "Szeretnél segíteni a fordításban? Kattints ide"
}
},
"resume-on-start": "Zene folytatása az alkalmazás indításakor",
"single-instance-lock": "Csak egy példány",
"start-at-login": "Futtatás rendszerindításkor",
"starting-page": {
"label": "Induláskor",
"unset": "Visszaállítás"
},
"tray": {
"label": "Tálca ikon",
"submenu": {
"disabled": "Letiltva",
"enabled-and-hide-app": "Engedélyezve és alkalmazás elrejtése",
"enabled-and-show-app": "Engedélyezve és alkalmazás megjelenítése",
"play-pause-on-click": "Lejátszás/Szünet az ikonra kattintással"
}
},
"visual-tweaks": {
"label": "Megjelenési beállítások",
"submenu": {
"custom-window-title": {
"label": "Saját ablak cím",
"prompt": {
"label": "Kérem az egyéni ablak címét: (hagyd üresen a kikapcsoláshoz)",
"placeholder": "Példa: {{applicationName}}"
}
},
"like-buttons": {
"default": "Alapértelmezett",
"force-show": "Megjelenítés kényszerítése",
"hide": "Elrejtése",
"label": "Reakció gombok"
},
"remove-upgrade-button": "Előfizetés gombjának eltávolítása",
"theme": {
"dialog": {
"button": {
"cancel": "Mégse",
"remove": "Eltávolít"
},
"remove-theme": "Biztos, hogy el szeretnéd távolítani az egyéni témát?",
"remove-theme-message": "Ez eltávolítja az egyéni témát"
},
"label": "Téma",
"submenu": {
"import-css-file": "Egyéni CSS fájl importálása",
"no-theme": "Nincs téma"
}
}
}
}
}
},
"plugins": {
"enabled": "Bekapcsolva",
"label": "Bővítmények",
"new": "ÚJ"
},
"view": {
"label": "Nézet",
"submenu": {
"force-reload": "Kényszerített újratöltés",
"reload": "Újratöltés",
"reset-zoom": "Alapértelmezett méret visszaállítása",
"toggle-fullscreen": "Teljes képernyő be/ki",
"zoom-in": "Szöveg nagyítása",
"zoom-out": "Szöveg kicsinyítése"
}
}
},
"tray": {
"next": "Következő",
"play-pause": "Lejátszás/Szünet",
"previous": "Előző",
"quit": "Kilépés",
"restart": "YT Music újraindítása",
"show": "Ablak megjelenítése",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Ha egy hirdetés elindul, elnémítja a hangot, és a lejátszási sebességet 16x-ra állítja",
"name": "Hirdetésgyorsítás"
},
"adblocker": {
"description": "Alapértelmezetten minden hirdetés és nyomkövetés blokkolása",
"menu": {
"blocker": "Blokkolási módszer"
},
"name": "Reklámblokkoló"
},
"album-actions": {
"description": "Hozzáadja a Tetszik, Nem tetszik és ezek visszavonására szolgáló gombokat, hogy ezeket az összes dalra alkalmazhasd egy lejátszási listán vagy albumban",
"name": "Album műveletek"
},
"album-color-theme": {
"description": "Dinamikus témát és vizuális effekteket alkalmaz az album színpalettája alapján",
"menu": {
"color-mix-ratio": {
"label": "Színkeverés mértéke",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Album színtéma"
},
"ambient-mode": {
"description": "Fényhatás effektust alkalmaz, amely a videóból származó lágy színeket vetíti a képernyő hátterére",
"menu": {
"blur-amount": {
"label": "Elmosódás mértéke",
"submenu": {
"pixels": "{{blurAmount}} pixel"
}
},
"buffer": {
"label": "Puffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Átlátszóság",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Minőség",
"submenu": {
"pixels": "{{quality}} pixel"
}
},
"size": {
"label": "Méret",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Sima átmenet",
"submenu": {
"during": "{{interpolationTime}} másodperc alatt"
}
},
"use-fullscreen": {
"label": "Teljes képernyő használata"
}
},
"name": "Ambient mód"
},
"amuse": {
"description": "Hozzáadja a {{applicationName}} támogatását az Amuse \"now playing\" widgethez a 6K Labs által",
"name": "Amuse",
"response": {
"query": "Az Amuse API szerver fut. Használja a GET /query kérést a dalinformációk lekéréséhez."
}
},
"api-server": {
"description": "Hozzáad egy API szervert a lejátszó vezérléséhez",
"dialog": {
"request": {
"buttons": {
"allow": "Engedélyez",
"deny": "Megtagad"
},
"message": "Engedélyezi, hogy {{ID}} ({{origin}}) hozzáférjen az API-hoz?",
"title": "API-hozzáférési kérelem"
}
},
"menu": {
"auth-strategy": {
"label": "Engedélyezési módszer",
"submenu": {
"auth-at-first": {
"label": "Engedélyezés az első kérésnél"
},
"none": {
"label": "Nincs engedélyezés"
}
}
},
"hostname": {
"label": "Kiszolgáló név"
},
"port": {
"label": "Port"
}
},
"name": "API szerver [Béta]",
"prompt": {
"hostname": {
"label": "Adja meg az API szerver kiszolgáló nevét (például 0.0.0.0):",
"title": "Kiszolgáló neve"
},
"port": {
"label": "Adja meg az API szerver portját:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Hang tömörítés alkalmazása (csökkenti a jel legzajosabb részeinek hangerősségét, és emeli a legcsendesebb részek hangerősségét)",
"name": "Hangtömörítő"
},
"auth-proxy-adapter": {
"menu": {
"disable": "Proxy adapder kikapcsolása",
"enable": "Proxy adapter bekapcsolása",
"hostname": {
"label": "Gazdanév"
},
"port": {
"label": "Port"
}
},
"prompt": {
"hostname": {
"label": "Adjon meg egy hosztnevet a lokális proxy szerverhez (újraindítást igényel):",
"title": "Proxy hosztnév"
},
"port": {
"label": "Adjon meg egy portot a lokális proxy szerverhez (újraindátást igényel):",
"title": "Proxy Port"
}
}
},
"blur-nav-bar": {
"description": "Átlátszóvá és elmosódottá teszi a navigációs sávot",
"name": "Navigációs sáv elmosása"
},
"bypass-age-restrictions": {
"description": "A Music Player korellenőrzését kihagyja, ezáltal nem kel meg erősíteni a zene meghallgatása elött. (Automatikusan megerősítve lesz.)",
"name": "Korellenőrzés kihagyása"
},
"captions-selector": {
"description": "Felirat választó a {{applicationName}} zenékhez",
"menu": {
"autoload": "Automatikusan kiválasztja az utoljára használt feliratot",
"disable-captions": "Alapértelmezetten nincsenek feliratok"
},
"name": "Feliratválasztó",
"prompt": {
"selector": {
"label": "Jelenlegi feliratnyelv: {{language}}",
"none": "Nincs",
"title": "Felirat nyelvének kiválasztása"
}
},
"templates": {
"title": "Feliratválasztó megnyitása"
},
"toast": {
"caption-changed": "Felirat {{language}} nyelvűre állítva",
"caption-disabled": "Feliratok kikapcsolva",
"no-captions": "Nincsenek elérhető feliratok ehhez a dalhoz"
}
},
"compact-sidebar": {
"description": "Mindig becsukva tartja a bal oldali savót, ahol a Kezdőlap. Felfedezés, Könyvtár és egyebek láthatók. (Amit bármikor ki lehet nyitni)",
"name": "Kompakt oldalsáv"
},
"crossfade": {
"description": "Áttünést biztosít a dalok között, ami folytonossá teszi a zenehallgatást anélkül, hogy érezhető lenne a váltás",
"menu": {
"advanced": "Haladó"
},
"name": "Áttünés [Béta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Áttünés időtartama (ms)",
"fade-out-duration": "Fokozatos halkítás időtartama (ms)",
"fade-scaling": {
"label": "Áttünés mértéke",
"linear": "Lineáris",
"logarithmic": "Logaritmikus"
},
"seconds-before-end": "Áttünés N másodperccel a vége előtt"
},
"title": "Áttünési beállítások"
}
}
},
"custom-output-device": {
"menu": {
"device-selector": "Eszköz kiválasztása"
},
"name": "Saját kimeneti eszköz",
"prompt": {
"device-selector": {
"label": "Válaszd ki a kimeneti eszközt",
"title": "Kimeneti eszköz választása"
}
}
},
"disable-autoplay": {
"description": "Ez a funkció kikapcsolja az automatikus lejátszást, így a zenék nem indulnak el maguktól. Amikor egy album vagy egy dal lejátszása véget ér, a következő szám nem kezdődik el automatikusan. A bővítmény használata során minden zenét manuálisan kell elindítani",
"menu": {
"apply-once": "Csak induláskor alkalmazza"
},
"name": "Automatikus lejátszás letiltása"
},
"discord": {
"backend": {
"already-connected": "Kapcsolódás kísérlete aktív kapcsolattal",
"connected": "Kapcsolódva a Discord-hoz",
"disconnected": "Kapcsolat bontva a Discord-al"
},
"description": "Mutassa meg barátainak, hogy mit hallgat a Rich Presence segítségével. (Ehez a Discord-on is engedélyezve kel lennie a Tevékenységállapot megosztásának [DC Beállítások -> Tevékenyég-adatvédelem -> Megoszthatod az észlelt tevékenységeidet másokkal])",
"menu": {
"auto-reconnect": "Automatikus újracsatlakozás",
"clear-activity": "Tevékenység törlése",
"clear-activity-after-timeout": "Tevékenység törlése időkorlát után",
"connected": "Kapcsolódva",
"disconnected": "Nincs Kapcsolódva",
"hide-duration-left": "Hátralévő idő elrejtése",
"hide-github-button": "GitHub url gombjának elrejtése",
"play-on-application": "Lejátszás a {{applicationName}}-on",
"set-inactivity-timeout": "Inaktivitási időkorlát beállítása",
"set-status-display-type": {
"label": "Tevékenység szöveg",
"submenu": {
"artist": "Hallgatja: {artist}",
"application": "Hallgatja: {{applicationName}}",
"title": "Hallgatja: {song title}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Írja be az inaktivitási időkorlátot másodpercben:",
"title": "Inaktivitási időkorlát beállítása"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Rendben"
},
"message": "Hoppá! Elnézést, a letöltés sikertelen volt…",
"title": "A letöltés során hiba történt!"
},
"start-download-playlist": {
"buttons": {
"ok": "Rendben"
},
"detail": "({{playlistSize}} dal)",
"message": "A(z) {{playlistTitle}} lejátszási lista letöltése",
"title": "A letöltés elindult"
}
},
"feedback": {
"conversion-progress": "Konvetálás: {{percent}}%",
"converting": "Konvertálás…",
"done": "Kész: {{filePath}}",
"download-info": "Letöltés: {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Letöltés: {{percent}}%",
"downloading": "Letöltés folyamatban…",
"downloading-counter": "Letöltés: {{current}}/{{total}}…",
"downloading-playlist": "Letöltés a lejátszási listáról \"{{playlistTitle}}\" - {{playlistSize}} dal ({{playlistId}})",
"error-while-downloading": "Hiba a \"{{author}} - {{title}}\" letöltésekor: {{error}}",
"folder-already-exists": "A {{playlistFolder}} nevű mappa már létezik",
"getting-playlist-info": "Lejátszási lista információinak lekérése…",
"loading": "Betöltés…",
"playlist-has-only-one-song": "A lejátszási listában csak egy elem van, letöltés közvetlenül",
"playlist-id-not-found": "Nem található lejátszási lista azonosítója",
"playlist-is-empty": "Lejátszási lista üres",
"playlist-is-mix-or-private": "Hiba a lejátszási lista információinak lekérésekor: győződjön meg róla, hogy nem privát vagy \"Saját egyveleg\" lejátszási lista\n\n{{error}}",
"preparing-file": "Fájl előkészítése…",
"saving": "Mentés…",
"trying-to-get-playlist-id": "Playlist ID lekérése: {{playlistId}}",
"video-id-not-found": "Videó nem található",
"writing-id3": "ID3 címkék írása…"
}
},
"description": "MP3 / forrás hanganyag letöltése közvetlenül az interfészről",
"menu": {
"choose-download-folder": "Letöltési mappa kiválasztása",
"download-finish-settings": {
"label": "Letöltés befejezéskor",
"prompt": {
"last-percent": "x százalék után",
"last-seconds": "Utolsó x másodperc",
"title": "Letöltés idejének beállítása"
},
"submenu": {
"advanced": "Speciális",
"enabled": "Engedélyezve",
"mode": "Időmód",
"percent": "Százalék",
"seconds": "Másodpercek"
}
},
"download-playlist": "Lejátszási lista letöltése",
"presets": "Sablonok",
"skip-existing": "Meglévő fájlok kihagyása"
},
"name": "Letöltő",
"renderer": {
"can-not-update-progress": "A haladást nem lehet frissíteni"
},
"templates": {
"button": "Letöltés"
}
},
"equalizer": {
"description": "Hangszínszabályzót ad hozzá a zenelejátszóhoz",
"menu": {
"presets": {
"label": "Hangprofil",
"list": {
"bass-booster": "Basszuskiemelés"
}
}
},
"name": "Hangszínszabályzó"
},
"exponential-volume": {
"description": "Az hangerő csúszka exponenciálissá tételével könnyebbé válik az alacsony hangerő kiválasztása.",
"name": "Exponenciális hangerő"
},
"in-app-menu": {
"description": "Menüsávok stílusos, sötét vagy album-színű megjelenítése",
"menu": {
"hide-dom-window-controls": "DOM ablakvezérlők elrejtése"
},
"name": "Alkalmazáson belüli menü"
},
"lumiastream": {
"description": "Lumia Stream támogatás hozzáadása",
"name": "Lumia Stream [Béta]"
},
"lyrics-genius": {
"description": "Dalszöveg támogatást ad a legtöbb dalhoz",
"menu": {
"romanized-lyrics": "Latin betűs dalszövegek"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Dalszövegek lekérése a Genius-ról"
}
},
"music-together": {
"description": "Lehetővé teszi a lejátszási listák megosztását másokkal. Amikor a házigazda lejátszik egy dalt, mindenki más is ugyanazt a dalt fogja hallani",
"dialog": {
"enter-host": "Adja meg a házigazda azonosítóját"
},
"internal": {
"save": "Mentés",
"track-source": "Zeneszám forrása",
"unknown-user": "Ismeretlen felhasználó"
},
"menu": {
"click-to-copy-id": "Házigazda azonosítójának másolása",
"close": "Zene együtt bezárása",
"connected-users": "Csatlakozott felhasználók",
"disconnect": "Zene együtt kapcsolatának megszakítása",
"empty-user": "Nincs csatlakozva felhasználó",
"host": "Music Together Házigazda",
"join": "Csatlakozás a Zene együtt-höz",
"permission": {
"all": "Engedélyezi a vendégeknek a lejátszási lista és a lejátszó vezérlését",
"host-only": "Csak a házigazda tudja vezérelni a lejátszási listát és a lejátszót",
"playlist": "Engedélyezi a vendégeknek a lejátszási lista vezérlését"
},
"set-permission": "Vezérlési engedély módosítása",
"status": {
"disconnected": "Kapcsolat bontva",
"guest": "Csatlakozva vendégként",
"host": "Csatlakozva házigazdaként"
}
},
"name": "Zene együtt [Béta]",
"toast": {
"add-song-failed": "Sikertelen volt a dal hozzáadása",
"closed": "Zene együtt bezárva",
"disconnected": "Kapcsolat megszakadt a Music Together-el",
"host-failed": "Sikertelen volt a Zene együtt indítása",
"id-copied": "Házigazda azonosító a vágólapra másolva",
"id-copy-failed": "Nem sikerült a Házigazda azonosítóját a vágólapra másolni",
"join-failed": "Nem sikerült csatlakozni a Music Together-hez",
"joined": "Csatlakozott a Music Together-hez",
"permission-changed": "Music Together engedély megváltoztatva \"{{permission}}\" -re",
"remove-song-failed": "A dal eltávolítása sikertelen",
"user-connected": "{{name}} csatlakozott a Music Together-hez",
"user-disconnected": "{{name}} elhagyta a Music Together-t"
}
},
"navigation": {
"description": "Következő/Vissza navigációs nyilak közvetlenül az interfészbe integrálva, mint a kedvenc böngésződben",
"name": "Navigáció",
"templates": {
"back": {
"title": "Előző oldal"
},
"forward": {
"title": "Következő oldal"
}
}
},
"no-google-login": {
"description": "A Bejelentkezés gomb eltávolítása az interfészről (Jobb fentről eltünik a bejelentkezés gomb.)",
"name": "Nincs Google bejelentkezés"
},
"notifications": {
"description": "Értesítés megjelenítése, amikor egy dal elindul (interaktív értesítések elérhetők Windows-on)",
"menu": {
"interactive": "Interaktív Értesítések",
"interactive-settings": {
"label": "Interaktív beállítások",
"submenu": {
"hide-button-text": "Gombok szövegének elrejtése",
"refresh-on-play-pause": "Frissítés lejátszás/szünet megnyomásakor",
"tray-controls": "Megnyitás/Bezárás tálca ikonra kattintva"
}
},
"priority": "Értesítési prioritás",
"toast-style": "Értesítés stílusa",
"unpause-notification": "Értesítés megjelenítése a lejátszás folytatásakor"
},
"name": "Értesítések"
},
"performance-improvement": {
"description": "Teljesítmény fejlesztése kísérleti kódok engedélyezésével",
"name": "Teljesítmény fejlesztése [Béta]"
},
"picture-in-picture": {
"description": "Lehetővé teszi az alkalmazás kép a képben módra váltását",
"menu": {
"always-on-top": "Mindig látható",
"hotkey": {
"label": "Gyorsbillentyű",
"prompt": {
"keybind-options": {
"hotkey": "Gyorsbillentyű"
},
"label": "Válassz egy gyorsbillentyűt a kép a képben mód váltásához",
"title": "Kép a képben gyorsbillentyű"
}
},
"save-window-position": "Ablakpozíciójának mentése",
"save-window-size": "Ablakméretének mentése",
"use-native-pip": "A böngésző natív PiP(Kép a képben) használata"
},
"name": "Kép a képben",
"templates": {
"button": "Kép a képben"
}
},
"playback-speed": {
"description": "Hallgassd gyorsan, hallgassd lassan! Hozzáad egy csúszkát, amely szabályozza a dal sebességét",
"name": "Lejátszás sebessége",
"templates": {
"button": "Sebesség"
}
},
"precise-volume": {
"description": "A hangerő precíz szabályozása egérgörgővel/gyorsbillentyűkkel, egy egyedi HUD és testreszabható hangerő csuszka segítségével",
"menu": {
"arrows-shortcuts": "Helyi nyíl-billentyűkkel való vezérlés",
"custom-volume-steps": "Egyedi hangerőléptetés beállítása",
"global-shortcuts": "Globális Gyorsbillentyűk"
},
"name": "Precíz hangerő",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Hangerő csökkentése",
"increase": "Hangerő növelése"
},
"label": "Válaszd ki a globális hangerő gyorsbillentyűket:",
"title": "Globális hangerő gyorsbillentyűk"
},
"volume-steps": {
"label": "Hangerő növelés/csökkentés léptékének kiválasztása",
"title": "Hangerő lépték"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Jelenlegi minőség: {{quality}}",
"message": "Válaszd ki a videó minőségét:",
"title": "Válaszd ki a videó minőségét"
}
}
},
"description": "Lehetővé teszi a videó minőségének megváltoztatását egy gombbal a videó fedvényen",
"name": "Videóminőség módosító"
},
"scrobbler": {
"description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm hitelesítése nem sikerült\nA felugró ablak elrejtése a következő újraindításig.",
"title": "Hitelesítés sikertelen"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API beállítások"
},
"listenbrainz": {
"token": "Add meg a ListenBrainz felhasználói tokenedet"
},
"scrobble-alternative-title": "Alternatív címek használata",
"scrobble-other-media": "Más média scrobbelése"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last.fm API kulcs",
"api-secret": "Last.fm titkos API kulcs"
},
"listenbrainz": {
"token": {
"label": "Add meg a ListenBrainz felhasználói tokenedet:",
"title": "ListenBrainz kulcs"
}
}
}
},
"shortcuts": {
"description": "Lehetővé teszi globális gyorsbillentyűk beállítását a lejátszáshoz (lejátszás/szünet/következő/előző), valamint a média OSD kikapcsolását a médiagombok felülírásával. Bekapcsolja a Ctrl/CMD + F billentyűkombinációt a kereséshez, a Linux MPRIS támogatását a médiagombokhoz, és egyedi gyorsbillentyűket a haladó felhasználók számára",
"menu": {
"override-media-keys": "Médiagombok felülírása",
"set-keybinds": "Globális zenevezérlők beállítása"
},
"name": "Gyorsbillentyűk (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Következő",
"play-pause": "Lejátszás / Szünet",
"previous": "Előző"
},
"label": "Globális billentyűparancsok választása a dalok vezérléséhez:",
"title": "Globális gyorsbillentyűk"
}
}
},
"skip-disliked-songs": {
"description": "Kihagyja a nem kedvelt dalokat",
"name": "Nem kedvelt dal kihagyása"
},
"skip-silences": {
"description": "Automatikusan kihagyja a csendes részeket a dalokban",
"name": "Csend kihagyása"
},
"sponsorblock": {
"description": "Automatikusan kihagyja a nem zenés részeket, mint például az intro/outro vagy a zenei videók olyan részeit, ahol a zene nem szól",
"name": "SzponzorBlokk"
},
"synced-lyrics": {
"description": "Szinkronizált dalszövegeket biztosít dalokhoz, LRClib-hez hasonló szolgáltatókat használva.",
"errors": {
"fetch": "⚠️\tHiba történt a dalszöveg lekérése közben.\n\tKérjük, próbálja meg később újra.",
"not-found": "⚠️ - Ehhez a dalhoz nem található dalszöveg."
},
"menu": {
"default-text-string": {
"label": "Alapértelmezett karakter a dalszövegek között",
"tooltip": "Válassza ki az alapértelmezett karaktert, amelyet a dalszövegek közötti szünethez használni szeretne"
},
"line-effect": {
"label": "Soreffekt",
"submenu": {
"fancy": {
"label": "Díszes",
"tooltip": "Használj nagy, alkalmazásszerű effektusokat az aktuális sorhoz"
},
"focus": {
"label": "Fókuszált",
"tooltip": "Az aktuális sor kijelőlése fehérrel"
},
"offset": {
"label": "Eltolás",
"tooltip": "Az aktuális sort jobbra tolja. (mintha tabulálnád)"
},
"scale": {
"label": "Méretezett",
"tooltip": "Az aktuális sort kissé nagyobbra méretezi, kiemelve azt a többi sor közül"
}
},
"tooltip": "Válassza ki az aktuális sorra alkalmazandó effektust"
},
"precise-timing": {
"label": "Dalszöveg tökéletes szinkronizálása",
"tooltip": "Számítsa ki az aktuális sor megjelenítésének idejét ezredmásodperc pontossággal (ez kis mértékben befolyásolhatja a teljesítményt)"
},
"romanization": {
"label": "Latin betűs szöveg",
"tooltip": "Idegennyelvű szöveg esetén próbálkozás a szöveglatin betűs megjelenítésével."
},
"show-lyrics-even-if-inexact": {
"label": "Pontatlan időzítésű dalszövegek megjelenítése",
"tooltip": "Ha a dalt nem találja, a bővítmény újra próbálkozik egy másik keresési lekérdezéssel.\nAz eredmény a második próbálkozás után nem biztos, hogy pontos lesz."
},
"show-time-codes": {
"label": "Időkódok megjelenítése",
"tooltip": "Az időkódok megjelenítése a dalszövegek mellett"
}
},
"name": "Szinkronizált dalszövegek",
"refetch-btn": {
"fetching": "Lekérés folyamatban...",
"normal": "Dalszöveg újra lekérése"
},
"warnings": {
"duration-mismatch": "⚠️ - A dalszövegek időzítése eltérhet a zene hossza miatt.",
"inexact": "⚠️ - Ennek a zenének a dalszövege pontatlan lehet",
"instrumental": "⚠️ - Ez egy hangszerekkel játszott zene"
}
},
"taskbar-mediacontrol": {
"description": "Lejátszás vezérlése a Windows tálcáról",
"name": "Médiavezérlés a tálcán"
},
"touchbar": {
"description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz",
"name": "TouchBar"
},
"tuna-obs": {
"description": "Integráció az OBS Tuna pluginjával",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Megakadályozza a lejátszó felugrását zenehallgatás közben",
"name": "Rejtett lejátszó"
},
"video-toggle": {
"description": "Hozzáad egy gombot a Videó/Dal mód közötti váltáshoz. Opcionálisan teljesen eltávolíthatja a videó fület is",
"menu": {
"align": {
"label": "Igazítás",
"submenu": {
"left": "Balra",
"middle": "Középre",
"right": "Jobbra"
}
},
"force-hide": "Videó fül kényszeritett eltávolítása",
"mode": {
"label": "Mód",
"submenu": {
"custom": "Egyedi kapcsoló",
"disabled": "Letiltva",
"native": "Natív kapcsoló"
}
}
},
"name": "Videó váltó",
"templates": {
"button-song": "Zeneszám"
}
},
"visualizer": {
"description": "Vizualizációt ad a lejátszóhoz",
"menu": {
"visualizer-type": "Vizualizáció típus"
},
"name": "Vizualizáció"
}
}
}
================================================
FILE: src/i18n/resources/id.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Gagal menjalankan plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dieksekusi pada {{ms}}ms",
"initialize-failed": "Gagal dalam menginisialisasi plugin \"{{pluginName}}\"",
"load-all": "Memuat semua plugin",
"load-failed": "Gagal memuat plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" dimuat",
"unload-failed": "Gagal untuk memuat plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" telah dikeluarkan"
}
}
},
"language": {
"code": "id",
"local-name": "Bahasa Indonesia",
"name": "Indonesian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Selesai memuat. DevTools terbuka"
},
"i18n": {
"loaded": "i18n selesai dimuat"
},
"second-instance": {
"receive-command": "Menerima instruksi lewat protokol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "File CSS \"{{cssFile}}\" tidak ditemukan, 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": "Sembunyikan Menu diaktifkan",
"title": "Sembunyikan Menu Diaktifkan"
},
"need-to-restart": {
"buttons": {
"later": "Nanti",
"restart-now": "Restart Sekarang"
},
"detail": "\"{{pluginName}}\" Plugin memerlukan pengaktifan ulang agar dapat diterapkan",
"message": "\"{{pluginName}}\" harus dimulai ulang",
"title": "Restart Diperlukan"
},
"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": {
"custom-window-title": {
"label": "Judul jendela kustom",
"prompt": {
"label": "Masukkan judul jendela kustom (kosongkan untuk menonaktifkan)",
"placeholder": "Contoh: {{applicationName}}"
}
},
"like-buttons": {
"default": "Standar",
"force-show": "Pertunjukan paksa",
"hide": "Sembunyikan",
"label": "Tombol suka",
"swap": "Tukar urutan tombol suka"
},
"remove-upgrade-button": "Hapus tombol peningkatan",
"theme": {
"dialog": {
"button": {
"cancel": "Batalkan",
"remove": "Hapus"
},
"remove-theme": "Apakah kamu yakin ingin menhapus tema ini?",
"remove-theme-message": "Ini akan menghapus tema ini"
},
"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": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Jika iklan diputar, audio akan dimatikan dan kecepatan pemutaran akan diatur ke 16x",
"name": "Percepatan Iklan"
},
"adblocker": {
"description": "Blokir semua iklan dan pelacakan di luar kotak",
"menu": {
"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}}%"
}
},
"enable-seekbar": "Aktifkan tema seekbar"
},
"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"
},
"amuse": {
"description": "Menambahkan dukungan {{applicationName}} untuk widget Amuse yang sedang diputar oleh 6K Labs",
"name": "Amuse",
"response": {
"query": "Server API Amuse sedang berjalan. GET /query untuk mendapatkan info lagu."
}
},
"api-server": {
"description": "Menambahkan server API untuk mengontrol pemutar",
"dialog": {
"request": {
"buttons": {
"allow": "Izinkan",
"deny": "Menolak"
},
"message": "Izinkan {{ID}} ({{origin}}) untuk mengakses API?",
"title": "Permintaan otorisasi API"
}
},
"menu": {
"auth-strategy": {
"label": "Strategi otorisasi",
"submenu": {
"auth-at-first": {
"label": "Otorisasi pada permintaan pertama"
},
"none": {
"label": "Tidak ada otorisasi"
}
}
},
"hostname": {
"label": "Nama host"
},
"https": {
"label": "HTTPS & Sertifikat",
"submenu": {
"cert": {
"dialogTitle": "Pilih berkas sertifikat HTTPS",
"label": "Berkas sertifikat (.crt/.pem)"
},
"enable-https": {
"label": "Aktifkan HTTPS"
},
"key": {
"dialogTitle": "Pilih berkas kunci privat HTTPS",
"label": "Berkas kunci privat (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Masukkan nama host (seperti 0.0.0.0) untuk server API:",
"title": "Nama host"
},
"port": {
"label": "Masukkan port untuk server API:",
"title": "Port"
}
}
},
"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"
},
"auth-proxy-adapter": {
"description": "Dukungan untuk penggunaan layanan proxy autentikasi",
"menu": {
"disable": "Nonaktifkan Adapter Proxy",
"enable": "Aktifkan Adapter Proxy",
"hostname": {
"label": "Nama host"
},
"port": {
"label": "Port"
}
},
"name": "Adapter Proxy Autentikasi",
"prompt": {
"hostname": {
"label": "Masukkan nama host untuk server proxy lokal (memerlukan restart):",
"title": "Proxy Nama host"
},
"port": {
"label": "Masukkan port untuk server proxy lokal (memerlukan restart):",
"title": "Port Proxy"
}
}
},
"blur-nav-bar": {
"description": "Jadikan bar navigasi blur dan transparan",
"name": "Buramkan Bar Navigasi"
},
"bypass-age-restrictions": {
"description": "Lewati verifikasi umur dari Music Player",
"name": "Lewati batasan umur"
},
"captions-selector": {
"description": "Pemilih caption untuk trek audio {{applicationName}}",
"menu": {
"autoload": "Pilih caption terakhir secara otomatis",
"disable-captions": "Tidak ada caption secara default"
},
"name": "Pemilih Caption",
"prompt": {
"selector": {
"label": "Bahasa caption saat ini: {{language}}",
"none": "Tidak ada",
"title": "Pilih bahasa caption"
}
},
"templates": {
"title": "Buka pemilih caption"
},
"toast": {
"caption-changed": "caption diganti ke bahasa {{language}}",
"caption-disabled": "Caption dinonaktifkan",
"no-captions": "tidak tersedia caption untuk lagu ini"
}
},
"clock": {
"description": "Tambahkan jam ke bilah navigasi",
"menu": {
"format": {
"24-hour-format": "Format 24 jam",
"display-seconds": "Tampilkan detik",
"label": "Format"
}
},
"name": "Jam"
},
"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": "Skala fade",
"linear": "Linear",
"logarithmic": "Logaritmik"
},
"seconds-before-end": "Crossfade N detik sebelum berakhir"
},
"title": "Pilihan crossfade"
}
}
},
"custom-output-device": {
"description": "Atur perangkat media keluaran khusus untuk lagu-lagu",
"menu": {
"device-selector": "Pilih Perangkat"
},
"name": "Perangkat Keluaran Kustom",
"prompt": {
"device-selector": {
"label": "Pilih perangkat media keluaran yang akan digunakan",
"title": "Pilih Perangkat Keluaran"
}
}
},
"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": "Tunjukkan apa yang kamu dengarkan dengan Rich Presence",
"menu": {
"auto-reconnect": "Reconnect otomatis",
"clear-activity": "Hapus riwayat aktifitas",
"clear-activity-after-timeout": "Hapus aktivitas setelah timeout",
"connected": "Terhubung",
"disconnected": "Terputus",
"hide-duration-left": "Sembunyikan sisa durasi",
"hide-github-button": "Sembunyikan tombol link GitHub",
"play-on-application": "Mainkan di {{applicationName}}",
"set-inactivity-timeout": "Tetapkan batas waktu tidak aktif",
"set-status-display-type": {
"label": "Teks status",
"submenu": {
"application": "Sedang mendengarkan {{applicationName}}",
"artist": "Sedang mendengarkan {artist}",
"title": "Sedang mendengarkan {song title}"
}
}
},
"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, dowload gagal…",
"title": "Terjadi kesalahan dalam unduhan!"
},
"start-download-playlist": {
"buttons": {
"ok": "Oke"
},
"detail": "({{playlistSize}} lagu-lagu)",
"message": "Mengunduh Daftar Putar {{playlistTitle}}",
"title": "Pengunduhan 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 Daftar Putar \"{{playlistTitle}}\" - {{playlistSize}} lagu-lagu ({{playlistId}})",
"error-while-downloading": "Gagal dalam 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-finish-settings": {
"label": "Unduh setelah selesai",
"prompt": {
"last-percent": "x persen terakhir",
"last-seconds": "x detik terakhir",
"title": "Konfigurasikan kapan akan mengunduh"
},
"submenu": {
"advanced": "Lanjutan",
"enabled": "Diaktifkan",
"mode": "Mode waktu",
"percent": "Persen",
"seconds": "Detik"
}
},
"download-playlist": "Unduh daftar putar",
"presets": "Prasetel",
"skip-existing": "Lewati berkas yang sudah ada"
},
"name": "Pengunduh",
"renderer": {
"can-not-update-progress": "Tidak dapat memperbarui proses"
},
"templates": {
"button": "Unduh"
}
},
"equalizer": {
"description": "Menambahkan equalizer ke pemutar",
"menu": {
"presets": {
"label": "Prasetel",
"list": {
"bass-booster": "Penguat Bass"
}
}
},
"name": "Ekualiser"
},
"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",
"templates": {
"back": {
"title": "Kunjungi halaman sebelumnya"
},
"forward": {
"title": "pergi ke halaman berikutnya"
}
}
},
"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"
},
"performance-improvement": {
"description": "Tingkatkan kinerja dengan mengaktifkan skrip eksperimental",
"name": "Peningkatan kinerja [Beta]"
},
"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",
"renderer": {
"quality-settings-button": {
"label": "pengubah kualitas pemain terbuka"
}
}
},
"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-alternative-artist": "Pindah ke artis lain",
"scrobble-alternative-title": "Gunakan judul alternatif",
"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"
},
"synced-lyrics": {
"description": "Menyediakan lirik lagu yang disinkronkan, menggunakan penyedia seperti LRClib.",
"errors": {
"fetch": "⚠️\tTerjadi kesalahan saat mengambil lirik.\n\tSilakan coba lagi nanti.",
"not-found": "⚠️ Tidak ada lirik yang ditemukan untuk lagu ini."
},
"menu": {
"convert-chinese-character": {
"label": "Konversi karakter Tionghoa",
"submenu": {
"disabled": {
"label": "Dinonaktifkan",
"tooltip": "Nonaktifkan konversi karakter Tionghoa"
},
"simplified-to-traditional": {
"label": "Sederhana ke Tradisional",
"tooltip": "Konversi Tionghoa Sederhana ke Tionghoa Tradisional"
},
"traditional-to-simplified": {
"label": "Tradisional ke Sederhana",
"tooltip": "Konversi Tionghoa Tradisional ke Tionghoa Sederhana"
}
},
"tooltip": "Konversi karakter Tionghoa ke Tradisional atau Sederhana"
},
"default-text-string": {
"label": "Karakter default antara lirik",
"tooltip": "Pilih karakter default yang akan digunakan untuk celah antar lirik"
},
"line-effect": {
"label": "Efek garis",
"submenu": {
"fancy": {
"label": "Mewah",
"tooltip": "Gunakan efek besar seperti aplikasi pada baris saat ini"
},
"focus": {
"label": "Fokus",
"tooltip": "Jadikan hanya baris saat ini berwarna putih"
},
"offset": {
"label": "Offset",
"tooltip": "Mengimbangi garis saat ini di sebelah kanan"
},
"scale": {
"label": "Skala",
"tooltip": "Skala garis saat ini"
}
},
"tooltip": "Pilih efek yang akan diterapkan ke baris saat ini"
},
"precise-timing": {
"label": "Buat liriknya tersinkronisasi dengan sempurna",
"tooltip": "Hitung hingga milidetik tampilan baris berikutnya (dapat berdampak kecil pada kinerja)"
},
"preferred-provider": {
"label": "Penyedia Pilihan",
"none": {
"label": "Tidak ada",
"tooltip": "Tidak ada penyedia pilihan"
},
"tooltip": "Pilih penyedia bawaan untuk dipakai"
},
"romanization": {
"label": "Romanize Liriknya",
"tooltip": "Apabila lirik berada dalam bahasa berbeda, cobalah untuk menampilkan versi latinnya."
},
"show-lyrics-even-if-inexact": {
"label": "Tampilkan lirik meskipun tidak tepat",
"tooltip": "Jika lagu tidak ditemukan, plugin akan mencoba lagi dengan kueri pencarian yang berbeda.\nHasil dari percobaan kedua mungkin tidak tepat."
},
"show-time-codes": {
"label": "Tampilkan kode waktu",
"tooltip": "Tampilkan kode waktu di samping lirik"
}
},
"name": "Lirik yang Disinkronkan",
"refetch-btn": {
"fetching": "Mengambil...",
"normal": "Ambil ulang lirik"
},
"warnings": {
"duration-mismatch": "⚠️ - Liriknya mungkin tidak sinkron karena ketidakcocokan durasi.",
"inexact": "⚠️ - Lirik lagu ini mungkin tidak tepat",
"instrumental": "⚠️ - Ini adalah lagu instrumental"
}
},
"taskbar-mediacontrol": {
"description": "Kendalikan pemutaran dari bilah alat Windows",
"name": "Pengendali Media di Bilah Alat"
},
"touchbar": {
"description": "Tambahkan widget TouchBar untuk pengguna macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Buat jendela aplikasi transparan",
"menu": {
"opacity": {
"label": "Opasitas",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipe",
"submenu": {
"acrylic": "Akrilik",
"mica": "Mika",
"none": "TIdak ada",
"tabbed": "Tabulasi"
}
}
},
"name": "Pemutar Transparan"
},
"tuna-obs": {
"description": "Integrasi dengan plugin Tuna OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Cegah pemutar musik muncul ketika memutar musik",
"name": "Pemutar simpel (tidak menganggu)"
},
"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-song": "Lagu",
"button-video": "video"
}
},
"visualizer": {
"description": "Tambahkan visualisator ke pemutar",
"menu": {
"visualizer-type": "Tipe Visualisator"
},
"name": "Visualisator"
}
}
}
================================================
FILE: src/i18n/resources/is.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Tókst ekki að framkvæma tengiforrit {{pluginName}}::{{contextName}}",
"executed-at-ms": "Tengiforrit {{pluginName}}::{{contextName}} var framkvæmd í {{ms}}ms",
"initialize-failed": "Tókst ekki að frumstilla tengiforrit \"{{pluginName}}\"",
"load-all": "Er að hlaða öllum tengiforritum",
"load-failed": "Tókst ekki að hlaða tengiforritinu \"{{pluginName}}\"",
"loaded": "Tengiforrit \"{{pluginName}}\" hlaðið",
"unload-failed": "Tókst ekki að afhlaða tengiforritinu \"{{pluginName}}\"",
"unloaded": "Tengiforrit „{{pluginName}}“ óhlaðin"
}
}
},
"language": {
"code": "is",
"local-name": "Íslenska",
"name": "Icelandic"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Lokið við hleðslu. DevTools opnuð"
},
"i18n": {
"loaded": "i18n hlaðið"
},
"second-instance": {
"receive-command": "Fengið skipun yfir prótókoll: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS skrá \"{{cssFile}}\" er ekki til, er að hunsa"
},
"unresponsive": {
"details": "Viðbragðslaust Villa!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Er að hreinsa forritabúfera"
},
"window": {
"tried-to-render-offscreen": "Gluggi reyndi að birta utan skjás, gluggastærð={{windowSize}}, skjástærð={{displaySize}}, stöðu={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Valmyndin er falin, notaðu 'Breytingarlykil' til að sýna hana (eða 'Útfararlykil' ef þú notar valmynd í forriti)",
"message": "Fela Valmynd er virkjuð",
"title": "Fela Valmynd Virkjuð"
},
"need-to-restart": {
"buttons": {
"later": "Seinna",
"restart-now": "Endurræsa Núna"
},
"detail": "\"{{pluginName}}\" tengiforrit þarfnast endurræsingar til að taka gildi",
"message": "\"{{pluginName}}\" þarf að endurræsa",
"title": "Endurræsa Krafist"
},
"unresponsive": {
"buttons": {
"quit": "Hætta",
"relaunch": "Endurræsa",
"wait": "Bíddu"
},
"detail": "Við biðjumst velvirðingar á óþægindunum! vinsamlegast veldu hvað á að gera:",
"message": "Umsóknin svarar ekki",
"title": "Gluggi er svarar ekki"
},
"update-available": {
"buttons": {
"disable": "Gera Uppfærslur Óvirkar",
"download": "Sækja",
"ok": "Í lagi"
},
"detail": "Ný útgáfa er fáanleg og hægt er að hlaða henni niður á {{downloadLink}}",
"message": "Ný útgáfa er fáanleg",
"title": "Uppfærsla Fáanleg"
}
},
"menu": {
"about": "Um",
"navigation": {
"label": "Leiðsögn",
"submenu": {
"copy-current-url": "Afritaðu núverandi vefslóð",
"go-back": "Farðu til baka",
"go-forward": "Farðu áfram",
"quit": "Útganga",
"restart": "Endurræstu Forritið"
}
},
"options": {
"label": "Valkostir",
"submenu": {
"advanced-options": {
"label": "Ítarlegravalkostir",
"submenu": {
"auto-reset-app-cache": "Endurstilltu skyndiminni forritsins þegar forritið ræsir",
"disable-hardware-acceleration": "Slökktu á vélbúnaðarhröðun",
"edit-config-json": "Breyta config.json",
"override-user-agent": "Hneka Notandaumboðsmanni",
"restart-on-config-changes": "Endurræstu við stillingarbreytingar",
"set-proxy": {
"label": "Stilla umboð",
"prompt": {
"label": "Sláðu inn umboðsfang: (skilið eftir autt til að slökkva á)",
"placeholder": "Dæmi: SOCKS5://127.0.0.1:9999",
"title": "Stilla umboð"
}
},
"toggle-dev-tools": "Breyta DevTools"
}
},
"always-on-top": "Alltaf á toppnum",
"auto-update": "Sjálfvirk Uppfærsla",
"hide-menu": {
"dialog": {
"message": "Valmyndin verður falin við næstu ræsingu, notaðu [Alt] til að sýna hana (eða bakaðu við [`] ef þú notar valmynd í forriti)",
"title": "Fela Valmynd Virkjuð"
},
"label": "Fela Valmynd"
},
"language": {
"dialog": {
"message": "Tungumáli verður breytt eftir endurræsingu",
"title": "Tungumáli Breytt"
},
"label": "Tungumál",
"submenu": {
"to-help-translate": "Viltu hjálpa til við að þýða? Smellið hér"
}
},
"resume-on-start": "Haltu áfram síðasta lagi þegar forritið byrjar",
"single-instance-lock": "Eittdæmilás",
"start-at-login": "Byrjaðu á innskráningu",
"starting-page": {
"label": "Upphafssíða",
"unset": "Ósetja"
},
"tray": {
"label": "Bakki",
"submenu": {
"disabled": "Fötluð",
"enabled-and-hide-app": "Bakki virkt, og fela forritsgluggi",
"enabled-and-show-app": "Virkjað og sýna forrit",
"play-pause-on-click": "Spila/hlé við smell"
}
},
"visual-tweaks": {
"label": "Sjónrænaraðlögun",
"submenu": {
"like-buttons": {
"default": "Sjálfgefinn",
"force-show": "Þvingaðu sýna",
"hide": "Fela",
"label": "Líkartakkar"
},
"remove-upgrade-button": "Fjarlægja uppgræðartakkan",
"theme": {
"dialog": {
"button": {
"cancel": "Hætta við",
"remove": "Fjarlægja"
},
"remove-theme": "Ertu viss um að þú viljir fjarlægja þetta sérsniðna þema?",
"remove-theme-message": "Þetta mun fjarlægja sérsniðna þema"
},
"label": "Þema",
"submenu": {
"import-css-file": "Flytja inn sérsniðna CSS skrá",
"no-theme": "Engin þema"
}
}
}
}
}
},
"plugins": {
"enabled": "Virkt",
"label": "Tengiforrit",
"new": "NÝR"
},
"view": {
"label": "Útsýni",
"submenu": {
"force-reload": "Þvingaðu Endurhleðslu",
"reload": "Endurhlaða",
"reset-zoom": "Raunveruleg Stærð",
"toggle-fullscreen": "Breyta Fullskjá",
"zoom-in": "Aðdráttur",
"zoom-out": "Aðdráttur út"
}
}
},
"tray": {
"next": "Næst",
"play-pause": "Spila/Hlé",
"previous": "Fyrri",
"quit": "Útganga",
"restart": "Endurræstu Forritið",
"show": "Sýna glugga",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Ef auglýsing spilar slökknar hún á hljóðinu og stillir spilunarhraðann á 16x",
"name": "Auglýsingahraða"
},
"adblocker": {
"description": "Lokaðu fyrir allar auglýsingar og rakningar úr kassanum",
"menu": {
"blocker": "Blokkari"
},
"name": "Auglýsingablokkari"
},
"album-actions": {
"description": "Bætir Ódíslika, Mislíkt, Líkt, og Ólíkt til að nota þetta á öll lög á spilunarlista eða albúm",
"name": "Albúmsaðgerðir"
},
"album-color-theme": {
"description": "Beitir kraftmikið þema og sjónrænum áhrifum sem byggjast á litavali albúmsins",
"menu": {
"color-mix-ratio": {
"label": "Litablöndunarhlutfall",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Albúmlitaþema"
},
"ambient-mode": {
"description": "Beitir lýsingaráhrifum með því að varpa mildum litum úr myndbandinu í bakgrunn skjásins",
"menu": {
"blur-amount": {
"label": "Þokuupphæð",
"submenu": {
"pixels": "{{blurAmount}} pixlum"
}
},
"buffer": {
"label": "Stuðpúði",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Ógegnsæi",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Gæði",
"submenu": {
"pixels": "{{quality}} pixlum"
}
},
"size": {
"label": "Sæði",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Slétt umskipti",
"submenu": {
"during": "Meðan á {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Er að nota fullskjár"
}
},
"name": "Umhverfishamur"
},
"api-server": {
"description": "Bætir API netþjóni til að stjórna spilaranum",
"dialog": {
"request": {
"buttons": {
"allow": "Leyfa",
"deny": "Óleyfa"
},
"message": "Leyfa {{ID}} ({{origin}}) að aðganga API-ið?",
"title": "API heimildarbeiðni"
}
},
"menu": {
"auth-strategy": {
"label": "Heimildarstefna",
"submenu": {
"auth-at-first": {
"label": "Heimila á fyrst beiðni"
},
"none": {
"label": "Nei heimild"
}
}
},
"hostname": {
"label": "Hýsitölvunafn"
},
"port": {
"label": "Tengi"
}
},
"name": "API-Netþjónn [Beta]",
"prompt": {
"hostname": {
"label": "Sláðu inn hýsitölvunafnið (eins og 0.0.0.0) fyrir API-netþjónninn:",
"title": "Hýsitölvunafn"
},
"port": {
"label": "Sláðu inn tengið fyrir API-netþjónninn:",
"title": "Tengi"
}
}
},
"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 Music Player",
"name": "Farið Framhjá Aldurstakmörkunum"
},
"captions-selector": {
"description": "Skjátextavali fyrir {{applicationName}} hljóðrásir",
"menu": {
"autoload": "Veldu sjálfkrafa síðast notaða myndatexta",
"disable-captions": "Engir skjátextar sjálfgefið"
},
"name": "Yfirskriftarval",
"prompt": {
"selector": {
"label": "Núverandi tungumál skjátexta: {{language}}",
"none": "Enginn",
"title": "Veldu tungumál fyrir skjátexta"
}
},
"templates": {
"title": "Opnaðu skjátextavali"
}
},
"compact-sidebar": {
"description": "Stilltu hliðarstikuna alltaf í þétta stillingu",
"name": "Fyrirferðarlítillhliðarstika"
},
"crossfade": {
"description": "Krossfæra á milli lög",
"menu": {
"advanced": "Háþróaður"
},
"name": "Krossfæra [Prófunarútgáfa]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Dvína í lengd (ms)",
"fade-out-duration": "Dvína út lengd (ms)",
"fade-scaling": {
"label": "Fölunarskala",
"linear": "Línulegt",
"logarithmic": "Logaritmískt"
},
"seconds-before-end": "Krossfæra N sekúndum fyrir enda"
},
"title": "Krossfæravalkosti"
}
}
},
"disable-autoplay": {
"description": "Gerir lag að byrja í \"hlé\" ham",
"menu": {
"apply-once": "Á aðeins við ræsingu"
},
"name": "Slökkva á sjálfvirkri spilun"
},
"discord": {
"backend": {
"already-connected": "Reyndi að tengja við virka tengingu",
"connected": "Tengdur við Discord",
"disconnected": "Aftengdur frá Discord"
},
"description": "Sýndu vinum þínum hvað þú hlustar á með Rík Nærvera",
"menu": {
"auto-reconnect": "Sjálfvirk endurtengja",
"clear-activity": "Hreinsa virkni",
"clear-activity-after-timeout": "Hreinsa virkni eftir tímamörk",
"connected": "Tengt",
"disconnected": "Aftengt",
"hide-duration-left": "Fela tímalengd til vinstri",
"hide-github-button": "Fela GitHub tengilhnapp",
"play-on-application": "Spilaðu á {{applicationName}}",
"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-finish-settings": {
"label": "Sækja þegar lokið",
"prompt": {
"last-percent": "Eftir x sekúndur",
"last-seconds": "Síðustu x sekúndur",
"title": "Stilla hvenær á að hlaða niður"
},
"submenu": {
"advanced": "Ítarlegri",
"enabled": "Virkt",
"mode": "Tímastilling",
"percent": "Hlutfall",
"seconds": "Sekúndur"
}
},
"download-playlist": "Sækja spilunarlista",
"presets": "Forstillingar",
"skip-existing": "Slepptu núverandi skrám"
},
"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"
},
"synced-lyrics": {
"description": "Veitir samstillta texta við lög, með því að nota veitur eins og LRClib.",
"errors": {
"fetch": "⚠️ - Villa kom upp við að sækja textann. Vinsamlegast reyndu aftur síðar.",
"not-found": "⚠️ - Enginn texti fannst við þetta lag."
},
"menu": {
"default-text-string": {
"label": "Sjálfgefið tákn á milli texta",
"tooltip": "Veldu sjálfgefna tákn til að nota fyrir bilið á milli texta"
},
"line-effect": {
"label": "Línuafleiðing",
"submenu": {
"focus": {
"label": "Brennidepill",
"tooltip": "Gerðu aðeins núverandi línu hvíta"
},
"offset": {
"label": "Fararbyrjun",
"tooltip": "Fararbyrjun á hægri af núverandi línan"
},
"scale": {
"label": "Skali",
"tooltip": "Skala núverandi línu"
}
},
"tooltip": "Veldu áhrif til að nota á núverandi línu"
},
"precise-timing": {
"label": "Gera textana fullkomlega samstillta",
"tooltip": "Reikna upp á millisekúndu birtingu næstu línu (getur haft lítil áhrif á frammistöðu)"
},
"show-lyrics-even-if-inexact": {
"label": "Sýna texta, jafnvel þótt hann sé ónákvæmur",
"tooltip": "Ef lagið finnst ekki reynir tengiforritið aftur með annarri leitarfyrirspurn.\nNiðurstaðan úr annarri tilraun er kannski ekki nákvæm."
},
"show-time-codes": {
"label": "Sýna tímikóðar",
"tooltip": "Sýna tímakóðana við hliðina á textanum"
}
},
"name": "Samstilltur texti",
"refetch-btn": {
"fetching": "Er að sækja",
"normal": "Endursækja texta"
},
"warnings": {
"duration-mismatch": "⚠️ - Textarnir gætu verið ekki samstilltir vegna tímalengdar.",
"inexact": "⚠️ - Textinn við þetta lag er kannski ekki nákvæmur",
"instrumental": "⚠️ - Þetta er hljóðfærilegt lag"
}
},
"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-song": "Lag"
}
},
"visualizer": {
"description": "Bætir sýndarstýringar við spilarann",
"menu": {
"visualizer-type": "Sýndarstýringartegund"
},
"name": "Sýndarstýringar"
}
}
}
================================================
FILE: src/i18n/resources/it.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "È stato impossibile eseguire il plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Il plugin {{pluginName}}:{{contextName}} è stato eseguito a {{ms}}ms",
"initialize-failed": "Inizializzazione del plugin \"{{pluginName}}\" fallita",
"load-all": "Carica tutti i plugin",
"load-failed": "Caricamento del plugin \"{{pluginName}}\" non riuscito",
"loaded": "Plugin \"{{pluginName}}\" caricato",
"unload-failed": "Rimozione del plugin \"{{pluginName}}\" fallita",
"unloaded": "Plugin \"{{pluginName}}\" rimosso"
}
}
},
"language": {
"code": "it",
"local-name": "Italiano",
"name": "Italian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Caricamento concluso. DevTools aperto"
},
"i18n": {
"loaded": "i18n caricato"
},
"second-instance": {
"receive-command": "Comando ricevuto tramite protocollo: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Il file CSS \"{{cssFile}}\" non esiste, ignorato"
},
"unresponsive": {
"details": "Errore di mancata risposta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Sto liberando la cache dell'app"
},
"window": {
"tried-to-render-offscreen": "La finestra ha cercato di renderizzare fuori schermo, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}\""
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Il menu è nascosto, utilizza 'Alt' per visualizzarlo (o 'Escape' se si utilizza il Menu In-App)\"",
"message": "'Nascondi menu' è attivo",
"title": "'Nascondi menu' attivo"
},
"need-to-restart": {
"buttons": {
"later": "In seguito",
"restart-now": "Riavvia ora"
},
"detail": "Riavviare per attivare il plugin\"{{pluginName}}\"",
"message": "\"{{pluginName}}\" deve essere riavviato",
"title": "Riavvio richiesto"
},
"unresponsive": {
"buttons": {
"quit": "Arresta",
"relaunch": "Riavvia",
"wait": "Attendi"
},
"detail": "Ci dispiace per l'inconveniente! Scegli cosa fare:",
"message": "L'applicazione non risponde",
"title": "La finestra non risponde"
},
"update-available": {
"buttons": {
"disable": "Disattiva gli aggiornamenti",
"download": "Download",
"ok": "OK"
},
"detail": "È disponibile una nuova versione scaricabile all'indirizzo {{downloadLink}}",
"message": "È disponibile una nuova versione",
"title": "Aggiornamento disponibile"
}
},
"menu": {
"about": "Informazioni",
"navigation": {
"label": "Navigazione",
"submenu": {
"copy-current-url": "Copia l'URL corrente",
"go-back": "Pagina indietro",
"go-forward": "Pagina avanti",
"quit": "Esci",
"restart": "Riavvia l'app"
}
},
"options": {
"label": "Opzioni",
"submenu": {
"advanced-options": {
"label": "Opzioni avanzate",
"submenu": {
"auto-reset-app-cache": "Reimposta la cache dell'app quando viene riavviata",
"disable-hardware-acceleration": "Disabilita l'accelerazione hardware",
"edit-config-json": "Modificare config.json",
"override-user-agent": "Sovrascrivi User-Agent",
"restart-on-config-changes": "Riavvia alla modifica delle impostazioni",
"set-proxy": {
"label": "Imposta il proxy",
"prompt": {
"label": "Inserisci l'indirizzo proxy: (lascia vuoto per disabilitare)",
"placeholder": "Esempio: SOKS5://127.0.0.1:9999",
"title": "Imposta il proxy"
}
},
"toggle-dev-tools": "Attiva/disattiva DevTools"
}
},
"always-on-top": "Sempre in cima",
"auto-update": "Aggiornamento automatico",
"hide-menu": {
"dialog": {
"message": "Il menu verrà nascosto al prossimo avvio. Utilizzare [Alt] per mostrarlo (o backtick [`] se si utilizza il Menu In-App)",
"title": "Nascondi menu abilitato"
},
"label": "Nascondi menu"
},
"language": {
"dialog": {
"message": "La lingua verrà cambiata dopo il riavvio",
"title": "Lingua cambiata"
},
"label": "Lingua",
"submenu": {
"to-help-translate": "Vuoi aiutare a tradurre? Clicca qui"
}
},
"resume-on-start": "Riprendi a riprodurre l'ultimo brano all'avvio dell'app",
"single-instance-lock": "Permetti una sola istanza dell'app",
"start-at-login": "Avvia al login",
"starting-page": {
"label": "Pagina iniziale",
"unset": "Non impostato"
},
"tray": {
"label": "Mostra icona nel tray",
"submenu": {
"disabled": "Disabilita",
"enabled-and-hide-app": "Abilita e nascondi l'app",
"enabled-and-show-app": "Abilita e mostra l'app",
"play-pause-on-click": "Riproduci/Pausa al click sull'icona"
}
},
"visual-tweaks": {
"label": "Miglioramenti visivi",
"submenu": {
"custom-window-title": {
"label": "Personalizza titolo finestra",
"prompt": {
"label": "Inserisci un titolo della finestra personalizzato: (lascia vuoto per disattivare)",
"placeholder": "Esempio: {{applicationName}}"
}
},
"like-buttons": {
"default": "Predefinito",
"force-show": "Forza la visualizzazione",
"hide": "Nascondi",
"label": "Pulsanti Like",
"swap": "Scambia l'ordine dei pulsanti like"
},
"remove-upgrade-button": "Rimuovi il pulsante aggiorna",
"theme": {
"dialog": {
"button": {
"cancel": "Annulla",
"remove": "Rimuovi"
},
"remove-theme": "Sei sicuro di voler rimuovere il tema personalizzato?",
"remove-theme-message": "Questo rimuoverà il tema personalizzato"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importa file CSS personalizzato",
"no-theme": "Nessun tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Attivato",
"label": "Plugin",
"new": "NUOVO"
},
"view": {
"label": "Visualizzazione",
"submenu": {
"force-reload": "Forza l'aggiornamento",
"reload": "Aggiorna",
"reset-zoom": "Ripristina dimensione",
"toggle-fullscreen": "Attiva/disattiva Schermo Intero",
"zoom-in": "Zoom in",
"zoom-out": "Zoom out"
}
}
},
"tray": {
"next": "Prossimo",
"play-pause": "Riproduci/Pausa",
"previous": "Precedente",
"quit": "Esci",
"restart": "Riavvia l'app",
"show": "Mostra finestra",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Se viene riprodotto un annuncio, l'audio viene disattivato e viene impostata la velocità di riproduzione su 16x",
"name": "Accelerazione ad"
},
"adblocker": {
"description": "Blocca tutti gli annunci e i tracker",
"menu": {
"blocker": "Blocco"
},
"name": "Ad blocker"
},
"album-actions": {
"description": "Aggiunge i pulsanti Undislike, Dislike, Like e Unlike a tutti i brani di una playlist o di un album",
"name": "Azioni album"
},
"album-color-theme": {
"description": "Applica un tema dinamico e degli effetti visivi basandosi sul colore dell'album",
"menu": {
"color-mix-ratio": {
"label": "Percentiuale colore",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Abilita tematizzazione della seekbar"
},
"name": "Tema abbinato a colore album"
},
"ambient-mode": {
"description": "Applica un effetto di illuminazione proiettando i colori delicati del video sullo sfondo dello schermo",
"menu": {
"blur-amount": {
"label": "Intensità sfocatura",
"submenu": {
"pixels": "{{blurAmount}} pixel"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Trasparenza",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualità",
"submenu": {
"pixels": "{{quality}} pixel"
}
},
"size": {
"label": "Dimensione",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Fluidità transizione",
"submenu": {
"during": "Per {{interpolationTime}} _s"
}
},
"use-fullscreen": {
"label": "Utilizzo di schermo intero"
}
},
"name": "Modalità Ambiente"
},
"amuse": {
"description": "Aggiunge il supporto a {{applicationName}} per il widget Amuse Now Playing di 6K Labs",
"name": "Amuse",
"response": {
"query": "Il server API di Amuse è in funzione. GET /query per ottenere informazioni sui brani."
}
},
"api-server": {
"description": "Aggiunge un server API per controllare il player",
"dialog": {
"request": {
"buttons": {
"allow": "Permetti",
"deny": "Nega"
},
"message": "Consentire a {{ID}} ({{origin}}) di accedere all'API?",
"title": "Autorizzazione API richiesta"
}
},
"menu": {
"auth-strategy": {
"label": "Metodo di autorizzazione",
"submenu": {
"auth-at-first": {
"label": "Autorizza alla prima richiesta"
},
"none": {
"label": "Nessuna autorizzazione"
}
}
},
"hostname": {
"label": "Hostname"
},
"https": {
"label": "HTTPS & Certificati",
"submenu": {
"cert": {
"dialogTitle": "Seleziona file di certificato HTTPS",
"label": "File di certificato (.crt/.pem)"
},
"enable-https": {
"label": "Abilita HTTPS"
},
"key": {
"dialogTitle": "Seleziona il file della chiave privata HTTPS",
"label": "File della chiave privata (.key/.pem)"
}
}
},
"port": {
"label": "Porta"
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Inserisci il nome host (ad esempio 0.0.0.0) per il server API:",
"title": "Hostname"
},
"port": {
"label": "Inserisci la porta per il server API:",
"title": "Porta"
}
}
},
"audio-compressor": {
"description": "Attiva la compressione audio (abbassa il volume delle parti più alte e alza quello delle parti più basse del segnale)",
"name": "Compressore audio"
},
"auth-proxy-adapter": {
"description": "Supporto per l'utilizzo di servizi proxy di autenticazione",
"menu": {
"disable": "Disattiva adattatore proxy",
"enable": "Attiva adattatore proxy",
"hostname": {
"label": "Nome host"
},
"port": {
"label": "Porta"
}
},
"name": "Adattatore proxy di autenticazione",
"prompt": {
"hostname": {
"label": "Inserire hostname del server proxy locale (richiede riavvio):",
"title": "Nome host del proxy"
},
"port": {
"label": "Inserire porta del server proxy locale (richiede riavvio):",
"title": "Porta Proxy"
}
}
},
"blur-nav-bar": {
"description": "Rende la barra di navigazione trasparente e sfuocata",
"name": "Barra di navigazione trasparente"
},
"bypass-age-restrictions": {
"description": "Bypassa la verifica dell'età di Music Player",
"name": "Aggira i limiti d'età"
},
"captions-selector": {
"description": "Selettore sottotitolo per le tracce audio di {{applicationName}}",
"menu": {
"autoload": "Seleziona automaticamente l'ultimo sottotitolo utilizzato",
"disable-captions": "Disattiva i sottotitoli"
},
"name": "Selettore Sottotitoli",
"prompt": {
"selector": {
"label": "Lingua del sottotitolo attuale: {{language}}",
"none": "Nessuno",
"title": "Scegli la lingua del sottotitolo"
}
},
"templates": {
"title": "Apri il selettore dei sottotitoli"
},
"toast": {
"caption-changed": "Sottotitoli cambiati in {{language}}",
"caption-disabled": "Sottotitoli disattivati",
"no-captions": "Nessun sottotitolo disponibile per questa canzone"
}
},
"clock": {
"description": "Aggiungi un orologio alla barra di navigazione",
"menu": {
"format": {
"24-hour-format": "Formato in 24 ore",
"display-seconds": "Mostra Secondi",
"label": "Formato"
}
},
"name": "Orologio"
},
"compact-sidebar": {
"description": "Imposta sempre la barra laterale in modalità compatta",
"name": "Barra laterale compatta"
},
"crossfade": {
"description": "Crossfade tra i brani",
"menu": {
"advanced": "Impostazioni avanzate"
},
"name": "Crossfade [beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Durata dissolvenza in entrata (ms)",
"fade-out-duration": "Durata dissolvenza in uscita (ms)",
"fade-scaling": {
"label": "Transizione dissolvenza",
"linear": "Lineare",
"logarithmic": "Logaritmica"
},
"seconds-before-end": "N° secondi di crossfade prima della fine"
},
"title": "Opzioni crossfade"
}
}
},
"custom-output-device": {
"description": "Scegli da quale uscita audio vuoi riprodurre i brani",
"menu": {
"device-selector": "Seleziona un'uscita"
},
"name": "Dispositivo di Output Personalizzato",
"prompt": {
"device-selector": {
"label": "Scegli il dispositivo d'output da utilizzare",
"title": "Scegli il dispositivo d'output"
}
}
},
"disable-autoplay": {
"description": "Fa iniziare i brani in modalità \"pausa\"",
"menu": {
"apply-once": "Solo all'avvio"
},
"name": "Disattiva autoplay"
},
"discord": {
"backend": {
"already-connected": "Tenta di connettersi con connessione attiva",
"connected": "Connesso a Discord",
"disconnected": "Scollegato da Discord"
},
"description": "Mostra ai tuoi amici cosa ascolti con Rich Presence",
"menu": {
"auto-reconnect": "Riconnessione automatica",
"clear-activity": "Rimuovi attività",
"clear-activity-after-timeout": "Cancella attività dopo il timeout",
"connected": "Connesso",
"disconnected": "Disconnesso",
"hide-duration-left": "Nascondi la durata rimasta",
"hide-github-button": "Nascondi il pulsante link a GitHub",
"play-on-application": "Riproduci su {{applicationName}}",
"set-inactivity-timeout": "Imposta il timeout di inattività",
"set-status-display-type": {
"label": "Testo dello status",
"submenu": {
"application": "Ascoltando {{applicationName}}",
"artist": "Stai ascoltando {artist}",
"title": "Stai ascoltando {song title}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Inserisci il timeout di inattività in secondi:",
"title": "Imposta il timeout di inattività"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Mi dispiace, download fallito…",
"title": "Errore nel download!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} canzoni)",
"message": "Scarica Playlist {{playlistTitle}}",
"title": "Download iniziato"
}
},
"feedback": {
"conversion-progress": "Conversione: {{percent}}%",
"converting": "Sto convertendo…",
"done": "Fatto: {{filePath}}",
"download-info": "Sto scaricando {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Download: {{percent}}%",
"downloading": "Sto scaricando…",
"downloading-counter": "Sto scaricando {{current}}/{{total}}…",
"downloading-playlist": "Sto scaricando la playlist \"{{playlistTitle}}\" - {{playlistSize}} brani({{playlistId}})",
"error-while-downloading": "Errore di download \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "La cartella {{playlistFolder}} è già esistente",
"getting-playlist-info": "Sto ottenendo le info sulla playlist…",
"loading": "Caricamento…",
"playlist-has-only-one-song": "La playlist ha un solo elemento, lo sto scaricando direttamente",
"playlist-id-not-found": "Nessun ID playlist trovato",
"playlist-is-empty": "La playlist è vuota",
"playlist-is-mix-or-private": "Errore nell'ottenere info sulla playlist: assicurati che non sia una playlist privata o un \"Mixtape per te\"\n\n{{error}}",
"preparing-file": "Sto preparando il file…",
"saving": "Sto salvando…",
"trying-to-get-playlist-id": "Sto cercando di ottenere l'ID della playlist: {{playlistId}}",
"video-id-not-found": "Video non trovato",
"writing-id3": "Sto scrivendo i tag ID3…"
}
},
"description": "Download MP3 / sorgenti audio direttamente dall'interfaccia",
"menu": {
"choose-download-folder": "Scegli cartella download",
"download-finish-settings": {
"label": "Scarica al termine",
"prompt": {
"last-percent": "Dopo x percento",
"last-seconds": "Ultimi x secondi",
"title": "Configura quando scaricare"
},
"submenu": {
"advanced": "Avanzato",
"enabled": "Abilitato",
"mode": "Modalità tempo",
"percent": "Percentuale",
"seconds": "Secondi"
}
},
"download-playlist": "Scarica la playlist",
"presets": "Preimpostazioni",
"skip-existing": "Salta i file esistenti"
},
"name": "Downloader",
"renderer": {
"can-not-update-progress": "Impossibile aggiornare l'avanzamento"
},
"templates": {
"button": "Scarica"
}
},
"equalizer": {
"description": "Aggiunge un equalizzatore al player",
"menu": {
"presets": {
"label": "Preset",
"list": {
"bass-booster": "Booster dei bassi"
}
}
},
"name": "Equalizzatore"
},
"exponential-volume": {
"description": "Rende esponenziale il cursore del volume, in modo da facilitare la selezione di volumi più bassi.",
"name": "Volume esponenziale"
},
"in-app-menu": {
"description": "Migliora l'aspetto delle barre del menu con un look scuro o basato sul colore dell'album",
"menu": {
"hide-dom-window-controls": "Nascondi i controlli delle finestre DOM"
},
"name": "Menu In-App"
},
"lumiastream": {
"description": "Aggiungi supporto per Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Aggiunge il supporto dei testi per la maggior parte delle canzoni",
"menu": {
"romanized-lyrics": "Alfabeto latino per i brani con testo in caratteri orientali"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Testi recuperati per Genius"
}
},
"music-together": {
"description": "Condividi una playlist con altri. Quando l'Host riproduce un brano, tutti gli altri ascolteranno lo stesso brano",
"dialog": {
"enter-host": "Inserisci l'ID dell'Host"
},
"internal": {
"save": "Salva",
"track-source": "Traccia sorgente",
"unknown-user": "Utente sconosciuto"
},
"menu": {
"click-to-copy-id": "Copia l'ID dell'Host",
"close": "Chiudi Music Together",
"connected-users": "Utenti connessi",
"disconnect": "Disconetti Music Together",
"empty-user": "Utenti non connessi",
"host": "Host di Music Together",
"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",
"templates": {
"back": {
"title": "Vai alla pagina precedente"
},
"forward": {
"title": "Vai alla pagina successiva"
}
}
},
"no-google-login": {
"description": "Rimuovi i pulsanti di accesso e i link di Google dall'interfaccia",
"name": "Nessun login di Google"
},
"notifications": {
"description": "Mostra una notifica quando viene riprodotto un brano (le notifiche interattive sono disponibili su Windows)",
"menu": {
"interactive": "Notifiche interattive",
"interactive-settings": {
"label": "Impostazioni dell'interazione",
"submenu": {
"hide-button-text": "Nascondi il testo del pulsante",
"refresh-on-play-pause": "Refresh quando si preme Riproduci/Pausa",
"tray-controls": "Apri/chiudi cliccando l'icona nel tray"
}
},
"priority": "Priorità di notifica",
"toast-style": "Stile Toast",
"unpause-notification": "Mostra notifica quando riprendi ascolto"
},
"name": "Notifiche"
},
"performance-improvement": {
"description": "Migliora le prestazioni abilitando gli script sperimentali",
"name": "Miglioramento prestazioni [Beta]"
},
"picture-in-picture": {
"description": "Consente di far passare l'app alla modalità Picture-in-Picture",
"menu": {
"always-on-top": "Sempre in primo piano",
"hotkey": {
"label": "Tasto di scelta rapida",
"prompt": {
"keybind-options": {
"hotkey": "Tasto di scelta rapida"
},
"label": "Scegliere un'hotkey per attivare Picture-in-picture",
"title": "Tasto di scelta rapida per Picture-in-picture"
}
},
"save-window-position": "Salva la posizione della finestra",
"save-window-size": "Salva la dimensione della finestra",
"use-native-pip": "Usa il PiP nativo del browser"
},
"name": "Picture-in-Picture",
"templates": {
"button": "Picture-in-Picture"
}
},
"playback-speed": {
"description": "Ascolto veloce, ascolto lento! Aggiunge un cursore che controlla la velocità di riproduzione del brano",
"name": "Velocità riproduzione",
"templates": {
"button": "Velocità"
}
},
"precise-volume": {
"description": "Controlla con precisione il volume utilizzando la rotella del mouse, le hotkey o i tasti freccia e usando incrementi di volume personalizzabili",
"menu": {
"arrows-shortcuts": "Controlla con i tasti freccia",
"custom-volume-steps": "Imposta incrementi di volume personalizzati",
"global-shortcuts": "Hotkey globali"
},
"name": "Volume preciso",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Diminuisci volume",
"increase": "Aumenta volume"
},
"label": "Scegli i tasti di scelta rapida regolazione volume:",
"title": "Tasti di scelta rapida regolazione volume"
},
"volume-steps": {
"label": "Seleziona l'incremento/decremento del volume",
"title": "Incrementi volume"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualità attuale: {{quality}}",
"message": "Qualità Video:",
"title": "Scegli la qualità video"
}
}
},
"description": "Permette di cambiare la qualità del video con un pulsante in sovrimpressione",
"name": "Cambia qualità video",
"renderer": {
"quality-settings-button": {
"label": "Apri il selettore di qualità del player"
}
}
},
"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-alternative-artist": "Usa artisti alternativi",
"scrobble-alternative-title": "Usa titoli alternativi",
"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",
"menu": {
"override-media-keys": "Ridefinisci i tasti multimediali",
"set-keybinds": "Imposta i controlli brano globali"
},
"name": "Scorciatoie (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Prossimo",
"play-pause": "Riproduci / Pausa",
"previous": "Precedente"
},
"label": "Scegli combinazioni di tasti per il controllo dei brani:",
"title": "Combinazioni di tasti"
}
}
},
"skip-disliked-songs": {
"description": "Salta le canzoni non gradite",
"name": "Salta i brani che non ti piacciono"
},
"skip-silences": {
"description": "Salta automaticamente le parti silenziose nei brani",
"name": "Salta silenzi"
},
"sponsorblock": {
"description": "Salta automaticamente le parti non musicali, come l'intro/outro delle canzoni o le parti dei video musicali in cui non viene riprodotto il brano",
"name": "Blocco sponsor"
},
"synced-lyrics": {
"description": "Fornisce testi sincronizzati alle canzoni, utilizzando provider come LRClib.",
"errors": {
"fetch": "⚠️ \tSi è verificato un errore nel recuperare il testo.\n\tPer favore riprova più tardi.",
"not-found": "⚠️ Nessun testo trovato per questa canzone."
},
"menu": {
"default-text-string": {
"label": "Carattere predefinito tra i testi",
"tooltip": "Scegliere il carattere predefinito da utilizzare per l'intervallo tra i testi"
},
"line-effect": {
"label": "Effetto linea",
"submenu": {
"fancy": {
"label": "Fantasia",
"tooltip": "Usa effetti grandi, simili a quelli di un'app sulla riga attuale"
},
"focus": {
"label": "Focus",
"tooltip": "Rendi bianca solo la riga corrente"
},
"offset": {
"label": "Offset",
"tooltip": "Offset a destra della riga corrente"
},
"scale": {
"label": "Ingrandimento",
"tooltip": "Ingrandisci la linea corrente"
}
},
"tooltip": "Scegli l'effetto da applicare alla linea corrente"
},
"precise-timing": {
"label": "Rendi i testi perfettamente sincronizzati",
"tooltip": "Calcola al millisecondo la visualizzazione della riga successiva (può avere un piccolo impatto sulle prestazioni)"
},
"preferred-provider": {
"label": "Provider preferito",
"none": {
"label": "Nessuno",
"tooltip": "Nessun provider preferito"
},
"tooltip": "Scegli quale provider predefinito utilizzare"
},
"romanization": {
"label": "Testi in caratteri occidentali",
"tooltip": "Qualora il testo fosse scritto in una lingua non occidentale, prova a visualizzarlo in caratteri latini."
},
"show-lyrics-even-if-inexact": {
"label": "Mostra le lyric anche se incorrette",
"tooltip": "Se il brano non viene trovato, il plugin riprova con un'altra query di ricerca.\nIl risultato del secondo tentativo potrebbe non essere esatto."
},
"show-time-codes": {
"label": "Mostra time code",
"tooltip": "Mostra i codici temporali accanto ai testi"
}
},
"name": "Testi sincronizzati",
"refetch-btn": {
"fetching": "Caricamento...",
"normal": "Recupera i testi"
},
"warnings": {
"duration-mismatch": "⚠️ - I testi potrebbero non essere sincronizzati a causa di una mancata corrispondenza della durata.",
"inexact": "⚠️ - Il testo di questa canzone potrebbe essere inesatto",
"instrumental": "⚠️ - Questo è un brano strumentale"
}
},
"taskbar-mediacontrol": {
"description": "Controlla riproduzione dalla taskbar di Windows",
"name": "Controlli multimediali sulla taskbar"
},
"touchbar": {
"description": "Aggiunge un widget TouchBar per gli utenti macOS",
"name": "Touch Bar (per MacOS)"
},
"transparent-player": {
"description": "Rende trasparente la finestra del programma",
"menu": {
"opacity": {
"label": "Opacità",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrilico",
"mica": "Mica",
"none": "Nessuno",
"tabbed": "In scheda"
}
}
},
"name": "Player Trasparente"
},
"tuna-obs": {
"description": "Integrazione con il plugin OBS Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Evita che il player si apra automaticamente durante la riproduzione di un brano",
"name": "Player Discreto"
},
"video-toggle": {
"description": "Aggiunge un pulsante per passare dalla modalità Video a quella Brano. Può anche rimuovere l'intera scheda Brano/Video",
"menu": {
"align": {
"label": "Allineamento",
"submenu": {
"left": "Sinistra",
"middle": "Centro",
"right": "Destra"
}
},
"force-hide": "Rimuovi la scheda Brano/Video",
"mode": {
"label": "Modalità",
"submenu": {
"custom": "Brano/Video personalizzato",
"disabled": "Disattivato",
"native": "Brano/Video nativo"
}
}
},
"name": "Selettore Brano/Video",
"templates": {
"button-song": "Brano",
"button-video": "Video"
}
},
"visualizer": {
"description": "Sostituisce al Video un visualizzatore grafico",
"menu": {
"visualizer-type": "Tipo di visualizzazione"
},
"name": "Visualizzatore grafico"
}
}
}
================================================
FILE: src/i18n/resources/ja.json
================================================
{
"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": "ja",
"local-name": "日本語",
"name": "Japanese"
},
"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": "OK"
},
"detail": "新しいバージョンが利用可能です。{{downloadLink}} からダウンロードできます",
"message": "新しいバージョンが利用可能",
"title": "アップデートが利用可能"
}
},
"menu": {
"about": "このアプリについて",
"navigation": {
"label": "移動",
"submenu": {
"copy-current-url": "現在のURLをコピー",
"go-back": "戻る",
"go-forward": "進む",
"quit": "終了",
"restart": "アプリを再起動"
}
},
"options": {
"label": "設定",
"submenu": {
"advanced-options": {
"label": "高度な設定",
"submenu": {
"auto-reset-app-cache": "アプリの開始時にキャッシュをリセット",
"disable-hardware-acceleration": "ハードウェアアクセラレーションの無効化",
"edit-config-json": "config.json を編集",
"override-user-agent": "ユーザーエージェントの上書き",
"restart-on-config-changes": "設定変更時に再起動",
"set-proxy": {
"label": "プロキシ設定",
"prompt": {
"label": "プロキシのアドレスを入力: (空で無効化)",
"placeholder": "例: SOCKS5://127.0.0.1:9999",
"title": "プロキシ"
}
},
"toggle-dev-tools": "DevToolsの切り替え"
}
},
"always-on-top": "常に最前面に表示",
"auto-update": "自動アップデート",
"hide-menu": {
"dialog": {
"message": "メニューは次の起動から非表示になります。表示するには[Alt]キーを使用します (in-app-menuを使用している場合は[`]を使用します)",
"title": "メニューの非表示が有効"
},
"label": "メニューの非表示"
},
"language": {
"dialog": {
"message": "言語は再起動後に変更されます",
"title": "言語が変更されました"
},
"label": "言語設定",
"submenu": {
"to-help-translate": "翻訳をサポートしたいですか?こちらをクリック"
}
},
"resume-on-start": "起動時に最後の曲を再開する",
"single-instance-lock": "単一インスタンスロック",
"start-at-login": "windowsのログイン時に起動",
"starting-page": {
"label": "スターティングページ",
"unset": "未設定"
},
"tray": {
"label": "トレイアイコン",
"submenu": {
"disabled": "無効",
"enabled-and-hide-app": "有効 + アプリを非表示",
"enabled-and-show-app": "有効 + アプリを表示",
"play-pause-on-click": "クリックで再生/一時停止"
}
},
"visual-tweaks": {
"label": "見た目の微調整",
"submenu": {
"custom-window-title": {
"label": "カスタムウィンドウタイトル",
"prompt": {
"label": "カスタムウィンドウタイトルを入力: (未入力の場合無効になります)",
"placeholder": "例: {{applicationName}}"
}
},
"like-buttons": {
"default": "デフォルト",
"force-show": "強制的に表示",
"hide": "非表示",
"label": "いいねボタン"
},
"remove-upgrade-button": "アップグレードボタンを削除",
"theme": {
"dialog": {
"button": {
"cancel": "キャンセル",
"remove": "削除"
},
"remove-theme": "本当にカスタムテーマを削除しますか?",
"remove-theme-message": "カスタムテーマを削除します"
},
"label": "テーマ",
"submenu": {
"import-css-file": "CSSファイルをインポート",
"no-theme": "テーマなし"
}
}
}
}
}
},
"plugins": {
"enabled": "有効",
"label": "プラグイン",
"new": "新着"
},
"view": {
"label": "表示",
"submenu": {
"force-reload": "強制再読み込み",
"reload": "再読み込み",
"reset-zoom": "実際のサイズ",
"toggle-fullscreen": "全画面表示を切り替え",
"zoom-in": "拡大",
"zoom-out": "縮小"
}
}
},
"tray": {
"next": "次の曲",
"play-pause": "再生/一時停止",
"previous": "前の曲",
"quit": "終了",
"restart": "アプリを再起動",
"show": "ウィンドウを表示",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "広告が再生されると、自動的にミュートされ、再生速度が16倍に設定されます",
"name": "広告のスピードを上げる"
},
"adblocker": {
"description": "すべての広告とトラッカーをブロックj",
"menu": {
"blocker": "ブロッカー"
},
"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": "動画の内容に合った淡い色に画面の背景を変化させるライティング効果を適応します",
"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": "アンビエント モード"
},
"amuse": {
"description": "6K LabsのAmuse再生中ウィジェットが{{applicationName}}に対応しました",
"name": "Amuse",
"response": {
"query": "AmuseのAPIサーバーが稼働中です。GET /query で楽曲情報を取得できます。"
}
},
"api-server": {
"description": "プレイヤーを制御するAPIサーバーを追加",
"dialog": {
"request": {
"buttons": {
"allow": "許可",
"deny": "拒否"
},
"message": "{{ID}}が{{origin}}にアクセスすることを許可しますか?",
"title": "API承認リクエスト"
}
},
"menu": {
"auth-strategy": {
"label": "許可方法",
"submenu": {
"auth-at-first": {
"label": "初回リクエスト時に承認"
},
"none": {
"label": "不許可"
}
}
},
"hostname": {
"label": "ホスト名"
},
"port": {
"label": "ポート"
}
},
"name": "APIサーバー(Beta)",
"prompt": {
"hostname": {
"label": "APIサーバーのポート名(0.0.0.0など)を入力:",
"title": "ホスト名"
},
"port": {
"label": "APIサーバーのポートを入力:",
"title": "ポート"
}
}
},
"audio-compressor": {
"description": "オーディオにコンプレッサーを適用します(信号での一番大きい部分の音量を下げ、小さい部分の音量を上げる)",
"name": "オーディオコンプレッサー"
},
"auth-proxy-adapter": {
"description": "認証プロキシサービスの利用サポート",
"menu": {
"disable": "プロキシアダプターを無効にする",
"enable": "プロキシアダプターを有効にする",
"hostname": {
"label": "ホスト名"
},
"port": {
"label": "ポート"
}
},
"name": "認証プロキシアダプタ",
"prompt": {
"hostname": {
"label": "ローカルプロキシサーバのホスト名を入力します(再起動が必要です):",
"title": "プロキシホスト名"
},
"port": {
"label": "ローカルプロキシサーバのポートを入力します(再起動が必要です):",
"title": "プロキシポート"
}
}
},
"blur-nav-bar": {
"description": "ナビゲーションバーを透明かつぼやけにします",
"name": "ナビゲーションバーの曇り効果"
},
"bypass-age-restrictions": {
"description": "音楽プレーヤーの年齢確認をバイパスする",
"name": "年齢制限迂回"
},
"captions-selector": {
"description": "{{applicationName}}トラック用字幕選択機",
"menu": {
"autoload": "最後の字幕を自動に選択",
"disable-captions": "デフォルトで字幕を無効化"
},
"name": "字幕選択機",
"prompt": {
"selector": {
"label": "選択した字幕言語: {{language}}",
"none": "なし",
"title": "字幕の言語を選択"
}
},
"templates": {
"title": "字幕選択機を開く"
},
"toast": {
"caption-changed": "字幕を{{language}}に変更しました",
"caption-disabled": "字幕を無効にしました",
"no-captions": "この曲には字幕がありません"
}
},
"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": "クロスフェード設定"
}
}
},
"custom-output-device": {
"description": "曲用のカスタム出力メディアデバイスを構成する",
"menu": {
"device-selector": "デバイスの選択"
},
"name": "カスタム出力デバイス",
"prompt": {
"device-selector": {
"label": "使用する出力メディアデバイスを選択します",
"title": "出力デバイスの選択"
}
}
},
"disable-autoplay": {
"description": "曲を「一時停止」モードで始めさせます",
"menu": {
"apply-once": "起動時のみ適用"
},
"name": "自動再生を無効化"
},
"discord": {
"backend": {
"already-connected": "すでに有効になっている接続に接続を試みました",
"connected": "ディスコードに接続中",
"disconnected": "Discordから切断されました"
},
"description": "アクティビティ ステータスで、あなたが聴いている曲を友達に見せましょう",
"menu": {
"auto-reconnect": "自動再接続",
"clear-activity": "アクティビティの削除",
"clear-activity-after-timeout": "タイムアウト発生時にアクティビティを削除",
"connected": "接続済み",
"disconnected": "切断済み",
"hide-duration-left": "残りの再生時間を隠す",
"hide-github-button": "GitHubリンクボタンを隠す",
"play-on-application": "{{applicationName}}で再生",
"set-inactivity-timeout": "タイムアウト時間を設定",
"set-status-display-type": {
"label": "ステータステキスト",
"submenu": {
"artist": "{artist}を聴いている",
"application": "{{applicationName}}を聴く",
"title": "{曲名}を聴いている"
}
}
},
"name": "Discordアクティビティステータス",
"prompt": {
"set-inactivity-timeout": {
"label": "非アクティブ時のタイムアウトを秒単位で入力:",
"title": "非アクティブタイムアウト"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "ダウンロード失敗!ごめんね…",
"title": "ダウンロード中にエラーが発生しました!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}}曲)",
"message": "プレイリスト {{playlistTitle}} をダウンロード中",
"title": "ダウンロード開始"
}
},
"feedback": {
"conversion-progress": "変換:{{percent}}%",
"converting": "変換中…",
"done": "完了:{{filePath}}",
"download-info": "{{artist}}ー{{title}} {{videoId}} をダウンロード中",
"download-progress": "ダウンロード:{{percent}}%",
"downloading": "ダウンロード中…",
"downloading-counter": "ダウンロード中:{{current}}/{{total}}…",
"downloading-playlist": "プレイリストをダウンロード中:\"{{playlistTitle}}\" -{{playlistSize}}曲({{playlistId}})",
"error-while-downloading": "\"{{author}}ー{{title}}\"ダウンロード中にエラー発生:{{error}}",
"folder-already-exists": "フォルダー {{playlistFolder}}が既に存在します",
"getting-playlist-info": "プレイリスト情報を取得中…",
"loading": "ロード中…",
"playlist-has-only-one-song": "プレイリストに1曲しかありません。直接ダウンロードします",
"playlist-id-not-found": "プレイリストIDが見つかりません",
"playlist-is-empty": "プレイリストは空です",
"playlist-is-mix-or-private": "プレイリスト情報をダウンロード中にエラーが発生しました: プレイリストが非公開ではないこと、\"Mixed for you\"ではないことを確認してください\n\n{{error}}",
"preparing-file": "ファイルを準備中…",
"saving": "保存中…",
"trying-to-get-playlist-id": "プレイリストIDを取得中:{{playlistId}}",
"video-id-not-found": "動画が見つかりません",
"writing-id3": "ID3タグ作成中…"
}
},
"description": "UIから直にMP3・ソースオーディオをダウンロードします",
"menu": {
"choose-download-folder": "ダウンロードフォルダ",
"download-finish-settings": {
"label": "完了時にダウンロード",
"prompt": {
"last-percent": "x パーセント後",
"last-seconds": "最後の x 秒",
"title": "保存するタイミング"
},
"submenu": {
"advanced": "高度な設定",
"enabled": "有効",
"mode": "時間モード",
"percent": "パーセント",
"seconds": "秒"
}
},
"download-playlist": "プレイリストをダウンロード",
"presets": "プリセット",
"skip-existing": "存在するファイルをスキップ"
},
"name": "ダウンローダー",
"renderer": {
"can-not-update-progress": "進捗を更新できません"
},
"templates": {
"button": "ダウンロード"
}
},
"equalizer": {
"description": "イコライザーを追加",
"menu": {
"presets": {
"label": "プリセット",
"list": {
"bass-booster": "ベースブースター"
}
}
},
"name": "イコライザー"
},
"exponential-volume": {
"description": "音量スライダを指数関数的にさせ、低い音量に設定しやすくなります。",
"name": "指数音量"
},
"in-app-menu": {
"description": "メニューバーをファンシー、ダーク、またはアルバムカラーの外観にする",
"menu": {
"hide-dom-window-controls": "DOMウィンドウコントロールを隠す"
},
"name": "アプリ内メニュー"
},
"lumiastream": {
"description": "Lumia Streamのサポートを追加",
"name": "Lumia Stream [ベータ]"
},
"lyrics-genius": {
"description": "より広い範囲の曲に歌詞を付けます",
"menu": {
"romanized-lyrics": "ローマ字歌詞"
},
"name": "Genius 歌詞",
"renderer": {
"fetched-lyrics": "Geniusから歌詞取得完了"
}
},
"music-together": {
"description": "プレイリストを他の人と共有します。 ホストが曲を再生すると、他の全員にも同じ曲が聞こえます",
"dialog": {
"enter-host": "ホストIDを入力"
},
"internal": {
"save": "保存",
"track-source": "トラックソース",
"unknown-user": "不明なユーザー"
},
"menu": {
"click-to-copy-id": "ホストIDをコピー",
"close": "Music Togetherを閉じる",
"connected-users": "接続中のユーザー",
"disconnect": "Music Togetherから切断",
"empty-user": "接続中のユーザーはいません",
"host": "Music Togetherをホスト",
"join": "Music Togetherに参加",
"permission": {
"all": "ゲストの再生リストとプレーヤーを制御を許可",
"host-only": "ホストのみがプレイリストとプレーヤーを制御",
"playlist": "ゲストによるプレイリストの制御を許可する"
},
"set-permission": "制御権限を変更",
"status": {
"disconnected": "切断されました",
"guest": "ゲストとして接続しました",
"host": "ホストとして接続されています"
}
},
"name": "Music Together [ベータ]",
"toast": {
"add-song-failed": "曲の追加に失敗しました",
"closed": "Music Together が閉じられました",
"disconnected": "Music Together が切断されました",
"host-failed": "Music Together のホストに失敗しました",
"id-copied": "ホストIDがクリップボードにコピーされました",
"id-copy-failed": "ホストIDをクリップボードにコピー出来ませんでした",
"join-failed": "Music Together に参加出来ませんでした",
"joined": "Music Together に参加しました",
"permission-changed": "Music Togetherの権限が \"{{permission}}\" に変更されました",
"remove-song-failed": "曲の削除に失敗しました",
"user-connected": "{{name}} がMusic Togetherに参加しました",
"user-disconnected": "{{name}} がMusic Togetherを退出しました"
}
},
"navigation": {
"description": "ブラウザの戻る・進むボタンのようにUIからコントロールできるボタン",
"name": "ナビゲーション",
"templates": {
"back": {
"title": "前のページに戻ります"
},
"forward": {
"title": "次のページに進みます"
}
}
},
"no-google-login": {
"description": "インターフェースからGoogleのログインボタンとリンクを削除",
"name": "No Google Login"
},
"notifications": {
"description": "曲の再生開始時に通知を表示する(Windowsではインタラクティブ通知が利用可能)",
"menu": {
"interactive": "インタラクティブ通知",
"interactive-settings": {
"label": "インタラクティブ通知 設定",
"submenu": {
"hide-button-text": "ボタンのテキストを非表示",
"refresh-on-play-pause": "再生/一時停止時に更新",
"tray-controls": "トレイアイコンのクリック時に開閉"
}
},
"priority": "通知の優先度",
"toast-style": "トーストのスタイル",
"unpause-notification": "再生再開時に通知を表示"
},
"name": "通知"
},
"performance-improvement": {
"description": "実験的スクリプトを有効にすることによってパフォーマンス改善します",
"name": "パフォーマンス改善 [ベータ]"
},
"picture-in-picture": {
"description": "アプリでピクチャ・イン・ピクチャを切り替えられるようになります",
"menu": {
"always-on-top": "常に最前面に表示",
"hotkey": {
"label": "ホットキー",
"prompt": {
"keybind-options": {
"hotkey": "ホットキー"
},
"label": "ピクチャインピクチャを切り替えるためのホットキーを選択",
"title": "ピクチャインピクチャのホットキー"
}
},
"save-window-position": "ウィンドウの位置を保存",
"save-window-size": "ウィンドウのサイズを保存",
"use-native-pip": "ブラウザ標準のPiPを使用"
},
"name": "ピクチャインピクチャ",
"templates": {
"button": "ピクチャインピクチャ"
}
},
"playback-speed": {
"description": "速く聴く、遅く聴く!曲のスピードをコントロールするスライダーを追加",
"name": "再生速度",
"templates": {
"button": "速度"
}
},
"precise-volume": {
"description": "カスタムHUDとカスタマイズ可能な音量ステップで、マウスホイール/ホットキーを使って音量を正確にコントロールします",
"menu": {
"arrows-shortcuts": "ローカル矢印キー操作",
"custom-volume-steps": "カスタム音量ステップを設定",
"global-shortcuts": "グローバル ホットキー"
},
"name": "正確な音量",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "音量を下げる",
"increase": "音量を上げる"
},
"label": "グローバルキーバインドを選択:",
"title": "グローバル 音量 キーバインド"
},
"volume-steps": {
"label": "音量の増減ステップを選択",
"title": "音量ステップ"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "現在の品質: {{quality}}",
"message": "ビデオ品質を選択:",
"title": "ビデオ品質を選択:"
}
}
},
"description": "ビデオオーバーレイのボタンを使用してビデオ品質を変更できるようにします",
"name": "ビデオ品質チェンジャー",
"renderer": {
"quality-settings-button": {
"label": "ビデオ品質チェンジャーを開きます"
}
}
},
"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-alternative-artist": "代替アーティストを使用する",
"scrobble-alternative-title": "代替タイトルを使用する",
"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": {
"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"
},
"synced-lyrics": {
"description": "LRClibのようなプロバイダを使って、楽曲に同期した歌詞を使用する。",
"errors": {
"fetch": "⚠️ \t歌詞の取得中にエラーが発生しました。\n\t後でもう一度お試しください。",
"not-found": "⚠️ この曲の歌詞は見つかりませんでした。"
},
"menu": {
"default-text-string": {
"label": "デフォルトの歌詞間の文字",
"tooltip": "歌詞と歌詞の間に使用するデフォルトの文字を選択してください"
},
"line-effect": {
"label": "歌詞表示のエフェクト",
"submenu": {
"fancy": {
"label": "ファンシー",
"tooltip": "現在の行にアプリのような大きなエフェクトを使う"
},
"focus": {
"label": "フォーカス",
"tooltip": "現在の行だけを白くする"
},
"offset": {
"label": "オフセット",
"tooltip": "オフセットを現在の行の右側にする"
},
"scale": {
"label": "サイズ",
"tooltip": "現在の行のサイズ変更をする"
}
},
"tooltip": "現在の行に適用するエフェクトを選択"
},
"precise-timing": {
"label": "歌詞を完璧に同期させる",
"tooltip": "次の行の表示をミリ秒単位で計算する(パフォーマンスに若干の影響を与える可能性があります)"
},
"preferred-provider": {
"label": "優先プロバイダー",
"none": {
"label": "なし",
"tooltip": "指定医療機関なし"
},
"tooltip": "使用するデフォルトプロバイダを選択してください"
},
"romanization": {
"label": "ローマ字歌詞",
"tooltip": "歌詞が異なる言語で書かれている場合は、ラテン語バージョンを表示するようにしてください。"
},
"show-lyrics-even-if-inexact": {
"label": "歌詞が不正確でも表示する",
"tooltip": "曲が見つからなかった場合、プラグインは別の検索クエリで再試行します。\nただし、再試行の結果は正確でない可能性があります。"
},
"show-time-codes": {
"label": "タイムコードを表示",
"tooltip": "歌詞の横にタイムコードを表示"
}
},
"name": "歌詞を同期",
"refetch-btn": {
"fetching": "取得中...",
"normal": "歌詞を再取得"
},
"warnings": {
"duration-mismatch": "⚠️ - タイミングが合わないため、歌詞が同期されていない可能性があります。",
"inexact": "⚠️ - この曲の歌詞は正確ではないかもしれません",
"instrumental": "⚠️ - これは演奏のみの曲です"
}
},
"taskbar-mediacontrol": {
"description": "Windowsタスクバーから再生をコントロール",
"name": "タスクバーメディアコントロール"
},
"touchbar": {
"description": "masOSユーザー向けにTouchBarウィジェットを追加",
"name": "TouchBar"
},
"transparent-player": {
"description": "アプリウィンドウを透明にする",
"menu": {
"opacity": {
"label": "不透明度",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "タイプ",
"submenu": {
"acrylic": "アクリル",
"mica": "マイカ(Mica)",
"none": "なし",
"tabbed": "タブ付き"
}
}
},
"name": "透明プレイヤー"
},
"tuna-obs": {
"description": "OBSのプラグインTunaの統合",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "曲の再生時にプレーヤーがポップアップしないようにする",
"name": "控えめなプレーヤー"
},
"video-toggle": {
"description": "ビデオ/ソングモードを切り替えるボタンを追加します。オプションでビデオタブ全体を削除することもできます",
"menu": {
"align": {
"label": "位置",
"submenu": {
"left": "左",
"middle": "中央",
"right": "右"
}
},
"force-hide": "強制的にビデオタブを削除",
"mode": {
"label": "モード",
"submenu": {
"custom": "カスタム切り替え",
"disabled": "無効",
"native": "標準の切り替え"
}
}
},
"name": "動画の切り替え",
"templates": {
"button-song": "曲",
"button-video": "動画"
}
},
"visualizer": {
"description": "視覚効果(ビジュアライザー)をプレイヤーに追加します",
"menu": {
"visualizer-type": "ビジュアライザーの種類"
},
"name": "視覚効果"
}
}
}
================================================
FILE: src/i18n/resources/ka.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "დამატების შესრულების შეცდომა {{pluginName}}::{{contextName}}",
"executed-at-ms": "პლაგინი {{pluginName}}::{{contextName}} გაეშვა {{ms}} მილიწამში",
"initialize-failed": "პლაგინის ინიციალიზაცია ვერ მოხდა\"{{pluginName}}\"",
"load-all": "იტვირთება ყველა პლაგინი",
"load-failed": "პლაგინის ჩატვირთვა ვერ მოხდა \"{{pluginName}}\"",
"loaded": "პლაგინი \"{{pluginName}}\" ჩაიტვირთა",
"unload-failed": "პლაგინის {{pluginName}} გათიშვა ვერ მოხერხდა",
"unloaded": "პლაგინი {{pluginName}} გათიშულია"
}
}
},
"language": {
"code": "ka",
"local-name": "ქართული",
"name": "Georgian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "ჩატვირთვა დასრულებულია. DevTools გახსნილია"
},
"i18n": {
"loaded": "i18n ჩართულია"
},
"second-instance": {
"receive-command": "მიღებულია ბრძანება პროტოკოლზე: {{command}}"
},
"theme": {
"css-file-not-found": "CSS ფაილი {{cssFile}} არ არსებობს, იგნორირება"
},
"unresponsive": {
"details": "უპასუხო შეცდომა!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "აპლიკაციის ქეშის გაწმენდვა"
},
"window": {
"tried-to-render-offscreen": "ფანჯარამ სცადა, ეკრანსმიღმა გახსნილიყო, ფანჯრის ზომა={{windowSize}}, ეკრანის ზომა={{displaySize}}, მდებარეობა={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "მენიუ დამალულია, გამოიყენეთ 'Alt', რათა გამოაჩინოთ ის (ან 'Escape' თუ იყენებთ აპლიკაციის შიგნითა მენიუს)",
"message": "მენიუს დამალვა ჩართულია",
"title": "მენიუს დამალვა ჩართულია"
},
"need-to-restart": {
"buttons": {
"later": "მოგვიანებით",
"restart-now": "გადატვირთვა ახლავე"
},
"detail": "„{{pluginName}}“ დანამატის ძალაში შესასვლელად გადატვირთვა საჭიროა",
"message": "\"{{pluginName}}\" საჭიროებს გადატვირთვას",
"title": "საჭიროებს გადატვირთვას"
},
"unresponsive": {
"buttons": {
"quit": "გასვლა",
"relaunch": "თავიდან გაშვება",
"wait": "მოცდა"
},
"detail": "ბოდიშს გიხდით მოუხერხელობისათვის! გთხოვთ აირჩიეთ რა უნდა გაკეთდეს:",
"message": "აპლიკაცია არ პასუხობს",
"title": "ფანჯარა არ პასუხობს"
},
"update-available": {
"buttons": {
"disable": "განახლებების გამორთვა",
"download": "გადმოწერა",
"ok": "დიახ"
},
"detail": "ახალი ვერსიაა ხელმისაწვდომი, მისი ჩამოტვირთვა შესაძლებელია {{downloadLink}}-დან",
"message": "ახალი ვერსია ხელმისაწვდომია",
"title": "განახლება ხელმისაწვდომია"
}
},
"menu": {
"about": "შესახებ",
"navigation": {
"label": "ნავიგაცია",
"submenu": {
"copy-current-url": "მიმდინარე URL-ის დაკოპირება",
"go-back": "უკან დაბრუნება",
"go-forward": "წინ გადასვლა",
"quit": "გასვლა",
"restart": "აპლიკაციის გადატვირთვა"
}
},
"options": {
"label": "მორგება",
"submenu": {
"advanced-options": {
"label": "გაფართოებული პარამეტრები",
"submenu": {
"auto-reset-app-cache": "აპლიკაციის ქეშის გადატვირთვა როცა აპლიკაცია დაიწყება",
"disable-hardware-acceleration": "აპარატურული აჩქარების გამორთვა",
"edit-config-json": "config.json-ის რედაქტირება",
"override-user-agent": "მომხმარებლის აგენტის შეცვლა",
"restart-on-config-changes": "გადატვირთვა კონფიგურაციის ცვლილებების დროს",
"set-proxy": {
"label": "პროქსის დაყენება",
"prompt": {
"label": "შეიყვანეთ პროქსის მისამართი: (გამორთვისთვის დატოვეთ ცარიელი)",
"placeholder": "მაგალითი: SOCKS5://127.0.0.1:9999",
"title": "პროქსის დაყენება"
}
},
"toggle-dev-tools": "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": "გამორთულია"
}
},
"visual-tweaks": {
"submenu": {
"like-buttons": {
"default": "ნაგულისხმევი",
"hide": "დამალვა"
},
"theme": {
"dialog": {
"button": {
"cancel": "გაუქმება",
"remove": "წაშლა"
}
},
"label": "თემა"
}
}
}
}
},
"plugins": {
"enabled": "ჩართულია",
"label": "დამატებები",
"new": "ახალი"
},
"view": {
"label": "ხედი",
"submenu": {
"reload": "თავიდან ჩატვირთვა"
}
}
},
"tray": {
"next": "შემდეგი",
"play-pause": "დაკვრა/შეჩერება",
"previous": "წინა",
"quit": "გასვლა"
}
},
"plugins": {
"adblocker": {
"menu": {
"blocker": "დამბლოკავი"
}
},
"album-color-theme": {
"menu": {
"color-mix-ratio": {
"submenu": {
"percent": "{{ratio}}%"
}
}
}
},
"ambient-mode": {
"menu": {
"buffer": {
"label": "ბუფერი",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "გაუმჭვირვალობა",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "ხარისხი"
},
"size": {
"label": "ზომა",
"submenu": {
"percent": "{{size}}%"
}
},
"use-fullscreen": {
"label": "სრული ეკრანის გამოყენება"
}
},
"name": "გარემოს რეჟიმი"
},
"amuse": {
"name": "გაკვირვება"
},
"api-server": {
"dialog": {
"request": {
"buttons": {
"allow": "დაშვება",
"deny": "აკრძალვა"
}
}
},
"menu": {
"hostname": {
"label": "ჰოსტის სახელი"
},
"port": {
"label": "პორტი"
}
},
"prompt": {
"hostname": {
"title": "ჰოსტის სახელი"
},
"port": {
"title": "პორტი"
}
}
},
"captions-selector": {
"prompt": {
"selector": {
"none": "არცერთი"
}
}
},
"crossfade": {
"menu": {
"advanced": "დამატებით"
},
"prompt": {
"options": {
"multi-input": {
"fade-scaling": {
"linear": "წრფივი",
"logarithmic": "ლოგარითმული"
}
}
}
}
},
"discord": {
"menu": {
"connected": "დაკავშირებული",
"disconnected": "გათიშული"
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "დიახ"
}
},
"start-download-playlist": {
"buttons": {
"ok": "დიახ"
}
}
},
"feedback": {
"converting": "გადაყვანა…",
"downloading": "გადმოწერა…",
"loading": "ჩატვირთვა…",
"saving": "შენახვა…"
}
},
"menu": {
"download-finish-settings": {
"submenu": {
"advanced": "დამატებით",
"enabled": "ჩართულია",
"percent": "პროცენტი",
"seconds": "წამი"
}
},
"presets": "პრესეტი"
},
"name": "გადმომწერი",
"templates": {
"button": "გადმოწერა"
}
},
"music-together": {
"internal": {
"save": "შენახვა"
},
"menu": {
"status": {
"disconnected": "გათიშული"
}
},
"toast": {
"closed": "Music Together-ის ორგანიზატორი დაიხურა",
"disconnected": "Music Together-ის კავშირი გათიშულია",
"host-failed": "Music Together-ის გამოცხადება ვერ მოხერხდა",
"id-copied": "გამოსაცხადებელი ID დაკოპირებულია ბუფერში",
"id-copy-failed": "გამოსაცხადებელი ID-ის ვერ დაკოპირდა ბუფერში"
}
},
"navigation": {
"name": "ნავიგაცია"
},
"notifications": {
"name": "გაფრთხილებები"
},
"picture-in-picture": {
"name": "სურათი სურათში",
"templates": {
"button": "სურათი სურათში"
}
},
"playback-speed": {
"templates": {
"button": "სიჩქარე"
}
},
"shortcuts": {
"prompt": {
"keybind": {
"keybind-options": {
"next": "შემდეგი",
"previous": "წინა"
}
}
}
},
"sponsorblock": {
"name": "სარეკლამო ბლოკი"
},
"synced-lyrics": {
"menu": {
"line-effect": {
"submenu": {
"focus": {
"label": "ფოკუსი"
}
}
}
}
},
"video-toggle": {
"menu": {
"mode": {
"label": "რეჟიმი",
"submenu": {
"custom": "მორგებული გადამრთველი",
"disabled": "გამორთულია",
"native": "ადგილობრივი გადართვა"
}
}
},
"name": "ვიდეოს გადართვა",
"templates": {
"button-song": "სიმღერა",
"button-video": "ვიდეო"
}
},
"visualizer": {
"description": "პლეიერს ვიზუალიზატორს უმატებს",
"menu": {
"visualizer-type": "ვიზუალიზატორის ტიპი"
},
"name": "ვიზუალიზატორი"
}
}
}
================================================
FILE: src/i18n/resources/kmr.json
================================================
{}
================================================
FILE: src/i18n/resources/kn.json
================================================
{
"language": {
"code": "kn",
"local-name": "ಕನ್ನಡ",
"name": "Kannada"
}
}
================================================
FILE: src/i18n/resources/ko.json
================================================
{
"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": "ko",
"local-name": "한국어",
"name": "Korean"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "로드 완료. 개발자 도구 실행됨"
},
"i18n": {
"loaded": "국제화 로드됨"
},
"second-instance": {
"receive-command": "프로토콜을 통해 명령을 받았습니다: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS 파일 \"{{cssFile}}\"이(가) 존재하지 않음. 무시됨"
},
"unresponsive": {
"details": "응답 없음 오류!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "앱 캐시 지우는 중"
},
"window": {
"tried-to-render-offscreen": "창이 오프스크린 렌더링을 시도했습니다, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "'Alt' 키를 눌러 숨겨진 메뉴를 표시할 수 있습니다 (인앱 메뉴를 사용하는 경우 'Esc' 키를 사용)",
"message": "메뉴 숨기기가 활성화되어 있음",
"title": "메뉴 숨기기 활성화됨"
},
"need-to-restart": {
"buttons": {
"later": "나중에",
"restart-now": "지금 재시작하기"
},
"detail": "\"{{pluginName}}\" 확장을 적용하려면 재시작해야 합니다",
"message": "\"{{pluginName}}\"은(는) 재시작이 필요합니다",
"title": "재시작 필요함"
},
"unresponsive": {
"buttons": {
"quit": "종료",
"relaunch": "재시작",
"wait": "기다리기"
},
"detail": "불편을 드려 죄송합니다! 다음 중 하나를 선택해 주세요.",
"message": "애플리케이션이 응답하지 않습니다",
"title": "창이 응답하지 않음"
},
"update-available": {
"buttons": {
"disable": "업데이트 비활성화하기",
"download": "다운로드",
"ok": "확인"
},
"detail": "새 버전을 {{downloadLink}}에서 설치할 수 있습니다",
"message": "새 버전을 사용할 수 있습니다",
"title": "업데이트 사용 가능"
}
},
"menu": {
"about": "정보",
"navigation": {
"label": "탐색",
"submenu": {
"copy-current-url": "현재 URL 복사",
"go-back": "뒤로 가기",
"go-forward": "앞으로 가기",
"quit": "종료",
"restart": "앱 재시작"
}
},
"options": {
"label": "설정",
"submenu": {
"advanced-options": {
"label": "고급 설정",
"submenu": {
"auto-reset-app-cache": "앱 시작 시 앱 캐시 초기화하기",
"disable-hardware-acceleration": "하드웨어 가속 비활성화",
"edit-config-json": "config.json 편집",
"override-user-agent": "User-Agent 재정의하기",
"restart-on-config-changes": "설정 변경 시 재시작하기",
"set-proxy": {
"label": "프록시 설정하기",
"prompt": {
"label": "프록시 주소를 입력하세요: (비어있을 시 비활성화됨)",
"placeholder": "예제: SOCKS5://127.0.0.1:9999",
"title": "프록시 설정하기"
}
},
"toggle-dev-tools": "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": {
"custom-window-title": {
"label": "사용자 정의 앱 제목",
"prompt": {
"label": "앱 제목으로 표시할 내용: (빈칸일 시 비활성화됨)",
"placeholder": "예: {{applicationName}}"
}
},
"like-buttons": {
"default": "기본",
"force-show": "강제로 표시하기",
"hide": "숨기기",
"label": "좋아요 버튼",
"swap": "\"좋아요\" 버튼 순서 변경하기"
},
"remove-upgrade-button": "업그레이드 버튼 제거",
"theme": {
"dialog": {
"button": {
"cancel": "취소",
"remove": "제거"
},
"remove-theme": "사용자 정의 테마를 제거하시겠습니까?",
"remove-theme-message": "이 설정을 변경하면 커스텀 테마가 삭제됩니다"
},
"label": "테마",
"submenu": {
"import-css-file": "사용자 정의 CSS 파일 가져오기",
"no-theme": "테마 없음"
}
}
}
}
}
},
"plugins": {
"enabled": "활성화",
"label": "확장",
"new": "새 플러그인"
},
"view": {
"label": "보기",
"submenu": {
"force-reload": "강제 새로고침",
"reload": "새로고침",
"reset-zoom": "원래 크기",
"toggle-fullscreen": "전체 화면 전환",
"zoom-in": "확대",
"zoom-out": "축소"
}
}
},
"tray": {
"next": "다음",
"play-pause": "재생/일시정지",
"previous": "이전",
"quit": "종료",
"restart": "앱 재시작",
"show": "창 표시",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "광고가 재생될 때 소리를 음소거하고 재생 속도를 16배로 설정합니다",
"name": "광고 배속"
},
"adblocker": {
"description": "모든 광고와 트래커를 즉시 차단합니다",
"menu": {
"blocker": "광고 차단 타입"
},
"name": "광고 차단기"
},
"album-actions": {
"description": "좋아요, 싫어요 버튼을 추가하고, 결과를 재생 목록 또는 앨범의 모든 노래에 적용합니다",
"name": "앨범 액션"
},
"album-color-theme": {
"description": "앨범 색상 팔레트를 기반으로 동적 테마 및 시각 효과를 적용합니다",
"menu": {
"color-mix-ratio": {
"label": "색상 혼합 비율",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "재생바 색조 변경 활성화"
},
"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": "앰비언트 모드"
},
"amuse": {
"description": "6K Labs Amuse의 'now playing' 위젯에 {{applicationName}} 지원 추가",
"name": "Amuse (어뮤즈)",
"response": {
"query": "Amuse API 서버가 실행 중입니다. GET /query로 노래 정보를 가져오세요."
}
},
"api-server": {
"description": "플레이어를 제어하기 위한 API 서버를 추가합니다",
"dialog": {
"request": {
"buttons": {
"allow": "허용",
"deny": "거부"
},
"message": "{{ID}} ({{origin}})이(가) API에 액세스하도록 허용하시겠습니까?",
"title": "API 권한 요청"
}
},
"menu": {
"auth-strategy": {
"label": "인증 정책",
"submenu": {
"auth-at-first": {
"label": "첫 번째 요청 시 인증"
},
"none": {
"label": "인증 없음"
}
}
},
"hostname": {
"label": "호스트 명"
},
"https": {
"label": "HTTPS 및 인증서",
"submenu": {
"cert": {
"dialogTitle": "HTTPS 인증서 파일을 선택해 주세요",
"label": "인증서 파일(.crt/.pem)"
},
"enable-https": {
"label": "HTTPS 활성화"
},
"key": {
"dialogTitle": "HTTPS 개인 키 파일을 선택해 주세요",
"label": "개인 키 파일(.key/.pem)"
}
}
},
"port": {
"label": "포트"
}
},
"name": "API 서버 [베타]",
"prompt": {
"hostname": {
"label": "API 서버가 사용할 호스트 명(예: 0.0.0.0)을 입력하세요:",
"title": "호스트 명"
},
"port": {
"label": "API 서버가 사용할 포트를 입력하세요:",
"title": "포트"
}
}
},
"audio-compressor": {
"description": "오디오에 컴프레서를 적용합니다 (신호에서 가장 시끄러운 부분의 음량을 낮추고 가장 조용한 부분의 음량을 높임)",
"name": "오디오 컴프레서"
},
"auth-proxy-adapter": {
"description": "인증이 필요한 프록시를 지원합니다",
"menu": {
"disable": "프록시 어댑터 비활성화",
"enable": "프록시 어댑터 활성화",
"hostname": {
"label": "호스트 명"
},
"port": {
"label": "포트"
}
},
"name": "권한 프록시 어댑터",
"prompt": {
"hostname": {
"label": "로컬 프록시 서버의 호스트명을 입력해주세요 (재시작이 필요합니다):",
"title": "프록시 호스트명"
},
"port": {
"label": "로컬 프록시 서버의 포트를 입력 해주세요 (재시작이 필요합니다):",
"title": "프록시 포트"
}
}
},
"blur-nav-bar": {
"description": "탐색 바를 투명하고 흐릿하게 만듭니다",
"name": "탐색 바 흐림 효과"
},
"bypass-age-restrictions": {
"description": "플레이어의 연령 확인 인증 우회",
"name": "나이 제한 우회"
},
"captions-selector": {
"description": "{{applicationName}} 트랙용 자막 선택기입니다",
"menu": {
"autoload": "마지막으로 사용한 자막을 자동으로 선택",
"disable-captions": "기본 자막 제거"
},
"name": "자막 선택기",
"prompt": {
"selector": {
"label": "현재 선택된 언어: {{language}}",
"none": "없음",
"title": "자막 언어 선택"
}
},
"templates": {
"title": "자막 선택기 열기"
},
"toast": {
"caption-changed": "자막 언어가 {{language}}(으)로 변경되었습니다",
"caption-disabled": "자막 비활성화됨",
"no-captions": "이 곡에는 자막이 없습니다"
}
},
"clock": {
"description": "네비게이션 바 옆 시계 추가하기",
"menu": {
"format": {
"24-hour-format": "24시간 형식",
"display-seconds": "초 표시하기",
"label": "형식"
}
},
"name": "시계"
},
"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": "크로스페이드 설정"
}
}
},
"custom-output-device": {
"description": "미디어를 출력할 장치 구성하기",
"menu": {
"device-selector": "장치를 선택하세요"
},
"name": "출력 장치 커스텀",
"prompt": {
"device-selector": {
"label": "사용할 미디어 출력 장치를 선택하세요",
"title": "출력 장치 선택"
}
}
},
"disable-autoplay": {
"description": "노래를 '일시 정지' 모드로 시작하게 합니다",
"menu": {
"apply-once": "첫 시작 시에만 적용"
},
"name": "자동 재생 해제"
},
"discord": {
"backend": {
"already-connected": "활성화 된 연결에 연결을 시도했습니다",
"connected": "Discord에 연결됨",
"disconnected": "Discord에서 연결이 끊김"
},
"description": "Rich Presence를 사용하여 친구들에게 내가 듣는 음악을 보여주세요",
"menu": {
"auto-reconnect": "자동 재연결",
"clear-activity": "활동 제거",
"clear-activity-after-timeout": "시간 초과 시 활동 제거",
"connected": "연결됨",
"disconnected": "연결 해제됨",
"hide-duration-left": "남은 재생 시간 숨기기",
"hide-github-button": "GitHub 링크 버튼 숨기기",
"play-on-application": "{{applicationName}} 에서 재생",
"set-inactivity-timeout": "비활성 시간 제한 설정",
"set-status-display-type": {
"label": "상태 텍스트",
"submenu": {
"application": "{{applicationName}} 듣는 중",
"artist": "{아티스트} 듣는 중",
"title": "{곡 제목} 듣는 중"
}
}
},
"name": "Discord 활동 상태",
"prompt": {
"set-inactivity-timeout": {
"label": "비활성 시간 제한을 초 단위로 입력하세요:",
"title": "비활성 시간 제한 설정"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "확인"
},
"message": "앗! 죄송합니다. 다운로드가 실패했습니다…",
"title": "다운로드 중 오류 발생!"
},
"start-download-playlist": {
"buttons": {
"ok": "확인"
},
"detail": "({{playlistSize}} 곡)",
"message": "재생목록 {{playlistTitle}} 다운로드 중",
"title": "다운로드 시작됨"
}
},
"feedback": {
"conversion-progress": "변환율: {{percent}}%",
"converting": "변환 중…",
"done": "완료: {{filePath}}",
"download-info": "{{artist}} - {{title}} [{{videoId}} 다운로드 중",
"download-progress": "다운로드: {{percent}}%",
"downloading": "다운로드 중…",
"downloading-counter": "다운로드 중 {{current}}/{{total}}…",
"downloading-playlist": "재생목록 다운로드 중: \"{{playlistTitle}}\" - {{playlistSize}} 곡 ({{playlistId}})",
"error-while-downloading": "\"{{author}} - {{title}}\" 다운로드 중 오류 발생: {{error}}",
"folder-already-exists": "{{playlistFolder}} 폴더가 이미 존재합니다",
"getting-playlist-info": "재생목록 정보를 가져오는 중…",
"loading": "로딩 중…",
"playlist-has-only-one-song": "재생목록에 한 항목만 존재합니다. 직접 다운로드합니다",
"playlist-id-not-found": "재생목록 ID를 찾을 수 없습니다",
"playlist-is-empty": "재생목록이 비어있습니다",
"playlist-is-mix-or-private": "재생목록 정보 가져오는 중 오류 발생: 비공개 재생목록 또는 \"나만을 위한 맞춤 믹스\" 재생목록이 아닌지 확인하세요\n\n{{error}}",
"preparing-file": "파일 준비 중…",
"saving": "저장 중…",
"trying-to-get-playlist-id": "재생목록 ID를 가져오는 중: {{playlistId}}",
"video-id-not-found": "영상을 찾을 수 없습니다",
"writing-id3": "ID3 태그 작성 중…"
}
},
"description": "UI에서 직접 MP3/소스 오디오를 다운로드하세요",
"menu": {
"choose-download-folder": "다운로드 폴더 선택",
"download-finish-settings": {
"label": "노래가 끝날 때 자동 다운로드",
"prompt": {
"last-percent": "x 퍼센트 이후에",
"last-seconds": "마지막 x 초에",
"title": "다운로드 시기 구성"
},
"submenu": {
"advanced": "고급",
"enabled": "활성화",
"mode": "시간 모드",
"percent": "퍼센트 기준",
"seconds": "초 기준"
}
},
"download-playlist": "재생목록 다운로드",
"presets": "프리셋",
"skip-existing": "이미 존재하는 파일 넘기기"
},
"name": "다운로더",
"renderer": {
"can-not-update-progress": "진행 상황을 업데이트 할 수 없음"
},
"templates": {
"button": "다운로드"
}
},
"equalizer": {
"description": "플레이어에 이퀄라이저를 추가합니다",
"menu": {
"presets": {
"label": "프리셋",
"list": {
"bass-booster": "베이스 부스터"
}
}
},
"name": "이퀄라이저"
},
"exponential-volume": {
"description": "음량 슬라이더를 지수적으로 만들어 더 낮은 음량을 쉽게 선택할 수 있도록 합니다.",
"name": "지수 음량"
},
"in-app-menu": {
"description": "메뉴 표시줄을 더 멋지게, 그리고 다크 또는 앨범의 색상으로 만듭니다",
"menu": {
"hide-dom-window-controls": "DOM 윈도우 컨트롤 숨기기"
},
"name": "인앱 메뉴"
},
"lumiastream": {
"description": "Lumia Stream 지원을 추가합니다",
"name": "Lumia Stream [베타]"
},
"lyrics-genius": {
"description": "더 많은 곡에 대해 가사 지원을 추가합니다",
"menu": {
"romanized-lyrics": "가사 로마자화"
},
"name": "Genius 가사",
"renderer": {
"fetched-lyrics": "Genius에서 가사 불러옴"
}
},
"music-together": {
"description": "여러명과 함께 플레이리스트를 공유합니다. 호스트가 음악을 재생하면, 다른 사용자들도 같은 노래를 들을 수 있습니다",
"dialog": {
"enter-host": "호스트 아이디를 입력하세요"
},
"internal": {
"save": "저장",
"track-source": "재생 중인 트랙 출처",
"unknown-user": "알 수 없는 사용자"
},
"menu": {
"click-to-copy-id": "호스트 아이디 복사",
"close": "Music Together 닫기",
"connected-users": "연결된 사용자",
"disconnect": "Music Together 연결 끊기",
"empty-user": "연결된 사용자 없음",
"host": "Music Together 호스트",
"join": "Music Together 참여",
"permission": {
"all": "게스트가 모두 제어 가능",
"host-only": "호스트만 제어 가능",
"playlist": "게스트가 재생목록 제어 가능"
},
"set-permission": "제어 권한 변경",
"status": {
"disconnected": "연결 끊김",
"guest": "게스트로 연결됨",
"host": "호스트로 연결됨"
}
},
"name": "Music Together [베타]",
"toast": {
"add-song-failed": "노래 추가 실패",
"closed": "Music Together가 닫혔습니다",
"disconnected": "Music Together 연결이 끊어졌습니다",
"host-failed": "Music Together를 열 수 없습니다",
"id-copied": "호스트 아이디가 클립보드에 복사되었습니다",
"id-copy-failed": "호스트 ID를 클립보드에 복사하지 못했습니다",
"join-failed": "Music Together에 참여할 수 없습니다",
"joined": "Music Together에 참여했습니다",
"permission-changed": "Music Together 제어 권한이 \"{{permission}}\"(으)로 변경되었습니다",
"remove-song-failed": "노래 제거 실패",
"user-connected": "{{name}}님이 Music Together에 참여했습니다",
"user-disconnected": "{{name}}님이 Music Together에서 나갔습니다"
}
},
"navigation": {
"description": "브라우저에서처럼, UI에 직접 통합된 앞으로/뒤로 탐색하는 화살표",
"name": "탐색",
"templates": {
"back": {
"title": "이전 페이지로 이동"
},
"forward": {
"title": "다음 페이지로 이동"
}
}
},
"no-google-login": {
"description": "UI에서 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": "알림"
},
"performance-improvement": {
"description": "실험적인 스크립트를 활성화하여 성능을 개선합니다",
"name": "성능 개선 [베타]"
},
"picture-in-picture": {
"description": "앱을 PiP 모드로 전환할 수 있게 허용합니다",
"menu": {
"always-on-top": "항상 맨 위에 표시",
"hotkey": {
"label": "단축키",
"prompt": {
"keybind-options": {
"hotkey": "단축키"
},
"label": "PiP를 전환하기 위한 단축키를 선택하세요",
"title": "PiP 단축키"
}
},
"save-window-position": "창 위치 저장",
"save-window-size": "창 크기 저장",
"use-native-pip": "브라우저 내장 PiP 사용"
},
"name": "PiP",
"templates": {
"button": "PiP"
}
},
"playback-speed": {
"description": "빨리 듣거나, 천천히 들어보세요! 노래 속도를 제어하는 슬라이더를 추가합니다",
"name": "재생 속도",
"templates": {
"button": "배속"
}
},
"precise-volume": {
"description": "사용자 지정 HUD와 사용자 지정 음량 단계로 마우스 휠/단축키를 사용하여 음량을 정확하게 제어하세요",
"menu": {
"arrows-shortcuts": "로컬 화살표 키 컨트롤",
"custom-volume-steps": "사용자 지정 음량 단계 설정",
"global-shortcuts": "전역 단축키"
},
"name": "정확한 음량",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "음량 감소",
"increase": "음량 증가"
},
"label": "전역 음량 키를 지정하세요:",
"title": "전역 음량 키 지정"
},
"volume-steps": {
"label": "음량 증가/감소 단계를 선택하세요",
"title": "음량 단계"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "현재 품질: {{quality}}",
"message": "영상 품질 선택:",
"title": "영상 품질 선택"
}
}
},
"description": "영상 오버레이의 버튼으로 영상 품질을 변경할 수 있습니다",
"name": "영상 품질 체인저",
"renderer": {
"quality-settings-button": {
"label": "영상 품질 선택기 열기"
}
}
},
"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-alternative-artist": "대체 아티스트 명 사용",
"scrobble-alternative-title": "대체 제목 사용하기",
"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 지원 활성화, 고급 사용자를 위한 사용자 지정 단축키 지원 추가",
"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"
},
"synced-lyrics": {
"description": "LRClib등의 가사 제공자에서 싱크 가사를 불러옵니다.",
"errors": {
"fetch": "⚠️\t가사를 불러오는 동안 오류가 발생했습니다.\n\t나중에 다시 시도해 주세요.",
"not-found": "⚠️ 이 노래의 가사를 찾을 수 없습니다."
},
"menu": {
"default-text-string": {
"label": "가사 사이에 표시할 문자",
"tooltip": "가사 사이의 빈 공간에 사용할 문자를 선택합니다"
},
"line-effect": {
"label": "줄 표시 효과",
"submenu": {
"fancy": {
"label": "예쁘게",
"tooltip": "유튜브 뮤직 앱처럼 커다란 효과를 현재 라인에 사용합니다"
},
"focus": {
"label": "포커스",
"tooltip": "현재 줄만 하얀색으로 표시"
},
"offset": {
"label": "오프셋",
"tooltip": "현재 줄의 오른쪽에 오프셋 적용"
},
"scale": {
"label": "스케일",
"tooltip": "현재 줄에 스케일 적용"
}
},
"tooltip": "현재 줄에 적용할 효과를 선택합니다"
},
"precise-timing": {
"label": "가사를 최대한 정교하게 동기화",
"tooltip": "다음 줄의 표시를 밀리초 단위로 계산합니다 (성능에 약간의 영향을 미칠 수 있음)"
},
"preferred-provider": {
"label": "선호하는 가사 제공자",
"none": {
"label": "없음",
"tooltip": "선호하는 가사 제공자 없음"
},
"tooltip": "사용할 기본 가사 제공자를 선택하세요"
},
"romanization": {
"label": "가사 로마자 변환",
"tooltip": "가사가 영어가 아닌 언어로 되어있는 경우, 로마자 표기를 표시합니다."
},
"show-lyrics-even-if-inexact": {
"label": "가사가 정확하지 않더라도 표시",
"tooltip": "노래를 찾을 수 없는 경우, 플러그인이 다른 검색어로 다시 검색합니다.\n두번째 검색 결과는 정확하지 않을 수 있습니다."
},
"show-time-codes": {
"label": "시간 코드 표시",
"tooltip": "가사 옆에 시간 코드 표시"
}
},
"name": "싱크 가사",
"refetch-btn": {
"fetching": "가져오는 중...",
"normal": "가사 다시 가져오기"
},
"warnings": {
"duration-mismatch": "⚠️ - 곡 길이 불일치로 인해 가사가 일치하지 않을 수 있습니다.",
"inexact": "⚠️ - 이 노래의 가사는 정확하지 않을 수 있습니다",
"instrumental": "⚠️ - 연주곡입니다"
}
},
"taskbar-mediacontrol": {
"description": "Windows 작업 표시줄에서 재생을 제어하세요",
"name": "작업표시줄 미디어 컨트롤"
},
"touchbar": {
"description": "macOS 사용자를 위한 TouchBar 위젯을 추가합니다",
"name": "TouchBar"
},
"transparent-player": {
"description": "애플리케이션 창을 투명하게 만듭니다",
"menu": {
"opacity": {
"label": "불투명도",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "종류",
"submenu": {
"acrylic": "아크릴",
"mica": "미카",
"none": "없음",
"tabbed": "탭"
}
}
},
"name": "투명 플레이어"
},
"tuna-obs": {
"description": "OBS의 확장인 Tuna와의 통합을 활성화합니다",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "노래 재생 중 플레이어가 팝업되는 것을 방지합니다",
"name": "플레이어 방해 금지"
},
"video-toggle": {
"description": "영상/노래 모드를 전환하는 버튼을 추가합니다. 선택적으로 전체 영상 탭을 제거할 수도 있습니다",
"menu": {
"align": {
"label": "정렬",
"submenu": {
"left": "왼쪽",
"middle": "가운데",
"right": "오른쪽"
}
},
"force-hide": "영상 탭 강제 제거",
"mode": {
"label": "모드",
"submenu": {
"custom": "사용자 지정 전환",
"disabled": "비활성화",
"native": "기본 토글"
}
}
},
"name": "영상 전환",
"templates": {
"button-song": "노래",
"button-video": "영상"
}
},
"visualizer": {
"description": "플레이어에 시각화 도구 추가",
"menu": {
"visualizer-type": "비주얼라이저 타입"
},
"name": "비주얼라이저"
}
}
}
================================================
FILE: src/i18n/resources/lt.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Nepavyko įvykdyti įskiepio {{pluginName}}::{{contextName}}",
"executed-at-ms": "Įskiepis {{pluginName}}::{{contextName}} įvykdytas per {{ms}}ms",
"initialize-failed": "Nepavyko inicijuoti įskiepio \"{{pluginName}}\"",
"load-all": "Kraunama visus įskiepius",
"load-failed": "Nepavyko užkrauti įskiepio \"{{pluginName}}\"",
"loaded": "Įskiepis \"{{pluginName}}\" užkrautas",
"unload-failed": "Nepavyko iškrauti įskiepio \"{{pluginName}}\"",
"unloaded": "Įskiepis \"{{pluginName}}\" iškrautas"
}
}
},
"language": {
"code": "lt",
"local-name": "Lietuvių kalba",
"name": "Lithuanian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Baigta krauti. „DevTools“ atidaryta"
},
"i18n": {
"loaded": "„i18n“ užkrauta"
},
"second-instance": {
"receive-command": "Gauta komanda per protokolą: „{{command}}“"
},
"theme": {
"css-file-not-found": "CSS failas „{{cssFile}}“ neegzistuoja, ignoruojama"
},
"unresponsive": {
"details": "Nereguojanti klaida!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Išvaloma programos talpykla"
},
"window": {
"tried-to-render-offscreen": "Langas bandė vaizduotis už ekrano ribų, langoDydis={{windowSize}}, ekranoDydis={{displaySize}}, pozicija={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Meniu yra paslėpta, naudokite 'Alt', kad ją parodyti (arba 'Escape' jei naudojama programos meniu)",
"message": "„Paslėpti Meniu“ yra įjungta",
"title": "Įjungta „Paslėpti Meniu“"
},
"need-to-restart": {
"buttons": {
"later": "Vėliau",
"restart-now": "Perkrauti Dabar"
},
"detail": "„{{pluginName}}“ įskiepis reikalauja perkrovimą, kad veiktų",
"message": "„{{pluginName}}“ reikia perkrovimo",
"title": "Reikiamas perkrovimas"
},
"unresponsive": {
"buttons": {
"quit": "Baigti",
"relaunch": "Perleisti",
"wait": "Palaukti"
},
"detail": "Mes apgailestaujame dėl nepatogumų! Prašome pasirinkti ką daryti:",
"message": "Programa Neatsako",
"title": "Langas Neatsako"
},
"update-available": {
"buttons": {
"disable": "Išjungti Atnaujinimus",
"download": "Atsisiųsti",
"ok": "Gerai"
},
"detail": "Nauja versija yra prieinama ir gali būti atsisiųsta {{downloadLink}}",
"message": "Nauja versija yra prieinama",
"title": "Prieinamas Atnaujinimas"
}
},
"menu": {
"about": "Apie",
"navigation": {
"label": "Navigacija",
"submenu": {
"copy-current-url": "Nukopijuoti dabartinį URL",
"go-back": "Grįžti atgal",
"go-forward": "Eiti į priekį",
"quit": "Išeiti",
"restart": "Perkrauti programą"
}
},
"options": {
"label": "Nustatymai",
"submenu": {
"advanced-options": {
"label": "Išplėstiniai nustatymai",
"submenu": {
"auto-reset-app-cache": "Perkrauti programos talpyklą, kai programa paleidžiama",
"disable-hardware-acceleration": "Išjungti aparatūros spartinimą",
"edit-config-json": "Redaguoti „config.json“",
"override-user-agent": "Perrašyti „User-Agent“",
"restart-on-config-changes": "Perkrauti po „config“ pasikeitimo",
"set-proxy": {
"label": "Nustatyti ‚proxy‘ serverį",
"prompt": {
"label": "Įvesti ‚proxy‘ serverio adresą: (palikti tuščią, kad išjungti)",
"placeholder": "Pavyzdys: SOCKS5://127.0.0.1:9999",
"title": "Nustatyti ‚proxy‘ serverį"
}
},
"toggle-dev-tools": "Įjungti/Išjungti „DevTools“"
}
},
"always-on-top": "Visada viršuje",
"auto-update": "Automatinis Atnaujinimas",
"hide-menu": {
"dialog": {
"message": "Meniu bus paslėpta per kitą paleidimą, naudokite [Alt], kad ją parodyti (arba kairinio kirčio ženklą [`] jei naudojama programos meniu)",
"title": "„Paslėpti Meniu“ įjungtas"
},
"label": "Paslėpti Meniu"
},
"language": {
"dialog": {
"message": "Kalba bus pakeista po perkrovimo",
"title": "Kalba Pakeista"
},
"label": "Kalba",
"submenu": {
"to-help-translate": "Norite padėti išversti? Paspauskite čia"
}
},
"resume-on-start": "Programai pasileidus, tęsti paskutinę dainą",
"single-instance-lock": "Vienkartinis užraktas",
"start-at-login": "Pradėti nuo prisijungimo",
"starting-page": {
"label": "Pradžios puslapis",
"unset": "Nenustatyta"
},
"tray": {
"label": "Padėklas",
"submenu": {
"disabled": "Išjungta",
"enabled-and-hide-app": "Įjungta ir slėpti programos langą",
"enabled-and-show-app": "Įjungta ir rodyti programos langą",
"play-pause-on-click": "Paleisti/Pristabdyti ant paspaudimo"
}
},
"visual-tweaks": {
"label": "Vizualiniai patobulinimai",
"submenu": {
"custom-window-title": {
"label": "Pasirinktinis lango pavadinimas",
"prompt": {
"label": "Įveskite pasirinktiną lango pavadinimą: (palikite tuščią kad išjungti)",
"placeholder": "Pavyzdys: {{applicationName}}"
}
},
"like-buttons": {
"default": "Numatytasis",
"force-show": "Priversti rodyti",
"hide": "Slėpti",
"label": "„Patinka“ mygtukai",
"swap": "Sukeisti „Patinka“ mygtukų vietas"
},
"remove-upgrade-button": "Nerodyti „Patobulinti“ mygtuko",
"theme": {
"dialog": {
"button": {
"cancel": "Atšaukti",
"remove": "Pašalinti"
},
"remove-theme": "Ar tikrai norite pašalinti pasirinktinę temą?",
"remove-theme-message": "Šis veiksmas pašalins pasirinktinę temą"
},
"label": "Tema",
"submenu": {
"import-css-file": "Įkelti pasirinktinį CSS failą",
"no-theme": "Be temos"
}
}
}
}
}
},
"plugins": {
"enabled": "Įjungta",
"label": "Įskiepiai",
"new": "NAUJIENA"
},
"view": {
"label": "Vaizdas",
"submenu": {
"force-reload": "Priverstinai perkrauti",
"reload": "Perkrauti",
"reset-zoom": "Tikras dydis",
"toggle-fullscreen": "Įjungti/Išjungti Pilną Ekraną",
"zoom-in": "Priartinti",
"zoom-out": "Nutolinti"
}
}
},
"tray": {
"next": "Kitas",
"play-pause": "Paleisti/Pristabdyti",
"previous": "Ankstesnis",
"quit": "Išeiti",
"restart": "Perkrauti programą",
"show": "Rodyti langą",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Jeigu gros reklama, įrašo garsas bus išjungtas ir pagreitintas 16x",
"name": "Reklamos Pagreitinimas"
},
"adblocker": {
"description": "Blokuoti visas reklamas ir seklius",
"menu": {
"blocker": "Blokuotojas"
},
"name": "Reklamų blokuotojas"
},
"album-actions": {
"description": "Prideda mygtukus pažymėti „Nepatinka“, „Patinka“, „Atžymėti Nepatinka“ bei „Atžymėti Patinka“ visas dainas grojaraštyje arba albume",
"name": "Albumo Veiksmai"
},
"album-color-theme": {
"description": "Pritaiko dinamišką temą ir vizualinius efektus pagal albumo spalvų paletę",
"menu": {
"color-mix-ratio": {
"label": "Spalvų maišymo santykis",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Įjungti paieškos juostos temavimą"
},
"name": "Albumo Spalvų Tema"
},
"ambient-mode": {
"description": "Pritaiko apšvietimo efektą, perteikdamas švelnias vaizdo įrašo spalvas į ekrano foną",
"menu": {
"blur-amount": {
"label": "Suliejimo kiekis",
"submenu": {
"pixels": "{{blurAmount}} pikseliai"
}
},
"buffer": {
"label": "Buferis",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Skaidrumas",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kokybė",
"submenu": {
"pixels": "{{quality}} pikseliai"
}
},
"size": {
"label": "Dydis",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Perliejimo švelnumas",
"submenu": {
"during": "Per {{interpolationTime}}s"
}
},
"use-fullscreen": {
"label": "Naudojamas visas ekranas"
}
},
"name": "Aplinkos rėžimas"
},
"amuse": {
"description": "Prideda {{applicationName}} palaikyma „Amuse“ grojimo valdikliui pagal „6K Labs“",
"name": "„Amuse“ (Platforma Dainininkams)",
"response": {
"query": "„Amuse“ API serveris veikia. Parašykite GET /query kad gautumėte dainos informacija."
}
},
"api-server": {
"description": "Prideda API serveri, kad būtų galima kontroliuoti grotuvą",
"dialog": {
"request": {
"buttons": {
"allow": "Leisti",
"deny": "Neleisti"
},
"message": "Leisti {{ID}} ({{origin}}) prieiga prie API?",
"title": "API įgaliojimo užklausa"
}
},
"menu": {
"auth-strategy": {
"label": "Įgaliojimo strategija",
"submenu": {
"auth-at-first": {
"label": "Įgalioti per pirmą užklausą"
},
"none": {
"label": "Nėra įgaliojimo"
}
}
},
"hostname": {
"label": "Serverio Pavadinimas"
},
"https": {
"label": "HTTPS ir sertifikatai",
"submenu": {
"cert": {
"dialogTitle": "Pasirinkti HTTPS sertifikato failą",
"label": "Sertifikato failas (.crt/.pem)"
},
"enable-https": {
"label": "Įjungti HTTPS"
},
"key": {
"dialogTitle": "Pasirinkti HTTPS privataus rakto failą",
"label": "Privataus rakto failas (.key/.pem)"
}
}
},
"port": {
"label": "Prievadas"
}
},
"name": "API Serverio {Beta}",
"prompt": {
"hostname": {
"label": "Įveskite serverio pavadinimą (pavyzdžiui kaip 0.0.0.0) skirtą API serveriui:",
"title": "Serverio Pavadinimas"
},
"port": {
"label": "Įveskite prievadą API serveriui:",
"title": "Prievadas"
}
}
},
"audio-compressor": {
"description": "Pritaikyti garso kompresiją (sumažina garsiausių signalo dalių garsumą ir padidina švelniausių dalių garsumą)",
"name": "Garso Kompresorius"
},
"auth-proxy-adapter": {
"description": "autentifikavimo „proxy“ paslaugų naudojimo palaikymas",
"menu": {
"disable": "Išjungti „Proxy“ adapterį",
"enable": "Įjungti „Proxy“ adapterį",
"hostname": {
"label": "Sistemos pavadinimas"
},
"port": {
"label": "Prievadas"
}
},
"name": "Autentifikavimo „Proxy“ adapteris",
"prompt": {
"hostname": {
"label": "Įveskite sistemos vardą vietiniui „Proxy“ serveriui (reikalingas perkrovimas):",
"title": "„Proxy“ Sistemos pavadinimas"
},
"port": {
"label": "Įveskite vietinio „Proxy“ serverio prievadą (reikalingas perkrovimas):",
"title": "„Proxy“ Prievadas"
}
}
},
"blur-nav-bar": {
"description": "Padaro navigacijos lentą permatomą ir susiliejusią",
"name": "Sulieti Navigacijos Lentą"
},
"bypass-age-restrictions": {
"description": "Apeiti muzikos grotuvo amžiaus patikrinimą",
"name": "Apeiti Amžiaus Apribojimus"
},
"captions-selector": {
"description": "„{{applicationName}}“ Garso takelių antraščių parinkiklis",
"menu": {
"autoload": "Automatiškai pasirinkti paskutinę naudotą antraštę",
"disable-captions": "Antraštės išjungtos pagal numatytuosius nustatymus"
},
"name": "Antraščių parinkiklis",
"prompt": {
"selector": {
"label": "Dabartinė antraščių kalba: {{language}}",
"none": "Joks",
"title": "Pasirinkti antraščių kalbą"
}
},
"templates": {
"title": "Atidaryti antraščių parinkiklį"
},
"toast": {
"caption-changed": "Antraštės pakeistos į {{language}}",
"caption-disabled": "Antraštės išjungtos",
"no-captions": "Šiai dainai antraštė nepasiekiama"
}
},
"clock": {
"description": "Pridėti laikrodį prie navigacijos lentos",
"menu": {
"format": {
"24-hour-format": "24‐Valandų Formatas",
"display-seconds": "Rodyti Sekundes",
"label": "Formatas"
}
},
"name": "Laikrodis"
},
"compact-sidebar": {
"description": "Visada nustatyti šoninę juostą kompaktiniame rėžime",
"name": "Kompaktinė šoninė juosta"
},
"crossfade": {
"description": "Perliejimas tarp dainų",
"menu": {
"advanced": "Išplėsti Nustatymai"
},
"name": "Perliejimas [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Pasirodymo trukmė (ms)",
"fade-out-duration": "Išnykimo trukmė (ms)",
"fade-scaling": {
"label": "Išblukimo stiprumas",
"linear": "Linijinis",
"logarithmic": "Logaritminis"
},
"seconds-before-end": "Pradėti lieti dainas N sekundžių prieš pabaigą"
},
"title": "Perliejimo nustatymai"
}
}
},
"custom-output-device": {
"description": "Konfigūruoti pasirinktiną medijos išvesties įrenginį dainoms",
"menu": {
"device-selector": "Pasirinkti įrenginį"
},
"name": "Pasirinktinas išvesties įrenginys",
"prompt": {
"device-selector": {
"label": "Pasirinkti naudotiną medijos išvesties įrenginį",
"title": "Pasirinkite išvesties įrenginį"
}
}
},
"disable-autoplay": {
"description": "Pradeda dainą „pristabdytame“ rėžime",
"menu": {
"apply-once": "Pritaiko tik per programos paleidimą"
},
"name": "Išjungti automatinį leidimą"
},
"discord": {
"backend": {
"already-connected": "Bandyta prisijungti naudojant aktyvų ryšį",
"connected": "Prisijungta prie „Discord“",
"disconnected": "Atsijungta nuo „Discord“"
},
"description": "Parodyk savo draugams ko tu klausaisi su „Rich Presence“",
"menu": {
"auto-reconnect": "Automatiškai prisijungti",
"clear-activity": "Išvalyti veiklą",
"clear-activity-after-timeout": "Išvalyti veiklą po skirtojo laiko",
"connected": "Prisijungta",
"disconnected": "Atsijungta",
"hide-duration-left": "Slėpti kiek liko laiko",
"hide-github-button": "Slėpti „GitHub“ nuorodos mygtuką",
"play-on-application": "Leisti naudojant „{{applicationName}}“",
"set-inactivity-timeout": "Nustatyti neveiklumo laiką",
"set-status-display-type": {
"label": "Būsenos tekstas",
"submenu": {
"application": "Klausosi {{applicationName}}",
"artist": "Klausosi {artist]",
"title": "Klausosi {song title}"
}
}
},
"name": "„Discord“ Turtingas Buvimas (Rich Presence)",
"prompt": {
"set-inactivity-timeout": {
"label": "Įveskite neveiklumo skirtąjį laiką sekundėmis:",
"title": "Nustatyti neveiklumo laiką"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Gerai"
},
"message": "Aaa! Apgailestaujame, nepavyko atsisiųsti…",
"title": "Klaida atsisiunčiant!"
},
"start-download-playlist": {
"buttons": {
"ok": "Gerai"
},
"detail": "({{playlistSize}} dainų)",
"message": "Atsisiunčiama {{playlistTitle}} grojaraštį",
"title": "Prasidėjo atsisiuntimas"
}
},
"feedback": {
"conversion-progress": "Konversija: {{percent}}%",
"converting": "Konvertuojama…",
"done": "Baigta: {{filePath}}",
"download-info": "Atsiunčiama {{artist}} - {{title}} {{videoId}}",
"download-progress": "Atsisiuntimas: {{percent}}%",
"downloading": "Atsisiunčiama…",
"downloading-counter": "Atsisiunčiama {{current}}/{{total}}…",
"downloading-playlist": "Atsisiunčiamas grojaraštis „{{playlistTitle}}“ - {{playlistSize}} dainų {{playlistId}}",
"error-while-downloading": "Klaida atsisiunčiant „{{author}} - {{title}}“: {{error}}",
"folder-already-exists": "Aplankas {{playlistFolder}} jau egzistuoja",
"getting-playlist-info": "Gaunama grojaraščio informacija…",
"loading": "Kraunama…",
"playlist-has-only-one-song": "Grojaraštis turi tik vieną elementą, jis atsisiunčiamas tiesiogiai",
"playlist-id-not-found": "Grojaraščio ID nerastas",
"playlist-is-empty": "Grojaraštis yra tuščias",
"playlist-is-mix-or-private": "Klaida gaunant grojaraščio informaciją: Pasitikrink, ar jis nėra privatus ar \"Surinkta specialiai jums\" grojaraštis\n\n{{error}}",
"preparing-file": "Failas ruošiamas…",
"saving": "Išsaugoma…",
"trying-to-get-playlist-id": "Bandoma gauti grojaraščio ID: {{playlistId}}",
"video-id-not-found": "Vaizdo įrašas nerastas",
"writing-id3": "Rašoma ID3 žymes…"
}
},
"description": "Atsisiunčia MP3 / „šaltinio“ garsą tiesiogiai iš sąsajos",
"menu": {
"choose-download-folder": "Pasirinkti atsisiuntimų aplanką",
"download-finish-settings": {
"label": "Atsisiųsti pabaigus dainą",
"prompt": {
"last-percent": "Po x procentų",
"last-seconds": "Paskutinės x sekundės",
"title": "Nustatyti kada atsisiųsti"
},
"submenu": {
"advanced": "Išplėsti Nustatymai",
"enabled": "Įjungtas",
"mode": "Laiko rėžimas",
"percent": "Procentai",
"seconds": "Sekundės"
}
},
"download-playlist": "Atsisiųsti grojaraštį",
"presets": "Išankstiniai nustatymai",
"skip-existing": "Praleisti egzistuojančius failus"
},
"name": "Atsiuntėjas",
"renderer": {
"can-not-update-progress": "Nepavyko atnaujinti eigos"
},
"templates": {
"button": "Atsisiųsti"
}
},
"equalizer": {
"description": "Prideda vienodintuvą į grotuvą",
"menu": {
"presets": {
"label": "Išankstiniai nustatymai",
"list": {
"bass-booster": "Žemų dažnių stiprintuvas"
}
}
},
"name": "Vienodintuvas"
},
"exponential-volume": {
"description": "Padaro garsumo slankiklį eksponentinį, kad būtų lengviau pasirinkti mažesnį garsumą.",
"name": "Eksponentinis garsas"
},
"in-app-menu": {
"description": "Duoda meniu lentoms įmantrią, tamsią ar albumo spalvos išvaizdą",
"menu": {
"hide-dom-window-controls": "Slėpti „DOM“ lango kontroles"
},
"name": "Programos Meniu"
},
"lumiastream": {
"description": "Prideda „Lumia Stream“ palaikymą",
"name": "„Lumia Stream“ [Beta]"
},
"lyrics-genius": {
"description": "Daugumai dainų prideda jų dainų žodžius",
"menu": {
"romanized-lyrics": "Romanizuoti dainų tekstai"
},
"name": "„Lyrics Genius“ dainų žodžiai",
"renderer": {
"fetched-lyrics": "Gauti žodžiai iš „Genius“"
}
},
"music-together": {
"description": "Pasidalinti grojaraščiu su kitais. Kai vedėjas paleis dainą, visi kiti girdės tą pačią dainą",
"dialog": {
"enter-host": "Įveskite vedėjo ID"
},
"internal": {
"save": "Išsaugoti",
"track-source": "Dainos Šaltinis",
"unknown-user": "Nežinomas Naudotojas"
},
"menu": {
"click-to-copy-id": "Kopijuoti Vedėjo ID",
"close": "Uždaryti „Muzika Kartu“",
"connected-users": "Prisijungę vartotojai",
"disconnect": "Išjungti „Muzika Kartu“",
"empty-user": "Nėra prisijungusių vartotojų",
"host": "„Muzika Kartu“ Vedėjas",
"join": "Prisijungti prie „Muzika Kartu“",
"permission": {
"all": "Leisti svečiams valdyti grojaraštį ir grotuvą",
"host-only": "Tik vedėjas gali valdyti grojaraštį ir grotuvą",
"playlist": "Leisti svečiams valdyti grojaraštį"
},
"set-permission": "Keisti valdymo leidimus",
"status": {
"disconnected": "Atsijungta",
"guest": "Prisijungta kaip Svečias",
"host": "Prisijungta kaip Vedėjas"
}
},
"name": "„Muzika Kartu“ [Beta]",
"toast": {
"add-song-failed": "Nepavyko pridėti dainos",
"closed": "„Muzika Kartu“ uždaryta",
"disconnected": "„Muzika Kartu“ atsijungė",
"host-failed": "Nepavyko surengti „Muzika Kartu“",
"id-copied": "Vedėjo ID nukopijuota į iškarpinę",
"id-copy-failed": "Nepavyko nukopijuoti vedėjo ID į iškarpinę",
"join-failed": "Nepavyko prisijungti prie „Muzika Kartu“",
"joined": "Prisijungta prie „Muzika Kartu“",
"permission-changed": "„Muzika Kartu“ leidimas pakeistas į „{{permission}}“",
"remove-song-failed": "Nepavyko pašalinti dainos",
"user-connected": "{{name}} prisijungė prie „Muzika Kartu“",
"user-disconnected": "{{name}} išėjo iš „Muzika Kartu“"
}
},
"navigation": {
"description": "Kitas/Ankstenis navigacijos rodyklės tiesiogiai integruotos sąsajoje, kaip tavo mėgstamiausioje naršyklėje",
"name": "Navigacija",
"templates": {
"back": {
"title": "Eiti į praeitą puslapį"
},
"forward": {
"title": "Eiti į kitą puslapį"
}
}
},
"no-google-login": {
"description": "Pašalinti „Google“ prisijungimo mygtukus ir nuorodas iš sąsajos",
"name": "Be „Google“ Prisijungimo"
},
"notifications": {
"description": "Rodyti pranešimą, kai pradeda groti daina (interaktyvūs pranešimai pasiekiami sistemoje „Windows“)",
"menu": {
"interactive": "Interaktyvūs pranešimai",
"interactive-settings": {
"label": "Interaktyvūs nustatymai",
"submenu": {
"hide-button-text": "Paslėpti mygtuko tekstą",
"refresh-on-play-pause": "Perkrauti po Paleidimo/Pristabdymo",
"tray-controls": "Atidaryti/Uždaryti po paspaudimo ant padėklo"
}
},
"priority": "Pranešimų pirmenybė",
"toast-style": "įspėjimo pranešimų stilius",
"unpause-notification": "Rodyti pranešimą po dainos paleidimo"
},
"name": "Pranešimai"
},
"performance-improvement": {
"description": "Pagerinkite našumą įjungdami eksperimentinius scenarijus",
"name": "Našumo patobulinimas [Beta]"
},
"picture-in-picture": {
"description": "Leidžia pakeisti programą į „nuotrauka-nuotraukoje“ rėžimą",
"menu": {
"always-on-top": "Visada ant viršaus",
"hotkey": {
"label": "Spartusis klavišas",
"prompt": {
"keybind-options": {
"hotkey": "Spartusis klavišas"
},
"label": "Pasirinkti spartųjį klavišą, kad įjungti/išjungti „nuotrauka-nuotraukoje“ rėžimą",
"title": "„Nuotrauka-Nuotraukoje“ Spartusis klavišas"
}
},
"save-window-position": "Išsaugoti lango poziciją",
"save-window-size": "Išsaugoti lango dydį",
"use-native-pip": "Naudoti naršyklės savąjį „PiP“"
},
"name": "Nuotrauka-nuotraukoje",
"templates": {
"button": "Nuotrauka-nuotraukoje"
}
},
"playback-speed": {
"description": "Klausyk greitai, klausyk lėtai! Prideda slankiklį, kuris valdo dainos greitį",
"name": "Atkūrimo Greitis",
"templates": {
"button": "Greitis"
}
},
"precise-volume": {
"description": "Tiksliai valdykite garsumą naudodami pelės ratuką / sparčiuosius klavišus, naudodami pritaikytą „HUD“ ir pritaikomus garsumo žingsnius",
"menu": {
"arrows-shortcuts": "Vietiniai rodyklių klavišų valdikliai",
"custom-volume-steps": "Nustatykite Pasirinktinius Garsumo Laiptus",
"global-shortcuts": "Pasauliniai spartieji klavišai"
},
"name": "Tikslus Garsas",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Pamažinti Garsą",
"increase": "Padidinti Garsą"
},
"label": "Pasirinkti Pasaulinius garso klavišus:",
"title": "Pasauliniai Garso Klavišai"
},
"volume-steps": {
"label": "Pasirinkti Garso Didinimo/Mažinimo Laipsnius",
"title": "Garso Laipsniai"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Dabartinė Kokybė: {{quality}}",
"message": "Pasirinkite Vaizdo Kokybę:",
"title": "Pasirinkite Vaizdo Kokybę"
}
}
},
"description": "Leidžia pakeisti vaizdo kokybę su mygtuku ant vaizdo perdangos",
"name": "Vaizdo Kokybės Keitėjas",
"renderer": {
"quality-settings-button": {
"label": "Atidaryti grotuvo kokybės keistuvą"
}
}
},
"scrobbler": {
"description": "Pridėti „scrobbling“ palaikymą (pvz., last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Nepavyko autentifikuotis su Last.fm\nPaslėpkite iššokantį langą iki kito paleidimo.",
"title": "Autentifikacija nepavyko"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API nustatymai"
},
"listenbrainz": {
"token": "Įvesti „ListenBrainz“ vartotojo žetoną"
},
"scrobble-alternative-artist": "Naudoti alternatyvius atlikėjus",
"scrobble-alternative-title": "Naudoti alternatyvius pavadinimus",
"scrobble-other-media": "„Scrobble“ kitas medijas"
},
"name": "„Scrobbler“",
"prompt": {
"lastfm": {
"api-key": "Last.fm API raktas",
"api-secret": "Last.fm API paslaptis"
},
"listenbrainz": {
"token": {
"label": "Įveskite savo „ListenBrainz“ vartotojo žetoną:",
"title": "„ListenBrainz“ žetonas"
}
}
}
},
"shortcuts": {
"description": "Leidžia nustatyti visuotinius atkūrimo sparčiuosius klavišus (paleisti / pristabdyti / kitą / ankstesnį) ir išjungti medijos OSD nepaisant medijos klavišų, įjungti Ctrl / CMD + F ieškoti, įjungti Linux MPRIS palaikymą medijos klavišams ir pasirinktinius sparčiuosius klavišus pažengusiems vartotojams",
"menu": {
"override-media-keys": "Perrašyti Medijos klavišus",
"set-keybinds": "Nustatyti Pasaulines Dainų Kontroles"
},
"name": "Spartieji klavišai (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Kitas",
"play-pause": "Paleisti / Pristabdyti",
"previous": "Ankstesnis"
},
"label": "Pasirinkti Pasaulinius Klavišus Dainų Kontroliavimui:",
"title": "Pasauliniai Klavišai"
}
}
},
"skip-disliked-songs": {
"description": "Praleidžia nepatinkančias dainas",
"name": "Praleisti Nepatinkančias Dainas"
},
"skip-silences": {
"description": "Automatiškai praleisti tylos dalis dainose",
"name": "Praleisti Tylumas"
},
"sponsorblock": {
"description": "Automatiškai praleidžia ne muzikines dalis, pvz., įžangą/užvedimą arba muzikinių vaizdo įrašų dalis, kuriose daina negrojama",
"name": "Rėmėjų blokuotojas"
},
"synced-lyrics": {
"description": "Teikia sinchronizuotus dainų žodžius, naudojantis tiekėjais kaip „LRClib“.",
"errors": {
"fetch": "⚠️\t\tĮvyko klaida gaunant dainos žodžius.\n\tPabandykite dar karta vėliau.",
"not-found": "⚠️ Šiai dainai nerasti dainos tekstai."
},
"menu": {
"convert-chinese-character": {
"label": "Konvertuoti Kinietišką ženklą",
"submenu": {
"disabled": {
"label": "Išjungtas",
"tooltip": "Išjungti Kinietiškų ženklų konvertavimą"
},
"simplified-to-traditional": {
"label": "Supaprastintą į Tradicinę",
"tooltip": "Konvertuoti Supaprastintą Kinų kalbą į Tradicinę Kinų kalbą"
},
"traditional-to-simplified": {
"label": "Tradicionalią į Supaprastintą",
"tooltip": "Konvertuoti Tradicionalią Kinų kalbą į Supaprastintą Kinų kalbą"
}
},
"tooltip": "Konvertuoti Kinietiškus ženklus į Tradicinę arba Supaprastintą"
},
"default-text-string": {
"label": "Numatyti simboliai tarp dainos žodžių",
"tooltip": "Pasirinkite numatytąjį simbolį kurį naudoti tarpui tarp dainos žodžių"
},
"line-effect": {
"label": "Teksto linijos efektas",
"submenu": {
"fancy": {
"label": "Prašmatnus",
"tooltip": "Naudoti didelius, panašius į programos efektus dabartinei eilutei"
},
"focus": {
"label": "Sutelkti dėmesį",
"tooltip": "Padaryti tik dabartinę eilutę baltą"
},
"offset": {
"label": "Nuokrypis",
"tooltip": "Dabartinę eilutę pastumti iš dešinės"
},
"scale": {
"label": "Skalė",
"tooltip": "Padidinti dabartinę eilutę"
}
},
"tooltip": "Pasirinkti efektą, kurį pritaikyti dabartinei linijai"
},
"precise-timing": {
"label": "Tobulai sinchronizuoti dainos žodžius",
"tooltip": "Apskaičiuoti kitos eilutės rodymą milisekundės tikslumu (gali turėti nedidelę įtaką našumui)"
},
"preferred-provider": {
"label": "Pageidaujamas tiekėjas",
"none": {
"label": "Jokio",
"tooltip": "Jokio pageidaujamo tiekėjo"
},
"tooltip": "Pasirinkti numatytąjį tiekėją kurį naudoti"
},
"romanization": {
"label": "Romanizuoti dainos žodžius",
"tooltip": "jei dainos žodžiai yra kita kalba, bandyti pateikti lotyniškų ženklų versiją."
},
"show-lyrics-even-if-inexact": {
"label": "Rodyti dainos žodžius, net jei jie netikslūs",
"tooltip": "Jeigu daina nerasta, įskiepis bando iš naujo su skirtinga paieškos užklausa.\nAntro bandymo rezultatas gali būti netikslus."
},
"show-time-codes": {
"label": "Rodyti laiko žymes",
"tooltip": "Rodyti laiko žymes kartu su dainos žodžiais"
}
},
"name": "„Synced Lyrics“",
"refetch-btn": {
"fetching": "Gaunama...",
"normal": "Atgauti dainos žodžius"
},
"warnings": {
"duration-mismatch": "⚠️ - Dainos žodžiai gali būti nesinchronizuoti dėl trukmės nesutikimo.",
"inexact": "⚠️ - Dainos žodžiai gali šiek tiek skirtis",
"instrumental": "⚠️ – Tai instrumentinė daina"
}
},
"taskbar-mediacontrol": {
"description": "Valdykite atkūrimą iš „Windows“ užduočių juostos",
"name": "Užduočių juostos medijos valdymas"
},
"touchbar": {
"description": "Pridedamas jutiklinės juostos valdiklis „MacOS“ vartotojams",
"name": "„TouchBar“"
},
"transparent-player": {
"description": "Padaro programos langą skaidrų",
"menu": {
"opacity": {
"label": "Skaidrumas",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipas",
"submenu": {
"acrylic": "Akrilas",
"mica": "Žėrutis",
"none": "Joks",
"tabbed": "Atskirtas"
}
}
},
"name": "Skaidrus Grotuvas"
},
"tuna-obs": {
"description": "Integracija su OBS papildiniu „Tuna“",
"name": "„Tuna OBS“"
},
"unobtrusive-player": {
"description": "Trukdo grotuvo atsidarymui grojant dainą",
"name": "Netrukdantis Grotuvas"
},
"video-toggle": {
"description": "Pridedamas mygtukas, skirtas perjungti vaizdo įrašo/dainos režimą. Taip pat galite pasirinktinai pašalinti visą vaizdo įrašo skirtuką",
"menu": {
"align": {
"label": "Lygiavimas",
"submenu": {
"left": "Kairė",
"middle": "Vidurys",
"right": "Dešinė"
}
},
"force-hide": "Priverstinai pašalinti vaizdo įrašo skirtuką",
"mode": {
"label": "Rėžimas",
"submenu": {
"custom": "Pasirinktinis perjungimas",
"disabled": "Išjungta",
"native": "Vietinis perjungimas"
}
}
},
"name": "Vaizdo įrašo perjungimas",
"templates": {
"button-song": "Daina",
"button-video": "Vaizdo įrašas"
}
},
"visualizer": {
"description": "Prie grotuvo pridedamas vizualizatorius",
"menu": {
"visualizer-type": "Vizualizatoriaus tipas"
},
"name": "Vizualizatorius"
}
}
}
================================================
FILE: src/i18n/resources/lv.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Problēma ielādēt plaginu {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plagins {{pluginName}}::{{contextName}} ielādējās par{{ms}}ms",
"initialize-failed": "Kļūda inicializējot plaginu \"{{pluginName}}\"",
"load-all": "Ielādē visus plaginus",
"load-failed": "Kļūda ielādējot plaginu \"{{pluginName}}\"",
"loaded": "Plagins \"{{pluginName}}\" ielādēts",
"unload-failed": "Kļūda izlādējot plaginu \"{{pluginName}}\"",
"unloaded": "Plagins \"{{pluginName}}\" izlādēts"
}
}
},
"language": {
"code": "lv",
"local-name": "Latviešu",
"name": "Latvian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Lejupielāde pabeigta. DevTools atvērts"
},
"i18n": {
"loaded": "i18n ielādēts"
},
"second-instance": {
"receive-command": "Saņemta komanda pāri protokolam: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS fails \"{{cssFile}}\" neeksistē, ignorējas"
},
"unresponsive": {
"details": "Lietotne neatbild\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Iztīra lietotnes kešu"
},
"window": {
"tried-to-render-offscreen": "Logs mēģināja atvērties ārpuss ekrāna barjeriem, loga izmērs={{windowSize}}, ekrāna izškirtspēja={{displaySize}}, pozīcija={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Izvēlne ir noslēpta, izmantojiet 'Alt' lai to parādītu (vai 'Escape' ja izmanto lietotnes iekšējo izvēlni)",
"message": "Izvēlnes noslēpšana ir ieslēgta",
"title": "Izvēlnes noslēpšana ieslēgta"
},
"need-to-restart": {
"buttons": {
"later": "Vēlāk",
"restart-now": "Restartēt Tagad"
},
"detail": "Restartējiet lietotni lai aktivizētu plaginu {{pluginName}}",
"message": "{{pluginName}} pieprasa restartēšanu",
"title": "Vajadzīga restartēšana"
},
"unresponsive": {
"buttons": {
"quit": "Iziet",
"relaunch": "Restartēt",
"wait": "Pagaidat"
},
"detail": "Atvainojamies par neērtībām! Lūdzu izvēlaties ko darīt:",
"message": "Lietotne neatbild",
"title": "Logs neatbild"
},
"update-available": {
"buttons": {
"disable": "Izslēgt atjauninājumus",
"download": "Lejupielādēt",
"ok": "OK"
},
"detail": "Jauna versija ir pieejama, un var tikt lejupielādēta šeit {{downloadLink}}",
"message": "Pieejama jauna versija",
"title": "Pieejams atjauninājums"
}
},
"menu": {
"about": "Par",
"navigation": {
"label": "Navigācija",
"submenu": {
"copy-current-url": "Kopēt pašreizējo URL",
"go-back": "Atgriezties",
"go-forward": "Uz priekšu",
"quit": "Iziet",
"restart": "Restartēt lietotni"
}
},
"options": {
"label": "Iestatījumi",
"submenu": {
"advanced-options": {
"label": "Paplašināti iestatījumi",
"submenu": {
"auto-reset-app-cache": "Iztīrīt kešu kad lietotne palaižas",
"disable-hardware-acceleration": "Atslēgt aparatūras paātrinājumu",
"edit-config-json": "Labot config.json",
"override-user-agent": "Pārrakstīt lietotāja aģenta informāciju",
"restart-on-config-changes": "Restartēt kad mainās konfigurācija",
"set-proxy": {
"label": "Iestatīt proxy",
"prompt": {
"label": "Ievadiet Proxy adresi: (atstajiet tukšu lai izslēgtu)",
"placeholder": "Piemērs: SOCKS5://127.0.0.1:9999",
"title": "Iestatīt proxy"
}
},
"toggle-dev-tools": "Ieslēgt DevTools"
}
},
"always-on-top": "Vienmēr priekšā",
"auto-update": "Automātiski atjauninājumi",
"hide-menu": {
"dialog": {
"message": "Izvēlne tiks paslēpta kad palaidīsiet letotni atkal, izmantojiet [Alt] lai parādīt to (vai [`] ja izmantojiet lietotnes iekšējo izvēlni)",
"title": "Izvēlnes paslepšana ieslēgta"
},
"label": "Paslēpt izvēlni"
},
"language": {
"dialog": {
"message": "Valoda tiks pamainīta pēc restartēšanas",
"title": "Valoda pamainīta"
},
"label": "Valoda",
"submenu": {
"to-help-translate": "Gribat palīdzēt ar tulkošanu? Spiežat šeit"
}
},
"resume-on-start": "Turpināt pēdējo dziesmu kad lietotne palaižas",
"single-instance-lock": "Liegums palaists vairākas lietotnes",
"start-at-login": "Palaist ieslēdzot datoru",
"starting-page": {
"label": "Sākuma lapa",
"unset": "Nav iestatīts"
},
"tray": {
"label": "Minimizēt uz teknīti",
"submenu": {
"disabled": "Izslēgts",
"enabled-and-hide-app": "Teknīte ieslēgta, paslēpt lietotnes logu",
"enabled-and-show-app": "Ielsēgts un rādīt lietotni",
"play-pause-on-click": "Atskaņot/Pauzēt piespiežot"
}
},
"visual-tweaks": {
"label": "Vizuāli Iestatījumi",
"submenu": {
"custom-window-title": {
"label": "Pielāgots loga nosaukums",
"prompt": {
"label": "Ievadiet pielāgotu loga nosaukumu: (atstājiet tukšu, lai atspējotu)",
"placeholder": "Piemērs: Pear mūzika"
}
},
"like-buttons": {
"default": "Noklusējums",
"force-show": "Vienmēr rādīt",
"hide": "Paslēpt",
"label": "Like pogas"
},
"remove-upgrade-button": "Noslēpt Premium pogu",
"theme": {
"dialog": {
"button": {
"cancel": "Atcelt",
"remove": "Noņemt"
},
"remove-theme": "Vai tiešām, gribat noņemt šo lietotāj tēmu?",
"remove-theme-message": "Šis noņems lietotāja tēmu"
},
"label": "Tēma",
"submenu": {
"import-css-file": "Importēt CSS failu",
"no-theme": "Bez tēmas"
}
}
}
}
}
},
"plugins": {
"enabled": "Ieslēgts",
"label": "Plagini",
"new": "JAUNS"
},
"view": {
"label": "Skats",
"submenu": {
"force-reload": "Piespiedu Restartēt",
"reload": "Atsvaidzināt",
"reset-zoom": "Noklusējuma izmērs",
"toggle-fullscreen": "Pārslēgt pilnekrāna režīmu",
"zoom-in": "Pietuvināt",
"zoom-out": "Attālināt"
}
}
},
"tray": {
"next": "Nākošais",
"play-pause": "Atskaņot/Pauzēt",
"previous": "Iepriekšējais",
"quit": "Iziet",
"restart": "Restartēt Lietotni",
"show": "Rādīt logu",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Ja reklāma atskaņojas, tad skaņa izlēdzas, un ātrums pārslēdzas uz 16x",
"name": "Reklāmu paātrinājums"
},
"adblocker": {
"description": "Bloķēt visas reklāmas un trekerus uzreiz pēc instalēšanas",
"menu": {
"blocker": "Bloķētājs"
},
"name": "Reklāmu bloķētājs"
},
"album-actions": {
"description": "Pievieno Undislike, Dislike, Like, un Unlike pogas lai izdarītu to visām dziesmām Mūzikas sarakstā vai albumā",
"name": "Darbības ar Albumu"
},
"album-color-theme": {
"description": "Iestata dinamisku motīvu un efektus, pamatojoties uz albuma krāsu paleti",
"menu": {
"color-mix-ratio": {
"label": "Krāsu attiecība",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Albuma krāsu tēma"
},
"ambient-mode": {
"description": "Pielieto apgaismojuma efektu, kas maigā krāsā atstaro video attēlu uz ekrāna fona",
"menu": {
"blur-amount": {
"label": "Izplūšanas itensitāte",
"submenu": {
"pixels": "{{blurAmount}} pikseļi"
}
},
"buffer": {
"label": "buferis",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Caurspīdīgums",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvalitāte",
"submenu": {
"pixels": "{{quality}} pikseļi"
}
},
"size": {
"label": "Izmērs",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Vienmērīga pāreja",
"submenu": {
"during": "Pa {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Izmantot pilnekrāna režīmu"
}
},
"name": "Ambientais Režīms"
},
"amuse": {
"description": "Pievieno {{applicationName}} atblastu priekš Amuse \"tagad spēlē\" no 6K Labs",
"name": "Amuse",
"response": {
"query": "Amuse API serveris ir palaists. GET /query lai dabūtu dziesmas info."
}
},
"api-server": {
"description": "Pievieno API serveri lai kontrolētu atskaņotāju",
"dialog": {
"request": {
"buttons": {
"allow": "Atļaut",
"deny": "Aizliegt"
},
"message": "Vai atļaut {{ID}} ({{origin}}) piekļūt pie API?",
"title": "API autorizācijas pieprasījums"
}
},
"menu": {
"auth-strategy": {
"label": "Autorizācijas veids",
"submenu": {
"auth-at-first": {
"label": "Autorizēt pie pirmā pieprasījuma"
},
"none": {
"label": "Bez autorizācijas"
}
}
},
"hostname": {
"label": "Hosta nosaukums"
},
"port": {
"label": "Ports"
}
},
"name": "API Serveris [BETA]",
"prompt": {
"hostname": {
"label": "Ievadiet hosta vārdu (piemēram 0.0.0.0) priekš API severa:",
"title": "Hosta vārds"
},
"port": {
"label": "Ievadiet portu priekš API servera:",
"title": "Ports"
}
}
},
"audio-compressor": {
"description": "Pievieno saspiešanu pie audio (samazina skaļāko audio daļu skaļumu un palielina mīkstāko daļu skaļumu)",
"name": "Audio saspiedējs"
},
"auth-proxy-adapter": {
"description": "Atbalsts priekš proxy servisu autentifikācijas",
"menu": {
"disable": "Izslēgt Proxy Adapteri",
"enable": "Ieslēgt Proxy Adapteri",
"hostname": {
"label": "Hosta vārds"
},
"port": {
"label": "Ports"
}
},
"name": "Autorizācijas Proxy Adapteris",
"prompt": {
"hostname": {
"label": "Ievadiet hosta vārdu priekš lokālā proxy servera (nepieciešama restartēšana):",
"title": "Proxy hosta vārds"
},
"port": {
"label": "Ievadiet portu priekš lokālā proxy servera (nepieciešams restartēt):",
"title": "Proxy Ports"
}
}
},
"blur-nav-bar": {
"description": "Taisa navigāzijas joslu caurspīdīgu un izplūdušu",
"name": "Izplūdusi Navigācijas Josla"
},
"bypass-age-restrictions": {
"description": "Apiet Music Player vecuma pārbaudi",
"name": "Apiet Vecuma Ierobežojumus"
},
"captions-selector": {
"description": "Subtitru izvēlne priekš {{applicationName}} audio ceļiem",
"menu": {
"autoload": "Automātiski izvēlēties pēdējo izmantotos subtitrus",
"disable-captions": "Bez subtitriem pēc noklusējuma"
},
"name": "Subtitru Izvēlne",
"prompt": {
"selector": {
"label": "Tekošā subtitru valoda: {{language}}",
"none": "Neviens",
"title": "Izvēlieties subtitru valodu"
}
},
"templates": {
"title": "Atvērt subtitru izvēlni"
},
"toast": {
"caption-changed": "Subtitri pamainīti uz {{language}}",
"caption-disabled": "Subtitri izslēgti",
"no-captions": "Subtitri nav pieejami priekš šīs dziesmas"
}
},
"compact-sidebar": {
"description": "Vienmēr iestatīt malējo paneli kompaktajā režīmā",
"name": "Kompakts malējais panelis"
},
"crossfade": {
"description": "Maiga pāreja starp dziesmām",
"menu": {
"advanced": "Paplašināts"
},
"name": "Maiga pāreja [BETA]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Ieplūšanas ilgums (ms)",
"fade-out-duration": "Izplūšanas ilgums (ms)",
"fade-scaling": {
"label": "Pārejas stiprums",
"linear": "Lineārs",
"logarithmic": "Logaritmisks"
},
"seconds-before-end": "Maigas pārejas N sekundes pirms beigām"
},
"title": "Maigas pārejas iestatījumi"
}
}
},
"custom-output-device": {
"description": "Pielāgotas izvades multivides ierīces konfigurēšana dziesmām",
"menu": {
"device-selector": "Izvēlieties ierīci"
},
"name": "Pielāgota izvades ierīce",
"prompt": {
"device-selector": {
"label": "Izvēlieties izmantojamo izvades multivides ierīci",
"title": "Izvēlieties izvades ierīci"
}
}
},
"disable-autoplay": {
"description": "Palaiž dziesmu nopauzētu",
"menu": {
"apply-once": "Iedarbojas tikai palaišanas brīdī"
},
"name": "Atspējot automātisku atskaņošanu"
},
"discord": {
"backend": {
"already-connected": "Mēģināja pieslēgties jau ar aktīvu savienojumu",
"connected": "Pieslēgts pie Discord",
"disconnected": "Atslēgts no Discord"
},
"description": "Parādiet saviem draugiem ko jūs klausaties ar Rich Presence",
"menu": {
"auto-reconnect": "Automātiska atkārtota pieslēgšanās",
"clear-activity": "Attīrīt aktivitāti",
"clear-activity-after-timeout": "Attīrīt aktivitāti pēc taimauta",
"connected": "Pievienojies",
"disconnected": "Atvienojies",
"hide-duration-left": "Paslēpt cik palika laika",
"hide-github-button": "Paslēpt GitHub saites pogu",
"play-on-application": "Atskaņot uz {{applicationName}}",
"set-inactivity-timeout": "Iestatīt neaktivitātes taimeru",
"set-status-display-type": {
"label": "Statusa teksts",
"submenu": {
"artist": "Klausos {mākslinieku}",
"title": "Klausos {dziesmas nosaukums}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Ievadiet neaktivitātes taimeru sekundēs:",
"title": "Iestatīt neaktivitātes taimeru"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Atvainojamies, lejupielāde neizdevās…",
"title": "Lejupielādes kļūda!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}}Dziesmas)",
"message": "Lejupielādē atskaņošanas sarakstu {{playlistTitle}}",
"title": "Lejupielāde sākta"
}
},
"feedback": {
"conversion-progress": "Konvertēšana: {{percent}}%",
"converting": "Konvertēšana…",
"done": "Pabeigts: {{filePath}}",
"download-info": "Lejupielādē {{artist}} - {{title}}{{videoId}}",
"download-progress": "Lejupielāde: {{percent}}%",
"downloading": "Lejupielādē…",
"downloading-counter": "Notiek lejupielāde {{current}}/{{total}}…",
"downloading-playlist": "Atskaņošanas saraksta lejupielāde \"{{playlistTitle}}\" - {{playlistSize}} songs ({{playlistId}})",
"error-while-downloading": "Kļūda lejuplādējot \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Mape {{playlistFolder}} jau eksistē",
"getting-playlist-info": "Tiek iegūta atskaņošanas saraksta informācija…",
"loading": "Ielādē…",
"playlist-has-only-one-song": "Sarakstā ir tikai viena dziesma, tā tiek lejupielādēta uzreiz",
"playlist-id-not-found": "Neizdevās atrast atskaņošanas saraksta ID",
"playlist-is-empty": "Atskaņošanas saraksts ir tukšs",
"playlist-is-mix-or-private": "Neizdevās iegūt atskaņošanas saraksta informāciju: pārliecinieties, ka tas nav privāts vai \"Jums izveidotā izlase\" saraksts.\n\n{{error}}",
"preparing-file": "Faila sagatavošana…",
"saving": "Saglabāšana…",
"trying-to-get-playlist-id": "Mēģinu iegūt atskaņošanas saraksta ID: {{playlistId}}",
"video-id-not-found": "Video nav atrasts",
"writing-id3": "Rakstam ID3 tegus…"
}
},
"description": "Lejupielādē MP3/avota audio tieši no saskarnes",
"menu": {
"choose-download-folder": "Izvēlieties lejupielādes mapi",
"download-finish-settings": {
"label": "Lejupielādēt pēc pabeigšanas",
"prompt": {
"last-percent": "Pēc x procentiem",
"last-seconds": "Pēdējās x sekundes",
"title": "Konfigurējiet lejupielādes laiku"
},
"submenu": {
"advanced": "Paplašināts",
"enabled": "Iespējots",
"mode": "Laika režīms",
"percent": "Procenti",
"seconds": "Sekundes"
}
},
"download-playlist": "Lejupielādēt atskaņošanas sarakstu",
"presets": "Iepriekšiestatījumi",
"skip-existing": "Izlaist esošos failus"
},
"name": "Lejupielādētājs",
"renderer": {
"can-not-update-progress": "Nevar atjaunināt progresu"
},
"templates": {
"button": "Lejupielādēt"
}
},
"equalizer": {
"description": "Pievieno atskaņotājam ekvalaizeru",
"menu": {
"presets": {
"label": "Iepriekšiestatījumi",
"list": {
"bass-booster": "Basu pastiprinātājs"
}
}
},
"name": "Ekvalaizers"
},
"exponential-volume": {
"description": "Padara skaļuma slīdni eksponenciālu, lai būtu vieglāk izvēlēties zemākus skaļumus.",
"name": "Eksponenciālais apjoms"
},
"in-app-menu": {
"description": "Piešķir izvēļņu joslām greznu, tumšu vai albuma krāsas izskatu",
"menu": {
"hide-dom-window-controls": "Slēpt DOM loga vadīklas"
},
"name": "Lietotnes izvēlne"
},
"lumiastream": {
"description": "Pievieno Lumia Stream atbalstu",
"name": "Lumia Stream [Beta versija]"
},
"lyrics-genius": {
"description": "Pievieno dziesmu tekstu atbalstu lielākajai daļai dziesmu",
"menu": {
"romanized-lyrics": "Romanizēti dziesmu teksti"
},
"name": "Dziesmu vārdu ģēnijs",
"renderer": {
"fetched-lyrics": "Iegūti dziesmu vārdi priekš oriģināla"
}
},
"music-together": {
"description": "Kopīgojiet atskaņošanas sarakstu ar citiem. Kad vadītājs atskaņo dziesmu, visi pārējie dzirdēs to pašu dziesmu.",
"dialog": {
"enter-host": "Ievadiet resursdatora ID"
},
"internal": {
"save": "Saglabāt",
"track-source": "Dziesmas avots",
"unknown-user": "Nezināms lietotājs"
},
"menu": {
"click-to-copy-id": "Kopēt resursdatora ID",
"close": "Aizvērt mūziku kopā",
"connected-users": "Savienotie lietotāji",
"disconnect": "Atvienojiet mūziku kopā",
"empty-user": "Nav pievienotu lietotāju",
"host": "Kopklausīšanās vadītājs",
"join": "Pievienojieties mūzikai kopā",
"permission": {
"all": "Atļaut viesiem kontrolēt atskaņošanas sarakstu un atskaņotāju",
"host-only": "Tikai resursdators var kontrolēt atskaņošanas sarakstu un atskaņotāju",
"playlist": "Atļaut viesiem kontrolēt atskaņošanas sarakstu"
},
"set-permission": "Izmainīt kontroles piekļuvi",
"status": {
"disconnected": "Atvienots",
"guest": "Pieslēdzies kā viesis",
"host": "Pieslēdzies kā vadītājs"
}
},
"name": "Mūzika Kopā [Beta]",
"toast": {
"add-song-failed": "Neizdevās pievienot dziesmu",
"closed": "Mūzika Kopā slēgta",
"disconnected": "Mūzika Kopā atvienota",
"host-failed": "Neizdevās uzsākt Mūziku Kopā"
}
}
}
}
================================================
FILE: src/i18n/resources/ml.json
================================================
{
"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": "ml",
"local-name": "മലയാളം",
"name": "Malayalam"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "ലോഡിങ് തീർത്തു. DevTools തുറന്നു"
},
"i18n": {
"loaded": "i18n ലോഡ് ചെയ്തു"
},
"second-instance": {
"receive-command": "പ്രോട്ടോക്കോളിലൂടെ കമാൻഡ് ലഭിച്ചു : \"{{command}}\""
},
"theme": {
"css-file-not-found": "\"{{cssFile}}\" എന്ന CSS file നിലവിൽ ഇല്ല. ഉപേക്ഷിക്കുന്നു"
},
"unresponsive": {
"details": "പ്രതികാരമില്ലാത്ത എറർ \n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "ആപ്പ് cache ക്ലിയർ ചെയ്യുന്നു"
},
"window": {
"tried-to-render-offscreen": "Window സ്ക്രീനിനു വെളിയിൽ render ചെയ്യാൻ ശ്രമിച്ചു, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu മറച്ചിരിക്കുന്നു, അവതരിപ്പിക്കാൻ 'Alt' ഉപയോഗിക്കു (In-App Menu ഉപയോഗിക്കുന്നെങ്കിൽ 'Escape' )",
"message": "Hide Menu പ്രവർത്തിയിൽ",
"title": "Hide Menu പ്രവർത്തിയിൽ"
},
"need-to-restart": {
"buttons": {
"later": "പിന്നീട",
"restart-now": "ഇപ്പോൾ പുനരാരംഭിക്കുക"
},
"detail": "\"{{pluginName}}\" പ്രവർത്തിയിൽ ആകാൻ restart വേണ്ടിയിരിക്കുന്നു",
"message": "\"{{pluginName}}\" restart ആവശ്യപെടുന്നു",
"title": "പുനരാരംഭിക്കേണ്ടതുണ്ട്"
},
"unresponsive": {
"buttons": {
"quit": "ഉപേക്ഷിക്കുക",
"relaunch": "പുനരാരംഭിക്കുക",
"wait": "കാത്തിരിക്കൂ"
},
"detail": "അസൗകര്യത്തിൽ ഞങ്ങൾ ഖേദിക്കുന്നു! എന്തുചെയ്യണമെന്ന് ദയവായി തിരഞ്ഞെടുക്കുക:",
"message": "ആപ്ലിക്കേഷൻ പ്രതികരിക്കുന്നില്ല",
"title": "വിൻഡോ പ്രതികരിക്കുന്നില്ല"
},
"update-available": {
"buttons": {
"disable": "അപ്ഡേറ്റുകൾ പ്രവർത്തനരഹിതമാക്കുക",
"download": "ഡൗൺലോഡ് ചെയ്യുക",
"ok": "ശരി"
},
"detail": "ഒരു പുതിയ പതിപ്പ് ലഭ്യമാണ്, അത് {{downloadLink}} എന്ന വിലാസത്തിൽ നിന്ന് ഡൗൺലോഡ് ചെയ്യാം",
"message": "ഒരു പുതിയ പതിപ്പ് ലഭ്യമാണ്",
"title": "അപ്ഡേറ്റ് ലഭ്യമാണ്"
}
},
"menu": {
"navigation": {
"label": "നാവിഗേഷൻ",
"submenu": {
"copy-current-url": "നിലവിലെ URL പകർത്തുക",
"go-back": "മടങ്ങിപ്പോവുക",
"go-forward": "മുന്നോട്ട് പോകുക",
"quit": "പുറത്തുകടക്കുക",
"restart": "ആപ്പ് പുനരാരംഭിക്കുക"
}
},
"options": {
"label": "ഓപ്ഷനുകൾ",
"submenu": {
"advanced-options": {
"label": "വിപുലമായ ഓപ്ഷനുകൾ",
"submenu": {
"restart-on-config-changes": "കോൺഫിഗറേഷൻ മാറ്റങ്ങൾ വരുത്തുമ്പോൾ പുനരാരംഭിക്കുക"
}
},
"hide-menu": {
"dialog": {
"message": "അടുത്ത ലോഞ്ചിൽ മെനു മറയ്ക്കപ്പെടും, അത് കാണിക്കാൻ [Alt] ഉപയോഗിക്കുക (അല്ലെങ്കിൽ ഇൻ-ആപ്പ്-മെനു ഉപയോഗിക്കുകയാണെങ്കിൽ ബാക്ക്ടിക്ക് [`])",
"title": "മെനു മറയ്ക്കൽ സജീവമാക്കി"
}
}
}
},
"plugins": {
"enabled": "പ്രവർത്തനക്ഷമമാക്കി"
}
}
},
"plugins": {
"video-toggle": {
"menu": {
"align": {
"label": "ക്രമപ്പെടുത്തൽ",
"submenu": {
"left": "ഇടത്",
"middle": "മധ്യഭാഗം",
"right": "വലത്"
}
}
},
"templates": {
"button-video": "വീഡിയോ"
}
}
}
}
================================================
FILE: src/i18n/resources/ms.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Pelaksaan plugin gagal {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dilaksanakan pada {{ms}}ms",
"initialize-failed": "Gagal untuk memulakan plugin \"{{pluginName}}\"",
"load-all": "Memuatkan semua plugin",
"load-failed": "Gagal untuk memuatkan \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" dimuatkan",
"unload-failed": "Gagal untuk memunggah plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" dipunggahkan"
}
}
},
"language": {
"code": "ms",
"local-name": "Bahasa Malaysia",
"name": "Malay"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Selesai memuat. DevTools dibuka"
},
"i18n": {
"loaded": "i18n dimuatkan"
},
"second-instance": {
"receive-command": "Menerima arahan atas protokol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Fail CSS \"{{cssFile}}\" tidak wujud, mengabaikan"
},
"unresponsive": {
"details": "Ralat Tidak Bertindak Balas!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Membersihkan cache aplikasi"
},
"window": {
"tried-to-render-offscreen": "Tetingkap cuba dirender di luar skrin, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu telah disembunyikan, guna 'Alt' untuk menunjukkannya (atau 'Escape' jika menggunakan In-App Menu)",
"message": "Sembunyikan Menu telah diaktifkan",
"title": "Sembunyikan Menu diaktifkan"
},
"need-to-restart": {
"buttons": {
"later": "Nanti",
"restart-now": "Restart Sekarang"
},
"detail": "\"{{pluginName}}\" plugin memerlukan mula semula untuk ambil keberkesanan",
"message": "\"{{pluginName}}\" perlu dimulakan semula",
"title": "Mulakan Semula Diperlukan"
},
"unresponsive": {
"buttons": {
"quit": "Berhenti",
"relaunch": "Lancar Semula",
"wait": "Tunggu"
},
"detail": "Kami memohon maaf atas kesulitan! sila pilih apa yang perlu dilakukan:",
"message": "Aplikasi Tidak Responsif",
"title": "Window Tidak Responsif"
},
"update-available": {
"buttons": {
"disable": "Lumpuhkan Kemas Kini",
"download": "Muat Turun",
"ok": "OK"
},
"detail": "Versi baharu kini tersedia dan boleh dimuat turun di {{downloadLink}}",
"message": "Versi baharu kini tersedia",
"title": "Kemas Kini Tersedia"
}
},
"menu": {
"about": "Mengenai",
"navigation": {
"label": "Navigasi",
"submenu": {
"copy-current-url": "Salin URL semasa",
"go-back": "Belakang",
"go-forward": "Depan",
"quit": "Keluar",
"restart": "Mula Semula Aplikasi"
}
},
"options": {
"label": "Tetapan",
"submenu": {
"advanced-options": {
"label": "Tetapan Lanjutan",
"submenu": {
"auto-reset-app-cache": "Tetapkan semula cache aplikasi apabila aplikasi dimulakan",
"disable-hardware-acceleration": "Lumpuhkan pecutan perkakasan",
"edit-config-json": "Ubah suai config.json",
"override-user-agent": "Gantikan Ejen Pengguna",
"restart-on-config-changes": "Mulakan semula pada perubahan konfigurasi",
"set-proxy": {
"label": "Tetapkan proksi",
"prompt": {
"label": "Masukkan Alamat Proksi: (biarkan kosong untuk menyahdayakan)",
"placeholder": "Contoh: SOCKS5://127.0.0.1:9999",
"title": "Set proksi"
}
},
"toggle-dev-tools": "Togol DevTools"
}
},
"always-on-top": "Sentiasa di atas",
"auto-update": "Kemas Kini Automatik",
"hide-menu": {
"dialog": {
"message": "Menu akan disembunyikan pada pelancaran seterusnya, gunakan [Alt] untuk menunjukkannya (atau backtick [`] jika menggunakan dalam aplikasi-menu)",
"title": "Sembunyikan Menu Diaktifkan"
},
"label": "Sembunyikan Menu"
},
"language": {
"dialog": {
"message": "Bahasa akan ditukar selepas dimulakan semula",
"title": "Bahasa diubah"
},
"label": "Bahasa",
"submenu": {
"to-help-translate": "Ingin membantu menterjemah? Klik di sini"
}
},
"resume-on-start": "Mulakan semula lagu terakhir apabila aplikasi dimulakan",
"single-instance-lock": "Kunci Kejadian Tunggal",
"start-at-login": "Mulakan semasa log masuk",
"starting-page": {
"label": "Halaman Permulaan",
"unset": "Nyahset"
},
"tray": {
"label": "Dulang",
"submenu": {
"disabled": "Dilumpuhkan",
"enabled-and-hide-app": "Didayakan dan sembunyikan aplikasi",
"enabled-and-show-app": "Didayakan dan paparkan aplikasi",
"play-pause-on-click": "Main / Hentikan pada klik"
}
},
"visual-tweaks": {
"label": "Pembaikan Visual",
"submenu": {
"custom-window-title": {
"label": "Tajuk tetingkap tersuai",
"prompt": {
"label": "Masukkan tajuk tetingkap tersuai: (biarkan kosong untuk matikan)",
"placeholder": "Contoh: {{applicationName}}"
}
},
"like-buttons": {
"default": "Lalai",
"force-show": "Paksa pamer",
"hide": "Sembunyi",
"label": "Butang suka"
},
"remove-upgrade-button": "Buang butang naik taraf",
"theme": {
"dialog": {
"button": {
"cancel": "Batalkan",
"remove": "Padam"
},
"remove-theme": "Adakah anda pasti mahu mengalih keluar tema tersuai?",
"remove-theme-message": "Ini akan membuang tema tersuai"
},
"label": "Tema",
"submenu": {
"import-css-file": "Import fail CSS tersuai",
"no-theme": "Tiada tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Didayakan",
"label": "Pemalam",
"new": "BARU"
},
"view": {
"label": "Pandangan",
"submenu": {
"force-reload": "Paksa Muat Semula",
"reload": "Muat semula",
"reset-zoom": "Saiz Sebenar",
"toggle-fullscreen": "Togol Skrin Penuh",
"zoom-in": "Zum Masuk",
"zoom-out": "Zum Keluar"
}
}
},
"tray": {
"next": "Seterusnya",
"play-pause": "Main / Jeda",
"previous": "Sebelumnya",
"quit": "Keluar",
"restart": "Mulakan Semula Aplikasi",
"show": "Papar tetingkap",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Jika iklan dimainkan, ia meredamkan audio dan menetapkan kelajuan main semula kepada 16x",
"name": "Kelajuan Iklan"
},
"adblocker": {
"description": "Sekat semua iklan dan penjejakan di luar kotak",
"menu": {
"blocker": "Penyekat"
},
"name": "Penyekat Iklan"
},
"album-actions": {
"description": "Menambah butang Suka dan Tidak Suka pada semua lagu dalam senarai main atau album",
"name": "Tindakan Album"
},
"album-color-theme": {
"description": "Menggunakan tema dinamik dan kesan visual berdasarkan palet warna album",
"menu": {
"color-mix-ratio": {
"label": "Nisbah campuran warna",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Tema Warna Album"
},
"ambient-mode": {
"description": "Menggunakan kesan pencahayaan dengan menghantar warna lembut daripada video, ke latar belakang skrin anda",
"menu": {
"blur-amount": {
"label": "Jumlah Kekaburan",
"submenu": {
"pixels": "{{blurAmount}}piksel"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Kelegapan",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kualiti",
"submenu": {
"pixels": "{{kualiti}} piksel"
}
},
"size": {
"label": "Saiz",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Kelancaran transisi",
"submenu": {
"during": "Semasa {{masaInterpolasi}}s"
}
},
"use-fullscreen": {
"label": "Menggunakan skrin penuh"
}
},
"name": "Mod Sekitaran"
},
"amuse": {
"description": "Menambahkan sokongan {{applicationName}} untuk widget sedang dimain Amuse oleh 6K Labs",
"name": "Terhibur",
"response": {
"query": "API server Amuse telah berjalan. GET /query untuk mendapatkan maklumat lagu."
}
},
"api-server": {
"description": "Perlukan server API untuk mengawal pemain",
"dialog": {
"request": {
"buttons": {
"allow": "Benarkan",
"deny": "Nafi"
},
"message": "Benarkan {{ID}} {{origin}} untuk akses API?",
"title": "Permintaan pemberian kuasa kepada API"
}
},
"menu": {
"auth-strategy": {
"label": "Strategi Pengesahan",
"submenu": {
"auth-at-first": {
"label": "Pengesahan pada permintaan pertama"
},
"none": {
"label": "Tiada pengesahan"
}
}
},
"hostname": {
"label": "Nama perumah"
},
"port": {
"label": "Port"
}
},
"name": "Server API [Beta]",
"prompt": {
"hostname": {
"label": "Masukkan nama perumah (seperti 0.0.0.0) untuk server API:",
"title": "Nama perumah"
},
"port": {
"label": "Masukkan port untuk API server:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Laksanakan pemampatan untuk audio (mengurangkan kelantangan bagi bahagian signal yang kuat dan menaikkan kelantangan untuk bahagian yang lembut)",
"name": "Pemampat Audio"
},
"auth-proxy-adapter": {
"description": "Sokongan untuk penggunaan perkhidmatan proksi pengesahan",
"menu": {
"disable": "Matikan adapter proksi",
"enable": "Nyalakan adapter proksi",
"hostname": {
"label": "Nama perumah"
},
"port": {
"label": "Port"
}
},
"name": "Adapter Auth Proksi",
"prompt": {
"hostname": {
"label": "Nyatakan nama hos untuk server proksi tempatan (memerlukan mulakan semula):",
"title": "Nama hos proksi"
},
"port": {
"label": "Masukkan port untuk server proksi tempatan (memerlukan restart):",
"title": "Port Proksi"
}
}
},
"blur-nav-bar": {
"description": "Menjadikan bar navigasi telus dan kabur",
"name": "Kaburkan Bar navigasi"
},
"bypass-age-restrictions": {
"description": "Pintas verifikasi umur Music Player",
"name": "Pintas Sekatan Umur"
},
"captions-selector": {
"description": "Pemilih kapsyen untuk trek audio {{applicationName}}",
"menu": {
"autoload": "Pilih kapsyen terakhir diguna secara automatik",
"disable-captions": "Tiada kapsyen secara lalai"
},
"name": "Pemilih kapsyen",
"prompt": {
"selector": {
"label": "Bahasa kapsyen sekarang: {{bahasa}}",
"none": "Tiada",
"title": "Pilih bahasa kapsyen"
}
},
"templates": {
"title": "Buka pemilih kapsyen"
},
"toast": {
"caption-changed": "Sarikata berubah kepada {{language}}",
"caption-disabled": "Sarikata dimatikan",
"no-captions": "Tiada sarikata untuk lagu ini"
}
},
"compact-sidebar": {
"description": "Sentiasa tetapkan bar sisi dalam mod padat",
"name": "Bar Sisi Padat"
},
"crossfade": {
"description": "Pudar silang antara lagu",
"menu": {
"advanced": "Maju"
},
"name": "Pudar Selang [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Pudar dalam tempoh (ms)",
"fade-out-duration": "Tempoh pudar (ms)",
"fade-scaling": {
"label": "Penskalaan pudar",
"linear": "Linear",
"logarithmic": "Logaritma"
},
"seconds-before-end": "Pudar silang N saat sebelum tamat"
},
"title": "Opsi pudar silang"
}
}
},
"custom-output-device": {
"description": "Konfigurasikan peranti media output tersuai untuk lagu",
"menu": {
"device-selector": "Pilih peranti"
},
"name": "Peranti Output Tersuai",
"prompt": {
"device-selector": {
"label": "Pilih peranti media output yang akan digunakan",
"title": "Pilih Peranti Output"
}
}
},
"disable-autoplay": {
"description": "Membuat lagu bermula dalam mod \"jeda\"",
"menu": {
"apply-once": "Terpakai hanya pada permulaan"
},
"name": "Matikan automain"
},
"discord": {
"backend": {
"already-connected": "Cuba untuk menyambung dengan sambungan aktif",
"connected": "Disambungkan ke Discord",
"disconnected": "Diputuskan sambungan daripada Discord"
},
"description": "Tunjukkan kepada rakan anda perkara yang anda dengar dengan Rich Presence",
"menu": {
"auto-reconnect": "Auto sambung semula",
"clear-activity": "Padamkan aktiviti",
"clear-activity-after-timeout": "Kosongkan aktiviti selepas tamat masa",
"connected": "Tersambung",
"disconnected": "Tidak disambungkan",
"hide-duration-left": "Sembunyikan tempoh yang tinggal",
"hide-github-button": "Sembunyikan Butang pautan GitHub",
"play-on-application": "Main di {{applicationName}}",
"set-inactivity-timeout": "Tetapkan tamat masa tidak aktif",
"set-status-display-type": {
"label": "Teks status",
"submenu": {
"artist": "Sedang mendengar {artist}",
"application": "Mendengar {{applicationName}}",
"title": "Sedang mendengar {tajuk lagu}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Masukkan tamat masa tidak aktif dalam beberapa saat:",
"title": "Tetapkan tamat masa tidak aktif"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Argh! Maaf, muat turun gagal…",
"title": "Ralat dalam muat turun!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} lagu)",
"message": "Memuat turun senarai main {{playlistTitle}}",
"title": "Memuat turn bermula"
}
},
"feedback": {
"conversion-progress": "Penukaran: {{percent}}%",
"converting": "Menukarkan…",
"done": "Selesai: {{filePath}}",
"download-info": "Memuat turun {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Muat turun: {{percent}}%",
"downloading": "Memuat turun…",
"downloading-counter": "Memuat turun {{current}}/{{total}}…",
"downloading-playlist": "Memuat turun senarai main \"{{playlistTitle}}\" - {{playlistSize}} lagu ({{playlistId}})",
"error-while-downloading": "Gagal memuat turun \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Folder {{playlistFolder}} sudah ada",
"getting-playlist-info": "Mendapatkan maklumat senarai main…",
"loading": "Memuat…",
"playlist-has-only-one-song": "Senarai main hanya mempunyai satu item, memuat turunnya terus",
"playlist-id-not-found": "ID senarai main tidak dijumpai",
"playlist-is-empty": "Senarai main kosong",
"playlist-is-mix-or-private": "Ralat dalam mendapatkan senarai info main: pastikan ia tidak peribadi atau di dalam senarai main \"Campuran untuk anda\"\n\n{{error}}",
"preparing-file": "Menyediakan fail…",
"saving": "Menyimpan…",
"trying-to-get-playlist-id": "Mencuba untuk mendapatkan ID senarai main: {{playlistId}}",
"video-id-not-found": "Video tidak dijumpai",
"writing-id3": "Sedang menulis tag ID3…"
}
},
"description": "Memuat turun audio MP3 / sumber terus dari antara muka",
"menu": {
"choose-download-folder": "Pilih folder muat turun",
"download-finish-settings": {
"label": "Muat turun selesai",
"prompt": {
"last-percent": "Selepas peratus x",
"last-seconds": "Saat x terakhir",
"title": "Konfigurasikan masa untuk memuat turun"
},
"submenu": {
"advanced": "Lanjutan",
"enabled": "Dinyalakan",
"mode": "Mod masa",
"percent": "Peratus",
"seconds": "Saat"
}
},
"download-playlist": "Muat turun senarai main",
"presets": "Pratetap",
"skip-existing": "Langkau fail sedia ada"
},
"name": "Pemuat turun",
"renderer": {
"can-not-update-progress": "Tidak boleh memuat turun perkembangan"
},
"templates": {
"button": "Memuat turun"
}
},
"equalizer": {
"description": "Menambahkan penyamaan kepada pemain",
"menu": {
"presets": {
"label": "Pratetap",
"list": {
"bass-booster": "Penggalak bass"
}
}
}
},
"exponential-volume": {
"description": "Menjadikan gelangsar kelantangan eksponen supaya lebih mudah memilih kelantangan yang lebih rendah."
},
"in-app-menu": {
"description": "Memberi bar menu rupa yang mewah, gelap atau warna album",
"menu": {
"hide-dom-window-controls": "Sembunyikan kawalan tetingkap DOM"
},
"name": "Menu Dalam Apl"
},
"lumiastream": {
"description": "Menambah sokongan Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Menambahkan sokongan lirik untuk kebanyakan lagu",
"menu": {
"romanized-lyrics": "Lirik Romanized"
},
"name": "Lirik Genius",
"renderer": {
"fetched-lyrics": "Lirik yang diambil untuk Genius"
}
},
"music-together": {
"description": "Kongsi senarai main dengan orang lain. Apabila hos memainkan lagu, semua orang akan mendengar lagu yang sama",
"dialog": {
"enter-host": "Masukkan ID Hos"
},
"internal": {
"save": "Simpan",
"unknown-user": "Pengguna tidak diketahui"
},
"menu": {
"click-to-copy-id": "Salin ID Hos",
"close": "Tutup Music Together",
"connected-users": "Pengguna yang telah berhubung",
"disconnect": "Putuskan Sambungan Music Together",
"empty-user": "Tiada pengguna yang disambungkan",
"host": "Hos Music Together",
"join": "Sertai Music Together",
"permission": {
"all": "Benarkan tetamu mengawal senarai main dan pemain",
"host-only": "Hanya hos boleh mengawal senarai main dan pemain",
"playlist": "Benarkan tetamu mengawal senarai main"
},
"set-permission": "Tukar Kebenaran Kawalan",
"status": {
"disconnected": "Terputus",
"guest": "Disambungkan sebagai Tetamu",
"host": "Disambungkan sebagai Hos"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Gagal menambah lagu",
"closed": "Music Together ditutup",
"disconnected": "Music Together terputus",
"host-failed": "Gagal untuk menjadi hos Music Together",
"id-copied": "ID hos disalin ke papan keratan",
"id-copy-failed": "Gagal menyalin ID Hos ke papan keratan"
}
},
"navigation": {
"name": "Navigasi"
},
"notifications": {
"name": "Notifikasi"
},
"scrobbler": {
"dialog": {
"lastfm": {
"auth-failed": {
"title": "Atuntikasi Gagal"
}
}
},
"prompt": {
"lastfm": {
"api-key": "kunci API Last.fm",
"api-secret": "rahasia API Last.fm"
}
}
},
"synced-lyrics": {
"errors": {
"not-found": "⚠️ Tak ada liriks untuk lagu ini."
},
"menu": {
"show-lyrics-even-if-inexact": {
"label": "Tunjukkan lirik walaupun tidak tepat",
"tooltip": "Jika lagu tidak ditemui, plugin cuba lagi dengan pertanyaan carian yang berbeza. \nHasil dari percubaan kedua mungkin tidak tepat."
},
"show-time-codes": {
"tooltip": "Tunjukkan kod masa di sebelah lirik"
}
}
},
"taskbar-mediacontrol": {
"description": "Kawalan main balik dari bar tugas Windows anda",
"name": "Kawalan Media Bar Tugas"
},
"video-toggle": {
"menu": {
"align": {
"submenu": {
"left": "Kiri",
"middle": "Tengah",
"right": "Kanan"
}
},
"force-hide": "Alih Keluar Tab Video",
"mode": {
"submenu": {
"disabled": "Tidak Aktif"
}
}
},
"templates": {
"button-song": "Lagu"
}
}
}
}
================================================
FILE: src/i18n/resources/nb.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Klarte ikke å kjøre programtillegg {{pluginName}}::{{contextName}}",
"executed-at-ms": "Det tok {{ms}} ms å kjøre {{pluginName}}::{{contextName}}",
"initialize-failed": "Klarte ikke å igangsette «{{pluginName}}»",
"load-all": "Laster inn alle programtillegg",
"load-failed": "Klarte ikke å laste inn {{pluginName}}-programtillegget",
"loaded": "Lastet inn {{pluginName}}-programtillegget",
"unload-failed": "Kunne ikke skru av {{pluginName}}-programtillegget",
"unloaded": "{{pluginName}}-programtillegg avskrudd"
}
}
},
"language": {
"code": "nb_NO",
"local-name": "Norsk bokmål",
"name": "Norwegian Bokmål"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Innlasting fullført. Utviklerverktøy åpnet."
},
"i18n": {
"loaded": "språkstøtte innlastet"
},
"second-instance": {
"receive-command": "Mottok kommando over protokoll: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS-filen «{{cssFile}}» finnes ikke. Ignorerer."
},
"unresponsive": {
"details": "Svarer ikke\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Tømmer programhurtigbuffer"
},
"window": {
"tried-to-render-offscreen": "Prøvde å tegne vindu utenfor skjermen, størrelse={{windowSize}}, skjermstørrelse={{displaySize}}, posisjon={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menyen er skjult, bruk 'Alt' for å vise den (eller 'Escape' for å bruke menyen i programmet)",
"message": "Meny skjult",
"title": "Meny vist"
},
"need-to-restart": {
"buttons": {
"later": "Senere",
"restart-now": "Start på ny nå"
},
"detail": "{{pluginName}}-programtillegget krever programomstart før bruk.",
"message": "{{pluginName}}-programtillegget krever programomstart.",
"title": "Programomstart kreves"
},
"unresponsive": {
"buttons": {
"quit": "Avslutt",
"relaunch": "Start igjen",
"wait": "Vent"
},
"detail": "Velg blandt følgende:",
"message": "Programmet svarer ikke",
"title": "Vinduet svarer ikke"
},
"update-available": {
"buttons": {
"disable": "Skru av oppgraderinger",
"download": "Last ned",
"ok": "OK"
},
"detail": "En ny versjon er tilgjengelig og kan lastes ned fra {{downloadLink}}",
"message": "En ny versjon er tilgjengelig",
"title": "Oppgradering tilgjengelig"
}
},
"menu": {
"about": "Om",
"navigation": {
"label": "Navigasjon",
"submenu": {
"copy-current-url": "Kopier nåværende nettadresse",
"go-back": "Tilbake",
"go-forward": "Framover",
"quit": "Avslutt",
"restart": "Programomstart"
}
},
"options": {
"label": "Alternativer",
"submenu": {
"advanced-options": {
"label": "Avanserte alternativer",
"submenu": {
"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",
"restart-on-config-changes": "Omstart ved oppsettsendringer",
"set-proxy": {
"label": "Sett mellomtjener",
"prompt": {
"label": "Skriv inn mellomtjeneradresse: (la stå tom for å skru av)",
"placeholder": "Eksempel: socks5://127.0.0.1:9999",
"title": "Sett mellomtjener"
}
},
"toggle-dev-tools": "Skru av/på utviklerverktøy"
}
},
"always-on-top": "Alltid på toppen",
"auto-update": "Auto-oppdatering",
"hide-menu": {
"dialog": {
"message": "Menyen vil bli skjult ved neste programstart. Bruk [Alt] for å vise den, eller gravistegn [`] hvis du bruker menyen i programmet).",
"title": "Skjuler meny"
},
"label": "Skjul meny"
},
"language": {
"dialog": {
"message": "Nytt språk vises når programmet startes på ny",
"title": "Språk endret"
},
"label": "Språk",
"submenu": {
"to-help-translate": "Klikk her for å bistå oversettelsen"
}
},
"resume-on-start": "Gjenoppta siste spor ved programstart",
"single-instance-lock": "Sperr én instans",
"start-at-login": "Start ved innlogging",
"starting-page": {
"label": "Startside",
"unset": "Opphev"
},
"tray": {
"label": "Systemkurv",
"submenu": {
"disabled": "Avskrudd",
"play-pause-on-click": "Spill av/pause ved klikk"
}
},
"visual-tweaks": {
"label": "Visuelle tilpasninger",
"submenu": {
"like-buttons": {
"default": "Forvalg",
"force-show": "Tving visning",
"hide": "Skjul",
"label": "Begunstningsknapper"
},
"remove-upgrade-button": "Fjern oppgraderingsknapp",
"theme": {
"label": "Drakt",
"submenu": {
"import-css-file": "Importer egendefinert CSS-fil",
"no-theme": "Ingen"
}
}
}
}
}
},
"plugins": {
"enabled": "Påskrudd",
"label": "Programtillegg"
},
"view": {
"label": "Vis",
"submenu": {
"force-reload": "Tving gjeninnlasting",
"reload": "Gjeninnlast",
"reset-zoom": "Faktisk størrelse",
"toggle-fullscreen": "Veksle fullskjermsvisning",
"zoom-in": "Forstørr",
"zoom-out": "Forminsk"
}
}
},
"tray": {
"next": "Neste",
"play-pause": "Spill av/pause",
"previous": "Forrige",
"quit": "Avslutt",
"restart": "Programomstart",
"show": "Vis vindu"
}
},
"plugins": {
"adblocker": {
"description": "Stenger ute reklame og sporing",
"menu": {
"blocker": "Blokkering"
},
"name": "Reklameblokkering"
},
"album-color-theme": {
"description": "Ifører dynamisk drakt og visuelle effekter basert på albumsfargepaletten",
"name": "Albumsfargedrakt"
},
"ambient-mode": {
"description": "Ifører lyseffekt ved å hente myke farger fra videoen inn i skjermens bakgrunn.",
"menu": {
"blur-amount": {
"label": "Tilsløringsmengde",
"submenu": {
"pixels": "{{blurAmount}} piksler"
}
},
"buffer": {
"label": "Mellomlager",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Dekkevne",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvalitet",
"submenu": {
"pixels": "{{quality}} piksler"
}
},
"size": {
"label": "Størrelse",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Mykhetsovergang",
"submenu": {
"during": "I løpet av {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Ved bruk av fullskjerm"
}
},
"name": "Omgivelsesmodus"
},
"audio-compressor": {
"description": "Anvend kompresjon for lyd (senker lydstyrken for de kraftigste delene av signalet og øker nivået i de svakeste)",
"name": "Lydkompressor"
},
"blur-nav-bar": {
"description": "Gjør navigeringsbjelken gjennomsiktig og tilslørt",
"name": "Tilslør navigasjonsfelt"
},
"bypass-age-restrictions": {
"description": "Omgå Music Player sin aldersgrenser",
"name": "Omgå aldersgrense"
},
"captions-selector": {
"description": "Undertekstverktøy for lydspor i {{applicationName}}",
"menu": {
"autoload": "Auto-velg sist brukte undertekst",
"disable-captions": "Ingen undertekst som forvalg"
},
"name": "Undertekstvelger",
"prompt": {
"selector": {
"label": "Nåværende tekstingsspråk: {{language}}",
"none": "Ingen",
"title": "Velg tekstingsspråk"
}
},
"templates": {
"title": "Åpne undertekstvelger"
}
},
"compact-sidebar": {
"description": "Alltid sett sidefeltet i kompakt modus",
"name": "Kompakt sidefelt"
},
"crossfade": {
"description": "Overgang mellom spor",
"menu": {
"advanced": "Avansert"
},
"name": "Overgang [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Inntoningsvarighet (ms)",
"fade-out-duration": "Uttoningsvarighet (ms)",
"fade-scaling": {
"label": "Overgangsskalering",
"linear": "Lineær",
"logarithmic": "Logaritmisk"
},
"seconds-before-end": "Overgang antall sekunder før slutt"
},
"title": "Overgangsalternativer"
}
}
},
"disable-autoplay": {
"description": "Merkerer sporstart i «pauset» modus",
"menu": {
"apply-once": "Har kun innvirkning ved oppstart"
},
"name": "Skru av autospilling"
},
"discord": {
"backend": {
"already-connected": "Forsøkte å koble til med aktiv tilkobling",
"connected": "Tilkoblet Discord",
"disconnected": "Frakoblet fra Discord"
},
"description": "Vis venne dine hva du lytter til med rik tilstedeværelse",
"menu": {
"auto-reconnect": "Automatisk retilkobling",
"clear-activity": "Tøm aktivitet",
"clear-activity-after-timeout": "Tøm aktivitet etter tidsavbrudd",
"connected": "Tilkoblet",
"disconnected": "Frakoblet",
"hide-duration-left": "Skjul gjenværende tid",
"hide-github-button": "Skjul GitHub-lenkeknapp",
"play-on-application": "Spill på {{applicationName}}",
"set-inactivity-timeout": "Sett tid før tidsavbrudd"
},
"name": "Rik tilstedeværelse for Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Skriv inn antall sekunder for inaktivitetstidsavbrudd:",
"title": "Sett inaktivitetstidsavbrudd"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Nedlasting mislyktes …",
"title": "Feil i nedlastning."
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} spor)",
"message": "Laster ned {{playlistTitle}}-spillelisten …",
"title": "Nedlasting startet"
}
},
"feedback": {
"conversion-progress": "Konvertering: {{percent}}%",
"converting": "Konverterer …",
"done": "Ferdig: {{filePath}}",
"download-info": "Laster ned {{artist}} — {{title}} [{{videoId}} …",
"download-progress": "Nedlastet: {{percent}}%",
"downloading": "Laster ned …",
"downloading-counter": "Laster ned {{current}}/{{total}} …",
"downloading-playlist": "Laster ned {{playlistTitle}}-spillelisten — {{playlistSize}} spor ({{playlistId}})",
"error-while-downloading": "Kunne ikke laste ned «{{author}} — {{title}}»: {{error}}",
"folder-already-exists": "{{playlistFolder}}-mappen finnes allerede",
"getting-playlist-info": "Henter spillelisteinfo …",
"loading": "Laster inn …",
"playlist-has-only-one-song": "Spillelisten har kun ett element. Laster ned direkte.",
"playlist-id-not-found": "Fant ingen spilleliste-ID",
"playlist-is-empty": "Tom spilleliste",
"playlist-is-mix-or-private": "Kunne ikke hente spillelisteinfo. Forsikre deg om at den ikke er privat eller «Mikset for deg».\n\n{{error}}",
"preparing-file": "Forbereder fil …",
"saving": "Lagrer …",
"trying-to-get-playlist-id": "Prøver å hente spilleliste-ID: {{playlistId}}",
"video-id-not-found": "Fant ikke videoen",
"writing-id3": "Skriver ID3-tagger …"
}
},
"description": "Laster ned MP3/kildelyd direkte fra grensesnittet",
"menu": {
"choose-download-folder": "Velg nedlastningsmappe",
"download-playlist": "Last ned spilleliste",
"presets": "Forhåndsinnstillinger",
"skip-existing": "Hopp over eksisterende filer"
},
"name": "Nedlaster",
"renderer": {
"can-not-update-progress": "Kan ikke oppdatere framdrift"
},
"templates": {
"button": "Last ned"
}
},
"exponential-volume": {
"description": "Gjør lydstyrkekontrollen eksponentiell, slik at det er enklere velge lavere lydstyrker.",
"name": "Eksponentiell lydstyrke"
},
"in-app-menu": {
"description": "Gir menybjelkene stilig, mørk, eller albumfarget utseende",
"menu": {
"hide-dom-window-controls": "Skjul DOM-vinduskontroller"
},
"name": "Meny i programmet"
},
"lumiastream": {
"description": "Legger til Lumia Stream-støtte",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Gir sangtekststøtte for de fleste spor",
"menu": {
"romanized-lyrics": "Romaniserte sangtekster"
},
"name": "Sangtekster fra Genius",
"renderer": {
"fetched-lyrics": "Henter sangtekster for Genius"
}
},
"navigation": {
"description": "Direkte integrering av neste/tilbake-navigasjonspilene i grensesnittet, som din favorittnettleser",
"name": "Navigasjon"
},
"no-google-login": {
"description": "Fjern Google-innloggingsknapper og lenker fra grensesnittet",
"name": "Ingen Google-innlogging"
},
"notifications": {
"description": "Vis en merknad når et spor starter avspilling (interaktive merknader er tilgjengelig på Windows)",
"menu": {
"interactive": "Interaktive merknader",
"interactive-settings": {
"label": "Interaktive innstillinger",
"submenu": {
"hide-button-text": "Skjul knappetekst",
"refresh-on-play-pause": "Gjenoppfrisk ved avspilling/pause",
"tray-controls": "Åpne/lukk med klikk i systemkurven"
}
},
"priority": "Merknadsprioritet",
"unpause-notification": "Vis merknad ved oppheving av pause"
},
"name": "Merknader"
},
"picture-in-picture": {
"description": "Tillater å bytte programmet til bilde-i-bilde modus",
"menu": {
"always-on-top": "Alltid på toppen",
"hotkey": {
"label": "Hurtigtast",
"prompt": {
"keybind-options": {
"hotkey": "Hurtigtast"
},
"label": "Velg en hurtigtast for veksling av bilde-i-bilde",
"title": "Hurtigtast for bilde-i-bilde"
}
},
"save-window-position": "Lagre vindusposisjon",
"save-window-size": "Lagre vindusstørrelse",
"use-native-pip": "Bruk nettleserens innebygde bilde-i-bilde"
},
"name": "Bilde-i-bilde",
"templates": {
"button": "Bilde-i-bilde"
}
},
"playback-speed": {
"description": "Legger til glidebryter som kontrollerer avspillingshastighet",
"name": "Avspillingshastighet",
"templates": {
"button": "Hastighet"
}
},
"precise-volume": {
"description": "Kontroller lydstyrken presist ved bruk av musehjul/hurtigtaster, med egendefinert skjermoverlag og tilpassbare steg",
"menu": {
"arrows-shortcuts": "Kontroller for lokale piltaster",
"custom-volume-steps": "Sett egendefinerte lydstyrkesteg",
"global-shortcuts": "Hurtigtaster i hele programmet"
},
"name": "Presis lydstyrkejustering",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Senk lydstyrke",
"increase": "Øk lydstyrke"
},
"label": "Velg tastetilknytninger for lydstyrkejusteringskontroller i hele programmet",
"title": "Tastetilknytninger for lydstyrkejusteringskontroller i hele programmet"
},
"volume-steps": {
"label": "Velg steg for økning/senking av lydstyrke",
"title": "Lydstyrkesteg"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Nåværende kvalitet: {{quality}}",
"message": "Velg videokvalitet:",
"title": "Velg videokvalitet"
}
}
},
"description": "Tillat endring av videokvalitet med en knapp i videooverlaget",
"name": "Vindukvalitetsvelger"
},
"shortcuts": {
"description": "Tillater bruk av hurtigtaster for hele programmet til avspilling (spill/pause/neste/forrige) + skru av media-videooverlag ved å overstyre mediataster + skru på Ctrl+CMD+F for å søke, pluss å egge til MPRIS støtte på linux for mediataster pluss egendefinerte hurtigtaster for avanserte brukere",
"menu": {
"override-media-keys": "Overstyr mediataster",
"set-keybinds": "Sett kontroller for spor i hele programmet"
},
"name": "Snarveier (og MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Neste",
"play-pause": "Spill av/pause",
"previous": "Forrige"
},
"label": "Velg kontroller for spor i hele programmet:",
"title": "Tastaturtilknytninger i hele programmet"
}
}
},
"skip-disliked-songs": {
"description": "Hopper over mislikte spor",
"name": "Hopp over mislikte spor"
},
"skip-silences": {
"description": "Hopp over stille deler av spor",
"name": "Hopp over pauser"
},
"sponsorblock": {
"description": "Hopper over ikke-musikalske deler, som intro/sluttsats, eller deler av musikkvideoer der ingen musikk spilles",
"name": "SponsorBlock"
},
"taskbar-mediacontrol": {
"description": "Kontroller avspilling fra din Windows-oppgavelinje",
"name": "Oppgavelinje-mediakontroll"
},
"touchbar": {
"description": "Legger til et pekefelt-miniprogram for macOS-brukere",
"name": "Pekefelt"
},
"tuna-obs": {
"description": "Integrasjon med Tuna-programtillegget i OBS",
"name": "OBS Tuna"
},
"video-toggle": {
"description": "Leger til en knapp for å bytte mellom video/spormodus. Kan også alternativt fjerne hele videofanen.",
"menu": {
"align": {
"label": "Justering",
"submenu": {
"left": "Venstre",
"middle": "Midten",
"right": "Høyre"
}
},
"force-hide": "Påtving fjerning av videofane",
"mode": {
"label": "Modus",
"submenu": {
"custom": "Egendefinert veksling",
"disabled": "Avskrudd",
"native": "Innebygd veksling"
}
}
},
"name": "Videoveksling",
"templates": {
"button-song": "Spor"
}
},
"visualizer": {
"description": "Legger til en visualisator i avspilleren",
"menu": {
"visualizer-type": "Visualisatortype"
},
"name": "Visualisator"
}
}
}
================================================
FILE: src/i18n/resources/ne.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "प्लगइन {{pluginName}}::{{contextName}} को कार्यान्वयन गर्न असफल भयो",
"executed-at-ms": "प्लगइन {{pluginName}}::{{contextName}} {{ms}} मिलिसेकेण्डमा कार्यान्वित भयो",
"initialize-failed": "प्लगइन \"{{pluginName}}\" आरम्भ गर्न मिलेन",
"load-all": "सबै प्लगइनहरू लोड हुँदैछ",
"load-failed": "प्लगइन \"{{pluginName}}\" लोड गर्न मिलेन",
"loaded": "प्लगइन \"{{pluginName}}\" लोड भयो",
"unload-failed": "प्लगइन \"{{pluginName}}\" अनलोड गर्न मिलेन",
"unloaded": "प्लगइन \"{{pluginName}}\" अनलोड भयो"
}
}
},
"language": {
"code": "ne",
"local-name": "नेपाली",
"name": "Nepali"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "लोडिंग समाप्त भयो। डेभटुल्स खोलियो"
},
"i18n": {
"loaded": "i18n लोड भयो"
},
"second-instance": {
"receive-command": "प्रोटोकल मार्फत कमान प्राप्त गरियो: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS फाइल \"{{cssFile}}\" मौजूद छैन, अनदेखी गर्दै छ"
},
"unresponsive": {
"details": "अनाक्रियतामा त्रुटि!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "एप क्यास खाली गर्दै"
},
"window": {
"tried-to-render-offscreen": "Windowले स्क्रीन बाहिर रेन्डर गर्न कोशिस गर्यो, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "मेनु लुकिएको छ, यसलाई देखाउन 'Alt' प्रयोग गर्नुहोस् (वा 'Escape' यदि इन-एप मेनु प्रयोग गर्नुहोस्)",
"message": "हाइड मेनु सक्षम गरिएको छ",
"title": "हाइड मेनु इनेबल गरियो"
},
"need-to-restart": {
"buttons": {
"later": "पछि",
"restart-now": "अहिले पुन: सुरु गर्नुहोस्"
},
"detail": "{{pluginName}}\" प्लगइनले प्रभाव समेत गर्नका लागि पुन: सुरु गर्नुपर्दछ",
"message": "{{pluginName}}\" पुनः सुरु गर्नुपर्छ",
"title": "पुनः सुरु गर्नुपर्छ"
},
"unresponsive": {
"buttons": {
"quit": "बन्द गर्नुहोस्",
"relaunch": "पुन: सुरु गर्नुहोस्",
"wait": "प्रतीक्षा गर्नुहोस्"
},
"detail": "हामी असुविधाका लागि क्षमा गर्दछौं! कृपया के गर्नुहोस् छन् छान्नुहोस्:",
"message": "अनुप्रयोग असार्थक छ",
"title": "विन्डो संपर्क नाघेको छ"
},
"update-available": {
"buttons": {
"disable": "अपडेटहरू निष्क्रिय गर्नुहोस्",
"download": "डाउनलोड गर्नुहोस्",
"ok": "ठिक छ"
},
"detail": "नयाँ संस्करण उपलब्ध छ र यसलाई {{downloadLink}} बाट डाउनलोड गर्न सकिन्छ",
"message": "नयाँ संस्करण उपलब्ध छ",
"title": "अपडेट उपलब्ध छ"
}
},
"menu": {
"about": "बारेमा",
"navigation": {
"label": "नेभिगेसन",
"submenu": {
"copy-current-url": "हालको URL प्रतिलिपि गर्नुहोस्",
"go-back": "पछाडि जानुहोस्",
"go-forward": "अघि जानुहोस्",
"quit": "बाहिर निस्कनुहोस्",
"restart": "अनुप्रयोग पुनः सुरु गर्नुहोस्"
}
},
"options": {
"label": "विकल्पहरू",
"submenu": {
"advanced-options": {
"label": "उन्नत विकल्पहरू",
"submenu": {
"auto-reset-app-cache": "एप सुरु हुँदा एप क्यास रिसेट गर्नुहोस्",
"disable-hardware-acceleration": "हार्डवेयर तेजीगरी निष्क्रिय गर्नुहोस्",
"edit-config-json": "config.json सम्पादन गर्नुहोस्",
"override-user-agent": "प्रयोगकर्ता-एजेन्ट अधिलेखन गर्नुहोस्",
"restart-on-config-changes": "कन्फिगरेसन परिवर्तनमा पुनः सुरु गर्नुहोस्",
"set-proxy": {
"label": "प्रोक्सी सेट गर्नुहोस्",
"prompt": {
"label": "प्रोक्सी ठेगाना प्रविष्टि: (निष्क्रिय गर्नका लागि खाली छोड्नुहोस्)",
"placeholder": "उदाहरण: SOCKS5://127.0.0.1:9999",
"title": "प्रोक्सी सेट गर्नुहोस्"
}
},
"toggle-dev-tools": "डेभटुल्स परिस्थिति परिवर्तन गर्नुहोस्"
}
},
"always-on-top": "सधैं माथिल्लोमा",
"auto-update": "स्वत: अपडेट",
"hide-menu": {
"dialog": {
"message": "मेनु अर्को लन्चमा लुकिनेछ, यसलाई देखाउनका लागि [Alt] प्रयोग गर्नुहोस् (वा इन-एप-मेनु प्रयोग गर्दा backtick [`])प्रयोग गर्नुहोस्",
"title": "हाइड मेनु इनेबल गरियो"
},
"label": "हाइड मेनु"
},
"language": {
"dialog": {
"message": "भाषा पुनः सुरु गर्नपछि परिवर्तन गरिनेछ",
"title": "भाषा परिवर्तित गरियो"
},
"label": "भाषा",
"submenu": {
"to-help-translate": "अनुवाद गर्न मद्दत गर्न चाहनुहुन्छ? यहाँ क्लिक गर्नुहोस्"
}
},
"resume-on-start": "एप सुरु हुँदा अन्तिम गीत पुनः सुरु गर्नुहोस्",
"single-instance-lock": "एकल उदाहरण तालिका",
"start-at-login": "लगइनमा सुरु गर्नुहोस्",
"starting-page": {
"label": "सुरु गर्नुहोस्",
"unset": "अनसेट"
},
"tray": {
"label": "ट्रे(tray)",
"submenu": {
"disabled": "हाल बन्द",
"enabled-and-hide-app": "ट्रे इनेबल गरिएको छ र एप बन्द गरिएको छ",
"enabled-and-show-app": "एप देखाउनुहोस्",
"play-pause-on-click": "क्लिकमा खेल्नुहोस्/रोक्नुहोस्"
}
},
"visual-tweaks": {
"label": "भिजुअल ट्वीक्स",
"submenu": {
"custom-window-title": {
"label": "अनुकूलन विन्डो शीर्षक",
"prompt": {
"label": "अनुकूलन विन्डो शीर्षक प्रविष्ट गर्नुहोस्: (असक्षम पार्न खाली छोड्नुहोस्)",
"placeholder": "उदाहरण: पियर डेस्कटप"
}
},
"like-buttons": {
"default": "पूर्वनिर्धारित",
"force-show": "देखाउनुहोस",
"hide": "लुकाउनुहोस",
"label": "लाइक बटनहरू"
},
"remove-upgrade-button": "अपग्रेड बटन हटाउनुहोस्",
"theme": {
"dialog": {
"button": {
"cancel": "रद्द गर्नुहोस्",
"remove": "हटाउनुहोस्"
},
"remove-theme": "के तपाईँ निश्चित हुनुहुन्छ कि तपाईँ कस्टम थिम हटाउन चाहनुहुन्छ?",
"remove-theme-message": "यसले कस्टम थिम हटाउनेछ"
},
"label": "थिम",
"submenu": {
"import-css-file": "कस्टम CSS फाइल आयात गर्नुहोस्",
"no-theme": "कुनै थिम छैन"
}
}
}
}
}
},
"plugins": {
"enabled": "सक्षम गरियो",
"label": "प्लगइनहरू",
"new": "नयाँ"
},
"view": {
"label": "हेर्नुहोस्",
"submenu": {
"force-reload": "फोर्स रिलोड",
"reload": "पुनः लोड गर्नुहोस्",
"reset-zoom": "वास्तविक आकार",
"toggle-fullscreen": "पूर्ण स्क्रिन टगल गर्नुहोस्",
"zoom-in": "जुम इन गर्नुहोस्",
"zoom-out": "जुम आउट गर्नुहोस्"
}
}
},
"tray": {
"next": "अर्को",
"play-pause": "खेल्नुहोस्/रोक्नुहोस्",
"previous": "अघिल्ला",
"quit": "बाहिर निस्कनुहोस्",
"restart": "एप पुनः सुरु गर्नुहोस्",
"show": "विन्डो देखाउनुहोस्",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "यदि कुनै विज्ञापन चल्छ भने, यसले अडियो म्यूट गर्छ र प्लेब्याक गतिको गति १६x मा सेट गर्छ",
"name": "विज्ञापन तीव्रगति"
},
"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": "परिवेश मोड"
},
"amuse": {
"description": "६के ल्याब्सद्वारा अम्यूस नाउ प्लेइङ विजेटका लागि युट्युब म्युजिक समर्थन थप्दछ",
"name": "अम्यूस",
"response": {
"query": "Amuse API सर्भर चलिरहेको छ। गीत जानकारी प्राप्त गर्न GET /query प्रयोग गर्नुहोस्।"
}
},
"api-server": {
"description": "प्लेयर नियन्त्रण गर्नका लागि API सर्भर थप्दछ",
"dialog": {
"request": {
"buttons": {
"allow": "अनुमति दिनुहोस्",
"deny": "अस्वीकार गर्नुहोस्"
},
"message": "{{ID}} ({{origin}}) लाई API पहुँच अनुमति दिनुहुन्छ?",
"title": "API अनुमतिको अनुरोध"
}
},
"menu": {
"auth-strategy": {
"label": "अनुमति रणनीति",
"submenu": {
"auth-at-first": {
"label": "पहिलो अनुरोधमै अनुमति दिनुहोस्"
},
"none": {
"label": "कुनै अनुमति आवश्यक छैन"
}
}
},
"hostname": {
"label": "होस्टनेम"
},
"port": {
"label": "पोर्ट"
}
},
"name": "API सर्भर [बिटा]",
"prompt": {
"hostname": {
"label": "API सर्भरका लागि होस्टनेम प्रविष्ट गर्नुहोस् (उदाहरण: 0.0.0.0):",
"title": "होस्टनेम"
},
"port": {
"label": "API सर्भरका लागि पोर्ट प्रविष्ट गर्नुहोस्:",
"title": "पोर्ट"
}
}
},
"audio-compressor": {
"description": "अडियोमा कम्प्रेसन लागू गर्नुहोस् (सङ्केतको सबैभन्दा चर्को भागहरूको भोल्युम कम गर्दछ र नरम भागहरूको भोल्युम बढाउँछ)",
"name": "अडियो कम्प्रेसर"
},
"auth-proxy-adapter": {
"description": "प्रमाणीकरण प्रोक्सी सेवाहरूको प्रयोगको लागि समर्थन",
"menu": {
"disable": "प्रोक्सी एडाप्टर बन्द गर्नुहोस्",
"enable": "प्रोक्सी एडाप्टर खोल्नुहोस्",
"hostname": {
"label": "होस्टनाम"
},
"port": {
"label": "पोर्ट"
}
},
"name": "प्रमाणीकरण प्रोक्सी एडाप्टर",
"prompt": {
"hostname": {
"label": "स्थानीय प्रोक्सी सर्भरको लागि होस्टनाम प्रविष्ट गर्नुहोस् (पुनःसुरु गर्न आवश्यक छ):",
"title": "प्रोक्सी होस्टनाम"
},
"port": {
"label": "स्थानीय प्रोक्सी सर्भरको लागि पोर्ट प्रविष्ट गर्नुहोस् (पुनःसुरु गर्न आवश्यक छ):",
"title": "प्रोक्सी पोर्ट"
}
}
},
"blur-nav-bar": {
"description": "नेभिगेसन बारलाई पारदर्शी र धुवाँलो बनाउँछ",
"name": "ब्लर नेभिगेसन बार"
},
"bypass-age-restrictions": {
"description": "युट्युबको उमेर प्रमाणिकरणलाई बाइपास गर्नुहोस्",
"name": "उमेरका प्रतिबन्धहरू बाइपास गर्नुहोस्"
},
"captions-selector": {
"description": "युट्युब सङ्गीत अडियो ट्र्याकहरूका लागि क्याप्सन चयनकर्ता",
"menu": {
"autoload": "स्वचालित रूपमा अन्तिम प्रयोग गरिएको क्याप्सन चयन गर्नुहोस्",
"disable-captions": "पूर्वनिर्धारित रूपमा कुनै क्याप्सनहरू छैनन्"
},
"name": "शीर्षक चयनकर्ता",
"prompt": {
"selector": {
"label": "हालको शीर्षक भाषाः {{language}}",
"none": "केही छैन",
"title": "क्याप्सन भाषा चयन गर्नुहोस्"
}
},
"templates": {
"title": "क्याप्सन चयनकर्ता खोल्नुहोस्"
},
"toast": {
"caption-changed": "क्याप्सन {{language}} मा परिवर्तन भयो",
"caption-disabled": "क्याप्सन उपलब्ध छैन",
"no-captions": "यो गीतको लागि कुनै क्याप्सन उपलब्ध छैन।"
}
},
"compact-sidebar": {
"description": "सँधै साइडबारलाई कम्प्याक्ट मोडमा सेट गर्नुहोस्",
"name": "कम्प्याक्ट साइडबार"
},
"crossfade": {
"description": "गीतहरू बिच क्रसफेड",
"menu": {
"advanced": "उन्नत"
},
"name": "क्रसफेड [बीटा]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "फेड इन समय (ms)",
"fade-out-duration": "फेड आउट समय (ms)",
"fade-scaling": {
"label": "फेड स्केलिङ",
"linear": "रैखिक",
"logarithmic": "लघुगणक"
},
"seconds-before-end": "अन्त्य हुनुभन्दा अघि क्रसफेड सेकेन्ड"
},
"title": "क्रसफेड विकल्पहरू"
}
}
},
"disable-autoplay": {
"description": "\"रोकिएको\" स्मोथिथिमा गीत सुरु गराउँछ",
"menu": {
"apply-once": "स्टार्टअपमा मात्र लागु हुन्छ"
},
"name": "स्वतः खेल निष्क्रिय गर्नुहोस्"
},
"discord": {
"backend": {
"already-connected": "सक्रिय जडानसँग जडान गर्ने प्रयास गरियो",
"connected": "डिस्कर्डमा जोडियो",
"disconnected": "डिस्कोर्डबाट डिसकनेक्ट गरियो"
},
"description": "Rich Presence प्रयोग गरेर साथीहरुलाइ के सुन्दैछु देखाउने",
"menu": {
"auto-reconnect": "आफै रिकनेक्ट होस्",
"clear-activity": "एकटिभिटी क्लियर गर",
"clear-activity-after-timeout": "समय पछि एकटिभिटी क्लियर गर",
"connected": "कनेक्टेड",
"disconnected": "डिसकन्एक्टेड",
"hide-duration-left": "बाकी समय लुकाऊ",
"hide-github-button": "GitHub लिंक लुकाऊ",
"play-on-application": "{{applicationName}} मा बजाउ",
"set-inactivity-timeout": "इनएक्टिभिटी टाइमआउट राख"
},
"name": "डिसकार्ड रिच प्रीसेंस",
"prompt": {
"set-inactivity-timeout": {
"label": "इनएक्टिभिटी टाइमआउट सेकन्डमा लेख्नुहोस्:",
"title": "इनएक्टिभिटी टाइमआउट राख्नुहोस्"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "ओके"
},
"message": "अह:! माफ गर्नुहोस्, डाउनलोड सफलहुन सकेन…",
"title": "डाउनलोडमा त्रुटि भयो!"
},
"start-download-playlist": {
"buttons": {
"ok": "ओके"
},
"detail": "({{playlistSize}}गाना)",
"message": "प्लेलिस्ट {{playlistTitle}} डाउनलोड हुदै छ",
"title": "डाउनलोड सुरुभयो"
}
},
"feedback": {
"conversion-progress": "रूपान्तरणः {{percent}}%",
"converting": "रूपान्तरण हुदैछ…",
"done": "गरियो: {{filePath}}",
"download-info": "{{artist}}को - {{title}}{{videoId}}डाउनलोड हुदैछ",
"download-progress": "डाउनलोड: {{percent}}%",
"downloading": "डाउनलोड हुदैछ…",
"downloading-counter": "{{current}}/{{total}} डाउनलोड गरिदै…",
"downloading-playlist": "प्लेलिस्ट \"{{playlistTitle}}\" -{{playlistSize}} गाना {{playlistId}} डाउनलोड गरिदैछ",
"error-while-downloading": "\"{{author}}\" - {{title}}\":{{error}} डाउनलोडमा समस्या आयो",
"folder-already-exists": "{{playlistFolder}} नाम गरेको फोल्डर अघाडी देखि छ",
"getting-playlist-info": "प्लेलिस्टको डाटा हासिल गरिदै छ…",
"loading": "लोडिंग…",
"playlist-has-only-one-song": "प्लेलिस्टमा एउता मात्रै गानाछ, तेस्लाई डाइरेक्ट डाउनलोड गर्नुहोस",
"playlist-id-not-found": "प्लेलिस्ट ID भेटियेन",
"playlist-is-empty": "प्लेलिस्ट खाली छ",
"playlist-is-mix-or-private": "प्लेलिस्ट जानकारी प्राप्त गर्न त्रुटिः निश्चित गर्नुहोस् कि यो निजी वा \"तपाईँका लागि मिश्रित\" प्लेलिस्ट होइन\n\n{{error}}",
"preparing-file": "फाइल तयार गरिदै छ…",
"saving": "सेव गरिदै…",
"trying-to-get-playlist-id": "प्लेलिस्ट आईडी: {{playlistId}} प्राप्त गर्ने प्रयास",
"video-id-not-found": "भिडियो फेला परेन",
"writing-id3": "ID3 ट्यागहरू लेखीदै…"
}
},
"description": "इन्टरफेसबाट सिधै MP3/स्रोत अडियो डाउनलोड गर",
"menu": {
"choose-download-folder": "डाउनलोड फोल्डर चयन गर्नुहोस्",
"download-finish-settings": {
"label": "समाप्त भएपछि डाउनलोड गर्नुहोस्",
"prompt": {
"last-percent": "x प्रतिशतपछि",
"last-seconds": "अन्तिम x सेकेन्ड",
"title": "कहिले डाउनलोड गर्ने भनेर कन्फिगर गर्नुहोस्"
},
"submenu": {
"advanced": "उन्नत",
"enabled": "सक्रिय गरिएको",
"mode": "समय मोड",
"percent": "प्रतिशत",
"seconds": "सेकेन्डहरू"
}
},
"download-playlist": "डाउनलोड प्लेलिस्ट",
"presets": "प्रिसेटहरू",
"skip-existing": "विद्यमान फाइलहरू स्किप गर्नुहोस्"
},
"name": "डाउनलोडर",
"renderer": {
"can-not-update-progress": "प्रगति अद्यावधिक गर्न सकिँदैन"
},
"templates": {
"button": "डाउनलोड"
}
},
"equalizer": {
"description": "प्लेयरमा इक्वलाइजर थप्दछ",
"menu": {
"presets": {
"label": "पूर्वसेटहरू",
"list": {
"bass-booster": "बास बूस्टर"
}
}
},
"name": "इक्वलाइजर"
},
"exponential-volume": {
"description": "भोल्युम स्लाइडरलाई घातीय बनाउँछ त्यसैले कम भोल्युमहरू चयन गर्न सजिलो हुन्छ।",
"name": "एक्सपोनेन्सियल भोल्युम"
},
"in-app-menu": {
"description": "मेनु-बारहरूलाई फेन्सी, गाढा वा एल्बम-रङ्गको रूप दिन्छ",
"menu": {
"hide-dom-window-controls": "DOM विन्डो नियन्त्रणहरू लुकाउनुहोस्"
},
"name": "इन-एप मेनु"
},
"lumiastream": {
"description": "लुमिया स्ट्रिम सपोर्ट थप्दछ",
"name": "लुमिया स्ट्रिम [बिटा]"
},
"lyrics-genius": {
"description": "धेरैजसो गीतहरूका लागि लिरिक्स थप्दछ",
"menu": {
"romanized-lyrics": "रोमनाइज्ड लिरिक्स"
},
"name": "लिरिक्स जिनियस",
"renderer": {
"fetched-lyrics": "लिरिक्स जिनियसबाट लिरिक्स प्राप्त गरियो"
}
},
"music-together": {
"description": "अरूसँग प्लेलिस्ट साझा गर्नुहोस्। जब होस्टले गीत बजाउँछ, अरू सबैले एउटै गीत सुन्नेछन्",
"dialog": {
"enter-host": "होस्ट आईडी प्रविष्ट गर्नुहोस्"
},
"internal": {
"save": "सेभ गर्नुहोस्",
"track-source": "गानाको स्रोत",
"unknown-user": "अज्ञात प्रयोगकर्ता"
},
"menu": {
"click-to-copy-id": "होस्ट आईडी कपी गर्नुहोस्",
"close": "मिउजीक टुगेदर बन्द गर्नुहोस्",
"connected-users": "जोडिएका प्रयोगकर्ताहरू",
"disconnect": "मिउजीक टुगेदर डिस्कनेक्ट गर्नुहोस्",
"empty-user": "कोही प्रयोगकर्ताहरू जोडिएका छैनन्",
"host": "मिउजीक टुगेदर होस्ट",
"join": "मिउजीक टुगेदर जोइन गनुहोस",
"permission": {
"all": "पाहुनाहरूलाई प्लेलिस्ट र प्लेयर नियन्त्रण गर्न अनुमति दिनुहोस्",
"host-only": "केवल होस्टले प्लेलिस्ट र प्लेयर नियन्त्रण गर्न सक्छ",
"playlist": "पाहुनाहरूलाई प्लेलिस्ट नियन्त्रण गर्न अनुमति दिनुहोस्"
},
"set-permission": "नियन्त्रण अनुमति परिवर्तन गर्नुहोस्",
"status": {
"disconnected": "विच्छेदित",
"guest": "पाहुनाका रूपमा जोडियो",
"host": "होस्टका रूपमा जोडियो"
}
},
"name": "मिउजीक टुगेदर [बिटा]",
"toast": {
"add-song-failed": "गीत थप्न असफल",
"closed": "मिउजीक टुगेदर बन्द गरियो",
"disconnected": "मिउजीक टुगेदर डिसकनेक्टेड",
"host-failed": "मिउजीक टुगेदर होस्ट गर्न असफल",
"id-copied": "होस्ट आईडी क्लिपबोर्डमा कपी गरियो",
"id-copy-failed": "होस्ट आईडी क्लिपबोर्डमा कपी गर्न असफल",
"join-failed": "मिउजीक टुगेदर जोइन हुन असफल",
"joined": "मिउजीक टुगेदरमा जोडियो",
"permission-changed": "मिउजीक टुगेदर अनुमति \"{{permission}}\" मा परिवर्तन गरियो",
"remove-song-failed": "गाना हटाउनमा समस्या आयो",
"user-connected": "मिउजीक टुगेदरमा {{name}} जोडीनुभायो",
"user-disconnected": "{{name}}ले मिउजीक टुगेदर छोडनु भयो"
}
},
"navigation": {
"description": "अर्को/पछाडि नेभिगेसन तपाईँको मनपर्ने ब्राउजरमा जस्तै सिधा इन्टरफेसमा एकीकृत तीरहरू",
"name": "नेभिगेसन",
"templates": {
"back": {
"title": "अघिल्लो पृष्ठमा जानुहोस्"
},
"forward": {
"title": "अर्को पृष्ठमा जानुहोस्"
}
}
},
"no-google-login": {
"description": "इन्टरफेसबाट गुगल लगइन बटन र लिङ्कहरू हटाउनुहोस्",
"name": "गुगल लगइन छैन"
},
"notifications": {
"description": "गीत बज्न थाल्दा सूचना देखाउनुहोस् (अन्तरक्रियात्मक सूचनाहरू Windowsमा उपलब्ध छन्)",
"menu": {
"interactive": "अन्तरक्रियात्मक सूचनाहरू",
"interactive-settings": {
"label": "अन्तरक्रियात्मक सेटिङहरू",
"submenu": {
"hide-button-text": "टेक्सट बटन लुकाउनुहोस्",
"refresh-on-play-pause": "प्ले/पोजमा ताजा गर्नुहोस्",
"tray-controls": "ट्रेमा क्लिक गर्दा खोल्नुहोस्/बन्द गर्नुहोस्"
}
},
"priority": "अधिसूचना प्राथमिकता",
"toast-style": "टोस्ट शैली",
"unpause-notification": "अनपउसमा सूचना देखाउनुहोस्"
},
"name": "सूचनाहरू"
},
"performance-improvement": {
"description": "प्रयोगात्मक स्क्रिप्टहरू सक्रिय गरेर कार्यसम्पादन सुधार गर्नुहोस्",
"name": "कार्यसम्पादन सुधार [प्रयोगात्मक]"
},
"picture-in-picture": {
"description": "एपलाई पिक्चर-इन-पिक्चर मोडमा परिवर्तन गर्न अनुमति दिन्छ",
"menu": {
"always-on-top": "सधैँ शीर्षमा",
"hotkey": {
"label": "हटकी",
"prompt": {
"keybind-options": {
"hotkey": "हटकी"
},
"label": "पिक्चर-इन-पिक्चर टगल गर्न हटकी छान्नुहोस्",
"title": "पिक्चर-इन-पिक्चर हटकी"
}
},
"save-window-position": "windowको स्थान सेव गर्नुहोस्",
"save-window-size": "windowको आकार सेव गर्नुहोस्",
"use-native-pip": "ब्राउजरको नेटिभ PiP प्रयोग गर्नुहोस्"
},
"name": "पिक्चर-इन-पिक्चर",
"templates": {
"button": "पिक्चर-इन-पिक्चर"
}
},
"playback-speed": {
"description": "छिटो सुन्नुहोस्, ढिलो सुन्नुहोस्! एसले गीतको गति नियन्त्रण गर्ने स्लाइडर थप्छ",
"name": "प्लेब्याक स्पीड",
"templates": {
"button": "स्पीड"
}
},
"precise-volume": {
"description": "कस्टम HUD र अनुकूलन योग्य भोल्युम चरणहरूको साथ माउसव्हील/हटकीहरू प्रयोग गरेर भोल्युम नियन्त्रण गर्नुहोस्",
"menu": {
"arrows-shortcuts": "लोकल एरो-की नियन्त्रणहरू",
"custom-volume-steps": "कस्टम भोल्युम चरणहरू सेट गर्नुहोस्",
"global-shortcuts": "ग्लोबल हटकीहरू"
},
"name": "सटीक भोल्युम",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "भोल्युम घटाउनुहोस्",
"increase": "भोल्युम बढाउनुहोस्"
},
"label": "ग्लोबल भोल्युम किबाइन्डहरू चयन गर्नुहोस्:",
"title": "ग्लोबल भोल्युम किबाइन्डहरू"
},
"volume-steps": {
"label": "भोल्युम वृद्धि/घटाउने चरणहरू चयन गर्नुहोस्",
"title": "भोल्युमका चरणहरू"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "हालको गुणस्तरः {{quality}}",
"message": "भिडियो गुणस्तर चयन गर्नुहोस्:",
"title": "भिडियोको गुणस्तर चयन गर्नुहोस्"
}
}
},
"description": "भिडियो ओभरलेमा बटनको साथ भिडियो गुणस्तर परिवर्तन गर्न अनुमति दिन्छ",
"name": "भिडियो गुणस्तर परिवर्तनकर्ता"
},
"scrobbler": {
"description": "स्क्रोब्लिङ समर्थन थप्नुहोस् (etc. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm सँग प्रमाणिकरण गर्न असफल\nअर्को पुनः सुरु नभएसम्म पपअप लुकाउनुहोस्।",
"title": "प्रमाणीकरण असफल"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API सेटिङहरू"
},
"listenbrainz": {
"token": "ListenBrainz प्रयोगकर्ता टोकन प्रविष्ट गर्नुहोस्"
},
"scrobble-alternative-title": "वैकल्पिक शीर्षकहरू प्रयोग गर्नुहोस्",
"scrobble-other-media": "अन्य मिडियालाई स्क्रबल गर्नुहोस्"
},
"name": "स्क्रबबलर",
"prompt": {
"lastfm": {
"api-key": "Last.fm API कुञ्जी",
"api-secret": "Last.fm एपीआई गुप्त"
},
"listenbrainz": {
"token": {
"label": "आफ्नो ListenBrainz प्रयोगकर्ता टोकन प्रविष्ट गर्नुहोस्:",
"title": "ListenBrainz टोकन"
}
}
}
},
"shortcuts": {
"description": "प्लेब्याक (प्ले/विराम/अर्को/अघिल्लो) का लागि ग्लोबल हटकीहरू सेट गर्न र मिडिया कुञ्जीहरू ओभरराइड गरेर मिडिया ओएसडी बन्द गर्न अनुमति दिन्छ, खोजी गर्न Ctrl/CMD + F सक्रिय गर्दछ, मिडिया कुञ्जीहरूका लागि लिनक्स MPRIS र उन्नत प्रयोगकर्ताहरूका लागि कस्टम हटकीहरू समर्थन सक्रिय गर्दछ",
"menu": {
"override-media-keys": "मिडिया कुञ्जीहरूलाई ओभरराइड गर्नुहोस्",
"set-keybinds": "विश्वव्यापी गीत नियन्त्रणहरू सेट गर्नुहोस्"
},
"name": "सर्टकटहरू (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "अर्को",
"play-pause": "खेल्नुहोस् / रोक्नुहोस्",
"previous": "अघिल्ला"
},
"label": "गीत नियन्त्रणका लागि ग्लोबल किबाइन्डहरू छान्नुहोस्:",
"title": "ग्लोबल किबाइन्डहरू"
}
}
},
"skip-disliked-songs": {
"description": "नापसन्द गरिएका गीतहरू छोड्नुहोस्",
"name": "मन नपरेका गीतहरू छोड्नुहोस्"
},
"skip-silences": {
"description": "गीतहरूमा मौन खण्डहरू स्वचालित रूपमा स्किप गर्नुहोस्",
"name": "मौन छोड्नुहोस्"
},
"sponsorblock": {
"description": "स्वचालित रूपमा गैर-सङ्गीत भागहरू जस्तै इन्ट्रो/आउट्रो वा सङ्गीत भिडियोका भागहरू छोड्नुहोस्",
"name": "स्पन्सरब्लक"
},
"synced-lyrics": {
"description": "LRClib जस्ता प्रदायकहरू प्रयोग गरेर गीतहरूका लागि समक्रमित गीतहरू प्रदान गर्छ।",
"errors": {
"fetch": "⚠️ गीतहरूको जानकारी ल्याउने क्रममा त्रुटि भयो।\n\tकृपया केही समयपछि फेरि प्रयास गर्नुहोस्।",
"not-found": "⚠️ यो गीतका लागि कुनै गीतशब्द फेला परेन।"
},
"menu": {
"default-text-string": {
"label": "पूर्वनिर्धारित अक्षर गीतशब्दहरू बीचमा",
"tooltip": "गीतशब्दहरू बीचको खाली ठाउँका लागि प्रयोग हुने पूर्वनिर्धारित अक्षर छनोट गर्नुहोस्"
},
"line-effect": {
"label": "रेखा प्रभाव",
"submenu": {
"fancy": {
"label": "रमणीय",
"tooltip": "हालको लाइनमा ठूलो, एप-जस्तै प्रभावहरू प्रयोग गर्नुहोस्"
},
"focus": {
"label": "केन्द्रित गर्नुहोस्",
"tooltip": "मात्रै हालको लाइन सेतो बनाउनुहोस्"
},
"offset": {
"label": "अफसेट",
"tooltip": "हालको लाइनलाई दायाँतर्फ अफसेट गर्नुहोस्"
},
"scale": {
"label": "स्केल",
"tooltip": "हालको लाइनको आकार परिवर्तन गर्नुहोस्"
}
},
"tooltip": "हालको लाइनमा लागू गर्ने प्रभाव चयन गर्नुहोस्"
},
"precise-timing": {
"label": "गीतशब्दहरू पूर्ण रूपमा समक्रमित बनाउनुहोस्",
"tooltip": "अर्को लाइनको प्रदर्शनलाई मिलिसेकेन्डमा गणना गर्नुहोस् (यसले प्रदर्शनमा हल्का प्रभाव पार्न सक्छ)"
},
"romanization": {
"label": "रोमनकृत शब्दहरू",
"tooltip": "यदि गीतका शब्दहरू फरक भाषामा छन् भने, ल्याटिन संस्करण प्रदर्शन गर्ने प्रयास गर्नुहोस्।"
},
"show-lyrics-even-if-inexact": {
"label": "गीतशब्दहरू अपर्याप्त भए पनि देखाउनुहोस्",
"tooltip": "यदि गीत फेला परेन भने, प्लगिनले फरक खोजी सोधपुछसँग पुन: प्रयास गर्छ।\nदोस्रो प्रयासको परिणाम ठ्याक्कै मिल्न नपनि सक्छ।"
},
"show-time-codes": {
"label": "समय कोडहरू देखाउनुहोस्",
"tooltip": "गीतशब्दहरूको छेउमा समय कोडहरू देखाउनुहोस्"
}
},
"name": "समक्रमित गीतशब्दहरू",
"refetch-btn": {
"fetching": "ल्याउँदैछ...",
"normal": "गीतशब्दहरू पुनः ल्याउनुहोस्"
},
"warnings": {
"duration-mismatch": "⚠️ - अवधि असमानताको कारण गीतशब्दहरू असमक्रमित हुन सक्छन्।",
"inexact": "⚠️ - यो गीतका लागि गीतशब्दहरू ठ्याक्कै मिल्दैनन्",
"instrumental": "⚠️ - यो एउटा वाद्य संगीत (इन्स्ट्रुमेन्टल) गीत हो"
}
},
"taskbar-mediacontrol": {
"description": "तपाईँको विन्डोज टास्कबारबाट प्लेब्याक नियन्त्रण गर्नुहोस्",
"name": "टास्कबार मेडिया कन्ट्रोल"
},
"touchbar": {
"description": "MacOS प्रयोगकर्ताहरूका लागि टचबार विजेट थप्दछ",
"name": "टचबार"
},
"tuna-obs": {
"description": "OBS को टुना प्लगइनसँग एकीकरण",
"name": "टुना OBS"
},
"unobtrusive-player": {
"description": "गीत बजाउँदा प्लेयरलाई पप अप हुनबाट रोक्छ",
"name": "अवरोधरहित संगीत प्लेयर"
},
"video-toggle": {
"description": "भिडियो/गीत मोड बीच स्विच गर्न बटन थप्दछ। वैकल्पिक रूपमा सम्पूर्ण भिडियो ट्याब हटाउन पनि सक्छ",
"menu": {
"align": {
"label": "संरेखण",
"submenu": {
"left": "बायाँ",
"middle": "बिचमा",
"right": "दायाँ"
}
},
"force-hide": "बलपूर्वक भिडियो ट्याब हटाउनुहोस्",
"mode": {
"label": "मोड",
"submenu": {
"custom": "कस्टम टगल",
"disabled": "अक्षम",
"native": "नेटिभ टगल"
}
}
},
"name": "भिडियो टगल",
"templates": {
"button-song": "गीत",
"button-video": "भिडियो"
}
},
"visualizer": {
"description": "प्लेयरमा भिजुअलाइजर थप्छ",
"menu": {
"visualizer-type": "भिजुअलाइजरको प्रकार"
},
"name": "भिजुअलाइजर"
}
}
}
================================================
FILE: src/i18n/resources/nl.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Mislukt om plugin {{pluginName}}::{{contextName}} uit te voeren",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} uitgevoerd in {{ms}}ms",
"initialize-failed": "Initialisatie van plugin \"{{pluginName}}\" mislukt",
"load-all": "Alle plugins aan het laden",
"load-failed": "Mislukt om plugin \"{{pluginName}}\" te laden",
"loaded": "Plugin \"{{pluginName}}\" geladen",
"unload-failed": "Mislukt om plugin \"{{pluginName}}\" te ontladen",
"unloaded": "Plugin \"{{pluginName}}\" gelost"
}
}
},
"language": {
"code": "nl",
"local-name": "Nederlands",
"name": "Nederlands"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Laden voltooid. DevTools geopend"
},
"i18n": {
"loaded": "i18n geladen"
},
"second-instance": {
"receive-command": "Commando ontvangen via protocol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS-bestand \"{{cssFile}}\" bestaat niet, wordt genegeerd"
},
"unresponsive": {
"details": "Niet-reagerende 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}}, weergavegrootte={{displaySize}}, positie={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu is verborgen, gebruik 'Alt' om het weer te geven (of 'Escape' als u de In-App Menu gebruikt)",
"message": "Menu verbergen is ingeschakeld",
"title": "Menu verbergen ingeschakeld"
},
"need-to-restart": {
"buttons": {
"later": "Later",
"restart-now": "Nu Opnieuw Opstarten"
},
"detail": "\"{{pluginName}}\" plugin vereist een herstart om van kracht te worden",
"message": "\"{{pluginName}}\" moet opnieuw worden opgestart",
"title": "Herstart Vereist"
},
"unresponsive": {
"buttons": {
"quit": "Stoppen",
"relaunch": "Opnieuw starten",
"wait": "Wachten"
},
"detail": "Het programma reageert niet! Kies wat u wilt doen:",
"message": "De applicatie reageert niet",
"title": "Venster reageert niet"
},
"update-available": {
"buttons": {
"disable": "Updates uitschakelen",
"download": "Downloaden",
"ok": "OK"
},
"detail": "Er is een nieuwe versie beschikbaar en kan worden gedownload op {{downloadLink}}",
"message": "Een nieuwe versie is beschikbaar",
"title": "Update Beschikbaar"
}
},
"menu": {
"about": "Over",
"navigation": {
"label": "Navigatie",
"submenu": {
"copy-current-url": "Huidige URL kopiëren",
"go-back": "Terug",
"go-forward": "Vooruit",
"quit": "Afsluiten",
"restart": "Applicatie Opnieuw Opstarten"
}
},
"options": {
"label": "Opties",
"submenu": {
"advanced-options": {
"label": "Geavanceerde opties",
"submenu": {
"auto-reset-app-cache": "App-cache resetten bij het starten van de app",
"disable-hardware-acceleration": "Hardwareversnelling uitschakelen",
"edit-config-json": "Config.json bewerken",
"override-user-agent": "Gebruikersagent overschrijven",
"restart-on-config-changes": "Herstarten bij configuratiewijzigingen",
"set-proxy": {
"label": "Proxy instellen",
"prompt": {
"label": "Proxy-adres invoeren: (leeg laten om uit te schakelen)",
"placeholder": "Voorbeeld: SOCKS5://127.0.0.1:9999",
"title": "Proxy instellen"
}
},
"toggle-dev-tools": "DevTools schakelen"
}
},
"always-on-top": "Altijd bovenop",
"auto-update": "Automatisch bijwerken",
"hide-menu": {
"dialog": {
"message": "Menu wordt verborgen bij de volgende start, gebruik [Alt] om het weer te geven (of backtick [`] als u de in-app-menu gebruikt)",
"title": "Menu Verbergen Ingeschakeld"
},
"label": "Menu Verbergen"
},
"language": {
"dialog": {
"message": "Taal wordt gewijzigd na herstart",
"title": "Taal Gewijzigd"
},
"label": "Taal",
"submenu": {
"to-help-translate": "Wil je helpen vertalen? Klik hier"
}
},
"resume-on-start": "Hervat laatste liedje bij het opstarten van de app",
"single-instance-lock": "Eenmalige instantievergrendeling",
"start-at-login": "Starten bij het inloggen",
"starting-page": {
"label": "Startpagina",
"unset": "Niet ingesteld"
},
"tray": {
"label": "Systeemvak",
"submenu": {
"disabled": "Uitgeschakeld",
"enabled-and-hide-app": "Ingeschakeld en applicatie verbergen",
"enabled-and-show-app": "Ingeschakeld en applicatie weergeven",
"play-pause-on-click": "Afspelen/Pauzeren bij klikken"
}
},
"visual-tweaks": {
"label": "Visuele Aanpassingen",
"submenu": {
"custom-window-title": {
"label": "Aangepaste venstertitel",
"prompt": {
"label": "Voer aangepaste venstertitel in: (laat leeg om uit te schakelen)",
"placeholder": "Voorbeeld: {{applicationName}}"
}
},
"like-buttons": {
"default": "Standaard",
"force-show": "Forceren weergeven",
"hide": "Verbergen",
"label": "Vind ik leuk-knoppen"
},
"remove-upgrade-button": "Upgrade-knop verwijderen",
"theme": {
"dialog": {
"button": {
"cancel": "Annuleren",
"remove": "Verwijderen"
},
"remove-theme": "Weet je zeker dat je het aangepaste thema wil verwijderen?",
"remove-theme-message": "Dit verwijdert het aangepaste thema"
},
"label": "Thema",
"submenu": {
"import-css-file": "Importeer aangepast CSS-bestand",
"no-theme": "Geen thema"
}
}
}
}
}
},
"plugins": {
"enabled": "Ingeschakeld",
"label": "Invoegtoepassingen",
"new": "NIEUW"
},
"view": {
"label": "Bekijken",
"submenu": {
"force-reload": "Forceer Herladen",
"reload": "Herladen",
"reset-zoom": "Ware Grootte",
"toggle-fullscreen": "Volledig Scherm Ja/Nee",
"zoom-in": "Inzoomen",
"zoom-out": "Uitzoomen"
}
}
},
"tray": {
"next": "Volgende",
"play-pause": "Afspelen/Pauze",
"previous": "Vorige",
"quit": "Verlaat",
"restart": "Herstarten App",
"show": "Weergeven Venster",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Bij het afspelen van een advertentie wordt het geluid gedempt en wordt de afspeelsnelheid wordt verhoogd naar 16x",
"name": "Advertenties versnellen"
},
"adblocker": {
"description": "Blokkeer alle advertenties en trackers buiten het kader",
"menu": {
"blocker": "Blokkeerder"
},
"name": "Advertentieblokkeerder"
},
"album-actions": {
"description": "Voegt niet-Niet Leuk, Niet Leuk, Leuk, niet-Leuk knoppen toe om dit toe te passen op alle nummers in een afspeellijst of album",
"name": "Albumacties"
},
"album-color-theme": {
"description": "Past een dynamisch thema en visuele effecten toe op basis van het kleurenpalet van het album",
"menu": {
"color-mix-ratio": {
"label": "Kleurmixverhouding",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Schakel thema's voor de schuifbalk in"
},
"name": "Albumkleurthema"
},
"ambient-mode": {
"description": "Past een verlichtingseffect toe door zachte kleuren van de video op het achtergrondscherm te werpen",
"menu": {
"blur-amount": {
"label": "Vervagingshoeveelheid",
"submenu": {
"pixels": "{{blurAmount}} pixels"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Transparantie",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kwaliteit",
"submenu": {
"pixels": "{{quality}} pixels"
}
},
"size": {
"label": "Formaat",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Vloeiende overgang",
"submenu": {
"during": "Tijdens {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Volledig scherm gebruiken"
}
},
"name": "Omgevingsmodus"
},
"amuse": {
"description": "Voegt {{applicationName}} ondersteuning toe voor de Amuse now playing widget van 6K Labs",
"name": "Amuse",
"response": {
"query": "Amuse API server loopt. Gebruik /query voor nummer informatie."
}
},
"api-server": {
"description": "Voegt een API server toe om de speler te besturen",
"dialog": {
"request": {
"buttons": {
"allow": "Toestaan",
"deny": "Weigeren"
},
"message": "Sta {{ID}} {{origin}} toegang toe tot de API?",
"title": "API autorisatieverzoek"
}
},
"menu": {
"auth-strategy": {
"label": "Autorisatie strategie",
"submenu": {
"auth-at-first": {
"label": "Autoriseer bij eerste verzoek"
},
"none": {
"label": "geen autorisatie"
}
}
},
"hostname": {
"label": "Hostnaam"
},
"https": {
"label": "HTTPS & Certificaten",
"submenu": {
"cert": {
"dialogTitle": "Selecteer HTTPS certificaat",
"label": "Certificaatbestand (.crt/.pem)"
},
"enable-https": {
"label": "HTTPS aanzetten"
},
"key": {
"dialogTitle": "Selecteer HTTPS privésleutel",
"label": "Privésleutelbestand (.key/.pem)"
}
}
},
"port": {
"label": "Poort"
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Voeg de hostnaam (bv. 0.0.0.0) voor de API server in:",
"title": "Hostnaam"
},
"port": {
"label": "Voeg de poort voor de API server in:",
"title": "Poort"
}
}
},
"audio-compressor": {
"description": "Past compressie toe op audio (verlaagt het volume van de luidste delen van het signaal en verhoogt het volume van de zachtste delen)",
"name": "Audiocompressor"
},
"auth-proxy-adapter": {
"description": "Ondersteuning voor het gebruik van authenticatie proxy diensten",
"menu": {
"disable": "Proxy adapter uitschakelen",
"enable": "Proxy adapter inschakelen",
"hostname": {
"label": "Hostnaam"
},
"port": {
"label": "Poort"
}
},
"name": "Proxy-authenticatieadapter",
"prompt": {
"hostname": {
"label": "Hostname voor lokale proxy server (vereist herstart):",
"title": "Proxy hostnaam"
},
"port": {
"label": "Poort voor lokale proxy server (herstart vereist):",
"title": "Proxy poort"
}
}
},
"blur-nav-bar": {
"description": "Maakt de navigatiebalk transparant en wazig",
"name": "Vervagen Navigatiebalk"
},
"bypass-age-restrictions": {
"description": "Omzeil de leeftijdsverificatie van Music Player",
"name": "Leeftijdsbeperkingen Omzeilen"
},
"captions-selector": {
"description": "Ondertitelkeuze voor {{applicationName}}-audiotracks",
"menu": {
"autoload": "Automatisch de laatst gebruikte ondertitel selecteren",
"disable-captions": "Standaard geen ondertitels"
},
"name": "Ondertitelkeuze",
"prompt": {
"selector": {
"label": "Huidige ondertitel taal: {{language}}",
"none": "Geen",
"title": "Selecteer ondertitel taal"
}
},
"templates": {
"title": "Open ondertitelkeuze"
},
"toast": {
"caption-changed": "Ondertitel veranderd naar {{language}}",
"caption-disabled": "Ondertitels uitgeschakeld",
"no-captions": "Geen ondertitels beschikbaar voor dit lied"
}
},
"clock": {
"description": "Voeg een klok toe aan de navigatiebalk",
"menu": {
"format": {
"24-hour-format": "24-uursformaat"
}
},
"name": "Klok"
},
"compact-sidebar": {
"description": "Stel de zijbalk altijd in op compacte modus",
"name": "Compacte Zijbalk"
},
"crossfade": {
"description": "Vervagen tussen nummers",
"menu": {
"advanced": "Geavanceerd"
},
"name": "Crossfade [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Fade-in-duur (ms)",
"fade-out-duration": "Fade-out-duur (ms)",
"fade-scaling": {
"label": "Vervagingschaal",
"linear": "Lineair",
"logarithmic": "Logaritmisch"
},
"seconds-before-end": "Vervagen N seconden voor het einde"
},
"title": "Crossfade-opties"
}
}
},
"custom-output-device": {
"description": "Configureer een aangepast media uitvoerapparaat voor liedjes",
"menu": {
"device-selector": "Selecteer je apparaat"
},
"name": "Aangepast uitvoerapparaat",
"prompt": {
"device-selector": {
"label": "Kies het uitvoermedia-apparaat dat gebruikt zal worden",
"title": "Selecteer het uitvoermedia"
}
}
},
"disable-autoplay": {
"description": "Zorgt ervoor dat nummers starten in 'gepauzeerde' modus",
"menu": {
"apply-once": "Alleen toepassen bij opstarten"
},
"name": "Automatisch afspelen uitschakelen"
},
"discord": {
"backend": {
"already-connected": "Geprobeerd verbinding te maken met een actieve verbinding",
"connected": "Verbonden met Discord",
"disconnected": "Verbinding met Discord verbroken"
},
"description": "Laat je vrienden zien waar je naar luistert met Rich Presence",
"menu": {
"auto-reconnect": "Automatisch opnieuw verbinden",
"clear-activity": "Activiteit wissen",
"clear-activity-after-timeout": "Activiteit na time-out wissen",
"connected": "Verbonden",
"disconnected": "Verbinding verbroken",
"hide-duration-left": "Verberg resterende tijd",
"hide-github-button": "GitHub-knop verbergen",
"play-on-application": "Afspelen op {{applicationName}}",
"set-inactivity-timeout": "Inactiviteitstime-out instellen",
"set-status-display-type": {
"label": "Status tekst",
"submenu": {
"application": "Naar {{applicationName}} aan het luisteren",
"artist": "Naar {artist} aan het luisteren",
"title": "Naar {song title} aan het luisteren"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Voer inactiviteitstime-out in seconden in:",
"title": "Inactiviteitstime-out instellen"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Oke"
},
"message": "Excuses, de download is mislukt…",
"title": "Er is een fout opgetreden tijdens het downloaden!"
},
"start-download-playlist": {
"buttons": {
"ok": "Oké"
},
"detail": "({{playlistSize}} nummers)",
"message": "Afspeellijst \"{{playlistTitle}}\" aan het downloaden",
"title": "Download is gestart"
}
},
"feedback": {
"conversion-progress": "Converteren: {{percent}}%",
"converting": "Converteren…",
"done": "Gereed: {{filePath}}",
"download-info": "{{artist}} - {{title}} ([{{videoId}}) aan het downloaden",
"download-progress": "Downloaden: {{percent}}%",
"downloading": "Aan het downloaden…",
"downloading-counter": "{{current}}/{{total}} aan het downloaden…",
"downloading-playlist": "Afspeellijst \"{{playlistTitle}}\" {{playlistId}} aan het downloaden ({{playlistSize}} nummers)",
"error-while-downloading": "Er is een fout opgetreden tijdens het downloaden van \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "De map \"{{playlistFolder}}\" bestaat al",
"getting-playlist-info": "Afspeellijst informatie ophalen…",
"loading": "Laden…",
"playlist-has-only-one-song": "Afspeellijst heeft maar 1 item, item direct aan het downloaden",
"playlist-id-not-found": "Geen playlist ID gevonden",
"playlist-is-empty": "Afspeellijst is leeg",
"playlist-is-mix-or-private": "Er is een fout opgetreden tijdens het ophalen van de afspeellijst informatie: zorg ervoor dat het geen verborgen of \"Mixed for you\" afspeellijst is\n\n{{error}}",
"preparing-file": "Bestand voorbereiden…",
"saving": "Opslaan…",
"trying-to-get-playlist-id": "Proberen om het afspeellijst ID op te halen: {{playlistId}}",
"video-id-not-found": "Video niet gevonden",
"writing-id3": "ID3 tags aan het schrijven…"
}
},
"description": "Download MP3 / bron-audio rechtstreeks vanuit de interface",
"menu": {
"choose-download-folder": "Kies de downloadmap",
"download-finish-settings": {
"label": "Downloaden bij voltooiing",
"prompt": {
"last-percent": "Na x procent",
"last-seconds": "Laatste x seconden",
"title": "Configureren wanneer te downloaden"
},
"submenu": {
"advanced": "Geavanceerd",
"enabled": "Ingeschakeld",
"mode": "Tijd-modus",
"percent": "Procent",
"seconds": "Seconden"
}
},
"download-playlist": "Afspeellijst downloaden",
"presets": "Voorinstellingen",
"skip-existing": "Bestaande bestanden overslaan"
},
"name": "Downloader",
"renderer": {
"can-not-update-progress": "Kan de voortgang niet bijwerken"
},
"templates": {
"button": "Download"
}
},
"equalizer": {
"description": "Voegt een equalizer toe aan de speler",
"menu": {
"presets": {
"label": "Voorinstellingen",
"list": {
"bass-booster": "Basversterker"
}
}
},
"name": "Equalizer"
},
"exponential-volume": {
"description": "Maakt de volumeschuif exponentieel zodat het gemakkelijker is om lagere volumes te selecteren.",
"name": "Exponentieel Volume"
},
"in-app-menu": {
"description": "Geeft menubalken een chique, donkere of albumkleurige uitstraling",
"menu": {
"hide-dom-window-controls": "Verberg DOM-vensterbedieningselementen"
},
"name": "In-App Menu"
},
"lumiastream": {
"description": "Voegt ondersteuning voor Lumia Stream toe",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Voegt songtekstondersteuning toe voor de meeste nummers",
"menu": {
"romanized-lyrics": "Geromaniseerde Teksten"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Teksten opgehaald voor Genius"
}
},
"music-together": {
"description": "Deel een afspeellijst met anderen. Wanneer de host een nummer afspeelt, hoort iedereen hetzelfde nummer",
"dialog": {
"enter-host": "Voer Host-ID in"
},
"internal": {
"save": "Opslaan",
"track-source": "Bron van nummers",
"unknown-user": "Onbekende gebruiker"
},
"menu": {
"click-to-copy-id": "Host-ID kopiëren",
"close": "Sluit Music Samen",
"connected-users": "Verbonden gebruikers",
"disconnect": "Music Samen verbreken",
"empty-user": "Geen verbonden gebruikers",
"host": "Music Samen Host",
"join": "Voeg Music Samen toe",
"permission": {
"all": "Gasten toestaan de afspeellijst en speler te bedienen",
"host-only": "Alleen de host kan de afspeellijst en speler bedienen",
"playlist": "Gasten toestaan de afspeellijst te bedienen"
},
"set-permission": "Bedieningsmachtiging wijzigen",
"status": {
"disconnected": "Verbroken",
"guest": "Verbonden als Gast",
"host": "Verbonden als Host"
}
},
"name": "Music Samen [Beta]",
"toast": {
"add-song-failed": "Toevoegen van nummer mislukt",
"closed": "Music Samen gesloten",
"disconnected": "Music Samen verbroken",
"host-failed": "Hosten van Music Samen mislukt",
"id-copied": "Host-ID gekopieerd naar klembord",
"id-copy-failed": "Kopiëren van Host-ID naar klembord mislukt",
"join-failed": "Aansluiten bij Music Samen mislukt",
"joined": "Music Samen toegetreden",
"permission-changed": "Music Samen machtiging gewijzigd naar \"{{permission}}\"",
"remove-song-failed": "Verwijderen van nummer mislukt",
"user-connected": "{{name}} heeft Music Samen toegetreden",
"user-disconnected": "{{name}} heeft Music Samen verlaten"
}
},
"navigation": {
"description": "Volgende/Vorige navigatiepijlen rechtstreeks geïntegreerd in de interface, zoals in je favoriete browser",
"name": "Navigatie",
"templates": {
"back": {
"title": "Ga naar de vorige pagina"
},
"forward": {
"title": "Ga naar de volgende pagina"
}
}
},
"no-google-login": {
"description": "Verwijder Google aanmeldknoppen en -links uit de interface",
"name": "Geen Google Aanmelding"
},
"notifications": {
"description": "Toont een melding wanneer een nummer begint te spelen (interactieve meldingen zijn beschikbaar op Windows)",
"menu": {
"interactive": "Interactieve Meldingen",
"interactive-settings": {
"label": "Interactieve instellingen",
"submenu": {
"hide-button-text": "Knoptekst verbergen",
"refresh-on-play-pause": "Herlaad bij het afspelen/pauzeren",
"tray-controls": "Open/Sluit op tray klik"
}
},
"priority": "Meldingprioriteit",
"toast-style": "Toast stijl",
"unpause-notification": "Laat een notificatie zijn bij het depauzeren"
},
"name": "Meldingen"
},
"performance-improvement": {
"description": "Verbeter prestaties door experimentale scripts aan te zetten",
"name": "Prestatie-verbetering"
},
"picture-in-picture": {
"description": "Laat de app toe om naar picture-in-picture modus om te schakelen",
"menu": {
"always-on-top": "Altijd bovenaan",
"hotkey": {
"label": "Sneltoets",
"prompt": {
"keybind-options": {
"hotkey": "Sneltoets"
},
"label": "Kies een sneltoets om tussen picture-in-picture te schakelen",
"title": "Picture-in-picture sneltoets"
}
},
"save-window-position": "Sla schermpositie op",
"save-window-size": "Sla schermgrootte op",
"use-native-pip": "Gebruik browser ingebouwde PiP"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Luister snel, luister langzaam! Voegt een schuifregelaar toe die de muzieksnelheid regelt",
"name": "Afspeelsnelheid",
"templates": {
"button": "Snelheid"
}
},
"precise-volume": {
"description": "Regel het volume nauwkeurig met behulp van het muiswiel/sneltoetsen, met een aangepaste HUD en aanpasbare volumestappen",
"menu": {
"arrows-shortcuts": "Lokale Pijltjestoetsen bediening",
"custom-volume-steps": "Stel aangepaste volumestappen in",
"global-shortcuts": "Globale sneltoetsen"
},
"name": "Nauwkeurig volume",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Volume verlagen",
"increase": "Volume verhogen"
},
"label": "Kies Globale Volume toetsen:",
"title": "Globale Volume toetsen"
},
"volume-steps": {
"label": "Kies Stappen voor volumeverhoging/-verlaging",
"title": "Volume Stappen"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Huidige kwaliteit: {{quality}}",
"message": "Kies videokwaliteit:",
"title": "Kies Videokwaliteit"
}
}
},
"description": "Maakt het mogelijk de videokwaliteit te wijzigen met een knop op de video-overlay",
"name": "Videokwaliteitwisselaar",
"renderer": {
"quality-settings-button": {
"label": "Open speler kwaliteitswisselaar"
}
}
},
"scrobbler": {
"description": "Ondersteuning voor scrobbling toevoegen (etc. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Kan niet verifiëren bij Last.fm\nVerberg de pop-up tot de volgende herstart.",
"title": "Authenticatie mislukt"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API Instellingen"
},
"listenbrainz": {
"token": "Voer het ListenBrainz-gebruikerstoken in"
},
"scrobble-alternative-artist": "Gebruik alternatieve artiesten",
"scrobble-alternative-title": "Gebruik alternatieve titels",
"scrobble-other-media": "Scrobble andere media"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last.fm API sleutel",
"api-secret": "Last.fm API geheim"
},
"listenbrainz": {
"token": {
"label": "Voer uw ListenBrainz-gebruikerstoken in:",
"title": "ListenBrainz token"
}
}
}
},
"shortcuts": {
"description": "Maakt het mogelijk algemene sneltoetsen in te stellen voor afspelen (afspelen/pauzeren/volgende/vorige) en het uitschakelen van media-OSD door mediatoetsen te overschrijven, het inschakelen van Ctrl/CMD + F om te zoeken, het inschakelen van Linux MPRIS-ondersteuning voor mediatoetsen en aangepaste sneltoetsen voor gevorderde gebruikers",
"menu": {
"override-media-keys": "Media toetsen overschrijven",
"set-keybinds": "Stel de algemene songbediening in"
},
"name": "Snelkoppelingen (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Volgende",
"play-pause": "Afspelen / Pauzeren",
"previous": "Vorige"
},
"label": "Kies globale toetsen voor nummer bediening:",
"title": "Globale toetsen"
}
}
},
"skip-disliked-songs": {
"description": "Slaat disliked nummers over",
"name": "Sla disliked nummers over"
},
"skip-silences": {
"description": "Sla automatisch stiltesecties in nummers over",
"name": "Stiltes overslaan"
},
"sponsorblock": {
"description": "Slaat automatisch niet-muziekgedeelten over, zoals intro/outro of gedeelten van muziekvideo's waarin het nummer niet wordt afgespeeld",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Biedt gesynchroniseerde songteksten voor nummers, met behulp van providers zoals LRClib.",
"errors": {
"fetch": "⚠️\tEr is een fout opgetreden bij het ophalen van de songtekst.\n\tProbeer het later opnieuw.",
"not-found": "⚠️ Er is geen songtekst gevonden voor dit nummer."
},
"menu": {
"convert-chinese-character": {
"submenu": {
"simplified-to-traditional": {
"label": "Vereenvoudigd naar Traditioneel",
"tooltip": "Zet Vereenvoudigd Chinees om naar Traditioneel Chinees"
},
"traditional-to-simplified": {
"label": "Traditioneel naar Vereenvoudigd",
"tooltip": "Zet Traditioneel Chinees om naar Vereenvoudigd Chinees"
}
}
},
"default-text-string": {
"label": "Standaardteken tussen songteksten",
"tooltip": "Kies het standaardteken dat u wilt gebruiken voor de opening tussen de songteksten"
},
"line-effect": {
"label": "Lijneffect",
"submenu": {
"fancy": {
"label": "Luxe",
"tooltip": "Gebruik grote, app-achtige effecten op de huidige regel"
},
"focus": {
"label": "Focus",
"tooltip": "Maak alleen de huidige regel wit"
},
"offset": {
"label": "Offset",
"tooltip": "Offset aan de rechterkant van de huidige lijn"
},
"scale": {
"label": "Schaal",
"tooltip": "Schaal de huidige regel"
}
},
"tooltip": "Kies het effect dat u op de huidige regel wilt toepassen"
},
"precise-timing": {
"label": "Zorg ervoor dat de songteksten perfect gesynchroniseerd zijn",
"tooltip": "Bereken tot op de milliseconde de weergave van de volgende regel (kan een kleine impact hebben op de prestaties)"
},
"preferred-provider": {
"label": "Voorkeur van Provider",
"none": {
"label": "Geen",
"tooltip": "Geen provider beschikbaar"
},
"tooltip": "Kies de standaardprovider om te gebruiken"
},
"romanization": {
"label": "Romaniseer songtekst",
"tooltip": "Als de songtekst in een andere taal is, probeer dan een Latijnse versie weer te geven."
},
"show-lyrics-even-if-inexact": {
"label": "Toon songteksten, zelfs als ze onnauwkeurig zijn",
"tooltip": "Als het nummer niet wordt gevonden, probeert de plug-in het opnieuw met een andere zoekopdracht.\nHet resultaat van de tweede poging is mogelijk niet exact."
},
"show-time-codes": {
"label": "Toon tijdcodes",
"tooltip": "Toon de tijdcodes naast de songtekst"
}
},
"name": "Gesynchroniseerde songteksten",
"refetch-btn": {
"fetching": "Ophalen...",
"normal": "Songteksten opnieuw ophalen"
},
"warnings": {
"duration-mismatch": "⚠️ - De songteksten zijn mogelijk niet synchroon vanwege een niet-overeenkomende duur.",
"inexact": "⚠️ - De songtekst van dit nummer is mogelijk niet exact",
"instrumental": "⚠️ - Dit is een instrumentaal nummer"
}
},
"taskbar-mediacontrol": {
"description": "Bedien het afspelen vanaf uw Windows-taakbalk",
"name": "Taakbalk Mediabediening"
},
"touchbar": {
"description": "Voegt een TouchBar-widget toe voor macOS-gebruikers",
"name": "TouchBar"
},
"transparent-player": {
"description": "Maakt de applicatiescherm doorzichtbaar",
"menu": {
"opacity": {
"label": "Dekking",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Soort",
"submenu": {
"acrylic": "Acrylic",
"mica": "Mica",
"none": "Geen",
"tabbed": "Tabbed"
}
}
},
"name": "Doorzichtige muziek speler"
},
"tuna-obs": {
"description": "Integratie met OBS's plug-in Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Voorkomt dat de speler omhoog springt tijdens het afspelen van een nummer",
"name": "Minder opdringerige speler"
},
"video-toggle": {
"description": "Voegt een knop toe om te schakelen tussen de video-/nummermodus. kan optioneel ook het hele videotabblad verwijderen",
"menu": {
"align": {
"label": "Uitlijning",
"submenu": {
"left": "Links",
"middle": "Midden",
"right": "Rechts"
}
},
"force-hide": "Forceer het verwijderen van het videotabblad",
"mode": {
"label": "Modus",
"submenu": {
"custom": "Aangepaste schakelaar",
"disabled": "Uitgeschakeld",
"native": "Native schakelaar"
}
}
},
"name": "Videoschakelaar",
"templates": {
"button-song": "Nummer",
"button-video": "Video"
}
},
"visualizer": {
"description": "Voegt een visualisator toe aan de speler",
"menu": {
"visualizer-type": "Visualisatietype"
},
"name": "Visualisator"
}
}
}
================================================
FILE: src/i18n/resources/pl.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Błąd uruchamiania wtyczki {{pluginName}}::{{contextName}}",
"executed-at-ms": "Wtyczka {{pluginName}}::{{contextName}} uruchomiona w {{ms}}ms",
"initialize-failed": "Błąd inicjalizacji wtyczki \"{{pluginName}}\"",
"load-all": "Ładowanie wszystkich wtyczek",
"load-failed": "Błąd w ładowaniu wtyczki \"{{pluginName}}\"",
"loaded": "Wtyczka \"{{pluginName}}\" załadowana",
"unload-failed": "Błąd w odłączaniu wtyczki \"{{pluginName}}\"",
"unloaded": "Wtyczka \"{{pluginName}}\" odłączona"
}
}
},
"language": {
"code": "pl",
"local-name": "Polski",
"name": "Polish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Ukończono ładowanie. Narzędzia deweloperskie otwarte"
},
"i18n": {
"loaded": "i18n załadowane"
},
"second-instance": {
"receive-command": "Otrzymano komendę przez protokół: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Plik CSS \"{{cssFile}}\" nie istnieje, ignoruję"
},
"unresponsive": {
"details": "Błąd! Brak odpowiedzi:\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Czyszczenie pamięci podręcznej aplikacji"
},
"window": {
"tried-to-render-offscreen": "Okno próbuje się renderować poza ekranem, rozmiar okna={{windowSize}}, rozmiar monitora={{displaySize}}, pozycja={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu jest ukryte, użyj przycisku [Alt] aby je pokazać (lub [Escape], jeśli używasz menu w aplikacji)",
"message": "Ukrywanie menu jest włączone",
"title": "Ukrywanie Menu Włączone"
},
"need-to-restart": {
"buttons": {
"later": "Później",
"restart-now": "Uruchom ponownie teraz"
},
"detail": "Wtyczka \"{{pluginName}}\" potrzebuje ponownego uruchomienia aby była aktywna",
"message": "\"{{pluginName}}\" potrzebuje ponownego uruchomienia",
"title": "Ponowne uruchomienie potrzebne"
},
"unresponsive": {
"buttons": {
"quit": "Wyjdź",
"relaunch": "Ponownie uruchom",
"wait": "Czekać"
},
"detail": "Przepraszamy za niedogodność! Proszę wybierz co zrobić:",
"message": "Aplikacja nie reaguje",
"title": "Okno nie reaguje"
},
"update-available": {
"buttons": {
"disable": "Wyłącz aktualizacje",
"download": "Pobierz",
"ok": "OK"
},
"detail": "Nowa wersja jest dostępna i możesz ją pobrać tutaj {{downloadLink}}",
"message": "Nowa wersja jest dostępna",
"title": "Aktualizacja jest dostępna"
}
},
"menu": {
"about": "O aplikacji",
"navigation": {
"label": "Nawigacja",
"submenu": {
"copy-current-url": "Skopiuj aktualny adres URL",
"go-back": "Wróć",
"go-forward": "Do przodu",
"quit": "Wyjście",
"restart": "Uruchom ponownie aplikację"
}
},
"options": {
"label": "Opcje",
"submenu": {
"advanced-options": {
"label": "Opcje zaawansowane",
"submenu": {
"auto-reset-app-cache": "Wyczyść pamięć podręczną aplikacji przy jej uruchomieniu",
"disable-hardware-acceleration": "Wyłącz przyspieszanie sprzętowe",
"edit-config-json": "Edytuj config.json",
"override-user-agent": "Zastąp klienta użytkownika (User-Agent)",
"restart-on-config-changes": "Uruchom ponownie po zmianie konfiguracji",
"set-proxy": {
"label": "Ustaw proxy",
"prompt": {
"label": "Podaj adres proxy: (zostaw pusty aby wyłączyć)",
"placeholder": "Przykład: SOCKS5://127.0.0.1:9999",
"title": "Ustaw proxy"
}
},
"toggle-dev-tools": "Przełącz narzędzia deweloperskie"
}
},
"always-on-top": "Zawsze na wierzchu",
"auto-update": "Automatyczne aktualizacje",
"hide-menu": {
"dialog": {
"message": "Menu będzie ukryte po następnym uruchomieniu, użyj przycisku [Alt] aby je pokazać (lub [`], jeśli używasz menu w aplikacji)",
"title": "Ukrywanie menu włączone"
},
"label": "Ukryj menu"
},
"language": {
"dialog": {
"message": "Język będzie zmieniony po ponownym uruchomieniu",
"title": "Język zmieniony"
},
"label": "Język",
"submenu": {
"to-help-translate": "Chcesz pomóc w tłumaczeniu? Kliknij tutaj"
}
},
"resume-on-start": "Wznów ostatni utwór po uruchomieniu aplikacji",
"single-instance-lock": "Zablokuj do jednej instancji aplikacji",
"start-at-login": "Uruchom po zalogowaniu",
"starting-page": {
"label": "Strona startowa",
"unset": "Pusty"
},
"tray": {
"label": "Ikona w zasobniku",
"submenu": {
"disabled": "Wyłączone",
"enabled-and-hide-app": "Włącz i ukryj aplikację",
"enabled-and-show-app": "Włącz i pokaż aplikację",
"play-pause-on-click": "Odtwórz/Wstrzymaj po kliknięciu"
}
},
"visual-tweaks": {
"label": "Poprawki wizualne",
"submenu": {
"custom-window-title": {
"label": "Niestandardowy tytuł okna",
"prompt": {
"label": "Podaj niestandardowy tytuł okna (zostaw puste, aby to wyłączyć)",
"placeholder": "Przykład: {{applicationName}}"
}
},
"like-buttons": {
"default": "Domyślne",
"force-show": "Wymuś pokazywanie",
"hide": "Ukryj",
"label": "Przyciski polubienia"
},
"remove-upgrade-button": "Usuń przycisk subskrypcji premium",
"theme": {
"dialog": {
"button": {
"cancel": "Anuluj",
"remove": "Usuń"
},
"remove-theme": "Czy na pewno chcesz usunąć niestandardowy motyw?",
"remove-theme-message": "Spowoduje to usunięcie niestandarowego motywu"
},
"label": "Motyw",
"submenu": {
"import-css-file": "Importuj własny plik CSS",
"no-theme": "Bez motywu"
}
}
}
}
}
},
"plugins": {
"enabled": "Włączone",
"label": "Wtyczki",
"new": "NOWOŚĆ"
},
"view": {
"label": "Widok",
"submenu": {
"force-reload": "Wymuś ponowne ładowanie",
"reload": "Ponowne ładowanie",
"reset-zoom": "Rozmiar rzeczywisty",
"toggle-fullscreen": "Przełącz pełny ekran",
"zoom-in": "Powiększ",
"zoom-out": "Pomniejsz"
}
}
},
"tray": {
"next": "Następny",
"play-pause": "Odtwórz/Pauza",
"previous": "Poprzedni",
"quit": "Wyjdź",
"restart": "Uruchom ponownie aplikację",
"show": "Pokaż okno",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{title}} (autorstwa {{artist}}) - {{applicationName}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Wycisza reklamę i przyśpiesza do 16x",
"name": "Przyśpieszacz reklam"
},
"adblocker": {
"description": "Blokuj wszystkie reklamy i śledzenie",
"menu": {
"blocker": "Metoda przechwytywania"
},
"name": "Blokowanie reklam"
},
"album-actions": {
"description": "Dodaje przyciski łapek w górę i dół do wszystkich piosenek podczas w albumach lub listach odtwarzania",
"name": "Akcje albumu"
},
"album-color-theme": {
"description": "Stosuje dynamiczny motyw i efekty wizualne w oparciu o paletę kolorów albumu",
"menu": {
"color-mix-ratio": {
"label": "Intensywność koloru",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Zezwól stylowanie paska wyszukiwań"
},
"name": "Motyw kolorów albumu"
},
"ambient-mode": {
"description": "Stosuje efekt świetlny, rzucając delikatne kolory z wideo na tło ekranu",
"menu": {
"blur-amount": {
"label": "Siła rozmycia",
"submenu": {
"pixels": "{{blurAmount}} pikseli"
}
},
"buffer": {
"label": "Bufor",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Nieprzezroczystość",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Jakość",
"submenu": {
"pixels": "{{quality}} pikseli"
}
},
"size": {
"label": "Rozmiar",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Płynność przejścia",
"submenu": {
"during": "W czasie {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Podczas pełnego ekranu"
}
},
"name": "Tryb otoczenia"
},
"amuse": {
"description": "Wspiera integrację {{applicationName}} z widgetami Amuse (od 6K Labs)",
"name": "Amuse",
"response": {
"query": "Serwer API Amuse działa. Użyj metody GET do /query, aby zdobyć informację o utworze."
}
},
"api-server": {
"description": "Steruj odtwarzaczem przez specjalny serwer API",
"dialog": {
"request": {
"buttons": {
"allow": "Zezwól",
"deny": "Odmów"
},
"message": "Zezwolić {{ID}} (pochodzenie: {{origin}}) na dostęp do API?",
"title": "Prośba o autoryzację API"
}
},
"menu": {
"auth-strategy": {
"label": "Strategia autoryzacji",
"submenu": {
"auth-at-first": {
"label": "Autoryzuj przy pierwszej prośbie"
},
"none": {
"label": "Nie autoryzuj"
}
}
},
"hostname": {
"label": "Nazwa hosta (IP)"
},
"https": {
"label": "HTTPS i Certyfikaty",
"submenu": {
"cert": {
"dialogTitle": "Wybierz plik certyfikatu HTTPS",
"label": "Certyfikat (.crt/.pem)"
},
"enable-https": {
"label": "Zezwól HTTPS"
},
"key": {
"dialogTitle": "Wybierz plik prywatnego klucza HTTPS",
"label": "Klucz prywatny (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "{{applicationName}} API",
"prompt": {
"hostname": {
"label": "Wpisz nazwę hosta (IP, np. 0.0.0.0), który będzie użyty do serwera API:",
"title": "Nazwa hosta"
},
"port": {
"label": "Wpisz port, z którego będzie korzystać serwer API:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Zastosuj kompresję do dźwięku (obniża głośność najgłośniejszych części sygnału i zwiększa głośność najcichszych części)",
"name": "Kompresor dźwięku"
},
"auth-proxy-adapter": {
"description": "Wparcie dla uwierzytalniających usług proxy",
"menu": {
"disable": "Wyłącz adapter proxy",
"enable": "Włącz adapter proxy",
"hostname": {
"label": "Nazwa hosta"
},
"port": {
"label": "Port"
}
},
"name": "Uwierzytelnij adapter proxy",
"prompt": {
"hostname": {
"label": "Wpisz nazwę hosta dla lokalnego serwera proxy (wymaga restaru):",
"title": "Nazwa hosta proxy"
},
"port": {
"label": "Wpisz port dla lokalnego serwera proxy (wymaga restartu):",
"title": "Port proxy"
}
}
},
"blur-nav-bar": {
"description": "Sprawia, że pasek nawigacji jest przezroczysty i rozmazany",
"name": "Rozmycie paska nawigacji"
},
"bypass-age-restrictions": {
"description": "Pomija weryfikację wieku",
"name": "Omiń ograniczenia wiekowe"
},
"captions-selector": {
"description": "Selektor napisów dla ścieżek audio {{applicationName}}",
"menu": {
"autoload": "Automatycznie wybierz ostatnio używanych napisów",
"disable-captions": "Domyślnie, brak napisów"
},
"name": "Selektor napisów",
"prompt": {
"selector": {
"label": "Bieżący język napisów: {{language}}",
"none": "Brak",
"title": "Wybierz język napisów"
}
},
"templates": {
"title": "Otwórz selektor napisów"
},
"toast": {
"caption-changed": "Zmieniono napisy na {{language}}",
"caption-disabled": "Napisy zostały wyłączone",
"no-captions": "Nie znaleziono napisów dla tego utworu"
}
},
"compact-sidebar": {
"description": "Zawsze ustawiaj pasek boczny w trybie kompaktowym",
"name": "Kompaktowy pasek boczny"
},
"crossfade": {
"description": "Pozwól odtwarzaczowi płynnie przechodzić między utworami",
"menu": {
"advanced": "Zaawansowane"
},
"name": "Płynne przejście [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Czas wnikania (ms)",
"fade-out-duration": "Czas zanikania (ms)",
"fade-scaling": {
"label": "Skalowanie zanikania",
"linear": "Liniowe",
"logarithmic": "Logarytmiczne"
},
"seconds-before-end": "Przenikanie N sekund przed końcem"
},
"title": "Opcje przenikania"
}
}
},
"custom-output-device": {
"description": "Skonfiguruj niestandardowe wyjście audio dla odtwarzanych utworów",
"menu": {
"device-selector": "Wybierz urządzenie"
},
"name": "Niestandardowe urządzenie wyjścia",
"prompt": {
"device-selector": {
"label": "Wybierz urządzenie wyjściowe",
"title": "Wybierz wyjście audio"
}
}
},
"disable-autoplay": {
"description": "Wyłącza automatyczne odtwarzanie utworów",
"menu": {
"apply-once": "Tylko przy uruchomieniu aplikacji"
},
"name": "Wyłącz automatyczne odtwarzanie"
},
"discord": {
"backend": {
"already-connected": "Próbowano połączyć się przy aktywnym połączeniu",
"connected": "Połączono z Discordem",
"disconnected": "Odłączono od Discorda"
},
"description": "Pokaż znajomym z Discorda czego słuchasz, dzięki technologii Rich Presence",
"menu": {
"auto-reconnect": "Automatyczne wznawianie połączenia",
"clear-activity": "Wyczyść aktywność",
"clear-activity-after-timeout": "Wyczyść aktywność po czasie",
"connected": "Połączono",
"disconnected": "Odłączono",
"hide-duration-left": "Ukryj pozostały czas trwania",
"hide-github-button": "Ukryj przycisk do GitHub",
"play-on-application": "Odtwórz w {{applicationName}}",
"set-inactivity-timeout": "Ustaw limit czasu bezczynności",
"set-status-display-type": {
"label": "Opis statusu",
"submenu": {
"application": "Słucha {{applicationName}}",
"artist": "Słucha {artist}",
"title": "Słucha {song title}"
}
}
},
"name": "Aktywność w Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Podaj limit czasu bezczynności w sekundach:",
"title": "Ustaw limit czasu bezczynności"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Argh! Przepraszamy, pobieranie nie powiodło się…",
"title": "Błąd podczas pobierania!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} utworów)",
"message": "Pobieranie playlisty {{playlistTitle}}",
"title": "Pobieranie rozpoczęte"
}
},
"feedback": {
"conversion-progress": "Konwertowanie: {{percent}}%",
"converting": "Konwertowanie…",
"done": "Gotowe: {{filePath}}",
"download-info": "Pobieranie {{artist}} - {{title}} {{videoId}}",
"download-progress": "Pobieranie: {{percent}}%",
"downloading": "Pobieranie…",
"downloading-counter": "Pobieranie {{current}}/{{total}} …",
"downloading-playlist": "Pobieranie playlisty \"{{playlistTitle}}\" - {{playlistSize}} utworów ({{playlistId}})",
"error-while-downloading": "Błąd pobierania \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Folder {{playlistFolder}} już istnieje",
"getting-playlist-info": "Pobieram informacje o playliście…",
"loading": "Ładowanie…",
"playlist-has-only-one-song": "Playlista zawiera tylko jeden element, zostanie pobrany bezpośrednio",
"playlist-id-not-found": "Nie znaleziono ID playlisty",
"playlist-is-empty": "Playlista jest pusta",
"playlist-is-mix-or-private": "Podczas pobierania informacji o playliście wystąpił błąd: upewnij się, że nie jest to playlista prywatna ani playlista „Składanki dla Ciebie”.\n\n{{error}}",
"preparing-file": "Przygotowuję plik…",
"saving": "Zapisuję…",
"trying-to-get-playlist-id": "Próbuję uzyskać ID playlisty: {{playlistId}}",
"video-id-not-found": "Nie znaleziono filmu",
"writing-id3": "Zapisywanie tagów ID3…"
}
},
"description": "Pobiera MP3/ źródło audio bezpośrednio z interfejsu",
"menu": {
"choose-download-folder": "Wybierz folder pobierania",
"download-finish-settings": {
"label": "Pobierz po zakończeniu",
"prompt": {
"last-percent": "Po x procentach",
"last-seconds": "Ostatnie x sekund",
"title": "Konfiguruj, kiedy pobierać"
},
"submenu": {
"advanced": "Zaawansowane",
"enabled": "Włączone",
"mode": "Tryb czasowy",
"percent": "Procenty",
"seconds": "Sekundy"
}
},
"download-playlist": "Pobierz playlistę",
"presets": "Predefiniowane ustawienia",
"skip-existing": "Pomiń istniejące pliki"
},
"name": "Pobieranie",
"renderer": {
"can-not-update-progress": "Nie można zaktualizować postępu"
},
"templates": {
"button": "Pobierz"
}
},
"equalizer": {
"description": "Dodaje equalizer do odtwarzacza",
"menu": {
"presets": {
"label": "Presety",
"list": {
"bass-booster": "Wzmacniacz basu"
}
}
},
"name": "Korektor"
},
"exponential-volume": {
"description": "Sprawia, że suwak głośności jest proporcjonalna, dzięki czemu łatwiej jest wybrać niższą głośność.",
"name": "Proporcjonalna głośność"
},
"in-app-menu": {
"description": "Nadaje paskom menu elegancki, ciemny lub albumowy wygląd",
"menu": {
"hide-dom-window-controls": "Ukryj kontrolki okna DOM"
},
"name": "Menu w aplikacji"
},
"lumiastream": {
"description": "Dodaje obsługę Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Dodaje obsługę tekstów dla większości piosenek",
"menu": {
"romanized-lyrics": "Teksty zromanizowane"
},
"name": "Tekst piosenek od Genius",
"renderer": {
"fetched-lyrics": "Tekst dostarczony przez Genius"
}
},
"music-together": {
"description": "Pozwala na udostępnianie listy odtwarzania 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",
"templates": {
"back": {
"title": "Przejdź do poprzedniej strony"
},
"forward": {
"title": "Przejdź do następnej strony"
}
}
},
"no-google-login": {
"description": "Usuń przyciski i linki logowania Google z interfejsu",
"name": "Usuń logowanie do Google"
},
"notifications": {
"description": "Wyświetl powiadomienie, gdy rozpocznie się odtwarzanie utworu (interaktywne powiadomienia są dostępne w systemie Windows)",
"menu": {
"interactive": "Interaktywne powiadomienia",
"interactive-settings": {
"label": "Interaktywne ustawienia",
"submenu": {
"hide-button-text": "Ukryj tekst przycisku",
"refresh-on-play-pause": "Odśwież podczas odtwarzania/pauzy",
"tray-controls": "Otwórz/zamknij po kliknięciu ikony na pasku zadań"
}
},
"priority": "Priorytet powiadomień",
"toast-style": "Styl powiadomień \"Toast\"",
"unpause-notification": "Pokaż powiadomienie po wznowieniu"
},
"name": "Powiadomienia"
},
"performance-improvement": {
"description": "Popraw wydajność przez włączenie skryptów eksperymentalnych",
"name": "Poprawienie wydajności [Beta]"
},
"picture-in-picture": {
"description": "Umożliwia przełączenie aplikacji w tryb obrazu w obrazie",
"menu": {
"always-on-top": "Zawsze na wierzchu",
"hotkey": {
"label": "Klawisz skrótu",
"prompt": {
"keybind-options": {
"hotkey": "Klawisz skrótu"
},
"label": "Wybierz klawisz skrótu do przełączania trybu obrazu w obrazie",
"title": "Klawisz skrótu obrazu w obrazie"
}
},
"save-window-position": "Zapisz pozycję okna",
"save-window-size": "Zapisz rozmiar okna",
"use-native-pip": "Użyj natywnego PiP dla przeglądarki"
},
"name": "Obraz w obrazie",
"templates": {
"button": "Obraz w obrazie"
}
},
"playback-speed": {
"description": "Słuchaj szybko, słuchaj powoli! Dodaje suwak kontrolujący prędkość utworu",
"name": "Szybkość odtwarzania",
"templates": {
"button": "Szybkość"
}
},
"precise-volume": {
"description": "Precyzyjnie kontroluj głośność za pomocą kółka myszy/klawiszy skrótu, z niestandardowym interfejsem HUD i konfigurowalnymi krokami głośności",
"menu": {
"arrows-shortcuts": "Sterowanie za pomocą klawiszy strzałek",
"custom-volume-steps": "Ustaw niestandardowe kroki głośności",
"global-shortcuts": "Globalne skróty klawiszowe"
},
"name": "Precyzyjna głośność",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Zmniejsz głośność",
"increase": "Zwiększ głośność"
},
"label": "Wybierz globalne skróty klawiaturowe głośności:",
"title": "Globalne skróty klawiszowe głośności"
},
"volume-steps": {
"label": "Wybierz kroki zwiększania/zmniejszania głośności",
"title": "Kroki głośności"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Aktualna jakość: {{quality}}",
"message": "Wybierz jakość wideo:",
"title": "Wybierz jakość wideo"
}
}
},
"description": "Umożliwia zmianę jakości wideo za pomocą przycisku na nakładce wideo",
"name": "Zmieniacz jakości wideo",
"renderer": {
"quality-settings-button": {
"label": "Otwórz manipulator jakości odtwarzacza"
}
}
},
"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-alternative-artist": "Urzyj alternatywnych artytów",
"scrobble-alternative-title": "Użyj alternatywnych tytułów",
"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",
"menu": {
"override-media-keys": "Zastąp klawisze multimediów",
"set-keybinds": "Ustaw globalne sterowanie utworem"
},
"name": "Skróty klawiszowe (oraz MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Następny",
"play-pause": "Odtwarzanie / Pauza",
"previous": "Poprzedni"
},
"label": "Wybierz globalne skróty klawiszowe do sterowania utworami:",
"title": "Globalne skróty klawiszowe"
}
}
},
"skip-disliked-songs": {
"description": "Pomija nieulubione piosenki (zaznaczone łapką w dół)",
"name": "Pomijanie nieulubionych piosenek"
},
"skip-silences": {
"description": "Automatycznie pomijaj sekcje bez dźwięku w utworach",
"name": "Pomiń ciszę"
},
"sponsorblock": {
"description": "Automatycznie pomija fragmenty niebędące muzyką, takie jak wstęp/zakończenie lub fragmenty teledysków, w których utwór nie jest odtwarzany",
"name": "Pomiń nieistotne fragmenty"
},
"synced-lyrics": {
"description": "Dodaje zsynchronizowane napisy do utworów używając między innymi LRClib.",
"errors": {
"fetch": "⚠️\tWystąpił błąd podczas pobierania tekstu utworu.\n\tSpróbuj ponownie później.",
"not-found": "⚠️ Nie znaleziono napisów dla tego utworu."
},
"menu": {
"default-text-string": {
"label": "Standardowy znak luki",
"tooltip": "Wybierz domyślny znak, który ma być wyświetlany jako pauza między słowami"
},
"line-effect": {
"label": "Efekty linijki",
"submenu": {
"fancy": {
"label": "Facy",
"tooltip": "Użyj specjalnych efektów w stylu aplikacji na obecną linię"
},
"focus": {
"label": "Fokus",
"tooltip": "Spraw, aby tylko obecna linijka była biała"
},
"offset": {
"label": "Przesunięcie",
"tooltip": "Przesuń w prawo obecną linijkę"
},
"scale": {
"label": "Skala",
"tooltip": "Zmień skalę aktualnej linijki"
}
},
"tooltip": "Wybierz efekt, by zastosować go do aktualnej linijki"
},
"precise-timing": {
"label": "Zsynchronizuj tekst utworu do perfekcji",
"tooltip": "Wylicz czas wyświetlania następnej linijki co do milisekundy (może mieć mały wpływ na wydajność systemu)"
},
"preferred-provider": {
"label": "Preferowane Źródło",
"none": {
"label": "Żaden",
"tooltip": "Brak preferowanego źródła"
},
"tooltip": "Wybierz domyślne źródło"
},
"romanization": {
"label": "Romanizacja utworów",
"tooltip": "Jeżeli tekst piosenki nie jest w alfabecie łacińskim, poddaje ją romanizacji, czyli przedstawia mowy za pomocą owych liter."
},
"show-lyrics-even-if-inexact": {
"label": "Pokaż teksty, mimo niezgodności",
"tooltip": "Jeżeli nie znaleziono tekstu piosenki z bazy danych, wtyczka spróbuje ponownie przez wyszukanie przybliżonej frazy.\nNależy jednak pamiętać, że następne próby mogą nie być trafne co do oryginału."
},
"show-time-codes": {
"label": "Pokaż znaczniki czasu",
"tooltip": "Pokaż znaczniki czasu obok linijek"
}
},
"name": "Napisy zsynchronizowane",
"refetch-btn": {
"fetching": "Pobieranie napisów...",
"normal": "Odśwież napisy"
},
"warnings": {
"duration-mismatch": "⚠️ - Napisy mogą nie być zsynchronizowane z powodu różnicy w czasie trwania utworu.",
"inexact": "⚠️ - Tekst utworu może się różnić od oryginału",
"instrumental": "⚠️ - To jest utwór instrumentalny"
}
},
"taskbar-mediacontrol": {
"description": "Steruj odtwarzaniem z paska zadań systemu Windows",
"name": "Kontroler odtwarzania z paska zadań"
},
"touchbar": {
"description": "Dodaje widżet do paska dotykowego dla użytkowników systemu macOS",
"name": "Pasek dotykowy"
},
"transparent-player": {
"description": "Sprawia, że okno aplikacji jest przeźroczyste",
"menu": {
"opacity": {
"label": "Nieprzezroczystość",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Akryl",
"mica": "Mika",
"none": "Brak",
"tabbed": "Zakładkowy"
}
}
},
"name": "Przeźroczysty odtwarzacz"
},
"tuna-obs": {
"description": "Integracja z wtyczką OBS Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Zapobiega wyświetlaniu się ekranu z utworem po wybraniu innego tytułu",
"name": "Niewidoczny odtwarzacz"
},
"video-toggle": {
"description": "Dodaje przycisk do przełączania między trybem wideo a piosenki. Może również opcjonalnie usunąć całą kartę wideo",
"menu": {
"align": {
"label": "Wyrównanie",
"submenu": {
"left": "Lewo",
"middle": "Środek",
"right": "Prawo"
}
},
"force-hide": "Wymuś usunięcie zakładki wideo",
"mode": {
"label": "Tryb",
"submenu": {
"custom": "Niestandardowy przełącznik",
"disabled": "Wyłączony",
"native": "Natywny przełącznik"
}
}
},
"name": "Przełącznik wideo",
"templates": {
"button-song": "Utwór",
"button-video": "Wideo"
}
},
"visualizer": {
"description": "Dodaje wizualizator do odtwarzacza",
"menu": {
"visualizer-type": "Typ wizualizatora"
},
"name": "Wizualizator"
}
}
}
================================================
FILE: src/i18n/resources/pt-BR.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Falha ao executar plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} executado em {{ms}} ms",
"initialize-failed": "Falha ao inicializar o plugin \"{{pluginName}}\"",
"load-all": "Carregando todos os plugins",
"load-failed": "Falha ao carregar o plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" carregado",
"unload-failed": "Falha ao descarregar o plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" descarregado"
}
}
},
"language": {
"code": "pt-BR",
"local-name": "Português (Brasil)",
"name": "Portuguese (Brazil)"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Carregamento concluído. DevTools aberto"
},
"i18n": {
"loaded": "i18n carregado"
},
"second-instance": {
"receive-command": "Comando recebido pelo protocolo: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Arquivo CSS \"{{cssFile}}\" não existe, ignorando"
},
"unresponsive": {
"details": "Erro por falta de resposta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Limpando cache do aplicativo"
},
"window": {
"tried-to-render-offscreen": "A janela tentou renderizar fora dos parâmetros da tela, tamanho da janela={{windowSize}}, tamanho da tela={{displaySize}}, posição={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "O menu está oculto, use 'Alt' para mostrá-lo (ou 'Esc' ao usar o menu dentro do aplicativo)",
"message": "Ocultar Menu está ativado",
"title": "Ocultar Menu ativado"
},
"need-to-restart": {
"buttons": {
"later": "Depois",
"restart-now": "Reiniciar Agora"
},
"detail": "O plugin \"{{pluginName}}\" requer uma reinicialização para entrar em vigor",
"message": "\"{{pluginName}}\" precisa reiniciar",
"title": "Necessário reiniciar"
},
"unresponsive": {
"buttons": {
"quit": "Fechar",
"relaunch": "Reiniciar",
"wait": "Aguardar"
},
"detail": "Lamentamos o inconveniente! Por favor, escolha o que fazer:",
"message": "O aplicativo não está respondendo",
"title": "Janela não responde"
},
"update-available": {
"buttons": {
"disable": "Desativar atualizações",
"download": "Baixar",
"ok": "OK"
},
"detail": "Uma versão mais recente está disponível em {{downloadLink}}",
"message": "Nova versão disponível",
"title": "Atualização disponível"
}
},
"menu": {
"about": "Sobre",
"navigation": {
"label": "Navegação",
"submenu": {
"copy-current-url": "Copiar URL atual",
"go-back": "Retornar",
"go-forward": "Avançar",
"quit": "Sair",
"restart": "Reiniciar aplicativo"
}
},
"options": {
"label": "Opções",
"submenu": {
"advanced-options": {
"label": "Opções avançadas",
"submenu": {
"auto-reset-app-cache": "Limpar cache ao iniciar aplicativo",
"disable-hardware-acceleration": "Desativar aceleração de hardware",
"edit-config-json": "Editar config.json",
"override-user-agent": "Substituir User-Agent",
"restart-on-config-changes": "Reiniciar ao alterar configurações",
"set-proxy": {
"label": "Definir proxy",
"prompt": {
"label": "Digite o endereço do proxy: (deixe em branco para desativar)",
"placeholder": "Exemplo: SOCKS5://127.0.0.1:9999",
"title": "Definir proxy"
}
},
"toggle-dev-tools": "Alternar DevTools"
}
},
"always-on-top": "Sempre no topo",
"auto-update": "Atualização automática",
"hide-menu": {
"dialog": {
"message": "O menu ficará oculto na próxima inicialização, use [Alt] para exibi-lo (ou a tecla de crase [`] se estiver usando o menu do aplicativo)",
"title": "Ocultar menu ativado"
},
"label": "Ocultar menu"
},
"language": {
"dialog": {
"message": "O idioma será alterado depois de reiniciar",
"title": "Idioma alterado"
},
"label": "Idioma",
"submenu": {
"to-help-translate": "Quer ajudar a traduzir? Clique aqui"
}
},
"resume-on-start": "Continuar última música ao iniciar o aplicativo",
"single-instance-lock": "Bloqueio de instância única",
"start-at-login": "Iniciar com o sistema",
"starting-page": {
"label": "Página inicial",
"unset": "Limpar"
},
"tray": {
"label": "Área de Notificação",
"submenu": {
"disabled": "Desativado",
"enabled-and-hide-app": "Ativado e aplicativo oculto",
"enabled-and-show-app": "Ativado e mostrar aplicativo",
"play-pause-on-click": "Reproduzir/Pausar ao clicar"
}
},
"visual-tweaks": {
"label": "Ajustes visuais",
"submenu": {
"custom-window-title": {
"label": "Título da janela customizado",
"prompt": {
"label": "Insira título customizado para a janela: (deixe em branco para desabilitar)",
"placeholder": "Exemplo: {{applicationName}}"
}
},
"like-buttons": {
"default": "Padrão",
"force-show": "Forçar exibir",
"hide": "Ocultar",
"label": "Botões de 'Curtir'",
"swap": "Inverter ordem dos botões de curtir"
},
"remove-upgrade-button": "Remover botão de atualização",
"theme": {
"dialog": {
"button": {
"cancel": "Cancelar",
"remove": "Remover"
},
"remove-theme": "Deseja realmente remover o tema personalizado?",
"remove-theme-message": "Isto removerá o tema personalizado"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importar arquivo CSS personalizado",
"no-theme": "Sem tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Ativado",
"label": "Plugins",
"new": "NOVO"
},
"view": {
"label": "Visualização",
"submenu": {
"force-reload": "Forçar recarregar",
"reload": "Recarregar",
"reset-zoom": "Tamanho atual",
"toggle-fullscreen": "Alternar tela cheia",
"zoom-in": "Ampliar",
"zoom-out": "Reduzir"
}
}
},
"tray": {
"next": "Próximo",
"play-pause": "Reproduzir/Pausar",
"previous": "Anterior",
"quit": "Sair",
"restart": "Reiniciar aplicativo",
"show": "Mostrar janela",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Se um anúncio for reproduzido, ele silencia o áudio e define a velocidade de reprodução para 16x",
"name": "Acelerador de Anúncios"
},
"adblocker": {
"description": "Bloqueio de todos os anúncios e rastreamentos imediatamente",
"menu": {
"blocker": "Bloqueador"
},
"name": "Bloqueador de Anúncios"
},
"album-actions": {
"description": "Adiciona botões Remover Não curtir, Não curtir, Curtir e Remover Curtir para aplicar em todas as músicas em uma lista ou álbum",
"name": "Ações do á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": "Proporção de mistura de cores",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Ativar personalização da barra de progresso"
},
"name": "Tema da cor do álbum"
},
"ambient-mode": {
"description": "Aplica um efeito de iluminação projetando cores suaves do vídeo no fundo da tela",
"menu": {
"blur-amount": {
"label": "Quantidade de desfoque",
"submenu": {
"pixels": "{{blurAmount}} pixels"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacidade",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualidade",
"submenu": {
"pixels": "{{quality}} pixels"
}
},
"size": {
"label": "Tamanho",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transição suave",
"submenu": {
"during": "Durante {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Usando tela cheia"
}
},
"name": "Modo ambiente"
},
"amuse": {
"description": "Adiciona suporte ao {{applicationName}} ao widget 'Reproduzindo agora' do Amuse da 6K Labs",
"name": "Amuse",
"response": {
"query": "Servidor API do Amuse em execução. GET /query para obter informações da música."
}
},
"api-server": {
"description": "Adiciona um servidor API para controlar o player",
"dialog": {
"request": {
"buttons": {
"allow": "Permitir",
"deny": "Negar"
},
"message": "Permitir que {{ID}} {{origin}} acesse o API?",
"title": "Pedido de autorização API"
}
},
"menu": {
"auth-strategy": {
"label": "Estratégia de autorização",
"submenu": {
"auth-at-first": {
"label": "Autorizar na primeira solicitação"
},
"none": {
"label": "Não autorizar"
}
}
},
"hostname": {
"label": "Nome do anfitrião"
},
"https": {
"label": "HTTPS & Certificados",
"submenu": {
"cert": {
"dialogTitle": "Selecione o certificado do HTTPS",
"label": "Arquivo de certificado (.crt/.pem)"
},
"enable-https": {
"label": "Habilitar HTTPS"
},
"key": {
"dialogTitle": "Selecione a chave privada do HTTPS",
"label": "Arquivo de chave privada (.key/.pem)"
}
}
},
"port": {
"label": "Porta"
}
},
"name": "Servidor API [Beta]",
"prompt": {
"hostname": {
"label": "Entre o nome do host (como 0.0.0.0) para o servidor API:",
"title": "Nome do anfitrião"
},
"port": {
"label": "Entre a porta do servidor API:",
"title": "Porta"
}
}
},
"audio-compressor": {
"description": "Aplicar compressão ao áudio (reduz o volume das partes mais altas e aumenta o volume das partes mais baixas)",
"name": "Compressor de áudio"
},
"auth-proxy-adapter": {
"description": "Suporte para o uso de serviços de proxy de autenticação",
"menu": {
"disable": "Desativar adaptador proxy",
"enable": "Ativar adaptador proxy",
"hostname": {
"label": "Nome do host"
},
"port": {
"label": "Porta"
}
},
"name": "Adaptador de proxy de autenticação",
"prompt": {
"hostname": {
"label": "Entre o nome do host do servidor proxy local (necessário reiniciar):",
"title": "Nome do host do proxy"
},
"port": {
"label": "Entre a porta do servidor proxy local (necessário reiniciar):",
"title": "Porta do proxy"
}
}
},
"blur-nav-bar": {
"description": "Torna a barra de navegação transparente e desfocada",
"name": "Desfocar barra de navegação"
},
"bypass-age-restrictions": {
"description": "Pular a verificação de idade do Music Player",
"name": "Ignorar restrições de idade"
},
"captions-selector": {
"description": "Seletor de legendas para faixas de áudio do {{applicationName}}",
"menu": {
"autoload": "Selecionar automaticamente a última legenda usada",
"disable-captions": "Sem legendas por padrão"
},
"name": "Seletor de legendas",
"prompt": {
"selector": {
"label": "Idioma atual da legenda: {{language}}",
"none": "Nenhum",
"title": "Selecionar idioma da legenda"
}
},
"templates": {
"title": "Abrir seletor de legendas"
},
"toast": {
"caption-changed": "Legenda alterada para {{language}}",
"caption-disabled": "Legendas desativadas",
"no-captions": "Sem legendas disponíveis para essa música"
}
},
"clock": {
"description": "Adicionar relógio na barra de navegação",
"menu": {
"format": {
"24-hour-format": "Formato 24 horas",
"display-seconds": "Mostrar segundos",
"label": "Formato"
}
},
"name": "Relógio"
},
"compact-sidebar": {
"description": "Sempre definir a barra lateral no modo compacto",
"name": "Barra lateral compacta"
},
"crossfade": {
"description": "Crossfade entre músicas",
"menu": {
"advanced": "Avançado"
},
"name": "Crossfade [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Duração do fade (ms)",
"fade-out-duration": "Duração do fade out (ms)",
"fade-scaling": {
"label": "Escala do fade",
"linear": "Linear",
"logarithmic": "Logarítmico"
},
"seconds-before-end": "Crossfade N segundos antes do fim"
},
"title": "Opções de crossfade"
}
}
},
"custom-output-device": {
"description": "Configure um dispositivo de saída de mídia personalizado para músicas",
"menu": {
"device-selector": "Selecionar dispositivo"
},
"name": "Dispositivo de saída personalizado",
"prompt": {
"device-selector": {
"label": "Escolha o dispositivo de saída de mídia que será usado",
"title": "Selecionar dispositivo de saída"
}
}
},
"disable-autoplay": {
"description": "Faz a música começar no modo \"pausado\"",
"menu": {
"apply-once": "Aplicar somente ao iniciar"
},
"name": "Desativar reprodução automática"
},
"discord": {
"backend": {
"already-connected": "Tentativa de conectar-se com conexão ativa",
"connected": "Conectado no Discord",
"disconnected": "Desconectado do Discord"
},
"description": "Mostre aos seus amigos o que você ouve com Rich Presence",
"menu": {
"auto-reconnect": "Reconexão automática",
"clear-activity": "Limpar atividades",
"clear-activity-after-timeout": "Limpar atividades após tempo limite",
"connected": "Conectado",
"disconnected": "Desconectado",
"hide-duration-left": "Ocultar duração restante",
"hide-github-button": "Ocultar botão do GitHub",
"play-on-application": "Reproduzir no {{applicationName}}",
"set-inactivity-timeout": "Definir tempo limite de inatividade",
"set-status-display-type": {
"label": "Texto de status",
"submenu": {
"application": "Ouvindo {{applicationName}}",
"artist": "Ouvindo {artist}",
"title": "Ouvindo {song title}"
}
}
},
"name": "Rich Presence do Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Digite o tempo de inatividade em segundos:",
"title": "Definir tempo limite de inatividade"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Ah! Desculpe, o download falhou…",
"title": "Erro no download!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} músicas)",
"message": "Baixando lista de reprodução {{playlistTitle}}",
"title": "Download iniciado"
}
},
"feedback": {
"conversion-progress": "Convertendo: {{percent}}%",
"converting": "Convertendo…",
"done": "Concluído: {{filePath}}",
"download-info": "Baixando {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Download: {{percent}}%",
"downloading": "Baixando…",
"downloading-counter": "Baixando {{current}}/{{total}}…",
"downloading-playlist": "Baixando lista de reprodução \"{{playlistTitle}}\" - {{playlistSize}} músicas ({{playlistId}})",
"error-while-downloading": "Erro ao baixar \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "A pasta {{playlistFolder}} já existe",
"getting-playlist-info": "Obtendo informações da playlist…",
"loading": "Carregando…",
"playlist-has-only-one-song": "Playlist possui apenas um item, baixando diretamente",
"playlist-id-not-found": "Nenhum playlist ID encontrado",
"playlist-is-empty": "Playlist está vazia",
"playlist-is-mix-or-private": "Erro ao obter informações da playlist: verifique se não é uma playlist privada ou “”Mixada para você”\n\n{{error}}",
"preparing-file": "Preparando arquivo…",
"saving": "Salvando…",
"trying-to-get-playlist-id": "Tentando obter playlist ID: {{playlistId}}",
"video-id-not-found": "Vídeo não encontrado",
"writing-id3": "Salvando tags ID3…"
}
},
"description": "Faça download do MP3 / fonte de áudio diretamente da interface",
"menu": {
"choose-download-folder": "Escolha a pasta de download",
"download-finish-settings": {
"label": "Baixar ao finalizar",
"prompt": {
"last-percent": "Após x %",
"last-seconds": "Últimos x segundos",
"title": "Configurar quando baixar"
},
"submenu": {
"advanced": "Avançado",
"enabled": "Ativado",
"mode": "Modo de tempo",
"percent": "Porcento",
"seconds": "Segundos"
}
},
"download-playlist": "Baixar playlist",
"presets": "Predefinições",
"skip-existing": "Pular arquivos existentes"
},
"name": "Downloader",
"renderer": {
"can-not-update-progress": "Não é possível atualizar o progresso"
},
"templates": {
"button": "Baixar"
}
},
"equalizer": {
"description": "Adiciona um equalizador ao player",
"menu": {
"presets": {
"label": "Predefinições",
"list": {
"bass-booster": "Reforço de graves"
}
}
},
"name": "Equalizador"
},
"exponential-volume": {
"description": "Torna o controle deslizante de volume exponencial para que seja mais fácil selecionar volumes mais baixos.",
"name": "Volume Exponencial"
},
"in-app-menu": {
"description": "Dá às barras de menu uma aparência elegante, escura ou com a cor do álbum",
"menu": {
"hide-dom-window-controls": "Ocultar controles da janela DOM"
},
"name": "Menu no aplicativo"
},
"lumiastream": {
"description": "Adiciona suporte ao Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Adiciona suporte a letras para a maioria das músicas",
"menu": {
"romanized-lyrics": "Letras Romanizadas"
},
"name": "Letras Genius",
"renderer": {
"fetched-lyrics": "Letras buscadas por Genius"
}
},
"music-together": {
"description": "Compartilhe uma playlist com outras pessoas. Quando o anfitrião toca uma música, todos os outros ouvirão",
"dialog": {
"enter-host": "Insira 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 Music Together",
"connected-users": "Usuários Conectados",
"disconnect": "Desconectar Music Together",
"empty-user": "Nenhum usuário conectado",
"host": "Anfitrião do Music Together",
"join": "Entrar no Music Together",
"permission": {
"all": "Permitir que os convidados controlem a lista de reprodução e o player",
"host-only": "Somente o host pode controlar a lista de reprodução e o player",
"playlist": "Permitir que os convidados controlem a lista de reprodução"
},
"set-permission": "Mudar Permissões de Controle",
"status": {
"disconnected": "Desconectado",
"guest": "Conectado como convidado",
"host": "Conectado como Anfitrião"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Falha ao adicionar música",
"closed": "Music Together fechado",
"disconnected": "Music Together desconectado",
"host-failed": "Falha ao hospedar o Music Together",
"id-copied": "ID do anfitrião copiado para a área de transferência",
"id-copy-failed": "Falha ao copiar o ID do anfitrião para a área de transferência",
"join-failed": "Falha ao ingressar no Music Together",
"joined": "Entrou no Music Together",
"permission-changed": "A permissão do Music Together foi alterada para \"{{permission}}\"",
"remove-song-failed": "Falha ao remover música",
"user-connected": "{{name}} juntou-se ao Music Together",
"user-disconnected": "{{name}} saiu do Music Together"
}
},
"navigation": {
"description": "Setas de navegação para avançar/retornar diretamente integradas na interface, como no seu navegador favorito",
"name": "Navegação",
"templates": {
"back": {
"title": "Ir para a página anterior"
},
"forward": {
"title": "Ir para a próxima página"
}
}
},
"no-google-login": {
"description": "Remova os botões e links de login do Google da interface",
"name": "Sem login do Google"
},
"notifications": {
"description": "Exibir uma notificação quando uma música começar a tocar (notificações interativas estão disponíveis no Windows)",
"menu": {
"interactive": "Notificações interativas",
"interactive-settings": {
"label": "Configurações interativas",
"submenu": {
"hide-button-text": "Ocultar texto do botão",
"refresh-on-play-pause": "Atualizar ao Reproduzir/Pausar",
"tray-controls": "Abrir/Fechar ao clicar na área de notificação"
}
},
"priority": "Prioridade da notificação",
"toast-style": "Estilo de alerta",
"unpause-notification": "Mostrar notificação ao despausar"
},
"name": "Notificações"
},
"performance-improvement": {
"description": "Melhore o desempenho habilitando scripts experimentais",
"name": "Melhoria de desempenho [Beta]"
},
"picture-in-picture": {
"description": "Permite alternar o aplicativo para o modo picture-in-picture",
"menu": {
"always-on-top": "Sempre no topo",
"hotkey": {
"label": "Tecla de atalho",
"prompt": {
"keybind-options": {
"hotkey": "Tecla de atalho"
},
"label": "Escolha uma tecla de atalho para alternar entre picture-in-picture",
"title": "Atalho do picture-in-picture"
}
},
"save-window-position": "Salvar posição da janela",
"save-window-size": "Salvar tamanho da janela",
"use-native-pip": "Usar PiP nativo do navegador"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Ouça rápido, ouça devagar! Adiciona um controle deslizante que controla a velocidade da música",
"name": "Velocidade de reprodução",
"templates": {
"button": "Velocidade"
}
},
"precise-volume": {
"description": "Controle o volume com precisão usando a roda do mouse/teclas de atalho, com um HUD personalizado e etapas de volume personalizáveis",
"menu": {
"arrows-shortcuts": "Controles de teclas de seta locais",
"custom-volume-steps": "Definir etapas de volume personalizadas",
"global-shortcuts": "Teclas de atalho globais"
},
"name": "Volume preciso",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Diminuir volume",
"increase": "Aumentar volume"
},
"label": "Selecione as teclas de atalho global do volume:",
"title": "Teclas de atalho global de volume"
},
"volume-steps": {
"label": "Escolha as etapas de aumento/diminuição do volume",
"title": "Fases de volume"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualidade atual: {{quality}}",
"message": "Escolher qualidade do vídeo:",
"title": "Escolher qualidade do vídeo"
}
}
},
"description": "Permite alterar a qualidade do vídeo com um botão na sobreposição de vídeo",
"name": "Alterador de qualidade do vídeo",
"renderer": {
"quality-settings-button": {
"label": "Abrir o player de troca de qualidade"
}
}
},
"scrobbler": {
"description": "Adicionar suporte para scrobbling (last.fm, Listenbrainz, etc.)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Falha ao autenticar com Last.fm\nOcultar o pop-up até a próxima reinicialização.",
"title": "Falha na autenticação"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Configurações da API do Last.fm"
},
"listenbrainz": {
"token": "Insira o token de usuário ListenBrainz"
},
"scrobble-alternative-artist": "Use artistas alternativos",
"scrobble-alternative-title": "Usar títulos alternativos",
"scrobble-other-media": "Scrobble outras mídias"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Chave de API do Last.fm",
"api-secret": "Chave secreta da API do Last.fm"
},
"listenbrainz": {
"token": {
"label": "Insira seu token de usuário do ListenBrainz:",
"title": "ListenBrainz token"
}
}
}
},
"shortcuts": {
"description": "Permite definir teclas de atalho globais para reprodução (reproduzir/pausar/próximo/anterior) e desativar 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 chaves de multimédia",
"set-keybinds": "Definir controles globais de música"
},
"name": "Atalhos (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Próximo",
"play-pause": "Reproduzir / Pausar",
"previous": "Anterior"
},
"label": "Escolha atalhos de teclado globais para controle de músicas:",
"title": "Atalhos de teclado global"
}
}
},
"skip-disliked-songs": {
"description": "Ignora músicas marcadas com \"não gostei\"",
"name": "Pular músicas marcadas com \"não gostei\""
},
"skip-silences": {
"description": "Pular automaticamente seções de silêncio em músicas",
"name": "Pular silêncios"
},
"sponsorblock": {
"description": "Pula automaticamente partes não musicais, como introdução/finalização ou partes de videoclipes onde a música não está tocando",
"name": "SponsorBlock [Bloquear patrocínios]"
},
"synced-lyrics": {
"description": "Fornece letras sincronizadas para músicas, usando provedores como LRClib.",
"errors": {
"fetch": "⚠️\tOcorreu um erro ao buscar a letra.\n\tTente novamente mais tarde.",
"not-found": "⚠️ Nenhuma letra encontrada para esta música."
},
"menu": {
"convert-chinese-character": {
"label": "Converter caracteres Chineses",
"submenu": {
"disabled": {
"label": "Desativado",
"tooltip": "Desativar conversão de caracteres Chineses"
},
"simplified-to-traditional": {
"label": "Simplificado para Tradicional",
"tooltip": "Converter Chinês Simplificado para Chinês Tradicional"
},
"traditional-to-simplified": {
"label": "Tradicional para Simplificado",
"tooltip": "Converter Chinês Tradicional para Chinês Simplificado"
}
},
"tooltip": "Converter caractere Chinês para Tradicional ou Simplificado"
},
"default-text-string": {
"label": "Caractere padrão entre letras",
"tooltip": "Escolha o caractere padrão a ser usado para o intervalo entre as letras"
},
"line-effect": {
"label": "Efeito de linha",
"submenu": {
"fancy": {
"label": "Fancy",
"tooltip": "Use efeitos grandes, semelhantes a aplicativos, na linha atual"
},
"focus": {
"label": "Foco",
"tooltip": "Deixe apenas a linha atual branca"
},
"offset": {
"label": "Deslocar",
"tooltip": "Deslocamento à direita da linha atual"
},
"scale": {
"label": "Aumentar",
"tooltip": "Aumentar a linha atual"
}
},
"tooltip": "Escolha o efeito a ser aplicado à linha atual"
},
"precise-timing": {
"label": "Deixa as letras perfeitamente sincronizadas",
"tooltip": "Calcular até o milissegundo a exibição da próxima linha (pode ter um pequeno impacto no desempenho)"
},
"preferred-provider": {
"label": "Provedor Preferido",
"none": {
"label": "Nenhum",
"tooltip": "Sem provedor preferido"
},
"tooltip": "Escolha o provedor padrão para uso"
},
"romanization": {
"label": "Letras romanizadas",
"tooltip": "Se as letras estiverem em um idioma diferente, tente exibir uma versão latina."
},
"show-lyrics-even-if-inexact": {
"label": "Mostrar letras mesmo que não sejam exatas",
"tooltip": "Se a música não for encontrada, o plugin tenta novamente com uma consulta de pesquisa diferente.\nO resultado da segunda tentativa pode não ser exato."
},
"show-time-codes": {
"label": "Mostrar códigos de tempo",
"tooltip": "Mostrar os códigos de tempo ao lado das letras"
}
},
"name": "Letras sincronizadas",
"refetch-btn": {
"fetching": "Buscando...",
"normal": "Buscar letras novamente"
},
"warnings": {
"duration-mismatch": "⚠️ - A letra pode estar dessincronizada devido a uma incompatibilidade de duração.",
"inexact": "⚠️ - A letra desta música pode não ser exata",
"instrumental": "⚠️ - Esta é uma música instrumental"
}
},
"taskbar-mediacontrol": {
"description": "Controle a reprodução na barra de tarefas do Windows",
"name": "Controle de mídia da barra de tarefas"
},
"touchbar": {
"description": "Adiciona um widget TouchBar para usuários do macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Faz a janela do app transparente",
"menu": {
"opacity": {
"label": "Opacidade",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrílico",
"mica": "Mica",
"none": "Nenhum",
"tabbed": "Em abas"
}
}
},
"name": "Player transparente"
},
"tuna-obs": {
"description": "Integração com o plugin Tuna do OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Evita que o player apareça ao tocar uma música",
"name": "Player discreto"
},
"video-toggle": {
"description": "Adiciona um botão para alternar entre o modo Vídeo/Música. Também é possível remover opcionalmente toda a aba de vídeo",
"menu": {
"align": {
"label": "Alinhamento",
"submenu": {
"left": "Esquerda",
"middle": "Meio",
"right": "Direita"
}
},
"force-hide": "Forçar remoção da aba de vídeo",
"mode": {
"label": "Modo",
"submenu": {
"custom": "Alternância personalizada",
"disabled": "Desativado",
"native": "Alternância nativa"
}
}
},
"name": "Alternar vídeo",
"templates": {
"button-song": "Música",
"button-video": "Vídeo"
}
},
"visualizer": {
"description": "Adiciona um visualizador ao player",
"menu": {
"visualizer-type": "Tipo de visualizador"
},
"name": "Visualizador"
}
}
}
================================================
FILE: src/i18n/resources/pt.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Falha ao executar o plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} executado em {{ms}} ms",
"initialize-failed": "Falha ao iniciar o plugin \"{{pluginName}}\"",
"load-all": "A carregar todos os plugins",
"load-failed": "Falha ao ativar o plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" ativado",
"unload-failed": "Falha ao desativar o plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" desativado"
}
}
},
"language": {
"code": "pt",
"local-name": "Português",
"name": "Portuguese"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Carregamento concluído. DevTools aberto"
},
"i18n": {
"loaded": "i18n carregado"
},
"second-instance": {
"receive-command": "Comando recebido através do protocolo: \"{{command}}\""
},
"theme": {
"css-file-not-found": "O ficheiro CSS \"{{cssFile}}\" não existe, a ignorar"
},
"unresponsive": {
"details": "Erro de falta de resposta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "A limpar cache da aplicação"
},
"window": {
"tried-to-render-offscreen": "Tentativa de desenho fora do ecrã na janela, tamanho da janela={{windowSize}}, tamanho do ecrã={{displaySize}}, posição={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "O menu está oculto, utilize \"Alt\" para o mostrar (ou \"Escape\" se estiver a utilizar o menu da aplicação)",
"message": "Ocultar menu está ativado",
"title": "Ocultar menu ativado"
},
"need-to-restart": {
"buttons": {
"later": "Depois",
"restart-now": "Reiniciar agora"
},
"detail": "Tem que reiniciar a aplicação para ativar \"{{pluginName}}\"",
"message": "Tem que reiniciar \"{{pluginName}}\"",
"title": "Tem que reiniciar"
},
"unresponsive": {
"buttons": {
"quit": "Fechar",
"relaunch": "Reiniciar",
"wait": "Esperar"
},
"detail": "Lamentamos o incómodo! Por favor, escolha o que fazer:",
"message": "A aplicação não está a responder",
"title": "A janela não está a responder"
},
"update-available": {
"buttons": {
"disable": "Desativar atualizações",
"download": "Descarregar",
"ok": "Aceitar"
},
"detail": "Pode descarregar a nova versão em {{downloadLink}}",
"message": "Está disponível uma nova versão",
"title": "Atualização disponível"
}
},
"menu": {
"about": "Acerca",
"navigation": {
"label": "Navegação",
"submenu": {
"copy-current-url": "Copiar URL atual",
"go-back": "Recuar",
"go-forward": "Avançar",
"quit": "Sair",
"restart": "Reiniciar aplicação"
}
},
"options": {
"label": "Opções",
"submenu": {
"advanced-options": {
"label": "Opções avançadas",
"submenu": {
"auto-reset-app-cache": "Repor cache ao iniciar a aplicação",
"disable-hardware-acceleration": "Desativar aceleração por hardware",
"edit-config-json": "Editar config.json",
"override-user-agent": "Substituir User-Agent",
"restart-on-config-changes": "Reiniciar após alterar as configurações",
"set-proxy": {
"label": "Definir proxy",
"prompt": {
"label": "Introduza o endereço do proxy: (deixe em branco para desativar)",
"placeholder": "Exemplo: SOCKS5://127.0.0.1:9999",
"title": "Definir proxy"
}
},
"toggle-dev-tools": "Ativar DevTools"
}
},
"always-on-top": "Sempre na frente",
"auto-update": "Atualizações automáticas",
"hide-menu": {
"dialog": {
"message": "O menu será ocultado após reiniciar a aplicação. Utilize [Alt] para o mostrar (ou [`] se estiver a utilizar o menu da aplicação)",
"title": "Ocultar menu ativado"
},
"label": "Ocultar menu"
},
"language": {
"dialog": {
"message": "O idioma será alterado após reiniciar",
"title": "Idioma alterado"
},
"label": "Idioma",
"submenu": {
"to-help-translate": "Deseja ajudar na tradução? Clique aqui"
}
},
"resume-on-start": "Continuar reprodução ao iniciar",
"single-instance-lock": "Limitar a uma instância",
"start-at-login": "Iniciar com o sistema",
"starting-page": {
"label": "Página inicial",
"unset": "Indefinida"
},
"tray": {
"label": "Área de notificação",
"submenu": {
"disabled": "Desativada",
"enabled-and-hide-app": "Ativada e ocultar aplicação",
"enabled-and-show-app": "Ativada e a mostrar aplicação",
"play-pause-on-click": "Reprodução/Pausa ao clicar"
}
},
"visual-tweaks": {
"label": "Ajustes visuais",
"submenu": {
"custom-window-title": {
"label": "Título de janela personalizado",
"prompt": {
"label": "Introduza um título: (deixe em branco para desativar)",
"placeholder": "Exemplo: {{applicationName}}"
}
},
"like-buttons": {
"default": "Padrão",
"force-show": "Mostrar sempre",
"hide": "Ocultar",
"label": "Botões \"Gosto\""
},
"remove-upgrade-button": "Remover botão \"Upgrade\"",
"theme": {
"dialog": {
"button": {
"cancel": "Cancelar",
"remove": "Remover"
},
"remove-theme": "Tem a certeza de que pretende remover o tema personalizado?",
"remove-theme-message": "Irá remover o tema personalizado"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importar ficheiro CSS",
"no-theme": "Sem tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Ativado",
"label": "Plugins",
"new": "Novo"
},
"view": {
"label": "Ver",
"submenu": {
"force-reload": "Impor recarregamento",
"reload": "Recarregar",
"reset-zoom": "Tamanho real",
"toggle-fullscreen": "Ativar ecrã completo",
"zoom-in": "Ampliar",
"zoom-out": "Reduzir"
}
}
},
"tray": {
"next": "Seguinte",
"play-pause": "Reprodução/Pausa",
"previous": "Anterior",
"quit": "Sair",
"restart": "Reiniciar aplicação",
"show": "Mostrar janela",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Se um anúncio for reproduzido, silencia o áudio e define a velocidade de reprodução para 16x",
"name": "Acelerador de anúncios"
},
"adblocker": {
"description": "Bloquear anúncios e monitorização",
"menu": {
"blocker": "Bloqueador"
},
"name": "Bloqueador de anúncios"
},
"album-actions": {
"description": "Adiciona os botões \"Anular Não gosto\", \"Não gosto\", \"Gosto\" e \"Não gosto\" para aplicar a todas as músicas de uma lista de reprodução ou álbum",
"name": "Ações do álbum"
},
"album-color-theme": {
"description": "Aplica um tema dinâmico e efeitos visuais baseados na paleta de cores do álbum",
"menu": {
"color-mix-ratio": {
"label": "Rácio de mistura de cores",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Ativar temas na barra de reprodução"
},
"name": "Tema de cores do álbum"
},
"ambient-mode": {
"description": "Aplica um efeito de iluminação, projetando cores suaves do vídeo para o fundo do ecrã",
"menu": {
"blur-amount": {
"label": "Quantidade de desfoque",
"submenu": {
"pixels": "{{blurAmount}} pixéis"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacidade",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Qualidade",
"submenu": {
"pixels": "{{quality}} pixéis"
}
},
"size": {
"label": "Tamanho",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Transição suave",
"submenu": {
"during": "Durante {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Utilizar ecrã completo"
}
},
"name": "Modo ambiente"
},
"amuse": {
"description": "Adiciona suporte ao {{applicationName}} para o widget Amuse now playing de 6K Labs",
"name": "Amuse",
"response": {
"query": "O servidor da API Amuse está a ser executado. GET/query para obter informações sobre a faixa."
}
},
"api-server": {
"description": "Adiciona um servidor API para controlar o reprodutor",
"dialog": {
"request": {
"buttons": {
"allow": "Permitir",
"deny": "Recusar"
},
"message": "Permitir que {{ID}} ({{origin}}) aceda à API?",
"title": "Pedido de autorização da API"
}
},
"menu": {
"auth-strategy": {
"label": "Estratégia de autorização",
"submenu": {
"auth-at-first": {
"label": "Autorizar no primeiro pedido"
},
"none": {
"label": "Sem autorização"
}
}
},
"hostname": {
"label": "Nome do anfitrião"
},
"https": {
"label": "HTTPS e Certificados",
"submenu": {
"cert": {
"dialogTitle": "Selecionar arquivo do certificado HTTPS",
"label": "Arquivo do certificado (.crt/.pem)"
},
"enable-https": {
"label": "Ativar HTTPS"
},
"key": {
"dialogTitle": "Selecionar arquivo da chave privada HTTPS",
"label": "Arquivo da chave privada (.key/.pen)"
}
}
},
"port": {
"label": "Porta"
}
},
"name": "Servidor API [Beta]",
"prompt": {
"hostname": {
"label": "Introduza o nome do anfitrião (como 0.0.0.0) para o servidor API:",
"title": "Nome do anfitrião"
},
"port": {
"label": "Introduza a porta para o servidor API:",
"title": "Porta"
}
}
},
"audio-compressor": {
"description": "Aplicar compressão ao áudio (diminui o volume nas partes mais altas do sinal e aumenta o volume nas partes mais suaves)",
"name": "Compressor de áudio"
},
"auth-proxy-adapter": {
"description": "Suporte para serviços de proxy de autenticação",
"menu": {
"disable": "Desativar adaptador proxy",
"enable": "Ativar adaptador proxy",
"hostname": {
"label": "Nome do anfitrião"
},
"port": {
"label": "Porta"
}
},
"name": "Adaptador do proxy de autenticação",
"prompt": {
"hostname": {
"label": "Introduza o nome do anfitrião para o servidor proxy local (tem que reiniciar):",
"title": "Nome do anfitrião do proxy"
},
"port": {
"label": "Introduza a porta para o servidor proxy local (tem que reiniciar):",
"title": "Porta do proxy"
}
}
},
"blur-nav-bar": {
"description": "Torna a barra de navegação transparente e desfocada",
"name": "Barra de navegação desfocada"
},
"bypass-age-restrictions": {
"description": "Ignorar verificação de idade do Music Player",
"name": "Ignorar restrições de idade"
},
"captions-selector": {
"description": "Seletor de legendas para as faixas de áudio do {{applicationName}}",
"menu": {
"autoload": "Selecionar automaticamente a última legenda utilizada",
"disable-captions": "Sem legendas por omissão"
},
"name": "Seletor de legendas",
"prompt": {
"selector": {
"label": "Idioma atual da legenda: {{language}}",
"none": "Nenhum",
"title": "Selecione o idioma da legenda"
}
},
"templates": {
"title": "Abrir seletor de legendas"
},
"toast": {
"caption-changed": "Legenda alterada para {{language}}",
"caption-disabled": "Legendas desativadas",
"no-captions": "Não existem legendas para esta faixa"
}
},
"compact-sidebar": {
"description": "Utilizar barra lateral no modo compacto",
"name": "Barra lateral compacta"
},
"crossfade": {
"description": "Transição entre faixas",
"menu": {
"advanced": "Avançado"
},
"name": "Transição entre faixas [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Duração da transição no início (ms)",
"fade-out-duration": "Duração da transição no fim (ms)",
"fade-scaling": {
"label": "Escala da transição",
"linear": "Linear",
"logarithmic": "Logarítmica"
},
"seconds-before-end": "Realizar transição N segundos antes do fim"
},
"title": "Opções da transição"
}
}
},
"custom-output-device": {
"description": "Configurar um dispositivo de saída padrão para as músicas",
"menu": {
"device-selector": "Selecionar dispositivo"
},
"name": "Dispositivo personalizado",
"prompt": {
"device-selector": {
"label": "Escolha o dispositivo de saída a utilizar",
"title": "Selecione o dispositivo de saída"
}
}
},
"disable-autoplay": {
"description": "Faz com que a música inicie no modo \"pausa\"",
"menu": {
"apply-once": "Aplicar apenas ao iniciar"
},
"name": "Desativar reprodução automática"
},
"discord": {
"backend": {
"already-connected": "Tentativa de conexão com ligação já ativa",
"connected": "Conectado a Discord",
"disconnected": "Desconectado de Discord"
},
"description": "Mostre aos seus amigos o que está a ouvir com Rich Presence",
"menu": {
"auto-reconnect": "Conexão automática",
"clear-activity": "Limpar atividade",
"clear-activity-after-timeout": "Limpar atividade após o tempo limite",
"connected": "Conectado",
"disconnected": "Desconectado",
"hide-duration-left": "Ocultar tempo restante",
"hide-github-button": "Ocultar botão GitHub",
"play-on-application": "Reproduzir em {{applicationName}}",
"set-inactivity-timeout": "Definir tempo de inatividade",
"set-status-display-type": {
"label": "Texto de estado",
"submenu": {
"application": "A reproduzir {{applicationName}}",
"artist": "A ouvir {artist}",
"title": "A ouvir {song title}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Introduza o tempo limite de inatividade em segundos:",
"title": "Definir tempo de inatividade"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Aceitar"
},
"message": "Falha ao descarregar…",
"title": "Erro ao descarregar!"
},
"start-download-playlist": {
"buttons": {
"ok": "Aceitar"
},
"detail": "({{playlistSize}} músicas)",
"message": "A descarregar lista de reprodução {{playlistTitle}}",
"title": "Descarga iniciada"
}
},
"feedback": {
"conversion-progress": "Conversão: {{percent}}%",
"converting": "A converter…",
"done": "Concluído: {{filePath}}",
"download-info": "A descarregar {{artist}} - {{title}} {{videoId}}",
"download-progress": "A descarregar: {{percent}}%",
"downloading": "A descarregar…",
"downloading-counter": "A descarregar {{current}}/{{total}}…",
"downloading-playlist": "A descarregar lista de reprodução \"{{playlistTitle}}\" - {{playlistSize}} músicas ({{playlistId}})",
"error-while-downloading": "Erro ao descarregar \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "A pasta {{playlistFolder}} já existe",
"getting-playlist-info": "A obter informações da lista…",
"loading": "A carregar…",
"playlist-has-only-one-song": "A lista de reprodução tem apenas um item, descarregar diretamente",
"playlist-id-not-found": "Não foi encontrado o ID da lista de reprodução",
"playlist-is-empty": "Lista de reprodução vazia",
"playlist-is-mix-or-private": "Erro ao obter as informações da lista: certifique-se de que não é uma lista ou mistura personalizada\n\n{{error}}",
"preparing-file": "A preparar ficheiro…",
"saving": "A guardar…",
"trying-to-get-playlist-id": "A tentar obter o ID da lista: {{playlistId}}",
"video-id-not-found": "Vídeo não encontrado",
"writing-id3": "A guardar etiquetas ID3…"
}
},
"description": "Descarregar MP3/fonte de áudio diretamente da interface",
"menu": {
"choose-download-folder": "Escolha a pasta para a descarga",
"download-finish-settings": {
"label": "Descarregar ao terminar",
"prompt": {
"last-percent": "Após uma %",
"last-seconds": "Últimos x segundos",
"title": "Configurar quando descarregar"
},
"submenu": {
"advanced": "Avançado",
"enabled": "Ativado",
"mode": "Modo de tempo",
"percent": "Percentagem",
"seconds": "Segundos"
}
},
"download-playlist": "Descarregar lista de reprodução",
"presets": "Predefinições",
"skip-existing": "Ignorar ficheiros existentes"
},
"name": "Descarregador",
"renderer": {
"can-not-update-progress": "Não é possível atualizar o progresso"
},
"templates": {
"button": "Descarregar"
}
},
"equalizer": {
"description": "Adiciona um equalizador ao reprodutor",
"menu": {
"presets": {
"label": "Predefinições",
"list": {
"bass-booster": "Amplificador de graves"
}
}
},
"name": "Equalizador"
},
"exponential-volume": {
"description": "Controlo do volume exponencial para que seja mais fácil selecionar volumes mais baixos",
"name": "Volume exponencial"
},
"in-app-menu": {
"description": "Barras de menu com aparência sofisticada, escura ou com a cor do álbum",
"menu": {
"hide-dom-window-controls": "Ocultar controlos da janela DOM"
},
"name": "Menu da aplicação"
},
"lumiastream": {
"description": "Adicionar suporte a Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Adiciona suporte a letras para a maioria das músicas",
"menu": {
"romanized-lyrics": "Letras romanizadas"
},
"name": "Letras Genius",
"renderer": {
"fetched-lyrics": "Letras encontradas no Genius"
}
},
"music-together": {
"description": "Partilhe uma lista de reprodução com terceiros. Sempre que o anfitrião tocar uma música, todos os outros ouvirão a mesma música",
"dialog": {
"enter-host": "Introduza o ID do anfitrião"
},
"internal": {
"save": "Guardar",
"track-source": "Origem da faixa",
"unknown-user": "Utilizador desconhecido"
},
"menu": {
"click-to-copy-id": "Copiar ID do anfitrião",
"close": "Fechar Music Together",
"connected-users": "Utilizadores conectados",
"disconnect": "Desconectar Music Together",
"empty-user": "Sem utilizadores conectados",
"host": "Anfitrião Music Together",
"join": "Integrar Music Together",
"permission": {
"all": "Permitir que os convidados controlem a a lista de reprodução e o reprodutor",
"host-only": "Apenas o anfitrião pode controlar a lista de reprodução e o reprodutor",
"playlist": "Permitir que os convidados controlem a lista de reprodução"
},
"set-permission": "Alterar permissões de controlo",
"status": {
"disconnected": "Desconectado",
"guest": "Conectado como convidado",
"host": "Conectado como anfitrião"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Falha ao adicionar música",
"closed": "Music Together fechado",
"disconnected": "Music Together desconectado",
"host-failed": "Falha ao alojar Music Together",
"id-copied": "ID do anfitrião copiado para a área de transferência",
"id-copy-failed": "Falha ao copiar a ID do anfitrião para a área de transferência",
"join-failed": "Falha ao entrar em Music Together",
"joined": "Entrou em Music Together",
"permission-changed": "A permissão Music Together foi alterada para \"{{permission}}\"",
"remove-song-failed": "Falha ao remover música",
"user-connected": "{{name}} entrou em Music Together",
"user-disconnected": "{{name}} saiu de Music Together"
}
},
"navigation": {
"description": "Setas de navegação Avançar/Recuar integradas na interface tal como acontece nos navegadores web",
"name": "Navegação",
"templates": {
"back": {
"title": "Ir para a página anterior"
},
"forward": {
"title": "Ir para a página seguinte"
}
}
},
"no-google-login": {
"description": "Remove os botões de início de sessão Google e as ligações na interface",
"name": "Sem acesso Google"
},
"notifications": {
"description": "Mostrar uma notificação ao iniciar a reprodução de uma música (notificações interativas em sistemas Windows)",
"menu": {
"interactive": "Notificações interativas",
"interactive-settings": {
"label": "Definições interativas",
"submenu": {
"hide-button-text": "Ocultar texto do botão",
"refresh-on-play-pause": "Recarregar ao reproduzir/pausa",
"tray-controls": "Abrir/fechar com um clique na bandeja"
}
},
"priority": "Prioridade da notificação",
"toast-style": "Estilo da notificação",
"unpause-notification": "Mostrar notificação ao retomar reprodução"
},
"name": "Notificações"
},
"performance-improvement": {
"description": "Melhore o desempenho ativando scripts experimentais",
"name": "Melhoria de desempenho [Beta]"
},
"picture-in-picture": {
"description": "Permite mudar a aplicação para o modo 'picture-in-picture'",
"menu": {
"always-on-top": "Sempre na frente",
"hotkey": {
"label": "Tecla de atalho",
"prompt": {
"keybind-options": {
"hotkey": "Tecla de atalho"
},
"label": "Escolha a tecla de atalho para ativar 'picture-in-picture'",
"title": "Tecla de atalho 'picture-in-picture'"
}
},
"save-window-position": "Guardar posição da janela",
"save-window-size": "Guardar tamanho da janela",
"use-native-pip": "Utilizar PiP nativo do navegador"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Adiciona um controlo deslizante para controlar a velocidade de reprodução",
"name": "Velocidade de reprodução",
"templates": {
"button": "Velocidade"
}
},
"precise-volume": {
"description": "Controle o volume com precisão utilizando a roda do rato/teclas de atalho, com um HUD personalizado e incrementos de volume personalizáveis",
"menu": {
"arrows-shortcuts": "Controlo preciso com as teclas de seta",
"custom-volume-steps": "Definir incrementos de volume",
"global-shortcuts": "Teclas de atalho globais"
},
"name": "Volume preciso",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Diminuir volume",
"increase": "Aumentar volume"
},
"label": "Escolha as teclas de atalho para o volume global:",
"title": "Teclas de atalho para volume global"
},
"volume-steps": {
"label": "Escolha o valor para aumentar/diminuir o volume",
"title": "Incrementos de volume"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Qualidade atual: {{quality}}",
"message": "Escolha a qualidade do vídeo:",
"title": "Escolha a qualidade do vídeo"
}
}
},
"description": "Permite alterar a qualidade do vídeo com um botão sobreposto ao vídeo",
"name": "Comutador da qualidade de vídeo",
"renderer": {
"quality-settings-button": {
"label": "Abrir comutador de qualidade"
}
}
},
"scrobbler": {
"description": "Adicionar suporte para 'scrobbling' (last.fm, Listenbrainz, etc.)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Falha de autenticação em Last.fm\nA janela será ocultada até reiniciar a aplicação.",
"title": "Falha na autenticação"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Definições da API Last.fm"
},
"listenbrainz": {
"token": "Introduza o 'token' do utilizador ListenBrainz"
},
"scrobble-alternative-artist": "Utilizar artistas alternativos",
"scrobble-alternative-title": "Utilizar títulos alternativos",
"scrobble-other-media": "Scrobble de outros conteúdos"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Chave da API Last.fm",
"api-secret": "Segredo da API Last.fm"
},
"listenbrainz": {
"token": {
"label": "Introduza o 'token' do utilizador ListenBrainz:",
"title": "Token ListenBrainz"
}
}
}
},
"shortcuts": {
"description": "Permite definir teclas de atalho globais para a reprodução (reproduzir/pausa/seguinte/anterior) e desativar o OSD, substituindo as teclas multimédia e ativando Ctrl/CMD + F para pesquisar, o suporte Linux MPRIS para teclas multimédia e teclas de atalho personalizadas para utilizadores avançados",
"menu": {
"override-media-keys": "Substituir teclas multimédia",
"set-keybinds": "Definir controlos globais para a música"
},
"name": "Atalhos (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Seguinte",
"play-pause": "Reproduzir/Pausa",
"previous": "Anterior"
},
"label": "Escolha as teclas globais para o controlo de músicas:",
"title": "Atalhos globais do teclado"
}
}
},
"skip-disliked-songs": {
"description": "Ignorar músicas de que não gostei",
"name": "Ignorar músicas não gostadas"
},
"skip-silences": {
"description": "Ignorar automaticamente as partes silenciosas das músicas",
"name": "Ignorar silêncio"
},
"sponsorblock": {
"description": "Ignorar automaticamente partes que não são música, como a introdução e outras partes dos vídeos em que a música não está a ser reproduzida",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Disponibiliza letras de músicas sincronizadas, utilizando fornecedores como o LRClib",
"errors": {
"fetch": "⚠️\tOcorreu um erro ao obter a letrad da música.\n\tPor favor tente mais tarde.",
"not-found": "⚠️ Não foram encontradas letras para esta música."
},
"menu": {
"default-text-string": {
"label": "Carácter padrão entre letras",
"tooltip": "Escolha o carácter padrão a utilizar para o intervalo entre as letras"
},
"line-effect": {
"label": "Efeito da linha",
"submenu": {
"fancy": {
"label": "Elegante",
"tooltip": "Utilizar grandes efeitos semelhantes aos da aplicação (na linha atual)"
},
"focus": {
"label": "Foco",
"tooltip": "Definir apenas a linha atual como branca"
},
"offset": {
"label": "Deslocação",
"tooltip": "Deslocação à direita da linha atual"
},
"scale": {
"label": "Escala",
"tooltip": "Ajustar linha atual"
}
},
"tooltip": "Escolha o efeito para aplicar à linha atual"
},
"precise-timing": {
"label": "Sincronização perfeita da entre letra e música",
"tooltip": "Calcular, ao milissegundo, a exibição da linha seguinte (pode ter um pequeno impacto no desempenho)"
},
"preferred-provider": {
"label": "Serviço preferencial",
"none": {
"label": "Nenhum",
"tooltip": "Nenhum serviço preferencial"
},
"tooltip": "Escolha o serviço padrão a utilizar"
},
"romanization": {
"label": "Letras romanas",
"tooltip": "Se as letras estiverem num idioma diferente, tentar mostrar uma versão em latim"
},
"show-lyrics-even-if-inexact": {
"label": "Mostrar letras, mesmo que imprecisas",
"tooltip": "Se a música não for encontrada, o plugin tenta novamente com uma consulta de pesquisa diferente.\nO resultado da segunda tentativa pode não ser exato."
},
"show-time-codes": {
"label": "Mostrar códigos temporais",
"tooltip": "Mostrar códigos temporais ao lado das letras"
}
},
"name": "Letras sincronizadas",
"refetch-btn": {
"fetching": "A obter...",
"normal": "Tentar nova obtenção"
},
"warnings": {
"duration-mismatch": "⚠️ - A letra da música pode não estar corretamente sincronizada devido a um erro de duração.",
"inexact": "⚠️ - A letra desta canção pode não ser exata",
"instrumental": "⚠️ - Esta é uma música instrumental"
}
},
"taskbar-mediacontrol": {
"description": "Controlar reprodução a partir da barra de tarefas do Windows",
"name": "Controlo multimédia na barra de tarefas"
},
"touchbar": {
"description": "Adicionar widget TouchBar para utilizadores macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Tornar janela da aplicação transparente",
"menu": {
"opacity": {
"label": "Opacidade",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrílico",
"mica": "Mica",
"none": "Nada",
"tabbed": "Separadores"
}
}
},
"name": "Reprodutor transparente"
},
"tuna-obs": {
"description": "Integração com o plugin Tuna do OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Impede o aparecimento do reprodutor durante a reprodução",
"name": "Reprodutor discreto"
},
"video-toggle": {
"description": "Adiciona um botão para alternar entre o modo Vídeo/Música. Opcionalmente, também pode remover completamente o separador de vídeo",
"menu": {
"align": {
"label": "Alinhamento",
"submenu": {
"left": "Esquerda",
"middle": "Centro",
"right": "Direita"
}
},
"force-hide": "Remover separador de vídeo",
"mode": {
"label": "Modo",
"submenu": {
"custom": "Ativar modo personalizado",
"disabled": "Desativado",
"native": "Ativar modo nativo"
}
}
},
"name": "Alternância de vídeo",
"templates": {
"button-song": "Música",
"button-video": "Vídeo"
}
},
"visualizer": {
"description": "Adiciona um visualizador ao reprodutor",
"menu": {
"visualizer-type": "Tipo de visualizador"
},
"name": "Visualizador"
}
}
}
================================================
FILE: src/i18n/resources/qu.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Pluginta mana ruwayta atirqanchu {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin nisqa {{pluginName}}::{{contextName}} ejecutado en {{ms}}ms",
"initialize-failed": "Plugin qallariyta mana atirqanchu \"{{pluginName}}\"",
"load-all": "Llapanta cargaspa",
"load-failed": "Pluginta mana kargayta atirqanchu \"{{pluginName}}\"",
"loaded": "Plugin nisqa \"{{pluginName}}\" cargado",
"unload-failed": "Pluginta mana uraykachiyta atirqanchu \"{{pluginName}}\""
}
}
}
}
================================================
FILE: src/i18n/resources/ro.json
================================================
{
"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 să ajuți la traducere? Apasă aici"
}
},
"resume-on-start": "Continuă ultimul cântec ascultat când pornește aplicația",
"single-instance-lock": "Blocare cu o singură instanță",
"start-at-login": "Începe de la autentificare",
"starting-page": {
"label": "Pagina de pornire",
"unset": "Deselectat"
},
"tray": {
"label": "Tray",
"submenu": {
"disabled": "Dezactivat",
"enabled-and-hide-app": "Activează și ascunde fereastra aplicației",
"enabled-and-show-app": "Activează și arata fereastra aplicației",
"play-pause-on-click": "Start/Pauza la click"
}
},
"visual-tweaks": {
"label": "Modificări Vizuale",
"submenu": {
"custom-window-title": {
"label": "Titlul ferestrei personalizate",
"prompt": {
"label": "Introduceți titlul ferestrei personalizate: (lăsați gol pentru a dezactiva)",
"placeholder": "Exemplu: {{applicationName}}"
}
},
"like-buttons": {
"default": "Default",
"force-show": "Forțează randarea",
"hide": "Ascunde",
"label": "Butoane de like"
},
"remove-upgrade-button": "Elimina butonul de upgrade",
"theme": {
"dialog": {
"button": {
"cancel": "Anulează",
"remove": "Elimină"
},
"remove-theme": "Ești sigur că vrei să elimini tema personalizata?",
"remove-theme-message": "Acesta va elimina tema personalizata"
},
"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": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Reclamele au sunetul dezactivat si viteza de redare este x16",
"name": "Accelerare reclame"
},
"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": "Fluiditatea tranzitiei",
"submenu": {
"during": "In timpul {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Ecran Plin în utilizare"
}
},
"name": "Mod ambiental"
},
"amuse": {
"description": "Adauga suport {{applicationName}} pentru Amuse se redă acum widget de 6K Labs",
"name": "Amuse",
"response": {
"query": "Server-ul API-ului Amuse rulează. GET /query pentru a obține informații despre melodie."
}
},
"api-server": {
"description": "Adaugă un server API pentru a controla player-ul",
"dialog": {
"request": {
"buttons": {
"allow": "Permite",
"deny": "Respinge"
},
"message": "Permite {{ID}} {{origin}} să acceseze API-ul?",
"title": "Cerere autorizare API"
}
},
"menu": {
"auth-strategy": {
"label": "Strategie de autorizare",
"submenu": {
"auth-at-first": {
"label": "Autorizare la prima cerere"
},
"none": {
"label": "Fără autorizare"
}
}
},
"hostname": {
"label": "Nume host"
},
"port": {
"label": "Port"
}
},
"name": "Server API [Beta]",
"prompt": {
"hostname": {
"label": "Introduceți nume host (0.0.0.0 de ex.) pentru server-ul API:",
"title": "Nume host"
},
"port": {
"label": "Introduceți port-ul pentru server-ul API:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Aplică compresie pe audio (scade volumul părților cele mai zgomotoase și crește volumul părților mai puțin zgomotoase)",
"name": "Compresor audio"
},
"auth-proxy-adapter": {
"description": "Suport pentru utilizarea serviciilor proxy de autentificare",
"menu": {
"disable": "Dezactivează Adaptorul Proxy",
"enable": "Activează Adaptorul Proxy",
"hostname": {
"label": "Nume host"
},
"port": {
"label": "Port"
}
},
"name": "Adaptor Proxy de Autentificare",
"prompt": {
"hostname": {
"label": "Introduceți numele de gazdă pentru serverul proxy local (necesită repornire):",
"title": "Nume host proxy"
},
"port": {
"label": "Introduceți portul pentru serverul proxy local (necesită repornire):",
"title": "Port Proxy"
}
}
},
"blur-nav-bar": {
"description": "Face bara de navigare semi-transparentă și difuză",
"name": "Estompează Bara de Navigație"
},
"bypass-age-restrictions": {
"description": "Treci peste verificarea de vârstă a Music Player",
"name": "Ignoră restricțiile de vârstă"
},
"captions-selector": {
"description": "Selector de subtitrări pentru piesele audio de pe {{applicationName}}",
"menu": {
"autoload": "Selectează automat ultima subtitrare folosită",
"disable-captions": "Fără subtitrări în mod implicit"
},
"name": "Selector de subtitrări",
"prompt": {
"selector": {
"label": "Limba curentă a subtitrărilor: {{language}}",
"none": "Niciuna",
"title": "Alege limba subtitrărilor"
}
},
"templates": {
"title": "Deschide selectorul de subtitrări"
},
"toast": {
"caption-changed": "Subtitrare schimbata in {{language}}",
"caption-disabled": "Subtitrari dezactivate",
"no-captions": "Nu exista subtitrari disponibile pentru aceasta piesa"
}
},
"compact-sidebar": {
"description": "Păstrează bara laterală mereu în modul compact",
"name": "Bara Laterală Compactă"
},
"crossfade": {
"description": "Tranziționează între melodii",
"menu": {
"advanced": "Avansat"
},
"name": "Tranziție [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Durată tranziție de început (ms)",
"fade-out-duration": "Durată tranziției de sfârșit (ms)",
"fade-scaling": {
"label": "Scalare de estompare",
"linear": "Liniar",
"logarithmic": "Logaritmic"
},
"seconds-before-end": "Tranziție N secunde înainte de final"
},
"title": "Opțiuni de tranziție"
}
}
},
"custom-output-device": {
"description": "Configurați un dispozitiv de ieșire personalizat pentru melodii",
"menu": {
"device-selector": "Selectați dispozitivul"
},
"name": "Dispozitiv de ieșire personalizat",
"prompt": {
"device-selector": {
"label": "Alegeți dispozitivul media de ieșire care va fi utilizat",
"title": "Selectați dispozitivul de ieșire"
}
}
},
"disable-autoplay": {
"description": "Face cântecul să înceapă în modul \"pauză\"",
"menu": {
"apply-once": "Se aplică doar la pornirea aplicației"
},
"name": "Dezactivează redarea automată"
},
"discord": {
"backend": {
"already-connected": "S-a încercat conectarea cu o conexiune activă",
"connected": "Conectat la Discord",
"disconnected": "Deconectat de la Discord"
},
"description": "Arată-le prietenilor ce asculți cu Rich Presence",
"menu": {
"auto-reconnect": "Reconectare automată",
"clear-activity": "Șterge activitatea",
"clear-activity-after-timeout": "Șterge activitatea după timeout",
"connected": "Conectat",
"disconnected": "Deconectat",
"hide-duration-left": "Ascunde timpul rămas",
"hide-github-button": "Ascunde butonul cu link-ul GitHub",
"play-on-application": "Redă pe {{applicationName}}",
"set-inactivity-timeout": "Setează intervalul de inactivitate",
"set-status-display-type": {
"label": "Text stare",
"submenu": {
"artist": "Ascultând {artist}",
"title": "Ascultând {song title}",
"application": "Ascultând {{applicationName}}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Introduceți perioada de inactivitate dorită în secunde:",
"title": "Setează timpul de inactivitate"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Ah! Scuze, descărcarea a eșuat…",
"title": "Eroare la descărcare!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} melodii)",
"message": "Se descarca Playlist-ul {{playlistTitle}}",
"title": "Descărcarea a început"
}
},
"feedback": {
"conversion-progress": "Conversie: {{percent}}%",
"converting": "Se convertește…",
"done": "Descărcat: {{filePath}}",
"download-info": "Se descarcă {{artist}} -{{title}} [{{videoId}}",
"download-progress": "Se descarcă: {{percent}}%",
"downloading": "Se descarcă…",
"downloading-counter": "Se descarcă {{current}}/{{total}}…",
"downloading-playlist": "Se descarcă lista de redare \"{{playlistTitle}}\" - {{playlistSize}} piese ({{playlistId}})",
"error-while-downloading": "Eroare la descarcareă piesei \"{{author}} - {{title}}\":{{error}}",
"folder-already-exists": "Dosarul {{playlistFolder}} există deja",
"getting-playlist-info": "Se adună informațiile despre lista de redare…",
"loading": "Se incarcă…",
"playlist-has-only-one-song": "Lista de redare are doar un element, acesta va fi descărcat direct",
"playlist-id-not-found": "Niciun ID al listei de redare nu a fost gasit",
"playlist-is-empty": "Lista de redare este goală",
"playlist-is-mix-or-private": "Eroare la colectarea informațiilor despre lista de redare: asigurați-vă că nu este privat sau o listă de redare \"Mixed for you\"\n\n{{error}}",
"preparing-file": "Se pregătește fișierul…",
"saving": "Se salvează…",
"trying-to-get-playlist-id": "Se încearcă obținerea ID-ului listei de redare: {{playlistId}}",
"video-id-not-found": "Videoclipul nu a fost găsit",
"writing-id3": "Se scriu tag-urile ID3…"
}
},
"description": "Descarcă MP3 / sursa audio direct din interfață",
"menu": {
"choose-download-folder": "Alege folderul de descărcări",
"download-finish-settings": {
"label": "Descărcare la finalizare",
"prompt": {
"last-percent": "După x la sută",
"last-seconds": "Ultimele x secunde",
"title": "Configurează când să se descarce"
},
"submenu": {
"advanced": "Avansat",
"enabled": "Activat",
"mode": "Mod timp",
"percent": "Procentaj",
"seconds": "Secunde"
}
},
"download-playlist": "Descarcă lista de redare",
"presets": "Setări implicite",
"skip-existing": "Treci peste fișierele existente"
},
"name": "Descărcător",
"renderer": {
"can-not-update-progress": "Nu se poate actualiza progresul"
},
"templates": {
"button": "Descarcă"
}
},
"equalizer": {
"description": "Adauă un egalizator la player",
"menu": {
"presets": {
"label": "Setări implicite",
"list": {
"bass-booster": "Amplificator de bas"
}
}
},
"name": "Egalizator"
},
"exponential-volume": {
"description": "Face glisorul de volum exponențial pentru a fi mai ușor de selectat volume reduse.",
"name": "Volum exponențial"
},
"in-app-menu": {
"description": "Oferă barelor de meniu un aspect extravagant, întunecat sau de culoarea albumului",
"menu": {
"hide-dom-window-controls": "Ascunde controalele ferestrei DOM"
},
"name": "Meniul aplicației"
},
"lumiastream": {
"description": "Adaugă asistenta pentru Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Adaugă versuri pentru majoritatea cântecelor",
"menu": {
"romanized-lyrics": "Versuri romantizate"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Versuri preluate de pe Genius"
}
},
"music-together": {
"description": "Împărtășește lista de redare cu alții. Când gazda va pune o piesă, toți ceilalți vor auzi aceeași melodie",
"dialog": {
"enter-host": "Introdu ID-ul host-ului"
},
"internal": {
"save": "Salvează",
"track-source": "Sursa piesei",
"unknown-user": "Utilizator necunoscut"
},
"menu": {
"click-to-copy-id": "Copiază ID-ul host-ului",
"close": "Închide Music Together",
"connected-users": "Utilizatori conecțati",
"disconnect": "Deconectează Music Together",
"empty-user": "Niciun utilizator conectat",
"host": "Gazda Music Together",
"join": "Alătura-te Music Together",
"permission": {
"all": "Permite invitaților să controleze lista de redare si player-ul",
"host-only": "Doar gazda poate controla lista de redare și player-ul",
"playlist": "Permite invitaților controlul asupra listei de redare"
},
"set-permission": "Schimbă controlul permisiunilor",
"status": {
"disconnected": "Deconectat",
"guest": "Conectat ca invitat",
"host": "Conectat ca gazda"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Adăugarea piesei a eșuat",
"closed": "Music Together închis",
"disconnected": "Music Together deconectat",
"host-failed": "Nu s-a reușit găzduirea Music Together",
"id-copied": "ID-ul host-ului a fost copiat în clipboard",
"id-copy-failed": "Eroare la copierea ID-ului host-ului în clipboard",
"join-failed": "Nu s-a reușit alăturarea la Music Together",
"joined": "V-ați alăturat Music Together",
"permission-changed": "Permisiunile Music Together s-au schimbat la \"{{permission}}\"",
"remove-song-failed": "Eroare la îndepărtarea melodiei",
"user-connected": "{{name}} s-a alăturat la Music Together",
"user-disconnected": "{{name}} a părăsit Music Together"
}
},
"navigation": {
"description": "Săgețile pentru Următorul/Anteriorul integrate direct în interfață, ca în browser-ul tău preferat",
"name": "Navigație",
"templates": {
"back": {
"title": "Inapoi la pagina anterioara"
},
"forward": {
"title": "Urmatoarea pagina"
}
}
},
"no-google-login": {
"description": "Elimină butonul de autentificare Google și link-urile din interfață",
"name": "Nicio autentificare Google"
},
"notifications": {
"description": "Afișează o notificare când începe să cânte o piesă (notificările interactive sunt disponibile pe Windows)",
"menu": {
"interactive": "Notificări interactive",
"interactive-settings": {
"label": "Setări interactive",
"submenu": {
"hide-button-text": "Ascunde textul butoanelor",
"refresh-on-play-pause": "Reîmprospătează la Redă/Pauză",
"tray-controls": "Deschide/Închide la apăsarea iconiței pentru meniul Tray"
}
},
"priority": "Prioritatea notificărilor",
"toast-style": "Stilul notificărilor",
"unpause-notification": "Arată notificările la pauză"
},
"name": "Notificări"
},
"performance-improvement": {
"description": "Îmbunătățește performanța activând scripturile experimentale",
"name": "Îmbunătățirea performanței [Beta]"
},
"picture-in-picture": {
"description": "Permite să schimbi aplicația la modul picture-in-picture",
"menu": {
"always-on-top": "Mereu deasupra",
"hotkey": {
"label": "Scurtături pe tastatură",
"prompt": {
"keybind-options": {
"hotkey": "Scurtături din taste"
},
"label": "Alege tasta pentru picture-in-picture",
"title": "Scurtătura Picture-in-picture"
}
},
"save-window-position": "Salvează poziția ferestrei",
"save-window-size": "Salvează mărimea ferestrei",
"use-native-pip": "Folosește PiP-ul nativ pentru broswer"
},
"name": "Picture-in-picture",
"templates": {
"button": "Picture-in-picture"
}
},
"playback-speed": {
"description": "Ascultă rapid, ascultă lent! Adaugă un slider pentru viteza de redare a melodiei",
"name": "Viteza de redare",
"templates": {
"button": "Viteză"
}
},
"precise-volume": {
"description": "Controlează volumul precis folosind rotița mouse-ului/scurtăturii din tastatură, cu un HUD personalizat și incremente de volum personalizate",
"menu": {
"arrows-shortcuts": "Control cu tastele-săgeți locale",
"custom-volume-steps": "Setează incrementele de volum",
"global-shortcuts": "Scurtături de tastatură globale"
},
"name": "Volum precis",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Redu volumul audio",
"increase": "Crește volumul audio"
},
"label": "Alege combinațiile de taste globale pentru volumul audio:",
"title": "Combinații globale de taste pentru volum"
},
"volume-steps": {
"label": "Alege pașii de increment pentru volum audio",
"title": "Incremente de volum"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Calitate actuală: {{quality}}",
"message": "Alegeți calitatea video:",
"title": "Alegeți calitatea video"
}
}
},
"description": "Permite schimbarea calității video cu un buton prezent peste video",
"name": "Schimbător de calitate video",
"renderer": {
"quality-settings-button": {
"label": "Deschide setările de calitate"
}
}
},
"scrobbler": {
"description": "Adaugă asistenta pentru scrobbling (etc. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Autentificarea cu Last.fm a eșuat\nAscunde acest pop-up până la următoarea repornire.",
"title": "Autentificare Eșuată"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Setări pentru API-ul Last.fm"
},
"listenbrainz": {
"token": "Introdu token-ul de utilizator ListenBrainz"
},
"scrobble-alternative-artist": "Utilizați artiști alternativi",
"scrobble-alternative-title": "Folosește titluri alternative",
"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 tău de utilizator ListenBrainz:",
"title": "Token-ul ListenBrainz"
}
}
}
},
"shortcuts": {
"description": "Permite setari globale pentru scurtături pe tastatură pentru redare (redă/pauză/următorul/anteriorul) și pentru oprirea media OSD prin suprascriera tastelor media, pentru folosirea combinației Ctrl/CMD + F pentru a căuta, pentru pornirea asistenței Linux MPRIS pentru taste media și pentru scurtături personalizate pentru utilizatori avansați",
"menu": {
"override-media-keys": "Suprascrie tastele media",
"set-keybinds": "Setează scurtăturile globale pentru melodii"
},
"name": "Scurtături (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Următorul",
"play-pause": "Redă / Pauză",
"previous": "Anteriorul"
},
"label": "Alege combinația de taste globală pentru controlul melodiilor:",
"title": "Scurtături pe tastatură globale"
}
}
},
"skip-disliked-songs": {
"description": "Sari peste melodiile neplăcute",
"name": "Treci peste melodiile neplăcute"
},
"skip-silences": {
"description": "Treci automat peste secțiunile de liniște din melodii",
"name": "Treci peste liniște"
},
"sponsorblock": {
"description": "Treci automat peste părțile non-muzicale precum intro/outro sau părți din video-ul melodiei, când nu se aude melodia",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Furnizează versuri sincronizate melodiilor, folosind furnizori precum LRClib.",
"errors": {
"fetch": "⚠️ - A apărut o eroare în timpul încărcării versurilor. \nTe rog încearcă din nou mai târziu.",
"not-found": "⚠️ Nu au fost găsite versuri pentru această melodie."
},
"menu": {
"default-text-string": {
"label": "Caracter implicit între versuri",
"tooltip": "Alege caracterul implicit folosit pentru spațiul dintre versuri"
},
"line-effect": {
"label": "Efect de linie",
"submenu": {
"fancy": {
"label": "Elegant",
"tooltip": "Folosește efecte largi pe linia curentă"
},
"focus": {
"label": "Focalizare",
"tooltip": "Doar linia curentă este albă"
},
"offset": {
"label": "Deplasare",
"tooltip": "Deplasare la dreapta pentru linia curentă"
},
"scale": {
"label": "Mărime",
"tooltip": "Schimbă dimensiunea liniei curente"
}
},
"tooltip": "Alege efectul aplicat liniei curente"
},
"precise-timing": {
"label": "Sincronizează versurile perfect",
"tooltip": "Calculează afisarea următoarei linii până la milisecundă (poate afecta performanța)"
},
"preferred-provider": {
"label": "Furnizor preferat",
"none": {
"label": "Niciunul",
"tooltip": "Niciun furnizor preferat"
},
"tooltip": "Alegeți furnizorul preferat"
},
"romanization": {
"label": "Transcrie versurile în alfabet latin",
"tooltip": "Dacă versurile sunt într-o altă limbă, încearcă să afișezi o versiune în alfabet latin."
},
"show-lyrics-even-if-inexact": {
"label": "Afișează versurile chiar dacă sunt inexacte",
"tooltip": "Dacă melodia nu este găsită, plugin-ul încearcă din nou cu o căutare diferită.\nRezultatul acestei încercări poate să nu fie exact."
},
"show-time-codes": {
"label": "Afișează codurile de timp",
"tooltip": "Afișează codurile de timp lângă versuri"
}
},
"name": "Versuri Sincronizate",
"refetch-btn": {
"fetching": "Încărcare...",
"normal": "Reîncărcare versuri"
},
"warnings": {
"duration-mismatch": "⚠️ - Versurile pot fi desincronizate din cauza unei nepotriviri de durație.",
"inexact": "⚠️ - Versurile pentru această melodie pot fi inexacte",
"instrumental": "⚠️ - Această melodie este instrumentală"
}
},
"taskbar-mediacontrol": {
"description": "Controlează redarea din Bara de Activități Windows",
"name": "Control media in Bara de Activitate"
},
"touchbar": {
"description": "Adaugă un widget TouchBar pentru utilizatorii macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Face fereastra aplicației transparentă",
"menu": {
"opacity": {
"label": "Opacitate",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tip",
"submenu": {
"acrylic": "Acrilic",
"mica": "Efect mica",
"none": "Niciunul",
"tabbed": "Cu file"
}
}
},
"name": "Player transparent"
},
"tuna-obs": {
"description": "Integrare cu plugin-ul OBS Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Împiedică playerul să apară atunci când redați o melodie",
"name": "Player discret"
},
"video-toggle": {
"description": "Adaugă un buton ce schimbă între modurile Video/Melodie. De asemenea se poate elimina opțional toată fila video",
"menu": {
"align": {
"label": "Aliniere",
"submenu": {
"left": "Stânga",
"middle": "Mijloc",
"right": "Dreapta"
}
},
"force-hide": "Forțează eliminarea filei video",
"mode": {
"label": "Mod",
"submenu": {
"custom": "Comutatoare personalizate",
"disabled": "Dezactivat",
"native": "Comutatoare native"
}
}
},
"name": "Comutator video",
"templates": {
"button-song": "Melodie",
"button-video": "Video"
}
},
"visualizer": {
"description": "Adaugă un vizualizator la player",
"menu": {
"visualizer-type": "Tip de vizualizator"
},
"name": "Vizualizator"
}
}
}
================================================
FILE: src/i18n/resources/ru.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Ошибка при выполнении плагина {{pluginName}}::{{contextName}}",
"executed-at-ms": "Плагин {{pluginName}}::{{contextName}} загружен за {{ms}}мс",
"initialize-failed": "Ошибка инициализации плагина \"{{pluginName}}\"",
"load-all": "Загружаем все плагины",
"load-failed": "Ошибка загрузки плагина \"{{pluginName}}\"",
"loaded": "Плагин \"{{pluginName}}\" загружен",
"unload-failed": "Ошибка при выгрузке плагина \"{{pluginName}}\"",
"unloaded": "Плагин \"{{pluginName}}\" выгружен"
}
}
},
"language": {
"code": "ru",
"local-name": "Русский",
"name": "Russian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Загрузка завершена. DevTools открыты"
},
"i18n": {
"loaded": "i18n загружен"
},
"second-instance": {
"receive-command": "Получена команда по протоколу: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS файл \"{{cssFile}}\" не существует, игнорирую"
},
"unresponsive": {
"details": "Приложение не отвечает!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Очищаю кеш приложения"
},
"window": {
"tried-to-render-offscreen": "Окно попробовало открыться за пределами экрана, размер окна={{windowSize}}, разрешение экрана={{displaySize}}, местоположение={{position}}"
}
},
"dialog": {
"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": "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": {
"custom-window-title": {
"label": "Собственное название окна",
"prompt": {
"label": "Введите собственное название окна: (оставьте пустым, чтобы отключить)",
"placeholder": "Например: {{applicationName}}"
}
},
"like-buttons": {
"default": "Default",
"force-show": "Всегда показывать",
"hide": "Скрывать",
"label": "Кнопка лайка"
},
"remove-upgrade-button": "Убрать кнопку Premium",
"theme": {
"dialog": {
"button": {
"cancel": "Отмена",
"remove": "Убрать"
},
"remove-theme": "Вы уверены, что хотите убрать пользовательскую тему?",
"remove-theme-message": "Это уберёт пользовательскую тему"
},
"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": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Если воспроизводится реклама, аудио заглушается и скорость воспроизведения устанавливается на 16х",
"name": "Ускоренная перемотка"
},
"adblocker": {
"description": "Блокируйте всю рекламу и трекинг сразу после установки",
"menu": {
"blocker": "Блокировщик"
},
"name": "Блокировщик рекламы"
},
"album-actions": {
"description": "Добавляет кнопки Undislike, Dislike и Unlike, чтобы применять их на все композиции в плейлисте или альбоме",
"name": "Действия с альбомом"
},
"album-color-theme": {
"description": "Применяет динамическую тему и визуальные эффекты на основе цветовой палитры альбома",
"menu": {
"color-mix-ratio": {
"label": "Соотношение цветов",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Включить тематическое оформление полосы прокрутки"
},
"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}} s"
}
},
"use-fullscreen": {
"label": "Использовать полноэкранный режим"
}
},
"name": "Режим Ambient"
},
"amuse": {
"description": "Добавляет {{applicationName}} поддержку виджета Amuse „сейчас играет“ от 6K Labs",
"name": "Amuse",
"response": {
"query": "Сервер Amuse API запущен. GET /query чтобы получить информацию о треке."
}
},
"api-server": {
"description": "Добавляет API сервер для контроля за плеером",
"dialog": {
"request": {
"buttons": {
"allow": "Разрешить",
"deny": "Отказать"
},
"message": "Разрешить {{ID}} ({{origin}}) доступ к API?",
"title": "Запрос на авторизацию в API"
}
},
"menu": {
"auth-strategy": {
"label": "Способ авторизации",
"submenu": {
"auth-at-first": {
"label": "Авторизация при первом запросе"
},
"none": {
"label": "Без авторизации"
}
}
},
"hostname": {
"label": "Имя хоста"
},
"https": {
"label": "HTTPS и сертификаты",
"submenu": {
"cert": {
"dialogTitle": "Выберите файл сертификата HTTPS",
"label": "Файл сертификата (.crt/.pem)"
},
"enable-https": {
"label": "Включить HTTPS"
},
"key": {
"dialogTitle": "Выберите файл приватного ключа HTTPS",
"label": "Файл приватного ключа (.key/.pem)"
}
}
},
"port": {
"label": "Порт"
}
},
"name": "API Сервер [БЕТА]",
"prompt": {
"hostname": {
"label": "Введите имя хоста (на подобии 0.0.0.0) для API сервера:",
"title": "Имя хоста"
},
"port": {
"label": "Введите порт для API сервера:",
"title": "Порт"
}
}
},
"audio-compressor": {
"description": "Применяет компрессию к аудио (уменьшает громкость самых громких частей сигнала и повышает громкость самых тихих частей)",
"name": "Нормализация аудио"
},
"auth-proxy-adapter": {
"description": "Поддержка использования сервисов аутентификационного прокси",
"menu": {
"disable": "Отключить адаптер прокси",
"enable": "Включить адаптер прокси",
"hostname": {
"label": "Имя хоста"
},
"port": {
"label": "Порт"
}
},
"name": "Адаптер аутентификационного прокси",
"prompt": {
"hostname": {
"label": "Введите имя хоста для локального прокси-сервера (требуется перезапуск):",
"title": "Имя хоста прокси"
},
"port": {
"label": "Введите порт для локального прокси-сервера (требуется перезапуск):",
"title": "Порт прокси"
}
}
},
"blur-nav-bar": {
"description": "Делает панель навигации прозрачной и размытой",
"name": "Размытие панели навигации"
},
"bypass-age-restrictions": {
"description": "Обход проверки возраста на Music Player",
"name": "Обход возрастных ограничений"
},
"captions-selector": {
"description": "Выбор субтитров для аудиотреков в {{applicationName}}",
"menu": {
"autoload": "Автоматически выбирать последние использованные субтитры",
"disable-captions": "Без субтитров по умолчанию"
},
"name": "Выбор субтитров",
"prompt": {
"selector": {
"label": "Текущий язык субтитров:{{language}}",
"none": "Нет",
"title": "Выберите язык субтитров"
}
},
"templates": {
"title": "Открыть выбор субтитров"
},
"toast": {
"caption-changed": "Субтитры изменены на {{language}}",
"caption-disabled": "Субтитры отключены",
"no-captions": "Нет доступных субтитров для этой песни"
}
},
"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": "Настройки плавного перехода"
}
}
},
"custom-output-device": {
"description": "Настройка устройства вывода медиа для песен",
"menu": {
"device-selector": "Выберите устройство"
},
"name": "Пользовательское устройство вывода",
"prompt": {
"device-selector": {
"label": "Выберите устройство вывода медиа, которое будет использоваться",
"title": "Выберите устройство вывода"
}
}
},
"disable-autoplay": {
"description": "Запускает песню сразу на паузе",
"menu": {
"apply-once": "Применимо только при запуске"
},
"name": "Отключить Автопроигрыш"
},
"discord": {
"backend": {
"already-connected": "Попытка подключения при уже существующем соединении",
"connected": "Подключено к Discord",
"disconnected": "Отключено от Discord"
},
"description": "Покажите своим друзьям что вы слушаете с помощью Rich Presence",
"menu": {
"auto-reconnect": "Авто переподключение",
"clear-activity": "Очистить активность",
"clear-activity-after-timeout": "Очищать активность после таймаута",
"connected": "Подключено",
"disconnected": "Отключено",
"hide-duration-left": "Скрыть сколько осталось времени",
"hide-github-button": "Скрыть ссылку на GitHub",
"play-on-application": "Воспроизвести на {{applicationName}}",
"set-inactivity-timeout": "Поставить таймер неактивности",
"set-status-display-type": {
"label": "Текст статуса",
"submenu": {
"application": "Слушает {{applicationName}}",
"artist": "Слушает {исполнитель}",
"title": "Слушает {название трека}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Введите таймер неактивности в секундах:",
"title": "Задать таймер неактивности"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Ой-ой! Прошу простить, загрузка дала сбой…",
"title": "Error in download!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} песен)",
"message": "Скачиваем плейлист {{playlistTitle}}",
"title": "Загрузка началась"
}
},
"feedback": {
"conversion-progress": "Конвертация: {{percent}}%",
"converting": "Конвертация…",
"done": "Выполнено: {{filePath}}",
"download-info": "Скачиваем {{artist}}-{{title}} {{videoId}}",
"download-progress": "Download: {{percent}}%",
"downloading": "Скачиваем…",
"downloading-counter": "Скачиваем {{current}}/{{total}}…",
"downloading-playlist": "Скачиваем плейлист \"{{playlistTitle}}\" - {{playlistSize}} песен {{playlistId}}",
"error-while-downloading": "Ошибка при скачивании \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Папка {{playlistFolder}} уже существует",
"getting-playlist-info": "Получаем информацию о плейлисте…",
"loading": "Загрузка…",
"playlist-has-only-one-song": "В плейлисте только одна песня, скачиваем напрямую",
"playlist-id-not-found": "Плейлист не найден",
"playlist-is-empty": "Плейлист пуст",
"playlist-is-mix-or-private": "Ошибка при получении данных о плейлисте: убедитесь что он не приватный или \"Только для вас\" плейлист\n\n{{error}}",
"preparing-file": "Подготавливаем файл…",
"saving": "Сохраняем…",
"trying-to-get-playlist-id": "Пытаемся получить ID плейлиста: {{playlistId}}",
"video-id-not-found": "Видео не найдено",
"writing-id3": "Пишем ID3 теги…"
}
},
"description": "Скачивать MP3 / исходное аудио напрямую из интерфейса",
"menu": {
"choose-download-folder": "Выберите папку для загрузок",
"download-finish-settings": {
"label": "Скачать по завершению",
"prompt": {
"last-percent": "После х процентов",
"last-seconds": "Осталось x сек",
"title": "Условия скачивания"
},
"submenu": {
"advanced": "Расширенные настройки",
"enabled": "Включено",
"mode": "Режим по времени",
"percent": "Процент",
"seconds": "Секунды"
}
},
"download-playlist": "Скачать плейлист",
"presets": "Пресеты",
"skip-existing": "Пропускать уже существующие файлы"
},
"name": "Загрузчик",
"renderer": {
"can-not-update-progress": "Невозможно обновить прогресс"
},
"templates": {
"button": "Скачать"
}
},
"equalizer": {
"description": "Добавляет эквалайзер к плееру",
"menu": {
"presets": {
"label": "Предустановки",
"list": {
"bass-booster": "Усилитель баса"
}
}
},
"name": "Эквалайзер"
},
"exponential-volume": {
"description": "Делает слайдер громкости расширенным чтобы было легче понижать громкость.",
"name": "Расширенная громкость"
},
"in-app-menu": {
"description": "Придает меню цветовую схему альбома",
"menu": {
"hide-dom-window-controls": "Скрыть элементы управления окном DOM"
},
"name": "Меню в приложении"
},
"lumiastream": {
"description": "Добавляет поддержку Lumia Stream",
"name": "Lumia Stream [бета]"
},
"lyrics-genius": {
"description": "Добавляет поддержку текстов для большинства композиций",
"menu": {
"romanized-lyrics": "Романизированный текст (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": "Навигация",
"templates": {
"back": {
"title": "Предыдущая страница"
},
"forward": {
"title": "Следующая страница"
}
}
},
"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": "Уведомления"
},
"performance-improvement": {
"description": "Улучшает производительность с помощью включения экспериментальных скриптов",
"name": "Улучшение производительности [Бета]"
},
"picture-in-picture": {
"description": "Позволяет переключить приложение в режим «картинка в картинке»",
"menu": {
"always-on-top": "Всегда наверху",
"hotkey": {
"label": "Горячая клавиша",
"prompt": {
"keybind-options": {
"hotkey": "Горячая клавиша"
},
"label": "Выберите горячую клавишу для переключения режима изображения в изображении",
"title": "Горячая клавиша для картинки в картинке"
}
},
"save-window-position": "Сохранить положение окна",
"save-window-size": "Сохранить размер окна",
"use-native-pip": "Использовать нативный PiP браузера"
},
"name": "Картинка в картинке",
"templates": {
"button": "Картинка в картинке"
}
},
"playback-speed": {
"description": "Слушайте быстро, слушайте медленно! Добавляет ползунок, регулирующий скорость композиции",
"name": "Скорость воспроизведения",
"templates": {
"button": "Скорость"
}
},
"precise-volume": {
"description": "Точное управление громкостью с помощью колеса мышки/горячих клавиш, пользовательский HUD и настраиваемые ступени громкости",
"menu": {
"arrows-shortcuts": "Локальное управление со стрелками",
"custom-volume-steps": "Настройка пользовательских шагов громкости",
"global-shortcuts": "Глобальные горячие клавиши"
},
"name": "Точная громкость",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Уменьшение громкости",
"increase": "Увеличение громкости"
},
"label": "Выберите глобальные привязки клавиш к громкости:",
"title": "Глобальные привязки клавиш громкости"
},
"volume-steps": {
"label": "Выберите шаги увеличения/уменьшения громкости",
"title": "Ступени громкости"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Текущее качество: {{quality}}",
"message": "Выберите качество видео:",
"title": "Выберите качество видео"
}
}
},
"description": "Позволяет изменять качество видео с помощью кнопки на оверлее видео",
"name": "Изменение качества видео",
"renderer": {
"quality-settings-button": {
"label": "Открыть настройки качества плеера"
}
}
},
"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-alternative-artist": "Использовать альтернативных исполнителей",
"scrobble-alternative-title": "Использовать альтернативные названия",
"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"
},
"synced-lyrics": {
"description": "Предоставляет синхронизированные слова для песен из таких источников, как LRClib.",
"errors": {
"fetch": "⚠️\tПроизошла ошибка во время получения слов.\n\tПовторите попытку позже.",
"not-found": "⚠️ Для этой песни не найдено слов."
},
"menu": {
"default-text-string": {
"label": "Стандартный символ между словами",
"tooltip": "Выберите стандартный символ для заполнения пространства между словами"
},
"line-effect": {
"label": "Эффект строки",
"submenu": {
"fancy": {
"label": "Красивый",
"tooltip": "Использовать большие эффекты строки, как в приложении"
},
"focus": {
"label": "Фокусировка",
"tooltip": "Делает только текущую строку белой"
},
"offset": {
"label": "Сдвиг",
"tooltip": "Сдвигает текущую строку вправо"
},
"scale": {
"label": "Увеличение",
"tooltip": "Увеличивает текущую строку"
}
},
"tooltip": "Выберите эффект применяемый к текущей строке"
},
"precise-timing": {
"label": "Идеально синхронизировать слова",
"tooltip": "До миллисекунды рассчитывает отображение следующей строки(может оказать небольшое влияние на производительность)"
},
"preferred-provider": {
"label": "Предпочитаемый источник",
"none": {
"label": "Никакой",
"tooltip": "Нет предпочитаемого источника"
},
"tooltip": "Выберите источник по умолчанию"
},
"romanization": {
"label": "Романизировать слова",
"tooltip": "Если слова на другом языке, пытаться отображать версию на латинице."
},
"show-lyrics-even-if-inexact": {
"label": "Показывать слова, даже если неточные",
"tooltip": "Если песня не найдена, плагин попытается снова с другим поисковым запросом.\nСо второй попытки результат может быть неточным."
},
"show-time-codes": {
"label": "Показывать временные метки",
"tooltip": "Показывает временные метки рядом со словами"
}
},
"name": "Синхронизированные тексты песен",
"refetch-btn": {
"fetching": "Сбор данных...",
"normal": "Обновить слова"
},
"warnings": {
"duration-mismatch": "⚠️ - Слова могут быть неточно синхронизированы из-за несовпадения длины трека.",
"inexact": "⚠️ - Слова для этой песни могут быть неточными",
"instrumental": "⚠️ - Это инструментальная музыка"
}
},
"taskbar-mediacontrol": {
"description": "Управляйте воспроизведением с панели задач Windows",
"name": "Управление мультимедиа на панели задач"
},
"touchbar": {
"description": "Добавляет виджет тачбара для пользователей macOS",
"name": "Тачбар"
},
"transparent-player": {
"description": "Делает окно приложения прозрачным",
"menu": {
"opacity": {
"label": "Непрозрачность",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Тип",
"submenu": {
"acrylic": "Акриловый",
"mica": "Слюдяной",
"none": "Отключено",
"tabbed": "Страничный"
}
}
},
"name": "Прозрачный плеер"
},
"tuna-obs": {
"description": "Интеграция с плагином Tuna от OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Предотвращает выскакивание плеера при воспроизведении",
"name": "Ненавязчивый плеер"
},
"video-toggle": {
"description": "Добавляет кнопку для переключения между режимами видео и песни. Также можно удалить всю вкладку с видео",
"menu": {
"align": {
"label": "Выравнивание",
"submenu": {
"left": "Слева",
"middle": "По центру",
"right": "Справа"
}
},
"force-hide": "Принудительное скрыть видео",
"mode": {
"label": "Режим",
"submenu": {
"custom": "Кастомный переключатель",
"disabled": "Отключен",
"native": "Стандартный переключатель"
}
}
},
"name": "Переключатель видео",
"templates": {
"button-song": "Песня",
"button-video": "Видео"
}
},
"visualizer": {
"description": "Заменяет обложку визуализатором музыки",
"menu": {
"visualizer-type": "Вид визуализации"
},
"name": "Визуализатор"
}
}
}
================================================
FILE: src/i18n/resources/sah.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Плагины толорор кыаҕа суох {{pluginName}}::{{contextName}}",
"executed-at-ms": "Плагин {{pluginName}}::{{contextName}} толоруллубут {{ms}}мс",
"initialize-failed": "\"{{pluginName}}\" плагины инициализациялааһын кыаллыбата",
"load-all": "Плагиннары загрузкалыбыт"
}
}
}
}
================================================
FILE: src/i18n/resources/si.json
================================================
{
"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": "si",
"local-name": "සිංහල",
"name": "Sinhala"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "පූරණය අවසන්. DevTools විවෘත වී ඇත"
},
"i18n": {
"loaded": "i18n පූරණය කරන ලදී"
},
"second-instance": {
"receive-command": "ප්රෝටෝකාල් හරහා විධානය ලැබුණි: \"{{command}}\""
},
"theme": {
"css-file-not-found": "css ගොනුව \"{{cssFile}}\" නොපවතී, නොසලකා හරී"
},
"unresponsive": {
"details": "ප්රතිචාර නොදක්වයි, දෝෂයක්\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "යෙදුමේ දත්ත සංචිතය හිස් කරමින්"
},
"window": {
"tried-to-render-offscreen": "වින්ඩෝව තිරයෙන් පිටත පෙන්වීමට උත්සාහ කළේය, වින්ඩෝවේ ප්රමාණය={{windowSize}}, තිරයෙ ප්රමාණය={{displaySize}}, පිහිටීම={{position}}"
}
},
"dialog": {
"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": "යොමුව පිටපත් කරගන්න",
"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": "Config change එකක් වෙද්දී app එක auto restart වෙන්න",
"set-proxy": {
"label": "Proxy එක set කරන්න",
"prompt": {
"label": "Proxy ලිපිනය ඇතුළත් කරන්න: (නවතන්න නම් හිස්ව තබන්න)",
"placeholder": "උදාහරණය: SOCKS5://127.0.0.1:9999",
"title": "Proxy එක set කරන්න"
}
},
"toggle-dev-tools": "DevTools On/Off කරන්න"
}
},
"always-on-top": "Always on top කරන්න",
"auto-update": "Auto Update කරන්න",
"hide-menu": {
"dialog": {
"message": "Menu එක ඊළඟ වතාවට ආරම්භක වෙලාවේ හෝ hide වෙයි. දැක්වීමට [Alt] හෝ in-app menu එකේ නම් [`] key එක ඔබන්න",
"title": "Menu hide කිරීම enable කරලා"
},
"label": "Menu hide කරන්න"
},
"language": {
"dialog": {
"message": "Restart කිරීමෙන් පසු භාෂාව වෙනස් වේ",
"title": "Language change වුනා"
},
"label": "භාෂාව",
"submenu": {
"to-help-translate": "පරිවර්තනය කිරීමට උදව් කරන්න ඕනේද? මෙතන ක්ලික් කරන්න"
}
},
"resume-on-start": "App එක start වෙද්දි අවසන් ගීතය නැවත ඇරඹෙන්න",
"single-instance-lock": "Single Instance Lock සක්රීය කරන්න",
"start-at-login": "ලොගින් වුන විට start කරන්න",
"starting-page": {
"label": "ආරම්භක පිටුව",
"unset": "අවලංගු කරන්න"
},
"tray": {
"label": "ට්රේ එක",
"submenu": {
"disabled": "Disable කරන ලදී",
"enabled-and-hide-app": "ට්රේ එක enable කරලා, app window එක hide කරන්න",
"enabled-and-show-app": "Enable කරලා app එක පෙන්වන්න",
"play-pause-on-click": "Click කළ විට play/ pause වෙනවා"
}
},
"visual-tweaks": {
"label": "Visual tweaks කරන්න",
"submenu": {
"like-buttons": {
"default": "මූලික",
"force-show": "Force show කරන්න",
"hide": "සඟවන්න",
"label": "Like බොත්තම්"
},
"remove-upgrade-button": "Upgrade බොත්තම ඉවත් කරන්න",
"theme": {
"dialog": {
"button": {
"cancel": "අවලංගු කරන්න",
"remove": "ඉවත් කරන්න"
},
"remove-theme": "ඔබට විශ්වාසද ඔබගේ custom theme එක ඉවත් කිරීමට අවශ්ය බව?",
"remove-theme-message": "මෙය custom theme එක ඉවත් කරයි"
},
"label": "Theme",
"submenu": {
"import-css-file": "Custom CSS ගොනුව import කරන්න",
"no-theme": "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": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"adblocker": {
"description": "සියලුම දැන්වීම් අවහිර කර කොටුවෙන් පිටත ලුහුබැඳීම"
}
}
}
================================================
FILE: src/i18n/resources/sk.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Nepodarilo sa spustiť plugin {{pluginName}}:{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} spustený za {{ms}}ms",
"initialize-failed": "Zlyhalo spustenie \"{{pluginName}}\" pluginu",
"load-all": "Načítavanie všetkých pluginov",
"load-failed": "Načítanie \"{{pluginName}}\" pluginu zlyhalo",
"loaded": "Plugin \"{{pluginName}}\" sa načítal",
"unload-failed": "Zlyhalo vypnutie \"{{pluginName}}\" pluginu",
"unloaded": "Plugin \"{{pluginName}}\" bol vypnutý"
}
}
},
"language": {
"code": "sk",
"local-name": "Slovenčina",
"name": "Slovak"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Načítanie dokončené. DevTools otvorené"
},
"i18n": {
"loaded": "i18n načítaný"
},
"second-instance": {
"receive-command": "Prijatý príkaz skrze protokol \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS súbor \"{{cssFile}}\" neexistuje, ignorované"
},
"unresponsive": {
"details": "Aplikácia nereaguje\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Čistenie medzipamäte aplikácie"
},
"window": {
"tried-to-render-offscreen": "Okno sa pokúšalo vykresliť na pozadí, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu je skryté, stlačte \"Alt\" na zobrazenie (alebo \"Escape\" keď používate Aplikačné menu)",
"message": "Skryté menu je zapnuté",
"title": "Skryté menu zapnuté"
},
"need-to-restart": {
"buttons": {
"later": "Neskôr",
"restart-now": "Reštartovať teraz"
},
"detail": "\"{{pluginName}}\" plugin potrebuje reštart aby sa spustil",
"message": "\"{{pluginName}}\" sa potrebuje reštartovať",
"title": "Reštart potrebný"
},
"unresponsive": {
"buttons": {
"quit": "Ukončiť",
"relaunch": "Spustiť znova",
"wait": "Počkajte"
},
"detail": "Ospravedlňujeme sa za nepríjemnosti! prosím vyberte, čo robiť:",
"message": "Aplikácia nereaguje",
"title": "Okno nereaguje"
},
"update-available": {
"buttons": {
"disable": "Vypnúť aktualizácie",
"download": "Stiahnuť",
"ok": "OK"
},
"detail": "Nová verzia je k dispozícii a je možné ju stiahnuť na {{downloadLink}}",
"message": "Nová verzia je k dispozícii",
"title": "Aktualizácia je k dispozícii"
}
},
"menu": {
"about": "O nás",
"navigation": {
"label": "Navigovať",
"submenu": {
"copy-current-url": "Skopírovať aktuálnu URL",
"go-back": "Späť",
"go-forward": "Dopredu",
"quit": "Skončiť",
"restart": "Reštartovať Aplikáciu"
}
},
"options": {
"label": "Možnosti",
"submenu": {
"advanced-options": {
"label": "Rozšírené možnosti",
"submenu": {
"auto-reset-app-cache": "Vymazať vyrovnávaciu pamäť, pri štarte",
"disable-hardware-acceleration": "Vypnúť hardvérovú akceleráciu",
"edit-config-json": "Upraviť config.json",
"override-user-agent": "Prepísať User-Agent",
"restart-on-config-changes": "Reštartovať pri zmene configu",
"set-proxy": {
"label": "Nastavtiť proxy",
"prompt": {
"label": "Zadajte Proxy adresu: (nechajte prázdne pre vypnutie)",
"placeholder": "Príklad: SOCKS5://127.0.0.1:9999",
"title": "Nastavtiť proxy"
}
},
"toggle-dev-tools": "Prepnúť DevTools"
}
},
"always-on-top": "Vždy na vrchu",
"auto-update": "Automatická aktualizácia",
"hide-menu": {
"dialog": {
"message": "Menu bude skryté pri ďalšom štarte, použite [Alt] na ukázanie (alebo [`] ak používate aplikačné menu)",
"title": "Skryté menu Zapnuté"
},
"label": "Skryť menu"
},
"language": {
"dialog": {
"message": "Jazyk sa zmení po reštarte",
"title": "Jazyk sa zmenil"
},
"label": "Jazyk",
"submenu": {
"to-help-translate": "Chcete pomôcť preložiť? Kliknite sem"
}
},
"resume-on-start": "Pokračovať od poslednej piesne, pri spustení aplikácie",
"single-instance-lock": "Zámok jedného spustenia",
"start-at-login": "Spustiť pri štarte",
"starting-page": {
"label": "Spúšťacia stránka",
"unset": "Nevybrať"
},
"tray": {
"label": "Panel oznámení",
"submenu": {
"disabled": "Vypnutý",
"enabled-and-hide-app": "Povolené a skry aplikáciu",
"enabled-and-show-app": "Povolené a ukáž aplikáciu",
"play-pause-on-click": "Prehrať/Pozastaviť na stlačenie"
}
},
"visual-tweaks": {
"label": "Vizuálne úpravy",
"submenu": {
"custom-window-title": {
"label": "Vlastný názov okna",
"prompt": {
"label": "Napíšte vlastný názov okna: (nechajte prázdne pre vypnutie)",
"placeholder": "Napríklad: {{applicationName}}"
}
},
"like-buttons": {
"default": "Základný",
"force-show": "Vynútiť zobrazenie",
"hide": "Skryť",
"label": "Like tlačítko",
"swap": "Otočiť poradie tlačidiel „Páči sa mi“"
},
"remove-upgrade-button": "Odstrániť tlačidlo povýšiť",
"theme": {
"dialog": {
"button": {
"cancel": "Zrušiť",
"remove": "Odstrániť"
},
"remove-theme": "Ste si istý že chcete odstrániť tento vlastný štýl?",
"remove-theme-message": "Toto odstráni vlastný štýl"
},
"label": "Štýl",
"submenu": {
"import-css-file": "Nahrať vlasný CSS súbor",
"no-theme": "Žiadny štýl"
}
}
}
}
}
},
"plugins": {
"enabled": "Zapnuté",
"label": "Rozšírenia",
"new": "NOVÝ"
},
"view": {
"label": "Náhľad",
"submenu": {
"force-reload": "Nasilu obnoviť",
"reload": "Obnoviť",
"reset-zoom": "Ozajstná veľkosť",
"toggle-fullscreen": "Prepnúť režim Celej Obrazovky",
"zoom-in": "Priblížiť",
"zoom-out": "Oddialiť"
}
}
},
"tray": {
"next": "Ďalšie",
"play-pause": "Prehrať/Pozastaviť",
"previous": "Predcházdajúce",
"quit": "Skončiť",
"restart": "Reštartovať aplikáciu",
"show": "Zobraziť okno",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Ak sa spustí reklama, stlmí sa zvuk a nastaví sa rýchlosť prehrávania na 16x",
"name": "Zrýchľovač reklám"
},
"adblocker": {
"description": "Blokovať všetky reklamy a sledovania hneď od začiatku",
"menu": {
"blocker": "Blokovač"
},
"name": "Blokovač reklám"
},
"album-actions": {
"description": "Pridáva Undislike, Dislike, Like, a Unlike tlačítka k aplikovaniu tohto ku všetkým pesničkám v zozname pesničiek alebo albume",
"name": "Možnosti albumu"
},
"album-color-theme": {
"description": "Aplikuje dynamický motív a vizuálne efekty na základe palety farieb albumu",
"menu": {
"color-mix-ratio": {
"label": "Pomer miešania farieb",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Povoliť farebnú tému aj v seekbare"
},
"name": "Farebná téma albumu"
},
"ambient-mode": {
"description": "Aplikuje svetelné efekty pomocou vrhania jemných farieb z videa, do vášho pozadia obrazovky",
"menu": {
"blur-amount": {
"label": "Hodnota rozmazania",
"submenu": {
"pixels": "{{blurAmount}} pixelov"
}
},
"buffer": {
"label": "Vyrovnávacia pamäť",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Nepriehľadnosť",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvalita",
"submenu": {
"pixels": "{{quality}} pixelov"
}
},
"size": {
"label": "Veľkosť",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Plynulý prechod",
"submenu": {
"during": "Počas {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Používanie režimu celej obrazovky"
}
},
"name": "Ambientný mód"
},
"amuse": {
"description": "Pridáva {{applicationName}} podporu pre Amuse práve prehráva widget od 6K Labs",
"name": "Amuse",
"response": {
"query": "Amuse API server beží. GET /query to get song info."
}
},
"api-server": {
"description": "Pridáva API server na ovládanie prehrávača",
"dialog": {
"request": {
"buttons": {
"allow": "Povoliť",
"deny": "Zakázať"
},
"message": "Povoliť {{ID}} ({{origin}}) prístup cez API?",
"title": "Žiadosť o autorizáciu API"
}
},
"menu": {
"auth-strategy": {
"label": "Stratégia autorizácie",
"submenu": {
"auth-at-first": {
"label": "Overiť pri prvom dotaze"
},
"none": {
"label": "Žiadna autorizácia"
}
}
},
"hostname": {
"label": "Hostname"
},
"https": {
"label": "HTTPS a Certifikáty",
"submenu": {
"cert": {
"dialogTitle": "Vybrať súbor certifikátu HTTPS",
"label": "Súbor certifikátov (.crt/.pem)"
},
"enable-https": {
"label": "Zapnúť HTTPS"
},
"key": {
"dialogTitle": "Vybrať súbor privátneho kľúča HTTPS",
"label": "Súbor privátneho kľúča (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Zadajte názov hostname(napr. 0.0.0.0) pre server API:",
"title": "Hostname"
},
"port": {
"label": "Zadajte port pre server API:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Aplikovať kompresiu k audiu (znižuje hlasitosť najhlasnejších častí signálu a zvyšuje hlasitosť najjemnejších častí)",
"name": "Audio kompresor"
},
"auth-proxy-adapter": {
"description": "Podpora používania autentifikačných proxy služieb",
"menu": {
"disable": "Vypnúť Proxy Adaptér",
"enable": "Zapnúť Proxy Adaptér",
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "Adaptér autentifikačného proxy servera",
"prompt": {
"hostname": {
"label": "Zadajte hostname pre lokálny proxy server (vyžaduje reštart):",
"title": "Proxy Hostname"
},
"port": {
"label": "Zadajte port pre lokálny proxy server (vyžaduje reštart):",
"title": "Proxy Port"
}
}
},
"blur-nav-bar": {
"description": "Navigačnú lištu nastaví ako priehľadnú a rozmazanú",
"name": "Rozmazať navigačnú lištu"
},
"bypass-age-restrictions": {
"description": "Obísť overenie veku v hudobnom prehrávači",
"name": "Obísť vekové obmedzenia"
},
"captions-selector": {
"description": "Selektor titulkov pre zvukové stopy {{applicationName}}",
"menu": {
"autoload": "Automatický výber naposledy použitých titulkov",
"disable-captions": "Žiadne titulky"
},
"name": "Selektor titulkov",
"prompt": {
"selector": {
"label": "Aktuálny jazyk titulkov: {{language}}",
"none": "Žiadne",
"title": "Výber jazyka titulkov"
}
},
"templates": {
"title": "Otvoriť selektor titulkov"
},
"toast": {
"caption-changed": "Titulky zmenené na {{language}}",
"caption-disabled": "Deaktivované titulky",
"no-captions": "Pre túto skladbu nie sú dostupné žiadne titulky"
}
},
"clock": {
"description": "Pridať hodiny do navigačnej lišty",
"menu": {
"format": {
"24-hour-format": "24-hodinový formát",
"display-seconds": "Zobraz sekundy",
"label": "Formát"
}
},
"name": "Hodiny"
},
"compact-sidebar": {
"description": "Vždy nastaviť bočný panel do kompaktného režimu",
"name": "Kompaktný bočný panel"
},
"crossfade": {
"description": "Prelínanie medzi skladbami",
"menu": {
"advanced": "Pokročilé"
},
"name": "Prelínanie [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Trvanie nábehu (ms)",
"fade-out-duration": "Trvanie doznievania (ms)",
"fade-scaling": {
"label": "Škálovanie prelínania",
"linear": "Lineárny",
"logarithmic": "Logaritmický"
},
"seconds-before-end": "Prechod N sekúnd pred koncom"
},
"title": "Možnosti prelínania"
}
}
},
"custom-output-device": {
"description": "Nastavte vlastné výstupné médium pre skladby",
"menu": {
"device-selector": "Výber Zariadenia"
},
"name": "Vlastné výstupné zariadenie",
"prompt": {
"device-selector": {
"label": "Vyberte výstupné médium, ktoré chcete použiť",
"title": "Výber výstupného zariadenia"
}
}
},
"disable-autoplay": {
"description": "Spustí skladbu v režime „pozastavené“",
"menu": {
"apply-once": "Platí len pri spustení"
},
"name": "Zakázať automatické prehrávanie"
},
"discord": {
"backend": {
"already-connected": "Pokus o pripojenie s aktívnym pripojením",
"connected": "Pripojené k Discordu",
"disconnected": "Odpojené od Discordu"
},
"description": "Ukážte svojim priateľom, čo počúvate, s funkciou Rich Presence",
"menu": {
"auto-reconnect": "Automatické opätovné pripojenie",
"clear-activity": "Zmazať aktivitu",
"clear-activity-after-timeout": "Zmazať aktivitu po uplynutí časového limitu",
"connected": "Pripojené",
"disconnected": "Odpojené",
"hide-duration-left": "Skryť zostávajúcu dobu trvania",
"hide-github-button": "Skryť tlačidlo s odkazom na GitHub",
"play-on-application": "Spustiť na {{applicationName}}",
"set-inactivity-timeout": "Nastaviť časový limit nečinnosti",
"set-status-display-type": {
"label": "Text stavu",
"submenu": {
"application": "Počúvať {{applicationName}}",
"artist": "Aktuálne počúva {artist}",
"title": "Aktuálne počúva {song title}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Zadajte časový limit nečinnosti v sekundách:",
"title": "Nastaviť časový limit nečinnosti"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Ach! Ospravedlňujeme sa, sťahovanie sa nepodarilo…",
"title": "Chyba pri sťahovaní!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} skladieb)",
"message": "Sťahovanie zoznamu {{playlistTitle}}",
"title": "Sťahovanie začalo"
}
},
"feedback": {
"conversion-progress": "Konverzia: {{percent}}%",
"converting": "Konvertujem…",
"done": "Hotovo: {{filePath}}",
"download-info": "Sťahujem {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Sťahovanie: {{percent}}%",
"downloading": "Sťahujem…",
"downloading-counter": "Sťahujem {{current}}/{{total}}…",
"downloading-playlist": "Sťahujem zoznam „{{playlistTitle}}“ - {{playlistSize}} skladieb ({{playlistId}})",
"error-while-downloading": "Chyba pri sťahovaní „{{author}} - {{title}}“: {{error}}",
"folder-already-exists": "Priečinok {{playlistFolder}} už existuje",
"getting-playlist-info": "Získavanie informácií o zozname…",
"loading": "Načítavam…",
"playlist-has-only-one-song": "Zoznam obsahuje len jednu položku, ktorá sa stiahne priamo",
"playlist-id-not-found": "Nebolo nájdené žiadne ID zoznamu",
"playlist-is-empty": "Zoznam je prázdny",
"playlist-is-mix-or-private": "Chyba pri získavaní informácií o zozname: uistite sa, že nejde o súkromný zoznam alebo zoznam „Pre Vás“\n\n{{error}}",
"preparing-file": "Pripravujem súbor…",
"saving": "Ukladám…",
"trying-to-get-playlist-id": "Snažím sa získať ID zoznam: {{playlistId}}",
"video-id-not-found": "Video nebolo nájdené",
"writing-id3": "Zapisujem ID3 značky…"
}
},
"description": "Sťahuje MP3 / zdrojový zvuk priamo z rozhrania",
"menu": {
"choose-download-folder": "Vyberte priečinok na sťahovanie",
"download-finish-settings": {
"label": "Stiahnuť po skončení",
"prompt": {
"last-percent": "Po x percentách",
"last-seconds": "Posledných x sekúnd",
"title": "Nastavte, kedy sa má stiahnuť"
},
"submenu": {
"advanced": "Rozšírené",
"enabled": "Povolené",
"mode": "Časový režim",
"percent": "Percent",
"seconds": "Sekundy"
}
},
"download-playlist": "Stiahnuť zoznam",
"presets": "Predvoľby",
"skip-existing": "Preskočiť už existujúce súbory"
},
"name": "Sťahovač",
"renderer": {
"can-not-update-progress": "Nie je možné aktualizovať priebeh"
},
"templates": {
"button": "Stiahnuť"
}
},
"equalizer": {
"description": "Pridáva ekvalizér do prehrávača",
"menu": {
"presets": {
"label": "Predvoľby",
"list": {
"bass-booster": "Zosilňovač basov"
}
}
},
"name": "Ekvalizér"
},
"exponential-volume": {
"description": "Zmení slider hlasitosti na exponenciálny, aby bolo ľahšie vybrať nižšiu hlasitosť.",
"name": "Exponenciálna hlasitosť"
},
"in-app-menu": {
"description": "Dodáva lištám ponúk elegantný, tmavý alebo farebný vzhľad albumu",
"menu": {
"hide-dom-window-controls": "Skryť ovládacie prvky DOM"
},
"name": "Aplikačné menu"
},
"lumiastream": {
"description": "Pridáva Lumia Stream podporu",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Pridáva podporu textov pre väčšinu piesní",
"menu": {
"romanized-lyrics": "Romanizované texty piesní"
},
"name": "Texty Genius",
"renderer": {
"fetched-lyrics": "Načítané texty piesní pre Genius"
}
},
"music-together": {
"description": "Zdieľajte zoznam s ostatnými. Keď hostiteľ prehrá skladbu, všetci ostatní budú počuť tú istú skladbu",
"dialog": {
"enter-host": "Zadajte Host ID"
},
"internal": {
"save": "Uložiť",
"track-source": "Sledovať zdroj",
"unknown-user": "Neznámy užívateľ"
},
"menu": {
"click-to-copy-id": "Kopírovať Host ID",
"close": "Zatvoriť Music Together",
"connected-users": "Pripojení užívatelia",
"disconnect": "Odpojiť Music Together",
"empty-user": "Žiadni pripojení užívatelia",
"host": "Hudba spolu Hostiteľ",
"join": "Pripojiť sa k Music Together",
"permission": {
"all": "Umožniť hosťom ovládať zoznam a prehrávač",
"host-only": "Len hostiteľ môže ovládať zoznam skladieb a prehrávač",
"playlist": "Umožniť hosťom ovládať zoznam"
},
"set-permission": "Povolenie na kontrolu zmien",
"status": {
"disconnected": "Odpojené",
"guest": "Pripojený ako hosť",
"host": "Pripojený ako hostiteľ"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Nepodarilo sa pridať skladbu",
"closed": "Music Together ukončené",
"disconnected": "Music Together odpojené",
"host-failed": "Nepodarilo sa hosťovať Music Together",
"id-copied": "Host ID bolo skopírované do schránky",
"id-copy-failed": "Nepodarilo sa skopírovať Host ID do schránky",
"join-failed": "Nepodarilo sa pripojiť k Music Together",
"joined": "Pripojený k Music Together",
"permission-changed": "Povolenie Music Together zmenené na „{{permission}}“",
"remove-song-failed": "Nepodarilo sa odstrániť skladbu",
"user-connected": "{{name}} sa pripojil k Music Together",
"user-disconnected": "{{name}} opustil Music Together"
}
},
"navigation": {
"description": "Šípky pre navigáciu vpred/vzad sú priamo integrované do rozhrania, rovnako ako vo vašom obľúbenom prehliadači",
"name": "Navigácia",
"templates": {
"back": {
"title": "Prejsť na predchádzajúcu stránku"
},
"forward": {
"title": "Prejsť na nasledujúcu stránku"
}
}
},
"no-google-login": {
"description": "Odstrániť tlačidlá a odkazy na prihlásenie do služby Google z rozhrania",
"name": "Žiadne prihlásenie do Google"
},
"notifications": {
"description": "Zobraziť upozornenie pri spustení prehrávania skladby (interaktívne upozornenia sú k dispozícii v systéme Windows)",
"menu": {
"interactive": "Interaktívne oznámenia",
"interactive-settings": {
"label": "Interaktívne nastavenia",
"submenu": {
"hide-button-text": "Skryť text tlačidla",
"refresh-on-play-pause": "Obnoviť pri prehrávaní/pozastavení",
"tray-controls": "Otvoriť/Zatvoriť kliknutím na lištu"
}
},
"priority": "Priorita oznámenia",
"toast-style": "Štýl toastu",
"unpause-notification": "Zobraziť upozornenie pri obnovení"
},
"name": "Upozornenia"
},
"performance-improvement": {
"description": "Zlepšite výkon povolením experimentálnych skriptov",
"name": "Zlepšenie výkonu [Beta]"
},
"picture-in-picture": {
"description": "Umožňuje prepnúť aplikáciu do režimu obraz v obraze",
"menu": {
"always-on-top": "Vždy na vrchu",
"hotkey": {
"label": "Klávesová skratka",
"prompt": {
"keybind-options": {
"hotkey": "Klávesová skratka"
},
"label": "Vyberte klávesovú skratku pre prepínanie obraz v obraze",
"title": "Obraz v obraze klávesová skratka"
}
},
"save-window-position": "Uložiť pozíciu okna",
"save-window-size": "Uložiť veľkosť okna",
"use-native-pip": "Použiť natívne PiP prehliadača"
},
"name": "Obraz v obraze",
"templates": {
"button": "Obraz v obraze"
}
},
"playback-speed": {
"description": "Počúvajte rýchlo, počúvajte pomaly! Pridáva slider, ktorý ovláda rýchlosť skladby",
"name": "Rýchlosť prehrávania",
"templates": {
"button": "Rýchlosť"
}
},
"precise-volume": {
"description": "Presne ovládajte hlasitosť pomocou kolieska myši/klávesových skratiek, s vlastným HUD a prispôsobiteľnými krokmi hlasitosti",
"menu": {
"arrows-shortcuts": "Ovládanie pomocou šípok",
"custom-volume-steps": "Nastavenie vlastných krokov hlasitosti",
"global-shortcuts": "Globálne klávesové skratky"
},
"name": "Presná hlasitosť",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Znížiť hlasitosť",
"increase": "Zvýšiť hlasitosť"
},
"label": "Vybrať globálne klávesové skratky:",
"title": "Globálne klávesové skratky pre hlasitosť"
},
"volume-steps": {
"label": "Vybrať kroky zvýšenia/zníženia hlasitosti",
"title": "Kroky hlasitosti"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Aktuálna kvalita: {{quality}}",
"message": "Výber kvality videa:",
"title": "Výber kvality videa"
}
}
},
"description": "Umožňuje zmeniť kvalitu videa pomocou tlačidla v prekrytí videa",
"name": "Zmena kvality videa",
"renderer": {
"quality-settings-button": {
"label": "Otvoriť nastavenia kvality prehrávača"
}
}
},
"scrobbler": {
"description": "Pridať podporu scrobbling (napr. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Nepodarilo sa autentifikovať s Last.fm\nSkryť vyskakovacie okno do ďalšieho reštartu.",
"title": "Autentifikácia zlyhala"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API Nastavenia"
},
"listenbrainz": {
"token": "Vložiť ListenBrainz používateľský token"
},
"scrobble-alternative-artist": "Použiť alternatívnych umelcov",
"scrobble-alternative-title": "Použiť alternatívne názvy",
"scrobble-other-media": "Scrobble iných médií"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last.fm API kľúč",
"api-secret": "Last.fm API tajomstvo"
},
"listenbrainz": {
"token": {
"label": "Vlož svoj ListenBrainz používateľský token:",
"title": "ListenBrainz token"
}
}
}
},
"shortcuts": {
"description": "Povoľuje nastaviť globálne klávesové skratky pre prehrávanie (Prehrať/Pozastaviť/Ďalšie/Predošlé) a vypínať media OSD prepisovaním media kľúčov, zapne Ctrl/CMD + F pre vyhľadávanie, zapne Linux MPRIS podporu pre media kľúče a vlastné klávesové skratky pre pokročilých používateľov",
"menu": {
"override-media-keys": "Prepísať Media Kľúče",
"set-keybinds": "Globálne ovládanie skladieb"
},
"name": "Skratky (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Ďalšia",
"play-pause": "Prehrať / Pauza",
"previous": "Predošlá"
},
"label": "Zvoliť globálne klávesové skratky na ovládanie skladieb:",
"title": "Globálne klávesové skratky"
}
}
},
"skip-disliked-songs": {
"description": "Preskakuje skladby označené Nepáči sa",
"name": "Preskakovať skladby označené Nepáči sa"
},
"skip-silences": {
"description": "Automaticky preskakovať tiché časti v hudbe",
"name": "Preskakuj tiché časti"
},
"sponsorblock": {
"description": "Automaticky preskakuje nehudbné časti ako intro/outro, alebo tie časti videoklipov v ktorých nehrá hudba",
"name": "Sponzorský blok"
},
"synced-lyrics": {
"description": "Poskytuje synchronizované texty k skladbám, pričom používa poskytovateľov ako LRClib.",
"errors": {
"fetch": "⚠️\tPri získavaní textu sa vyskytla chyba.\n Skúste znova neskôr.",
"not-found": "⚠️Pre túto skladbu nebol nájdený žiadny text."
},
"menu": {
"default-text-string": {
"label": "Predvolený charakter medzi textami",
"tooltip": "Vyberte predvolený charakter na použitie pre medzeru medzi textami"
},
"line-effect": {
"label": "Čiarový efekt",
"submenu": {
"fancy": {
"label": "Luxusné"
},
"focus": {
"label": "Zameranie"
},
"offset": {
"label": "Offset"
},
"scale": {
"label": "Mierka",
"tooltip": "Zväčšiť konkrétny riadok"
}
}
},
"precise-timing": {
"label": "Dokonale synchronizovať text piesne"
},
"preferred-provider": {
"label": "Preferovaný Poskytovateľ",
"none": {
"label": "Žiadne",
"tooltip": "Žiadny preferovaný poskytovateľ"
},
"tooltip": "Vybrať predvoleného poskytovateľa"
}
}
},
"touchbar": {
"name": "Dotykový Panel"
},
"transparent-player": {
"description": "Nastaví okno aplikácie ako priehľadné",
"menu": {
"opacity": {
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"submenu": {
"none": "Žiadne/y"
}
}
}
},
"tuna-obs": {
"description": "Integrácia s OBS pluginom Tuna",
"name": "Tuna OBS"
},
"video-toggle": {
"menu": {
"align": {
"label": "Zarovnanie",
"submenu": {
"left": "Vľavo",
"middle": "Stred",
"right": "Vpravo"
}
},
"mode": {
"label": "Mód",
"submenu": {
"disabled": "Vypnuté",
"native": "Natívny prepínač"
}
}
},
"name": "Prepínač Videa",
"templates": {
"button-song": "Skladba",
"button-video": "Video"
}
},
"visualizer": {
"description": "Pridá do prehrávača vizualizér",
"menu": {
"visualizer-type": "Typ Vizualizéra"
},
"name": "Vizualizér"
}
}
}
================================================
FILE: src/i18n/resources/sl.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Napaka pri inicilizaciji dodatka {{pluginName}}::{{contextName}}",
"executed-at-ms": "Dodatek {{pluginName}}::{{contextName}} izvršen pri {{ms}}ms",
"initialize-failed": "Napaka pri inicilizaciji dodatka \"{{pluginName}}\"",
"load-all": "Nalaganje dodatkov",
"load-failed": "Napaka pri nalaganju dodatka \"{{pluginName}}\"",
"loaded": "Dodatek \"{{pluginName}}\" naložen",
"unload-failed": "Napaka pri raztvorbi dodatka \"{{pluginName}}\"",
"unloaded": "Dodatek \"{{pluginName}}\" raztvorjen"
}
}
},
"language": {
"code": "sl",
"local-name": "Slovenščina",
"name": "Slovenian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Nalaganje končano. DevTools odprt"
},
"i18n": {
"loaded": "i18n naložen"
},
"second-instance": {
"receive-command": "Prejel ukaz preko protokola: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS datoteka \"{{cssFile}}\" ne obstaja, ignoriram"
},
"unresponsive": {
"details": "Neodzivna napaka!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Čiščenje predpomnilnika"
},
"window": {
"tried-to-render-offscreen": "Okno se je poskusilo prikazati izven ekrana, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Meni je skrit, pritisni 'Alt' za odpiranje (ali 'Escape' če uporabljaš In-App Meni)",
"message": "Skriti meni je prižgan",
"title": "Skrij meni vklopljen"
},
"need-to-restart": {
"buttons": {
"later": "Kasneje",
"restart-now": "Ponovno zaženi zdaj"
},
"detail": "\"{{pluginName}}\" dodatek potrebuje ponovni zagon",
"message": "\"{{pluginName}}\" je potrebno ponovno zagnati",
"title": "Potreben je ponovni zagon"
},
"unresponsive": {
"buttons": {
"quit": "Zapri",
"relaunch": "Ponovno zaženi",
"wait": "Počakaj"
},
"detail": "Opravičujemo se za nevšečnost! Prosim odločite se kaj narediti:",
"message": "Aplikacija se ne odziva",
"title": "Okno se ne odziva"
},
"update-available": {
"buttons": {
"disable": "Izklopi posodobitve",
"download": "Prenesi",
"ok": "OK"
},
"detail": "Nova verzija je na voljo, lahko jo naložiš na {{downloadLink}}",
"message": "Nova verzija je na voljo",
"title": "Posodobitev je na voljo"
}
},
"menu": {
"about": "O aplikaciji",
"navigation": {
"label": "Navigacija",
"submenu": {
"copy-current-url": "Kopiraj trenutni URL",
"go-back": "Nazaj",
"go-forward": "Naprej",
"quit": "Izhod",
"restart": "Ponovni zagon"
}
},
"options": {
"label": "Nastavitve",
"submenu": {
"advanced-options": {
"label": "Dodatne nastavitve",
"submenu": {
"auto-reset-app-cache": "Resetiraj predpomnilnik aplikacije ob zagonu",
"disable-hardware-acceleration": "Izklopi strojno pospeševanje",
"edit-config-json": "Spremeni config.json",
"override-user-agent": "Prepiši User-Agent",
"restart-on-config-changes": "Ponovni zagon ko se spremeni config",
"set-proxy": {
"label": "Nastavi proxy",
"prompt": {
"label": "Napiši Proxy naslov: (pusti prazno, da izklopiš)",
"placeholder": "Primer: SOCKS5://127.0.0.1:9999",
"title": "Nastavi Proxy"
}
},
"toggle-dev-tools": "Vklopi DevTools"
}
},
"always-on-top": "Vedno na vrhu",
"auto-update": "Avtomatsko posodobi",
"hide-menu": {
"dialog": {
"message": "Meni se bo skrit pri naslednjem zagonu, uporabi [Alt] da se prikaže (ali [`] v meniju aplikacije)",
"title": "Skrij meni vklopljen"
},
"label": "Skrij meni"
},
"language": {
"dialog": {
"message": "Jezik bo spremenjen po ponovnem zagonu",
"title": "Jezik je bil spremenjen"
},
"label": "Jezik",
"submenu": {
"to-help-translate": "Želiš pomagati pri prevajanju? Klikni tukaj"
}
},
"resume-on-start": "Predvajaj zadnjo pesem, ko se aplikacija zažene",
"single-instance-lock": "Zaklep ene instance",
"start-at-login": "Zaženi pri zagonu",
"starting-page": {
"label": "Začetna stran",
"unset": "Ni nastavljeno"
},
"tray": {
"label": "Pladenj",
"submenu": {
"disabled": "Izklopljeno",
"enabled-and-hide-app": "Vklopljeno in skrij",
"enabled-and-show-app": "Vklopljeno in pokaži",
"play-pause-on-click": "Predvajaj/Pavza z klikom"
}
},
"visual-tweaks": {
"label": "Vizualni popravki",
"submenu": {
"like-buttons": {
"default": "Privzeto",
"force-show": "Prisilno pokaži",
"hide": "Skrij",
"label": "Gumbi za všečkanje"
},
"remove-upgrade-button": "Odstrani gumb za nadgradnjo",
"theme": {
"dialog": {
"button": {
"cancel": "Prekliči",
"remove": "Odstrani"
},
"remove-theme": "Ali želite odstraniti temo po meri?",
"remove-theme-message": "Tema po meri bo odstranjena"
},
"label": "Tema",
"submenu": {
"import-css-file": "Vstavi poljubni CSS",
"no-theme": "Brez teme"
}
}
}
}
}
},
"plugins": {
"enabled": "Vključeno",
"label": "Dodatki",
"new": "NOVO"
},
"view": {
"label": "Pogled",
"submenu": {
"force-reload": "Prisilno ponovno naloži",
"reload": "Ponovno naloži",
"reset-zoom": "Prava velikost",
"toggle-fullscreen": "Prikaži cel zaslon",
"zoom-in": "Povečaj",
"zoom-out": "Pomanjšaj"
}
}
},
"tray": {
"next": "Naprej",
"play-pause": "Predvajaj/Pavza",
"previous": "Prejšni",
"quit": "Izhod",
"restart": "Ponovni zagon",
"show": "Pokaži okno",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Če se predvaja oglas se zvok utišja. Prav tako se hitrost predvajanja nastavi na 16 krat",
"name": "Pospeševanje oglasov"
},
"adblocker": {
"description": "Izklopi vse oglase in sledenje",
"menu": {
"blocker": "Blokator"
},
"name": "Blokator reklam"
},
"album-actions": {
"description": "Doda Undislike, Dislike, Like, in Unlike gumbe vsem glasbam v seznamu predvajanja ali albumu",
"name": "Nastavitve albuma"
},
"album-color-theme": {
"description": "Doda dinamično temo in vizualne efekte glede na barve albuma",
"menu": {
"color-mix-ratio": {
"label": "Razmerje barv",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Barvna tema Albuma"
},
"ambient-mode": {
"description": "Doda barvni učinek iz video posnetka na ozadje",
"menu": {
"blur-amount": {
"label": "Stopnja zameglitve",
"submenu": {
"pixels": "{{blurAmount}} pikslov"
}
},
"buffer": {
"label": "Medpomnilnik",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Prozornost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvaliteta",
"submenu": {
"pixels": "{{quality}} pikslov"
}
},
"size": {
"label": "Velikost",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Gladkost prehoda",
"submenu": {
"during": "Med {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Uporablja celoten zaslon"
}
},
"name": "Ambienten način"
},
"api-server": {
"description": "Doda API strežnik za nadzor predvajalnika",
"dialog": {
"request": {
"buttons": {
"allow": "Dovoli",
"deny": "Zavrni"
},
"message": "Dovolite {{ID}} ({{origin}}) da dostopa do API-ja?",
"title": "Prošnja za avtomatizacijo API-ja"
}
},
"menu": {
"auth-strategy": {
"label": "Strategija avtorizacije",
"submenu": {
"auth-at-first": {
"label": "Avtorizacija ob prvem zahtevku"
},
"none": {
"label": "Ni avtorizacije"
}
}
},
"hostname": {
"label": "Hostname"
},
"port": {
"label": "Port"
}
},
"name": "API strežnik [Beta]",
"prompt": {
"hostname": {
"label": "Vnesite hostname (npr. 0.0.0.0) za API strežnik:",
"title": "Hostname"
},
"port": {
"label": "Vnesite port za API strežnik:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Doda kompresijo zvoka (izenači ravni zvoka, zniža glasnost najglasnejših delov in zviša najtišje)",
"name": "Kompresija zvoka"
},
"blur-nav-bar": {
"description": "Naredi navigacijsko vrstico prozorno in zamegljeno",
"name": "Zameglji navigacijsko vrstico"
},
"bypass-age-restrictions": {
"description": "Preskoči YouTubo-vo preverjanje starosti",
"name": "Preskoči starostno omejitev"
},
"captions-selector": {
"description": "Izberi podnapise za {{applicationName}} zvočne posnetke",
"menu": {
"autoload": "Avtomatsko uporabi zadnje izbrane podnapise",
"disable-captions": "Avtomatsko brez podnapisov"
},
"name": "Izberi podnapise",
"prompt": {
"selector": {
"label": "Trenutni jezik podnapisov: {{language}}",
"none": "Brez",
"title": "Izberi jezik podnapisov"
}
},
"templates": {
"title": "Odpri izbiro podnapisov"
}
},
"compact-sidebar": {
"description": "Vedno izberi kompakten način stranske vrstice",
"name": "Kompaktna stranska vrstica"
},
"crossfade": {
"description": "Bledenje med pesmimi",
"menu": {
"advanced": "Dodatno"
},
"name": "Bledenje med pesmimi [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Čas zbledenja (v pesem) (ms)",
"fade-out-duration": "Čas zbledenja (izven pesemi) (ms)",
"fade-scaling": {
"label": "Zbledi skaliranje",
"linear": "Linearno",
"logarithmic": "Logaritmično"
},
"seconds-before-end": "Bledenje (crossfade) N sekund pred koncem"
},
"title": "Možnosti zbledenja"
}
}
},
"disable-autoplay": {
"description": "Začne pesem v zaustavljenem načinu",
"menu": {
"apply-once": "Uporabi samo ob zagonu"
},
"name": "Onemogoči samodejno predvajanje"
},
"discord": {
"backend": {
"already-connected": "Poizkus povezave z aktivno povezavo",
"connected": "Povezan na Discord",
"disconnected": "Povezava z Discord-om prekinjena"
},
"description": "Pokaži svojim prijateljem kaj poslušaš z bogato prisotnostjo (Rich Presence)",
"menu": {
"auto-reconnect": "Samodejna unovična povezava",
"clear-activity": "Počisti dejavnost",
"clear-activity-after-timeout": "Počisti dejavnost po časovni omejitvi",
"connected": "Povezan",
"disconnected": "Prekinjena povezava",
"hide-duration-left": "Skrij preostali čas",
"hide-github-button": "Skrij povezavo do GitHub-a",
"play-on-application": "Predvajaj v {{applicationName}}",
"set-inactivity-timeout": "Nastavite časovno omejitev neaktivnosti"
},
"name": "Discord bogata prisotnost (Rich Presence)",
"prompt": {
"set-inactivity-timeout": {
"label": "Vnesite časovno omejitev neaktivnosti v sekundah:",
"title": "Nastavite časovno omejitev nedejavnosti"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Uff! Se opravičujemo, prenos neuspešen…",
"title": "Napaka v prenosu!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} pesmi)",
"message": "Prenašanje seznama {{playlistTitle}}",
"title": "Prenos se je začel"
}
},
"feedback": {
"conversion-progress": "Konverzija: {{percent}}%",
"converting": "Pretvarjanje…",
"done": "Končano: {{filePath}}",
"download-info": "Prenašanje {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Prenos: {{percent}}%",
"downloading": "Prenašanje…",
"downloading-counter": "Prenašanje {{current}}/{{total}}…",
"downloading-playlist": "Prenašanje seznama \"{{playlistTitle}}\" - {{playlistSize}} pesmi ({{playlistId}})",
"error-while-downloading": "Napaka pri prenosu \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Ta mapa {{playlistFolder}} že obstaja",
"getting-playlist-info": "Pridobivam informacije o seznamu…",
"loading": "Nalaganje…",
"playlist-has-only-one-song": "Ta seznam ima samo eno pesem, uporabljam direkten prenos",
"playlist-id-not-found": "ID seznama ni najden",
"playlist-is-empty": "Seznam je prazen",
"playlist-is-mix-or-private": "Napaka v pridobivanju informacij o seznamu: poskrbite da seznam ni zaseben ali \"Mixed for you\" seznam\n\n{{error}}",
"preparing-file": "Pripravljanje datoteke…",
"saving": "Shranjujem…",
"trying-to-get-playlist-id": "Poizkušam pridobiti ID seznama: {{playlistId}}",
"video-id-not-found": "Videoposnetek ni najden",
"writing-id3": "Zapisujem ID3 oznake…"
}
},
"description": "Prenese MP3 / izviren zvok direktno iz vmesnika",
"menu": {
"choose-download-folder": "Izberite mapo s prenosi",
"download-finish-settings": {
"label": "Prenesi ob koncu",
"prompt": {
"last-percent": "Po x odstotkov",
"last-seconds": "Zadnjih x sekund",
"title": "Nastavite čas prenosa"
},
"submenu": {
"advanced": "Napredno",
"enabled": "Omogočen",
"mode": "Časovni način",
"percent": "Odstotek",
"seconds": "Sekunde"
}
},
"download-playlist": "Prenesi seznam",
"presets": "Prednastavitve",
"skip-existing": "Preskoči obstoječe datoteke"
},
"name": "Prenaševalec",
"renderer": {
"can-not-update-progress": "Nemorem osvežiti napredka"
},
"templates": {
"button": "Prenos"
}
},
"exponential-volume": {
"description": "Drsnik za glasnost naredi eksponenten, da bo lažje izbrati nižje glasnosti.",
"name": "Eksponentna glasnost"
},
"in-app-menu": {
"description": "Menijem doda eleganten videz v temnih barvah ali barvah albuma",
"menu": {
"hide-dom-window-controls": "Skrije DOM gumbe za okno"
},
"name": "Meni v aplikaciji"
},
"lumiastream": {
"description": "Doda podporo za Lumia pretočno predvajanje",
"name": "Lumina pretočno predavanje [Beta]"
},
"lyrics-genius": {
"description": "Doda podporo besedil za večino pesmi",
"menu": {
"romanized-lyrics": "Romanizerana besedila"
},
"name": "Besedila Genius",
"renderer": {
"fetched-lyrics": "Prestregel besedila za Genius"
}
},
"music-together": {
"description": "Delite seznam predvajanja z drugimi. Ko gostitelj predvaja skladbo, bodo vsi ostali slišali isto skladbo",
"dialog": {
"enter-host": "Vnesite Host ID"
},
"internal": {
"save": "Shrani",
"track-source": "Vir pesmi",
"unknown-user": "Neznan uporabnik"
},
"menu": {
"click-to-copy-id": "Kopiraj Host ID",
"close": "Zapri Glasba Skupaj",
"connected-users": "Povezani uporabniki",
"disconnect": "Prekini povezavo z Pesmi Skupaj",
"empty-user": "Ni povezanih uporabnikov",
"host": "Gkasba Skupaj gostitelj",
"join": "Pridruži se Glasba Skupaj",
"permission": {
"all": "Dovoli da gosti nadzorujejo seznam predvajanja in predvajalnik",
"host-only": "Samo gostitelj lahko spreminja seznam predvajanja in predvajalnik",
"playlist": "Dovoli da gostje nadzorujejo predvajalnik"
},
"set-permission": "Spremeni dovoljenje nadzora",
"status": {
"disconnected": "Odklopljen",
"guest": "Povezan kot Gost",
"host": "Povezan kot Gostitelj"
}
},
"name": "Gkasba Skupaj [Beta]",
"toast": {
"add-song-failed": "Skladba ni bila dodana",
"closed": "Glasba Skupaj zaprto",
"disconnected": "Gkasba Skupaj odklopljena",
"host-failed": "Gkasba Skupaj nisem mogel gostiti",
"id-copied": "Host ID je kopiran v odložišče",
"id-copy-failed": "Host ID ni bilo mogoče kopirati"
}
}
}
}
================================================
FILE: src/i18n/resources/sq.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Dështoi në ekzekutimin e plugin-it {{pluginName}}::{{contextName}}",
"executed-at-ms": "Shtojca {{pluginName}}::{{contextName}} u ekzekutua në {{ms}}ms",
"initialize-failed": "Dështoi ekzekutimi i plugin-it \"{{pluginName}}\"",
"load-all": "Duke ngarkuar të gjithe plugins",
"load-failed": "Dështoi në ngarkimin e plugin-it \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" u ngarkua",
"unload-failed": "Shkarkimi i plugin-it \"{{pluginName}}\" dështoi",
"unloaded": "Plugin \"{{pluginName}}\" u shkarkua"
}
}
},
"language": {
"name": "Emri i gjuhës në anglisht. p.sh. japonisht, koreanisht, anglisht, rusisht"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Ngarkimi përfundoi. DevTools u hap."
},
"i18n": {
"loaded": "i18n i ngarkuar"
},
"second-instance": {
"receive-command": "U mor komanda mbi protokollin: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Skedari CSS \"{{cssFile}}\" nuk ekziston, duke u injoruar"
},
"unresponsive": {
"details": "Gabim i Papërgjigjes!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Duke pastruar memorien e përkohshme të aplikacionit"
},
"window": {
"tried-to-render-offscreen": "Dritarja u përpoq të renderohej jashtë ekranit, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menuja është e fshehur, përdorni 'Alt' për ta shfaqur (ose 'Escape' nëse përdorni Menunë brenda aplikacionit)",
"message": "Fshehja e menusë është aktivizuar",
"title": "Fshih Menunë Aktivizuar"
},
"need-to-restart": {
"buttons": {
"later": "Më vonë",
"restart-now": "Rinisni Tani"
},
"detail": "\"{{pluginName}}\" kërkon një restart që të hyjë në fuqi",
"message": "\"{{pluginName}}\" kerkon restart"
}
}
}
}
================================================
FILE: src/i18n/resources/sr.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Neuspešno izvršavanje ekstenzije {{pluginName}}::{{contextName}}",
"executed-at-ms": "Ekstenzija {{pluginName}}::{{contextName}} se izvršila za {{ms}}ms",
"initialize-failed": "Greška pri učitavanju ekstenzije \"{{pluginName}}\"",
"load-all": "Učitavanje svih ekstenzija",
"load-failed": "Neuspešno učitavanje ekstenzije \"{{pluginName}}\"",
"loaded": "Ekstenzija \"{{pluginName}}\" je učitana",
"unload-failed": "Greška pri oslobađanju ekstenzije \"{{pluginName}}\"",
"unloaded": "Ekstenzija \"{{pluginName}}\" je oslobođena"
}
}
},
"language": {
"code": "sr",
"local-name": "Српски",
"name": "Serbian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Završeno učitavanje. DevTools alat je otvoren"
},
"i18n": {
"loaded": "i18n su učitani"
},
"second-instance": {
"receive-command": "Primljena je komanda kroz protokol: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS datoteka \"{{cssFile}}\" ne postoji, ignorišem"
},
"unresponsive": {
"details": "Aplikacija se ne odaziva!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Čišćenje keširanih podataka"
},
"window": {
"tried-to-render-offscreen": "Prozor je pokušao da se učita van ekrana: windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Meni je sakriven, pritisnite 'Alt' da biste ga prikazali (ili 'Escape' ako koristite meni u aplikaciji)",
"message": "Opcija sakrivanja menija je omogućena",
"title": "Opcija sakrivanja menija je omogućena"
},
"need-to-restart": {
"buttons": {
"later": "Kasnije",
"restart-now": "Ponovo pokreni odmah"
},
"detail": "Ekstenzija \"{{pluginName}}\" zahteva ponovno pokretanje aplikacije da bi se učitala",
"message": "\"{{pluginName}}\" zahteva ponovno pokretanje",
"title": "Ponovno pokretanje je neophodno"
},
"unresponsive": {
"buttons": {
"quit": "Izađi",
"relaunch": "Pokreni ponovo",
"wait": "Čekaj"
},
"detail": "Izvinjavamo se zbog neprijatnosti! Molimo odaberite sledeći korak:",
"message": "Aplikacija ne odgovara ili je zamrznuta",
"title": "Prozor ne odgovara ili je zamrznut"
},
"update-available": {
"buttons": {
"disable": "Onemogući ažuriranja",
"download": "Preuzmi",
"ok": "U redu"
},
"detail": "Nova verzija je dostupna za preuzimanje na {{downloadLink}}",
"message": "Nova verzija je dostupna",
"title": "Ažuriranje je dostupno"
}
},
"menu": {
"about": "O aplikaciji",
"navigation": {
"label": "Navigacija",
"submenu": {
"copy-current-url": "Kopiraj trenutnu adresu",
"go-back": "Nazad",
"go-forward": "Napred",
"quit": "Izađi",
"restart": "Ponovo pokreni aplikaciju"
}
},
"options": {
"label": "Opcije",
"submenu": {
"advanced-options": {
"label": "Napredne opcije",
"submenu": {
"auto-reset-app-cache": "Očisti keširane podatke pri pokretanju aplikacije",
"disable-hardware-acceleration": "Onemogući hardversko ubrzanje",
"edit-config-json": "Izmeni config.json",
"override-user-agent": "Prepiši User-Agent",
"restart-on-config-changes": "Pokreni ponovo po promeni konfiguracije",
"set-proxy": {
"label": "Podesi proxy",
"prompt": {
"label": "Unesi Proxy adresu: (ostavi prazno da bi proxy bio onemogućen)",
"placeholder": "Primer: SOCKS5://127.0.0.1:9999",
"title": "Podesi proxy"
}
},
"toggle-dev-tools": "Pokreni/napusti DevTools"
}
},
"always-on-top": "Uvek na vrhu",
"auto-update": "Automatsko ažuriranje",
"hide-menu": {
"dialog": {
"message": "Meni će biti sakriven posle sledećeg pokretanje, upotrebite [Alt] da ga ponovo prikažete (ili backtick [`] ukoliko koristite meni u aplikaciji)",
"title": "Sakrivanje menija je omogućeno"
},
"label": "Sakrij meni"
},
"language": {
"dialog": {
"message": "Jezik aplikacije će biti promenjen nakon sledećeg pokretanja aplikacije",
"title": "Jezik je uspešno promenjen"
},
"label": "Jezik",
"submenu": {
"to-help-translate": "Želite da pomognete u prevođenju? Kliknite ovde"
}
},
"resume-on-start": "Nastavi trenutnu numeru pri sledećem pokretanju",
"single-instance-lock": "Sprečavanje višestrukog pokretanja",
"start-at-login": "Pokreni po prijavi u sistem",
"starting-page": {
"label": "Početna strana",
"unset": "Poništi podešavanje"
},
"tray": {
"label": "Lista",
"submenu": {
"disabled": "Onemogućeno",
"enabled-and-hide-app": "Omogući sklanjanje aplikacije u sistemsku listu",
"enabled-and-show-app": "Omogući i prikaži aplikaciju",
"play-pause-on-click": "Pokreni/Pauziraj na klik"
}
},
"visual-tweaks": {
"label": "Vizuelna podešavanja",
"submenu": {
"custom-window-title": {
"label": "Prilagođeni naziv prozora",
"prompt": {
"label": "Unesite prilagođeni naslov prozora: (ostavite prazno da onemogućite)",
"placeholder": "Primer: {{applicationName}}"
}
},
"like-buttons": {
"default": "Podrazumevano",
"force-show": "Prinudno prikaži",
"hide": "Sakrij",
"label": "'Sviđa mi se' dugmići"
},
"remove-upgrade-button": "Ukloni dugme za nadogradnju",
"theme": {
"dialog": {
"button": {
"cancel": "Otkaži",
"remove": "Ukloni"
},
"remove-theme": "Da li ste sigurni da želite da uklonite prilagođenu temu?",
"remove-theme-message": "Ova opcija će ukloniti prilagođenu temu"
},
"label": "Tema",
"submenu": {
"import-css-file": "Uvezi prilagođenu CSS datoteku (stil)",
"no-theme": "Bez teme"
}
}
}
}
}
},
"plugins": {
"enabled": "Omogućeno",
"label": "Ekstenzije",
"new": "NOVO"
},
"view": {
"label": "Prikaz",
"submenu": {
"force-reload": "Prinudno osveži",
"reload": "Osveži",
"reset-zoom": "Realna veličina",
"toggle-fullscreen": "Aktiviraj/deaktiviraj pun ekran",
"zoom-in": "Uvećaj",
"zoom-out": "Umanji"
}
}
},
"tray": {
"next": "Sledeće",
"play-pause": "Pokreni/Pauziraj",
"previous": "Prethodno",
"quit": "Izađi",
"restart": "Ponovo pokreni aplikaciju",
"show": "Prikaži prozor",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Ukoliko dođe do prikaza reklame, zvuk se prigušuje i podešava se brzina na 16x",
"name": "Ubrzanje reklama"
},
"adblocker": {
"description": "Podrazumevano blokiranje svih reklama i praćenja",
"menu": {
"blocker": "Bloker"
},
"name": "Bloker reklama"
},
"album-actions": {
"description": "Dodaje mogućnost primene opcija 'Sviđa mi se, ne sviđa mi se' kao i opciju njihovog poništenja na nivou plejliste ili albuma",
"name": "Akcije albuma"
},
"album-color-theme": {
"description": "Primenjuje dinamičku temu i vizuelne efekte na osnovu palete boja albuma",
"menu": {
"color-mix-ratio": {
"label": "Odnos mešavine boja",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Omogući postavljanje teme \"seekbar\"-a"
},
"name": "Paleta boja albuma"
},
"ambient-mode": {
"description": "Primenjuje svetlosne efekte na pozadini ekrana upotrebom blagih boja iz video snimka",
"menu": {
"blur-amount": {
"label": "Zamagljenje",
"submenu": {
"pixels": "{{blurAmount}} piksela"
}
},
"buffer": {
"label": "Bafer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Prozirnost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvalitet",
"submenu": {
"pixels": "{{quality}} piksela"
}
},
"size": {
"label": "Veličina",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Blag prelaz",
"submenu": {
"during": "U trajanju od {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Korišćenje punog ekrana"
}
},
"name": "Ambijentalni režim"
},
"amuse": {
"description": "Dodaje podršku za 'Amuse now' vidžet (6K Labs) u {{applicationName}}",
"name": "Zabavi",
"response": {
"query": "Amuse API server je pokrenut. Koristite GET /query da biste dobili informacije o numeri."
}
},
"api-server": {
"description": "Dodaje API server za kontrolu muzičkog plejera",
"dialog": {
"request": {
"buttons": {
"allow": "Dozvoli",
"deny": "Odbij"
},
"message": "Dozvoli {{ID}} ({{origin}}) da pristupa API-ju?",
"title": "Zahtev za autorizaciju na API"
}
},
"menu": {
"auth-strategy": {
"label": "Strategija za autorizaciju",
"submenu": {
"auth-at-first": {
"label": "Autorizuj na prvom zahtevu"
},
"none": {
"label": "Bez autorizacije"
}
}
},
"hostname": {
"label": "Ime host-a"
},
"https": {
"label": "HTTPS i Sertifikati",
"submenu": {
"cert": {
"dialogTitle": "Izaberi HTTPS datoteku sertifikata",
"label": "Datoteka sertifikata (.crt/.pem)"
},
"enable-https": {
"label": "Omogući HTTPS"
},
"key": {
"dialogTitle": "Izaberi HTTPS datoteku privatnog ključa",
"label": "Datoteka privatnog ključa (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "API Server [Beta]",
"prompt": {
"hostname": {
"label": "Unesite ime host-a (npr. 0.0.0.0) za API server:",
"title": "Ime host-a"
},
"port": {
"label": "Unesite port za API server:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Primeni kompresiju audio trake (smanjuje jačinu zvuka na najglasnijim delovima signala, i povećava jačinu na najnižim)",
"name": "Kompresor zvuka"
},
"auth-proxy-adapter": {
"description": "Podrška za upotrebu proxy servisa za autentifikaciju",
"menu": {
"disable": "Onemogući Proxy adapter",
"enable": "Omogući Proxy adapter",
"hostname": {
"label": "Ime host-a"
},
"port": {
"label": "Port"
}
},
"name": "Proxy adapter za autentifikaciju",
"prompt": {
"hostname": {
"label": "Unesi ime host-a za lokalni proxy server (zahteva ponovno pokretanje):",
"title": "Ime host-a za Proxy"
},
"port": {
"label": "Unesi port za lokalni proxy server (zahteva ponovno pokretanje):",
"title": "Proxy Port"
}
}
},
"blur-nav-bar": {
"description": "Čini navigacioni meni prozirnim i zamućenim",
"name": "Zamuti navigacioni meni"
},
"bypass-age-restrictions": {
"description": "Preskoči starosnu verifikaciju za Music Player",
"name": "Preskoči starosna ograničenja"
},
"captions-selector": {
"description": "Odabir prevoda za numere/audio trake na {{applicationName}}",
"menu": {
"autoload": "Automatski odaberi prethodno odabrani prevod",
"disable-captions": "Podrazumevano bez prevoda"
},
"name": "Odabir prevoda",
"prompt": {
"selector": {
"label": "Trenutno odabrani jezik prevoda: {{language}}",
"none": "Ništa",
"title": "Odaberi jezik prevoda"
}
},
"templates": {
"title": "Otvori meni za izbor prevoda"
},
"toast": {
"caption-changed": "Prevod promenjen u {{language}}",
"caption-disabled": "Prevod onemogućen",
"no-captions": "Za ovu pesmu nisu dostupni prevodi"
}
},
"compact-sidebar": {
"description": "Uvek koristi kompaktni meni sa strane",
"name": "Kompaktni meni sa strane"
},
"crossfade": {
"description": "Ublaženi prelaz između numera",
"menu": {
"advanced": "Napredno"
},
"name": "Ublaženi prelaz [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Ulazno trajanje prelaza (ms)",
"fade-out-duration": "Izlazno trajanje prelaza (ms)",
"fade-scaling": {
"label": "Skaliranje prelaza",
"linear": "Linearno",
"logarithmic": "Logaritamsko"
},
"seconds-before-end": "Prelaz N sekundi pre kraja"
},
"title": "Opcije prelaza"
}
}
},
"custom-output-device": {
"description": "Konfigurišite prilagođeni izlazni medijski uređaj za pesme",
"menu": {
"device-selector": "Izaberite uređaj"
},
"name": "Prilagođeni izlazni uređaj",
"prompt": {
"device-selector": {
"label": "Izaberite izlazni medijski uređaj koji će se koristiti",
"title": "Izaberite izlazni uređaj"
}
}
},
"disable-autoplay": {
"description": "Numere se pokreću u pauziranom režimu",
"menu": {
"apply-once": "Primenjuje se samo pri pokretanju"
},
"name": "Onemogući automatsko reprodukovanje"
},
"discord": {
"backend": {
"already-connected": "Pokušano je da se poveže sa aktivnom vezom",
"connected": "Spojeno sa Discord-om",
"disconnected": "Poništeno povezivanje sa Discord-om"
},
"description": "Prikaži prijateljima šta slušaš uz 'Rich Presence'",
"menu": {
"auto-reconnect": "Automatsko ponovno povezivanje",
"clear-activity": "Očisti aktivnosti",
"clear-activity-after-timeout": "Očisti aktivnosti nakon isteka vremena",
"connected": "Povezano",
"disconnected": "Nije povezano",
"hide-duration-left": "Sakrij preostalo vreme",
"hide-github-button": "Sakrij dugme sa GitHub linkom",
"play-on-application": "Reprodukuj na {{applicationName}}",
"set-inactivity-timeout": "Podesi tajmer za neaktivnost",
"set-status-display-type": {
"label": "Tekst statusa",
"submenu": {
"application": "Slušanje {{applicationName}}",
"artist": "Slušanje {artist}",
"title": "Slušanje {song title}"
}
}
},
"name": "Discord Bogato Prisustvo",
"prompt": {
"set-inactivity-timeout": {
"label": "Unesi vreme za tajmer neaktivnosti u sekundama:",
"title": "Podesi tajmer neaktivnosti"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "U redu"
},
"message": "Greška! Izvinjavamo se, preuzimanje nije uspelo…",
"title": "Greška pri preuzimanju!"
},
"start-download-playlist": {
"buttons": {
"ok": "U redu"
},
"detail": "({{playlistSize}} numera)",
"message": "Preuzimanje plejliste {{playlistTitle}}",
"title": "Preuzimanje je započeto"
}
},
"feedback": {
"conversion-progress": "Konverzija: {{percent}}%",
"converting": "Konvertuje se…",
"done": "Završeno: {{filePath}}",
"download-info": "Preuzima se {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Preuzimanje: {{percent}}%",
"downloading": "Preuzimanje…",
"downloading-counter": "Preuzimanje {{current}}/{{total}}…",
"downloading-playlist": "Preuzimanje plejliste \"{{playlistTitle}}\" - {{playlistSize}} numera ({{playlistId}})",
"error-while-downloading": "Greška pri preuzimanju \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Direktorijum {{playlistFolder}} već postoji",
"getting-playlist-info": "Preuzimanje informacija o plejlisti…",
"loading": "Učitavanje…",
"playlist-has-only-one-song": "Plejlista sadrži samo jednu numeru, preuzima se direktno",
"playlist-id-not-found": "Nije pronađen ID plejliste",
"playlist-is-empty": "Plejlista je prazna",
"playlist-is-mix-or-private": "Greška pri preuzimanju informacija o plejlisti: proverite da li je možda skrivena ili \"Za tebe\" plejlista\n\n{{error}}",
"preparing-file": "Pripremanje datoteke…",
"saving": "Čuvanje…",
"trying-to-get-playlist-id": "Pokušaj preuzimanja ID plejliste: {{playlistId}}",
"video-id-not-found": "Video nije pronađen",
"writing-id3": "Zapisivanje ID3 tagova…"
}
},
"description": "Preuzimanje MP3 / izvornog zvuka direktno sa interfejsa",
"menu": {
"choose-download-folder": "Odaberite direktorijum za preuzimanja",
"download-finish-settings": {
"label": "Preuzmi na završetku",
"prompt": {
"last-percent": "Nakon x procenata",
"last-seconds": "Poslednjih x sekundi",
"title": "Podesite kad da se vrši preuzimanje"
},
"submenu": {
"advanced": "Napredno",
"enabled": "Omogućeno",
"mode": "Vremenski režim",
"percent": "Procenat(a)",
"seconds": "Sekundi"
}
},
"download-playlist": "Preuzmi plejlistu",
"presets": "Predefinisana podešavanja",
"skip-existing": "Preskoči postojeće datoteke"
},
"name": "Servis za preuzimanje",
"renderer": {
"can-not-update-progress": "Nemoguće ažuriranje trenutnog napretka"
},
"templates": {
"button": "Preuzimanje"
}
},
"equalizer": {
"description": "Dodaje ekvilajzer u muzički plejer",
"menu": {
"presets": {
"label": "Predefinisana podešavanja",
"list": {
"bass-booster": "Pojačivač basa"
}
}
},
"name": "Ekvilajzer"
},
"exponential-volume": {
"description": "Postavlja slajder jačine kao eksponencijalan radi lakšeg izbora nižih jačina.",
"name": "Eksponencijalna jačina"
},
"in-app-menu": {
"description": "Daje izbornim trakama fensi, tamni ili izgled prema boji albuma",
"menu": {
"hide-dom-window-controls": "Sakrij kontrole DOM prozora"
},
"name": "Izborni menu unutar aplikacije"
},
"lumiastream": {
"description": "Dodaje podršku za Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Dodaje podršku za tekstove za većinu pesama",
"menu": {
"romanized-lyrics": "Romanizovani tekstovi"
},
"name": "Genius tekstovi",
"renderer": {
"fetched-lyrics": "Dobavljen tekst pesme sa Genius-a"
}
},
"music-together": {
"description": "Podeli plejlistu sa drugima. Kada domaćin (host) pusti pesmu, svi ostali će čuti istu pesmu",
"dialog": {
"enter-host": "Unesi ID host-a"
},
"internal": {
"save": "Sačuvaj",
"track-source": "Izvor pesme",
"unknown-user": "Nepoznat korisnik"
},
"menu": {
"click-to-copy-id": "Kopiraj ID host-a",
"close": "Zatvori 'Music Together'",
"connected-users": "Spojeni korisnici",
"disconnect": "Prekini vezu sa 'Music Together'",
"empty-user": "Nema povezanih korisnika",
"host": "'Music Together' domaćin (host)",
"join": "Pridruži se 'Music Together'",
"permission": {
"all": "Dozvoli gostima da kontrolišu plejlistu i reprodukciju",
"host-only": "Samo domaćin (host) može da kontroliše plejlistu i reprodukciju",
"playlist": "Dozvoli gostima da kontrolišu plejlistu"
},
"set-permission": "Promeni dozvole za upravljanje",
"status": {
"disconnected": "Nije povezan",
"guest": "Povezan kao gost",
"host": "Povezan kao domaćin (host)"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Dodavanje pesme nije uspelo",
"closed": "'Music Together' je zatvorena",
"disconnected": "Veza sa 'Music Together' je prekinuta",
"host-failed": "Pokretanje 'Music Together' kao domaćin (host) nije uspelo",
"id-copied": "ID domaćina (host) kopiran u privremenu memoriju",
"id-copy-failed": "Kopiranje ID domaćina (host) u privremenu memoriju nije uspelo",
"join-failed": "Povezivanje na 'Music Together' neuspešno",
"joined": "Povezan na 'Music Together'",
"permission-changed": "Dozvola 'Music Together' promenjena na \"{{permission}}\"",
"remove-song-failed": "Uklanjanje pesme nije uspelo",
"user-connected": "{{name}} se pridružio na 'Music Together'",
"user-disconnected": "{{name}} je napustio 'Music Together'"
}
},
"navigation": {
"description": "Strelice za navigaciju napred/nazad su direktno integrisane u interfejs, kao i u tvom omiljenom pregledaču",
"name": "Navigacija",
"templates": {
"back": {
"title": "Vrati se na prethodnu stranicu"
},
"forward": {
"title": "Idi na sledeću stranicu"
}
}
},
"no-google-login": {
"description": "Ukloni Google dugmad i linkove za prijavu sa interfejsa",
"name": "Bez Google prijave"
},
"notifications": {
"description": "Prikaži obaveštenje kada numera krene sa reprodukcijom (interaktivna obaveštenja su dostupna na Windows-u)",
"menu": {
"interactive": "Interaktivna obaveštenja",
"interactive-settings": {
"label": "Podešavanja za interakciju",
"submenu": {
"hide-button-text": "Sakrij tekst dugmeta",
"refresh-on-play-pause": "Osveži prilikom Pokreni/Pauziraj",
"tray-controls": "Otvori/Zatvori klikom na sistemsku traku"
}
},
"priority": "Prioritet obaveštenja",
"toast-style": "Toast stil obaveštenja",
"unpause-notification": "Prikaži obaveštenje prilikom nastavka reprodukcije"
},
"name": "Obaveštenja"
},
"performance-improvement": {
"description": "Poboljšaj performanse omogućavanjem eksperimentalnih skripti",
"name": "Poboljšaj performanse [Beta]"
},
"picture-in-picture": {
"description": "Dozvoljava aplikaciji da se prebaci u režim slike-u-slici",
"menu": {
"always-on-top": "Uvek na vrhu",
"hotkey": {
"label": "Prečica",
"prompt": {
"keybind-options": {
"hotkey": "Prečica"
},
"label": "Izaberi prečicu za prebacivanje u režim slike-u-slici",
"title": "Prečica za režim slike-u-slici"
}
},
"save-window-position": "Sačuvaj poziciju prozora",
"save-window-size": "Sačuvaj veličinu prozora",
"use-native-pip": "Koristi izvorni režim slike-u-slici za pregledače"
},
"name": "Slika-u-slici",
"templates": {
"button": "Slika-u-slici"
}
},
"playback-speed": {
"description": "Slušaj brzo, slušaj sporo! Dodaje slajder koji kontroliše brzinu pesme",
"name": "Brzina reprodukcije",
"templates": {
"button": "Brzina"
}
},
"precise-volume": {
"description": "Precizno kontroliši jačinu zvuka korišćenjem točka na mišu/prečica, sa prilagođenim interfejsom i prilagodivim inkrementalnim koracima jačine",
"menu": {
"arrows-shortcuts": "Lokalne kontrole tastera sa strelicama",
"custom-volume-steps": "Postavi prilagođene korake za promenu jačine",
"global-shortcuts": "Globalne prečice"
},
"name": "Precizna jačina",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Smanji jačinu",
"increase": "Povećaj jačinu"
},
"label": "Izaberi globalne prečice na tastaturi za jačinu:",
"title": "Globalne prečice na tastaturi za jačinu"
},
"volume-steps": {
"label": "Izaberi korake za povećanje/smanjenje jačine",
"title": "Koraci za promenu jačine"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Trenutni kvalitet: {{quality}}",
"message": "Izaberi kvalitet videa:",
"title": "Izaberi kvalitet videa"
}
}
},
"description": "Dozvoljava promenu kvaliteta videa pomoću dugmeta na video preklapanju",
"name": "Promena kvaliteta videa",
"renderer": {
"quality-settings-button": {
"label": "Otvori meni za promenu kvaliteta plejera"
}
}
},
"scrobbler": {
"description": "Dodaj podršku za 'četkanje' (poput last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Autentifikacija sa Last.fm nije uspela.\nZatvori iskačući prozor do sledećeg ponovnog pokretanja.",
"title": "Autentifikacija neuspešna"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Podešavanja za Last.fm API"
},
"listenbrainz": {
"token": "Unesi korisnički žeton za ListenBrainz"
},
"scrobble-alternative-artist": "Koristi alternativne izvođače",
"scrobble-alternative-title": "Koristi alternativne naslove",
"scrobble-other-media": "Učetkaj druge medije"
},
"name": "Četkarnik",
"prompt": {
"lastfm": {
"api-key": "Last.fm API ključ",
"api-secret": "Last.fm API tajna"
},
"listenbrainz": {
"token": {
"label": "Unesi svoj ListenBrainz korisnički žeton:",
"title": "ListenBrainz žeton"
}
}
}
},
"shortcuts": {
"description": "Dozvoljava postavljanje globalnih prečica na tastaturi za reproduciju (pusti/pauziraj/sledeće/prethodno) i isključivanje OSD za medije tako što će prepisati tastere za medije, uključiti Ctrl/CMD + F za pretragu, isključiti MPRIS podršku za medija tastere na Linux-u, i prilagođene prečice za napredne korisnike",
"menu": {
"override-media-keys": "Prepiši medija tastere",
"set-keybinds": "Podesi globalne kontrole za pesme"
},
"name": "Prečice (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Sledeće",
"play-pause": "Pusti / Pauziraj",
"previous": "Prethodno"
},
"label": "Izaberi globalne prečice za upravljanje pesmama:",
"title": "Globalne prečice na tastaturi"
}
}
},
"skip-disliked-songs": {
"description": "Preskače pesme koje vam se nisu svidele",
"name": "Preskočite pesme koje vam se nisu svidele"
},
"skip-silences": {
"description": "Automatski preskočite delove pesama gde nema zvuka",
"name": "Preskoči tišine"
},
"sponsorblock": {
"description": "Automatski preskače delove pesama koji nisu muzika poput uvod/odjava ili delove muzičkih spotova u kojima nema muzike",
"name": "Blok sponzora"
},
"synced-lyrics": {
"description": "Obezbeđuje sinhronizovane lirike pesama, korišćenjem dobavljača poput LRClib.",
"errors": {
"fetch": "⚠️\tDošlo je do greške prilikom dobavljanja stihova pesme.\n\tMolimo vas da pokušate ponovo kasnije.",
"not-found": "⚠️ Tekst za ovu pesmu nije pronađen."
},
"menu": {
"default-text-string": {
"label": "Podrazumevani karakteri između tekstova pesama",
"tooltip": "Izaberi podrazumevane karaktere koji će biti korišćeni za razmake između tekstova pesama"
},
"line-effect": {
"label": "Efekat linije",
"submenu": {
"fancy": {
"label": "Kitnjast",
"tooltip": "Koristi velike (kao iz aplikacije) efekte na trenutnu liniju"
},
"focus": {
"label": "Fokus",
"tooltip": "Učini samo trenutnu liniju belom"
},
"offset": {
"label": "Pomeraj",
"tooltip": "Pomeraj na trenutnoj liniji"
},
"scale": {
"label": "Razmera",
"tooltip": "Promeni razmeru trenutne linije"
}
},
"tooltip": "Izaberi efekat koji će biti primenjen na trenutnoj liniji"
},
"precise-timing": {
"label": "Učini da tekst pesme bude savršeno usklađen",
"tooltip": "Izračunaj do milisekunde prikaz sledeće linije teksta (može malo uticati na učinak)"
},
"preferred-provider": {
"label": "Preferirani dobavljač",
"none": {
"label": "Nijedan",
"tooltip": "Bez preferiranog dobavljača"
},
"tooltip": "Izaberite podrazumevanog dobavljača kog ćete koristiti"
},
"romanization": {
"label": "Romanizuj tekstove pesama",
"tooltip": "Ako je tekst pesme na drugom jeziku, pokušaj da ga prikažeš u latiničnoj varijanti."
},
"show-lyrics-even-if-inexact": {
"label": "Prikaži tekst pesme čak iako nije tačan",
"tooltip": "Ako pesma nije pronađena, produžetak će pokušati ponovo sa novim upitom za pretragu.\nRezultat iz drugog pokušaja možda neće biti tačan."
},
"show-time-codes": {
"label": "Prikaži vremenske oznake",
"tooltip": "Prikaži vremenske oznake pored teksta pesme"
}
},
"name": "Sinhronizovani tekstovi pesama",
"refetch-btn": {
"fetching": "Dobavljanje...",
"normal": "Ponovo dobavi tekst pesme"
},
"warnings": {
"duration-mismatch": "⚠️ - Tekst pesme možda nije usklađen zbog neuklapanja u dužini trajanja.",
"inexact": "⚠️ - Tekst za ovu pesmu možda nije tačan",
"instrumental": "⚠️ - Ovo je instrumentalna pesma"
}
},
"taskbar-mediacontrol": {
"description": "Upravljaj reprodukcijom iz Windows trake sa zadacima",
"name": "Upravljanje medijima iz trake sa zadacima"
},
"touchbar": {
"description": "Dodaje dodatak dodirne trake za macOS korisnike",
"name": "Dodirna Traka"
},
"transparent-player": {
"description": "Čini prozor aplikacije transparentnim",
"menu": {
"opacity": {
"label": "Neprozirnost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tip",
"submenu": {
"acrylic": "Akrilan",
"mica": "Mika",
"none": "Nijedan",
"tabbed": "Kartice"
}
}
},
"name": "Transparentni plejer"
},
"tuna-obs": {
"description": "Integracija sa OBS-ovim Tuna dodatkom",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Onemogućava plejeru da iskače u toku pokretanja pesme",
"name": "Nenametljivi plejer"
},
"video-toggle": {
"description": "Dodaje dugme za promenu između režima za video/numeru. Opciono, može da ukloni celu karticu sa videom",
"menu": {
"align": {
"label": "Poravnanje",
"submenu": {
"left": "Levo",
"middle": "Sredina",
"right": "Desno"
}
},
"force-hide": "Nasilno ukloni karticu sa videom",
"mode": {
"label": "Režim",
"submenu": {
"custom": "Prilagođeno prebacivanje",
"disabled": "Onemogućeno",
"native": "Izvorno prebacivanje"
}
}
},
"name": "Video prebacivanje",
"templates": {
"button-song": "Pesma",
"button-video": "Video"
}
},
"visualizer": {
"description": "Dodaje vizualizaciju u plejer",
"menu": {
"visualizer-type": "Tip vizualizacije"
},
"name": "Vizualizacija"
}
}
}
================================================
FILE: src/i18n/resources/sv.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Misslyckades med att köra tillägget {{pluginName}}::{{contextName}}",
"executed-at-ms": "Tillägget {{pluginName}}::{{contextName}} kördes på {{ms}}ms",
"initialize-failed": "Misslyckades med att initialisera tillägget \"{{pluginName}}\"",
"load-all": "Laddar alla tillägg",
"load-failed": "Misslyckades med att ladda tillägget \"{{pluginName}}\"",
"loaded": "Tillägget \"{{pluginName}}\" laddades in",
"unload-failed": "Kunde inte inaktivera {{pluginName}}-tillägget",
"unloaded": "{{pluginName}}-tillägget inaktiverat"
}
}
},
"language": {
"code": "sv",
"local-name": "Svenska",
"name": "Swedish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Laddning slutförd. Utvecklarverktyg öppnad"
},
"i18n": {
"loaded": "i18n laddad"
},
"second-instance": {
"receive-command": "Mottog kommando via protokoll: \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS-filen \"{{cssFile}}\" finns inte, ignorerar"
},
"unresponsive": {
"details": "Oresponsivt fel!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Rensar appens cache"
},
"window": {
"tried-to-render-offscreen": "Fönstret försökte rendera utanför skärmen, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menyn är dold, använd 'Alt' för att visa den (eller 'Escape' om du använder inbyggd meny)",
"message": "Dölj meny är aktiverad",
"title": "Dölj meny aktiverad"
},
"need-to-restart": {
"buttons": {
"later": "Senare",
"restart-now": "Starta om nu"
},
"detail": "\"{{pluginName}}\"-tillägget kräver en omstart för att träda i kraft",
"message": "\"{{pluginName}}\"-tillägget behöver startas om",
"title": "Omstart krävs"
},
"unresponsive": {
"buttons": {
"quit": "Avsluta",
"relaunch": "Starta om",
"wait": "Vänta"
},
"detail": "Vi beklagar besväret! Välj vad du vill göra:",
"message": "Programmet svarar inte",
"title": "Fönstret svarar inte"
},
"update-available": {
"buttons": {
"disable": "Stäng av uppdateringar",
"download": "Ladda ned",
"ok": "OK"
},
"detail": "En ny version är tillgänglig och kan laddas ned på {{downloadLink}}",
"message": "En ny version är tillgänglig",
"title": "Uppdatering tillgänglig"
}
},
"menu": {
"about": "Om",
"navigation": {
"label": "Navigering",
"submenu": {
"copy-current-url": "Kopiera nuvarande länk",
"go-back": "Gå tillbaka",
"go-forward": "Gå framåt",
"quit": "Stäng",
"restart": "Starta om appen"
}
},
"options": {
"label": "Alternativ",
"submenu": {
"advanced-options": {
"label": "Avancerade alternativ",
"submenu": {
"auto-reset-app-cache": "Nollställ appcache när appen startar",
"disable-hardware-acceleration": "Stäng av hårdvaruacceleration",
"edit-config-json": "Redigera config.json",
"override-user-agent": "Ersätt User-Agent",
"restart-on-config-changes": "Starta om vid konfigurationsändringar",
"set-proxy": {
"label": "Ställ in proxy",
"prompt": {
"label": "Ange Proxy-adress: (lämna tomt för att inaktivera)",
"placeholder": "Exempel: SOCKS5://127.0.0.1:9999",
"title": "Ställ in proxy"
}
},
"toggle-dev-tools": "Utvecklarverktyg"
}
},
"always-on-top": "Alltid överst",
"auto-update": "Uppdatera automatiskt",
"hide-menu": {
"dialog": {
"message": "Menyn kommer att döljas efter omstart, använd [Alt] för att visa menyn (eller [`] vid användning av menyn inuti applikationen)",
"title": "Dölj meny aktiverad"
},
"label": "Dölj meny"
},
"language": {
"dialog": {
"message": "Språket ändras efter omstart",
"title": "Språket har ändrats"
},
"label": "Språk",
"submenu": {
"to-help-translate": "Vill du hjälpa till att översätta? Klicka här"
}
},
"resume-on-start": "Fortsätt spela när appen öppnas",
"single-instance-lock": "Lås enskild instans",
"start-at-login": "Starta vid inloggning",
"starting-page": {
"label": "Startsidа",
"unset": "Ej inställt"
},
"tray": {
"label": "Systemfält",
"submenu": {
"disabled": "Inaktiverad",
"enabled-and-hide-app": "Aktiverad och dölj app",
"enabled-and-show-app": "Aktiverad och visa app",
"play-pause-on-click": "Spela/Pausa vid klick"
}
},
"visual-tweaks": {
"label": "Visuella justeringar",
"submenu": {
"custom-window-title": {
"label": "Anpassad titel på fönstret",
"prompt": {
"label": "Ange anpassad fönstertitel: (lämna tomt för att inaktivera)",
"placeholder": "Exempelvis: {{applicationName}}"
}
},
"like-buttons": {
"default": "Standard",
"force-show": "Tvinga fram visning",
"hide": "Dölj",
"label": "Gilla-knappar"
},
"remove-upgrade-button": "Ta bort knappen för uppgradering",
"theme": {
"dialog": {
"button": {
"cancel": "Avbryt",
"remove": "Ta bort"
},
"remove-theme": "Vill du verkligen radera det anpassade temat?",
"remove-theme-message": "Det här raderar ditt anpassade tema"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importera anpassad CSS-fil",
"no-theme": "Inget tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Aktiverad",
"label": "Tillägg",
"new": "NY"
},
"view": {
"label": "Visa",
"submenu": {
"force-reload": "Tvinga omladdning",
"reload": "Ladda om",
"reset-zoom": "Verklig storlek",
"toggle-fullscreen": "Växla helskärm",
"zoom-in": "Zooma in",
"zoom-out": "Zooma ut"
}
}
},
"tray": {
"next": "Nästa",
"play-pause": "Spela/Pausa",
"previous": "Föregående",
"quit": "Stäng",
"restart": "Starta om appen",
"show": "Visa fönster",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Om en annons spelas, tystas ljudet och uppspelningshastigheten sätts till 16×",
"name": "Snabba upp annonser"
},
"adblocker": {
"description": "Blockerar annonser och spårning automatiskt",
"menu": {
"blocker": "Blockerare"
},
"name": "Annonsblockerare"
},
"album-actions": {
"description": "Lägger till knappar för Undislike, Dislike, Like och Unlike för att använda detta på alla spår i en spellista eller ett album",
"name": "Albumåtgärder"
},
"album-color-theme": {
"description": "Använder ett dynamiskt tema och visuella effekter baserat på albumets färgpalett",
"menu": {
"color-mix-ratio": {
"label": "Färgblandningsförhållande",
"submenu": {
"percent": "{{ratio}} %"
}
},
"enable-seekbar": "Aktivera temaanpassning av uppspelningsreglaget"
},
"name": "Albumfärgtema"
},
"ambient-mode": {
"description": "Ger en ljuseffekt genom att försiktigt kasta färger från videon på skärmens bakgrund",
"menu": {
"blur-amount": {
"label": "Oskärpa",
"submenu": {
"pixels": "{{blurAmount}} pixlar"
}
},
"buffer": {
"label": "Buffert",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opacitet",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvalitet",
"submenu": {
"pixels": "{{quality}} pixlar"
}
},
"size": {
"label": "Storlek",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Mjuk övergång",
"submenu": {
"during": "Under {{interpolationTime}} s"
}
},
"use-fullscreen": {
"label": "Använder helskärm"
}
},
"name": "Ambiensläge"
},
"amuse": {
"description": "Lägger till stöd för {{applicationName}} i Amuse ‘Now Playing’-widgeten av 6K Labs",
"name": "Amuse",
"response": {
"query": "Amuse API-servern körs. Använd GET /query för att hämta information om låt."
}
},
"api-server": {
"description": "Lägger till en API-server för att styra spelaren",
"dialog": {
"request": {
"buttons": {
"allow": "Tillåt",
"deny": "Avvisa"
},
"message": "Tillåt {{ID}} ({{origin}}) att få åtkomst till API:et?",
"title": "Förfrågan om API-åtkomst"
}
},
"menu": {
"auth-strategy": {
"label": "Metod för åtkomstkontroll",
"submenu": {
"auth-at-first": {
"label": "Ge åtkomst vid första begäran"
},
"none": {
"label": "Ingen åtkomstkontroll"
}
}
},
"hostname": {
"label": "Värdnamn"
},
"https": {
"label": "HTTPS och certifikat",
"submenu": {
"cert": {
"dialogTitle": "Välj HTTPS-certifikatfil",
"label": "Certifikatfil (.crt/.pem)"
},
"enable-https": {
"label": "Aktivera HTTPS"
},
"key": {
"dialogTitle": "Välj privat nyckelfil (.key/.pem)",
"label": "Privat nyckelfil (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "API-server [Beta]",
"prompt": {
"hostname": {
"label": "Ange värdnamnet (t.ex. 0.0.0.0) för API-servern:",
"title": "Värdnamn"
},
"port": {
"label": "Ange porten för API-servern:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Applicera komprimering på ljudet (sänker volymen på de starkaste delarna av signalen och höjer volymen på de svagaste delarna)",
"name": "Ljudkompressor"
},
"auth-proxy-adapter": {
"description": "Stöd för användning av autentiseringsproxy-tjänster",
"menu": {
"disable": "Inaktivera proxy-adapter",
"enable": "Aktivera proxy-adapter",
"hostname": {
"label": "Värdnamn"
},
"port": {
"label": "Port"
}
},
"name": "Adapter För Autentiseringsproxy",
"prompt": {
"hostname": {
"label": "Ange värdnamn för lokal proxyserver (kräver omstart):",
"title": "Proxy-värdnamn"
},
"port": {
"label": "Ange port för lokal proxyserver (kräver omstart):",
"title": "Port för proxy"
}
}
},
"blur-nav-bar": {
"description": "Gör navigeringsfältet transparent och suddigt",
"name": "Suddigt Navigeringsfält"
},
"bypass-age-restrictions": {
"description": "Hoppa över Music Player åldersverifiering",
"name": "Hoppa Över Åldersbegränsningar"
},
"captions-selector": {
"description": "Välj textning för {{applicationName}}-ljudspår",
"menu": {
"autoload": "Välj automatiskt senast använda textning",
"disable-captions": "Ingen textning som standard"
},
"name": "Textväljare",
"prompt": {
"selector": {
"label": "Aktuellt textningsspråk: {{language}}",
"none": "Inget",
"title": "Välj textspråk"
}
},
"templates": {
"title": "Öppna textväljaren"
},
"toast": {
"caption-changed": "Textning ändrad till {{language}}",
"caption-disabled": "Textning inaktiverad",
"no-captions": "Inga undertexter tillgängliga för denna låt"
}
},
"compact-sidebar": {
"description": "Sätt alltid sidomenyn i kompakt läge",
"name": "Kompakt Sidomeny"
},
"crossfade": {
"description": "Mjuk övergång mellan låtar",
"menu": {
"advanced": "Avancerat"
},
"name": "Mjuk Övergång [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Fade-in-varaktighet (ms)",
"fade-out-duration": "Fade-out-varaktighet (ms)",
"fade-scaling": {
"label": "Fade-skalning",
"linear": "Linjär",
"logarithmic": "Logaritmisk"
},
"seconds-before-end": "Övergång i sekunder före slutet"
},
"title": "Övergångsinställningar"
}
}
},
"custom-output-device": {
"description": "Konfigurera en anpassad utdataenhet för låtar",
"menu": {
"device-selector": "Välj enhet"
},
"name": "Anpassad Utdataenhet",
"prompt": {
"device-selector": {
"label": "Välj den utdataenhet som ska användas",
"title": "Välj utdataenhet"
}
}
},
"disable-autoplay": {
"description": "Starta låt i \"pausat\" läge",
"menu": {
"apply-once": "Gäller endast vid uppstart"
},
"name": "Inaktivera Automatisk Uppspelning"
},
"discord": {
"backend": {
"already-connected": "Försökte ansluta med aktiv anslutning",
"connected": "Ansluten till Discord",
"disconnected": "Frånkopplad från Discord"
},
"description": "Visa dina vänner vad du lyssnar på med Aktivitetsdelning",
"menu": {
"auto-reconnect": "Automatisk återanslutning",
"clear-activity": "Rensa aktivitet",
"clear-activity-after-timeout": "Rensa aktivitet efter tidsgräns",
"connected": "Ansluten",
"disconnected": "Frånkopplad",
"hide-duration-left": "Dölj återstående tid",
"hide-github-button": "Dölj knapp för GitHub-länk",
"play-on-application": "Spela på {{applicationName}}",
"set-inactivity-timeout": "Ställ in inaktivitetstid",
"set-status-display-type": {
"label": "Statusmeddelande",
"submenu": {
"application": "Lyssnar på {{applicationName}}",
"artist": "Lyssnar på {artist}",
"title": "Lyssnar på {song title}"
}
}
},
"name": "Discord Aktivitetsdelning",
"prompt": {
"set-inactivity-timeout": {
"label": "Ange inaktivitetstid i sekunder:",
"title": "Ställ in inaktivitetstid"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Hoppsan! Nedladdningen misslyckades…",
"title": "Fel vid nedladdning!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} låtar)",
"message": "Laddar ner {{playlistTitle}}-spellistan",
"title": "Nedladdning påbörjad"
}
},
"feedback": {
"conversion-progress": "Konvertering: {{percent}}%",
"converting": "Konverterar…",
"done": "Klart: {{filePath}}",
"download-info": "Laddar ner {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Nedladdning: {{percent}}%",
"downloading": "Laddar ner…",
"downloading-counter": "Laddar ner {{current}}/{{total}}…",
"downloading-playlist": "Laddar ner {{playlistTitle}}-spellistan — {{playlistSize}} spår ({{playlistId}})",
"error-while-downloading": "Fel vid nedladdning \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Mappen {{playlistFolder}} finns redan",
"getting-playlist-info": "Hämtar information om spellista…",
"loading": "Laddar…",
"playlist-has-only-one-song": "Spellistan innehåller bara ett objekt. Laddar ner direkt.",
"playlist-id-not-found": "Hittade inget ID för spellista",
"playlist-is-empty": "Spellistan är tom",
"playlist-is-mix-or-private": "Fel vid hämtning av spellisteinformation. Se till att den inte är privat eller en 'Mixed for you'-spellista\n\n{{error}}",
"preparing-file": "Förbereder fil…",
"saving": "Sparar…",
"trying-to-get-playlist-id": "Försöker hämta spelliste-ID: {{playlistId}}",
"video-id-not-found": "Videon hittades inte",
"writing-id3": "Skriver ID3-taggar…"
}
},
"description": "Laddar ner MP3 / originalljud direkt från gränssnittet",
"menu": {
"choose-download-folder": "Välj nedladdningsmapp",
"download-finish-settings": {
"label": "Ladda ner när klart",
"prompt": {
"last-percent": "Efter x procent",
"last-seconds": "Senaste x sekunderna",
"title": "Ställ in när nedladdning ska ske"
},
"submenu": {
"advanced": "Avancerat",
"enabled": "Aktiverad",
"mode": "Tidsläge",
"percent": "Procent",
"seconds": "Sekunder"
}
},
"download-playlist": "Ladda ner spellista",
"presets": "Förinställningar",
"skip-existing": "Hoppa över befintliga filer"
},
"name": "Nedladdare",
"renderer": {
"can-not-update-progress": "Kan inte uppdatera förlopp"
},
"templates": {
"button": "Ladda ner"
}
},
"equalizer": {
"description": "Lägger till en equalizer i spelaren",
"menu": {
"presets": {
"label": "Förinställningar",
"list": {
"bass-booster": "Basförstärkning"
}
}
},
"name": "Equalizer"
},
"exponential-volume": {
"description": "Gör volymreglaget exponentiellt så att det blir lättare att välja lägre volymer.",
"name": "Exponentiell Volym"
},
"in-app-menu": {
"description": "Ger menyrader ett snyggt, mörkt, eller albumfärgat utseende",
"menu": {
"hide-dom-window-controls": "Dölj DOM-fönsterkontroller"
},
"name": "Meny I Appen"
},
"lumiastream": {
"description": "Lägger till stöd för Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Lägger till stöd för texter till de flesta låtar",
"menu": {
"romanized-lyrics": "Romiserade texter"
},
"name": "Texter från Genius",
"renderer": {
"fetched-lyrics": "Hämtade texter från Genius"
}
},
"music-together": {
"description": "Dela en spellista med andra. När värden spelar en låt kommer alla andra höra samma låt",
"dialog": {
"enter-host": "Ange värd-ID"
},
"internal": {
"save": "Spara",
"track-source": "Ljudkälla",
"unknown-user": "Okänd användare"
},
"menu": {
"click-to-copy-id": "Kopiera värd-ID",
"close": "Stäng \"Music Together\"",
"connected-users": "Anslutna användare",
"disconnect": "Koppla från \"Music Together\"",
"empty-user": "Inga anslutna användare",
"host": "Värd för \"Music Together\"",
"join": "Gå med i \"Music Together\"",
"permission": {
"all": "Tillåt gäster att styra spellista och spelare",
"host-only": "Endast värden kan styra spellista och spelare",
"playlist": "Tillåt gäster att styra spellistan"
},
"set-permission": "Ändra behörighet för styrning",
"status": {
"disconnected": "Frånkopplad",
"guest": "Ansluten som gäst",
"host": "Ansluten som värd"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Misslyckades med att lägga till låt",
"closed": "\"Music Together\" stängdes",
"disconnected": "\"Music Together\" frånkopplad",
"host-failed": "Misslyckades med att vara värd för \"Music Together\"",
"id-copied": "Värd-ID kopierat till urklipp",
"id-copy-failed": "Misslyckades med att kopiera värd-ID till urklipp",
"join-failed": "Misslyckades med att gå med i \"Music Together\"",
"joined": "Gick med i \"Music Together\"",
"permission-changed": "Behörighet för \"Music Together\" ändrad till \"{{permission}}\"",
"remove-song-failed": "Misslyckades med att radera låt",
"user-connected": "{{name}} gick med i \"Music Together\"",
"user-disconnected": "{{name}} lämnade \"Music Together\""
}
},
"navigation": {
"description": "Direkt integrering av Nästa-/Tillbaka-navigeringspilar i gränssnittet, som i din favoritwebbläsare",
"name": "Navigering",
"templates": {
"back": {
"title": "Gå till föregående sida"
},
"forward": {
"title": "Gå till nästa sida"
}
}
},
"no-google-login": {
"description": "Ta bort Google-inloggningsknappar och länkar från gränssnittet",
"name": "Ingen Google-inloggning"
},
"notifications": {
"description": "Visa en notis när en låt börjar spelas (interaktiva notiser finns på Windows)",
"menu": {
"interactive": "Interaktiva notiser",
"interactive-settings": {
"label": "Interaktiva inställningar",
"submenu": {
"hide-button-text": "Dölj knapptext",
"refresh-on-play-pause": "Uppdatera vid Play/Pause",
"tray-controls": "Öppna/stäng vid klick i systemfältet"
}
},
"priority": "Notisprioritet",
"toast-style": "Stil för \"toast\"-notiser",
"unpause-notification": "Visa notis när uppspelning återupptas"
},
"name": "Notiser"
},
"performance-improvement": {
"description": "Förbättra prestanda genom att aktivera experimentella skript",
"name": "Prestandaförbättring [Beta]"
},
"picture-in-picture": {
"description": "Tillåter appen att växla till bild-i-bild-läge",
"menu": {
"always-on-top": "Alltid överst",
"hotkey": {
"label": "Snabbkommando",
"prompt": {
"keybind-options": {
"hotkey": "Snabbkommando"
},
"label": "Välj ett snabbkommando för att växla bild-i-bild-läge",
"title": "Bild-I-Bild genväg"
}
},
"save-window-position": "Spara fönsterposition",
"save-window-size": "Spara fönsterstorlek",
"use-native-pip": "Använd webbläsarens inbyggda bild-i-bild"
},
"name": "Bild-i-bild",
"templates": {
"button": "Bild-i-bild"
}
},
"playback-speed": {
"description": "Lägger till ett reglage för att ändra uppspelningshastighet",
"name": "Uppspelningshastighet",
"templates": {
"button": "Hastighet"
}
},
"precise-volume": {
"description": "Styr ljudstyrkan exakt med mushjul/snabbtangenter, med anpassat skärmlager och justerbara volymsteg",
"menu": {
"arrows-shortcuts": "Kontroller för lokala piltangenter",
"custom-volume-steps": "Ställ in egna volymsteg",
"global-shortcuts": "Globala snabbkommandon"
},
"name": "Noggrann Volymkontroll",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Sänk volymen",
"increase": "Öka volymen"
},
"label": "Välj globala kortkommandon för volym:",
"title": "Globala kortkommandon för volym"
},
"volume-steps": {
"label": "Välj volymsteg för ökning/minskning",
"title": "Volymsteg"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Nuvarande kvalitet: {{quality}}",
"message": "Välj videokvalitet:",
"title": "Välj videokvalitet"
}
}
},
"description": "Tillåter att ändra videokvalitet med en knapp i videons overlay",
"name": "Videokvalitetsväxlare",
"renderer": {
"quality-settings-button": {
"label": "Öppna kvalitetsväxlare för spelaren"
}
}
},
"scrobbler": {
"description": "Lägg till scrobbling-stöd (t.ex. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Misslyckades att autentisera med Last.fm\nDölj popup-fönstret till nästa omstart.",
"title": "Autentisering misslyckades"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API-inställningar"
},
"listenbrainz": {
"token": "Ange ListenBrainz användartoken"
},
"scrobble-alternative-artist": "Använd alternativa artister",
"scrobble-alternative-title": "Använd alternativa titlar",
"scrobble-other-media": "Scrobbla annan media"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last.fm API nyckel",
"api-secret": "Last.fm API-hemlighet"
},
"listenbrainz": {
"token": {
"label": "Ange din ListenBrainz användartoken:",
"title": "ListenBrainz token"
}
}
}
},
"shortcuts": {
"description": "Tillåter inställning av globala kortkommandon för uppspelning (spela/pausa/nästa/föregående) och inaktiverar medie-OSD genom att åsidosätta medietangenter. Aktiverar Ctrl/CMD + F för sökning. Aktiverar Linux MPRIS-stöd för medietangenter och anpassade kortkommandon för avancerade användare",
"menu": {
"override-media-keys": "Åsidosätt medietangenter",
"set-keybinds": "Ställ in globala kontroller för låtar"
},
"name": "Genvägar (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Nästa",
"play-pause": "Spela / Pausa",
"previous": "Föregående"
},
"label": "Välj globala kortkommandon för kontroll av låtar:",
"title": "Globala kortkommandon"
}
}
},
"skip-disliked-songs": {
"description": "Hoppar över låtar du inte gillar",
"name": "Hoppa Över Låtar Du Inte Gillar"
},
"skip-silences": {
"description": "Hoppa automatiskt över tysta partier i låtar",
"name": "Hoppa Över Tysta Partier"
},
"sponsorblock": {
"description": "Hoppar automatiskt över icke-musikdelar som intro/outro eller delar av musikvideor där ingen musik spelas",
"name": "Blockera Sponsorer"
},
"synced-lyrics": {
"description": "Visar synkroniserade låttexter med hjälp av tjänster som LRClib.",
"errors": {
"fetch": "⚠️ Ett fel uppstod när texterna skulle hämtas.\n\tFörsök igen senare.",
"not-found": "⚠️ Inga texter hittades för denna låt."
},
"menu": {
"default-text-string": {
"label": "Standardtecken mellan låttexter",
"tooltip": "Välj standardtecken att använda för mellanrummet mellan låttexter"
},
"line-effect": {
"label": "Linjeeffekt",
"submenu": {
"fancy": {
"label": "Stiligt",
"tooltip": "Använd stora, app-liknande effekter på den aktuella raden"
},
"focus": {
"label": "Fokus",
"tooltip": "Gör endast den aktuella raden vit"
},
"offset": {
"label": "Förskjutning",
"tooltip": "Förskjut den aktuella raden åt höger"
},
"scale": {
"label": "Skala",
"tooltip": "Skala den aktuella raden"
}
},
"tooltip": "Välj effekt att applicera på den aktuella raden"
},
"precise-timing": {
"label": "Gör låttexterna perfekt synkroniserade",
"tooltip": "Beräkna till millisekunden när nästa rad ska visas (kan ha en liten inverkan på prestanda)"
},
"preferred-provider": {
"label": "Föredragen leverantör",
"none": {
"label": "Ingen",
"tooltip": "Ingen föredragen leverantör"
},
"tooltip": "Välj standardleverantör att använda"
},
"romanization": {
"label": "Romanisera låttexter",
"tooltip": "Om låttexterna är på ett annat språk, försök visa en latinsk version."
},
"show-lyrics-even-if-inexact": {
"label": "Visa låttexter även om de inte är exakta",
"tooltip": "Om låten inte hittas försöker tillägget igen med en annan sökförfrågan.\nResultatet från det andra försöket kanske inte är exakt."
},
"show-time-codes": {
"label": "Visa tidskoder",
"tooltip": "Visa tidskoderna bredvid låttexterna"
}
},
"name": "Synkroniserade Låttexter",
"refetch-btn": {
"fetching": "Hämtar...",
"normal": "Hämta låttexter igen"
},
"warnings": {
"duration-mismatch": "⚠️ - Texterna kan vara osynkroniserade på grund av en skillnad i spårlängd.",
"inexact": "⚠️ - Låttexterna för den här låten kanske inte är exakta",
"instrumental": "⚠️ - Det här är en instrumentallåt"
}
},
"taskbar-mediacontrol": {
"description": "Kontrollera uppspelning från aktivitetsfältet i Windows",
"name": "Mediakontroll i aktivitetsfältet"
},
"touchbar": {
"description": "Lägger till en TouchBar-widget för macOS-användare",
"name": "TouchBar"
},
"transparent-player": {
"description": "Gör appfönstret genomskinligt",
"menu": {
"opacity": {
"label": "Opacitet",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Akryl",
"mica": "Mica",
"none": "Ingen",
"tabbed": "Flikad"
}
}
},
"name": "Genomskinlig Spelare"
},
"tuna-obs": {
"description": "Integration med OBS-pluginprogrammet Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Undviker att spelaren visas när musik spelas",
"name": "Diskret Spelare"
},
"video-toggle": {
"description": "Lägger till en knapp för att växla mellan video/musik-läge. Kan också valfritt ta bort hela videofliken",
"menu": {
"align": {
"label": "Justering",
"submenu": {
"left": "Vänster",
"middle": "Mitten",
"right": "Höger"
}
},
"force-hide": "Tvinga borttagning av videoflik",
"mode": {
"label": "Läge",
"submenu": {
"custom": "Anpassad växling",
"disabled": "Inaktiverad",
"native": "Inbyggd växling"
}
}
},
"name": "Video PÅ/AV",
"templates": {
"button-song": "Låt",
"button-video": "Video"
}
},
"visualizer": {
"description": "Lägger till en visualisering i spelaren",
"menu": {
"visualizer-type": "Visualiseringstyp"
},
"name": "Visualiserare"
}
}
}
================================================
FILE: src/i18n/resources/ta.json
================================================
{
"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": "ta",
"local-name": "தமிழ்",
"name": "Tamil"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "ஏற்றுதல் முடிந்தது. டெவ்டூல்ச் திறக்கப்பட்டது"
},
"i18n": {
"loaded": "ப18ல் ஏற்றப்பட்டது"
},
"second-instance": {
"receive-command": "நெறிமுறை மீது கட்டளை பெறப்பட்டது: \"{{command}}\""
},
"theme": {
"css-file-not-found": "சிஎச்எச் கோப்பு \"{{cssFile}}\" புறக்கணிக்கிறது"
},
"unresponsive": {
"details": "பதிலளிக்காத பிழை!\n {{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "பயன்பாட்டு தற்காலிக சேமிப்பை அழித்தல்"
},
"window": {
"tried-to-render-offscreen": "சாளரம் ஆஃப்ச்கிரீன், சாளரங்கள் = {{windowSize}}, டிச்ப்ளேச்ச் = {{displaySize}}, நிலை = {{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "பட்டியல் மறைக்கப்பட்டுள்ளது, அதைக் காட்ட 'ஆல்ட்' ஐப் பயன்படுத்தவும் (அல்லது பயன்பாட்டில் மெனுவைப் பயன்படுத்தினால் 'தப்பிக்க')",
"message": "மறை பட்டியல் இயக்கப்பட்டது",
"title": "பட்டியல் இயக்கப்பட்டது"
},
"need-to-restart": {
"buttons": {
"later": "பின்னர்",
"restart-now": "இப்போது மறுதொடக்கம் செய்யுங்கள்"
},
"detail": "\"{{pluginName}}\" சொருகி நடைமுறைக்கு மறுதொடக்கம் தேவை",
"message": "\"{{pluginName}}\" மறுதொடக்கம் செய்ய வேண்டும்",
"title": "மறுதொடக்கம் தேவை"
},
"unresponsive": {
"buttons": {
"quit": "வெளியேறு",
"relaunch": "மீண்டும் தொடங்கவும்",
"wait": "காத்திருங்கள்"
},
"detail": "சிரமத்திற்கு வருந்துகிறோம்! என்ன செய்ய வேண்டும் என்பதைத் தேர்வுசெய்க:",
"message": "பயன்பாடு பதிலளிக்கவில்லை",
"title": "சாளரம் பதிலளிக்கவில்லை"
},
"update-available": {
"buttons": {
"disable": "புதுப்பிப்புகளை முடக்கு",
"download": "பதிவிறக்கம்",
"ok": "சரி"
},
"detail": "ஒரு புதிய பதிப்பு கிடைக்கிறது மற்றும் {{downloadLink}} இல் பதிவிறக்கம் செய்யலாம்",
"message": "புதிய பதிப்பு கிடைக்கிறது",
"title": "புதுப்பிப்பு கிடைக்கிறது"
}
},
"menu": {
"about": "பற்றி",
"navigation": {
"label": "வானோடல்",
"submenu": {
"copy-current-url": "தற்போதைய முகவரி ஐ நகலெடுக்கவும்",
"go-back": "திரும்பிச் செல்லுங்கள்",
"go-forward": "முன்னோக்கிச் செல்லுங்கள்",
"quit": "வெளியேறு",
"restart": "பயன்பாட்டை மறுதொடக்கம் செய்யுங்கள்"
}
},
"options": {
"label": "விருப்பங்கள்",
"submenu": {
"advanced-options": {
"label": "மேம்பட்ட விருப்பங்கள்",
"submenu": {
"auto-reset-app-cache": "பயன்பாடு தொடங்கும் போது பயன்பாட்டு தற்காலிக சேமிப்பை மீட்டமைக்கவும்",
"disable-hardware-acceleration": "வன்பொருள் முடுக்கம் முடக்கு",
"edit-config-json": "Config.json ஐத் திருத்து",
"override-user-agent": "பயனர்-முகவர் மீறவும்",
"restart-on-config-changes": "கட்டமைப்பு மாற்றங்களை மறுதொடக்கம் செய்யுங்கள்",
"set-proxy": {
"label": "பதிலாள் அமைக்கவும்",
"prompt": {
"label": "பதிலாள் முகவரியை உள்ளிடவும்: (முடக்க காலியாக விடவும்)",
"placeholder": "எடுத்துக்காட்டு: SOCKS5: //127.0.0.1: 9999",
"title": "பதிலாள் அமைக்கவும்"
}
},
"toggle-dev-tools": "டெவ்டூல்சை மாற்றவும்"
}
},
"always-on-top": "எப்போதும் மேலே",
"auto-update": "ஆட்டோ புதுப்பிப்பு",
"hide-menu": {
"dialog": {
"message": "அடுத்த துவக்கத்தில் பட்டியல் மறைக்கப்படும், அதைக் காட்ட [Alt] ஐப் பயன்படுத்தவும் (அல்லது App-menu ஐப் பயன்படுத்தினால் [அல்லது பின்னணி [`])",
"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": {
"custom-window-title": {
"label": "தனிப்பயன் சாளர தலைப்பு",
"prompt": {
"label": "தனிப்பயன் சாளர தலைப்பை உள்ளிடவும்: (முடக்க காலியாக விடவும்)",
"placeholder": "எடுத்துக்காட்டு: {{applicationName}}"
}
},
"like-buttons": {
"default": "இயல்புநிலை",
"force-show": "படை நிகழ்ச்சி",
"hide": "மறை",
"label": "பொத்தான்கள் போன்றவை"
},
"remove-upgrade-button": "மேம்படுத்தல் பொத்தானை அகற்று",
"theme": {
"dialog": {
"button": {
"cancel": "ரத்துசெய்",
"remove": "அகற்று"
},
"remove-theme": "தனிப்பயன் கருப்பொருளை அகற்ற விரும்புகிறீர்களா?",
"remove-theme-message": "இது தனிப்பயன் கருப்பொருளை அகற்றும்"
},
"label": "கருப்பொருள்",
"submenu": {
"import-css-file": "தனிப்பயன் சிஎச்எச் கோப்பை இறக்குமதி செய்க",
"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": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "ஒரு விளம்பரம் விளையாடினால் அது ஆடியோவை முடக்குகிறது மற்றும் பின்னணி வேகத்தை 16x ஆக அமைக்கிறது",
"name": "விளம்பர விரைவு"
},
"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}} s இன் போது"
}
},
"use-fullscreen": {
"label": "முழுத்திரை பயன்படுத்துதல்"
}
},
"name": "சுற்றுப்புற முறை"
},
"amuse": {
"description": "6K ஆய்வகங்களால் இப்போது விட்செட்டில் விளையாடும் Amuse க்கு Music Player இசை ஆதரவை சேர்க்கிறது",
"name": "பொழுதுபோக்கு",
"response": {
"query": "பநிஇ சேவையகம் இயங்குகிறது. பாடல் தகவலைப் பெற /வினவல்."
}
},
"api-server": {
"description": "பிளேயரைக் கட்டுப்படுத்த பநிஇ சேவையகத்தைச் சேர்க்கிறது",
"dialog": {
"request": {
"buttons": {
"allow": "இசைவு",
"deny": "மறுக்கவும்"
},
"message": "பநிஇ ஐ அணுக {{ID}} ({{origin}}) அனுமதிக்கவா?",
"title": "பநிஇ அங்கீகார கோரிக்கை"
}
},
"menu": {
"auth-strategy": {
"label": "அங்கீகார உத்தி",
"submenu": {
"auth-at-first": {
"label": "முதல் கோரிக்கையின் பேரில் ஏற்பு"
},
"none": {
"label": "ஏற்பு இல்லை"
}
}
},
"hostname": {
"label": "புரவலன்பெயர்"
},
"port": {
"label": "துறைமுகம்"
}
},
"name": "பநிஇ சேவையகம் [பீட்டா]",
"prompt": {
"hostname": {
"label": "பநிஇ சேவையகத்திற்கு ஓச்ட்பெயரை (0.0.0.0 போன்றவை) உள்ளிடவும்:",
"title": "புரவலன்பெயர்"
},
"port": {
"label": "பநிஇ சேவையகத்திற்கான துறைமுகத்தை உள்ளிடவும்:",
"title": "துறைமுகம்"
}
}
},
"audio-compressor": {
"description": "ஆடியோவுக்கு சுருக்கத்தைப் பயன்படுத்துங்கள் (சமிக்ஞையின் உரத்த பகுதிகளின் அளவைக் குறைத்து, மென்மையான பகுதிகளின் அளவை உயர்த்துகிறது)",
"name": "ஆடியோ அமுக்கி"
},
"auth-proxy-adapter": {
"description": "அங்கீகார ப்ராக்ஸி சேவைகளைப் பயன்படுத்துவதற்கான ஆதரவு",
"menu": {
"disable": "ப்ராக்ஸி அடாப்டரைத் துண்டிக்கவும்",
"enable": "ப்ராக்ஸி அடாப்டரை இயக்கு",
"hostname": {
"label": "ஹோஸ்ட் பெயர்"
},
"port": {
"label": "port"
}
},
"name": "அங்கீகார பதிலாள் அடாப்டர்",
"prompt": {
"hostname": {
"label": "ப்ராக்ஸி சர்வருக்கான ஹோஸ்ட் பெயரை உள்ளிடவும் (மறுதொடக்கம் தேவை):",
"title": "ப்ராக்ஸி ஹோஸ்ட்பெயர்"
},
"port": {
"label": "ப்ராக்ஸி சர்வருக்கான போர்ட்டை உள்ளிடவும் (மறுதொடக்கம் தேவை):",
"title": "ப்ராக்ஸி போர்ட்"
}
}
},
"blur-nav-bar": {
"description": "வழிசெலுத்தல் பட்டியை வெளிப்படையானதாகவும் மங்கலாகவும் ஆக்குகிறது",
"name": "மங்கலான வழிசெலுத்தல் பட்டி"
},
"bypass-age-restrictions": {
"description": "Music Player அகவை சரிபார்ப்பு பைபாச்",
"name": "அகவை கட்டுப்பாடுகள் பைபாச்"
},
"captions-selector": {
"description": "{{applicationName}} இசை ஆடியோ டிராக்குகளுக்கான தலைப்பு தேர்வாளர்",
"menu": {
"autoload": "கடைசியாக பயன்படுத்தப்பட்ட தலைப்பை தானாகத் தேர்ந்தெடுக்கவும்",
"disable-captions": "முன்னிருப்பாக தலைப்புகள் இல்லை"
},
"name": "தலைப்புகள் தேர்வாளர்",
"prompt": {
"selector": {
"label": "தற்போதைய தலைப்பு மொழி: {{language}}",
"none": "எதுவுமில்லை",
"title": "தலைப்பு மொழியைத் தேர்ந்தெடுக்கவும்"
}
},
"templates": {
"title": "திறந்த தலைப்புகள் தேர்வாளர்"
},
"toast": {
"caption-changed": "தலைப்பு {{language}} என மாற்றப்பட்டது",
"caption-disabled": "தலைப்புகள் முடக்கப்பட்டன",
"no-captions": "இந்த பாடலுக்கு தலைப்புகள் எதுவும் கிடைக்கவில்லை"
}
},
"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": "கிராச்ஃபேட் விருப்பங்கள்"
}
}
},
"custom-output-device": {
"description": "பாடல்களுக்கான தனிப்பயன் வெளியீட்டு ஊடக சாதனத்தை கட்டமைக்கவும்",
"menu": {
"device-selector": "சாதனத்தை தேர்ந்தெடுக்கவும்"
},
"name": "விருப்பமான வெளியிட்டு சாதனம்",
"prompt": {
"device-selector": {
"label": "பயன்படுத்தப்பட வேண்டிய வெளிப்பாட்டு ஊடக சாதனத்தை தேர்ந்தெடுக்கவும்",
"title": "வெளியிட்டு சாதனத்தை தேர்ந்தெடுக்கவும்"
}
}
},
"disable-autoplay": {
"description": "\"இடைநிறுத்தப்பட்ட\" பயன்முறையில் பாடல் தொடங்குகிறது",
"menu": {
"apply-once": "தொடக்கத்தில் மட்டுமே பொருந்தும்"
},
"name": "ஆட்டோபிளேவை முடக்கு"
},
"discord": {
"backend": {
"already-connected": "செயலில் இணைப்புடன் இணைக்க முயற்சித்தது",
"connected": "முரண்பாட்டுடன் இணைக்கப்பட்டுள்ளது",
"disconnected": "முரண்பாட்டிலிருந்து துண்டிக்கப்பட்டது"
},
"description": "பணக்கார இருப்பைக் கொண்டு நீங்கள் கேட்பதை உங்கள் நண்பர்களுக்குக் காட்டுங்கள்",
"menu": {
"auto-reconnect": "ஆட்டோ மீண்டும் இணைக்கவும்",
"clear-activity": "தெளிவான செயல்பாடு",
"clear-activity-after-timeout": "காலக்கெடுவுக்குப் பிறகு தெளிவான செயல்பாடு",
"connected": "இணைக்கப்பட்டுள்ளது",
"disconnected": "துண்டிக்கப்பட்டது",
"hide-duration-left": "காலம் மீதமுள்ளதை மறைக்கவும்",
"hide-github-button": "அறிவிலிமையம் இணைப்பு பொத்தானை மறைக்கவும்",
"play-on-application": "யூடியூப் இசையில் விளையாடுங்கள்",
"set-inactivity-timeout": "செயலற்ற நேரம் முடிந்தது",
"set-status-display-type": {
"label": "நிலை உரை",
"submenu": {
"application": "வலையொளி இசையில் கேட்கிறது",
"artist": "{கலைஞர்} பாடலைக் கேட்கிறேன்",
"title": "பாடலைக் கேட்கிறேன்{பாடல் தலைப்பு}"
}
}
},
"name": "முரண்பாடு பணக்கார இருப்பு",
"prompt": {
"set-inactivity-timeout": {
"label": "நொடிகளில் செயலற்ற நேரத்தை உள்ளிடவும்:",
"title": "செயலற்ற நேரம் முடிந்தது"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "சரி"
},
"message": "ஆர்க்! மன்னிப்பு, பதிவிறக்கம் தோல்வியுற்றது…",
"title": "பதிவிறக்கத்தில் பிழை!"
},
"start-download-playlist": {
"buttons": {
"ok": "சரி"
},
"detail": "({{playlistSize}} பாடல்கள்)",
"message": "பிளேலிச்ட்டைப் பதிவிறக்குகிறது {{playlistTitle}}",
"title": "பதிவிறக்கம் தொடங்கியது"
}
},
"feedback": {
"conversion-progress": "மாற்றம்: {{percent}}%",
"converting": "மாற்றுகிறது…",
"done": "முடிந்தது: {{filePath}}",
"download-info": "பதிவிறக்கம் {{artist}} - {{title}} [{{videoId}}}",
"download-progress": "பதிவிறக்கம்: {{percent}}%",
"downloading": "பதிவிறக்கம்…",
"downloading-counter": "பதிவிறக்கம் {{current}}/{{total}}…",
"downloading-playlist": "பிளேலிச்ட்டைப் பதிவிறக்குதல் \"{{playlistTitle}}\" - {{playlistSize}} பாடல்கள் ({{playlistId}})",
"error-while-downloading": "பிழை \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "{{playlistFolder}}} ஏற்கனவே உள்ளது",
"getting-playlist-info": "பிளேலிச்ட் தகவலைப் பெறுதல்…",
"loading": "ஏற்றுகிறது…",
"playlist-has-only-one-song": "பிளேலிச்ட்டில் ஒரே ஒரு உருப்படி மட்டுமே உள்ளது, அதை நேரடியாக பதிவிறக்குகிறது",
"playlist-id-not-found": "பிளேலிச்ட் ஐடி எதுவும் கிடைக்கவில்லை",
"playlist-is-empty": "பிளேலிச்ட் காலியாக உள்ளது",
"playlist-is-mix-or-private": "பிளேலிச்ட் தகவலைப் பெறுவதில் பிழை: இது ஒரு தனிப்பட்ட அல்லது \"உங்களுக்காக கலப்பு\" பிளேலிச்ட் அல்ல என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்\n\n {{error}}",
"preparing-file": "கோப்பைத் தயாரித்தல்…",
"saving": "சேமிப்பு…",
"trying-to-get-playlist-id": "பிளேலிச்ட் ஐடியைப் பெற முயற்சிக்கிறது: {{playlistId}}}",
"video-id-not-found": "வீடியோ கிடைக்கவில்லை",
"writing-id3": "ஐடி 3 குறிச்சொற்களை எழுதுதல்…"
}
},
"description": "எம்பி 3 / மூல ஆடியோவை இடைமுகத்திலிருந்து நேரடியாக பதிவிறக்குகிறது",
"menu": {
"choose-download-folder": "பதிவிறக்க கோப்புறையைத் தேர்வுசெய்க",
"download-finish-settings": {
"label": "முடிவில் பதிவிறக்கவும்",
"prompt": {
"last-percent": "ஃச் சதவீதத்திற்குப் பிறகு",
"last-seconds": "கடைசி ஃச் விநாடிகள்",
"title": "எப்போது பதிவிறக்கம் செய்ய வேண்டும் என்பதை உள்ளமைக்கவும்"
},
"submenu": {
"advanced": "மேம்பட்ட",
"enabled": "இயக்கப்பட்டது",
"mode": "நேர முறை",
"percent": "விழுக்காடு",
"seconds": "நொடிகள்"
}
},
"download-playlist": "பிளேலிச்ட்டைப் பதிவிறக்கவும்",
"presets": "முன்னமைவுகள்",
"skip-existing": "இருக்கும் கோப்புகளைத் தவிர்க்கவும்"
},
"name": "பதிவிறக்குபவர்",
"renderer": {
"can-not-update-progress": "முன்னேற்றத்தை புதுப்பிக்க முடியாது"
},
"templates": {
"button": "பதிவிறக்கம்"
}
},
"equalizer": {
"description": "பிளேயருக்கு ஒரு சமநிலையைச் சேர்க்கிறது",
"menu": {
"presets": {
"label": "முன்னமைவுகள்",
"list": {
"bass-booster": "பாச் பூச்டர்"
}
}
},
"name": "சமநிலைப்படுத்தி"
},
"exponential-volume": {
"description": "தொகுதி ச்லைடர் அதிவேகமானது, எனவே குறைந்த தொகுதிகளைத் தேர்ந்தெடுப்பது எளிது.",
"name": "அதிவேக தொகுதி"
},
"in-app-menu": {
"description": "மெனு-பட்டியை ஒரு ஆடம்பரமான, இருண்ட அல்லது ஆல்பம்-வண்ண தோற்றத்தைக் கொடுங்கள்",
"menu": {
"hide-dom-window-controls": "டோம் சாளர கட்டுப்பாடுகளை மறைக்கவும்"
},
"name": "பயன்பாட்டில் பட்டியல்"
},
"lumiastream": {
"description": "லூமியா ச்ட்ரீம் ஆதரவை சேர்க்கிறது",
"name": "லூமியா ச்ட்ரீம் [பீட்டா]"
},
"lyrics-genius": {
"description": "பெரும்பாலான பாடல்களுக்கு பாடல் ஆதரவை சேர்க்கிறது",
"menu": {
"romanized-lyrics": "ரோமானிய பாடல்"
},
"name": "பாடல் சீனியச்",
"renderer": {
"fetched-lyrics": "சீனியசுக்கு பாடல் வரிகள்"
}
},
"music-together": {
"description": "ஒரு பிளேலிச்ட்டை மற்றவர்களுடன் பகிர்ந்து கொள்ளுங்கள். புரவலன் ஒரு பாடலை விளையாடும்போது, மற்றவர்கள் அனைவரும் ஒரே பாடலைக் கேட்பார்கள்",
"dialog": {
"enter-host": "புரவலன் ஐடியை உள்ளிடவும்"
},
"internal": {
"save": "சேமி",
"track-source": "ட்ராக் சோர்ச்",
"unknown-user": "தெரியாத பயனர்"
},
"menu": {
"click-to-copy-id": "புரவலன் ஐடியை நகலெடுக்கவும்",
"close": "ஒன்றாக இசையை மூடு",
"connected-users": "இணைக்கப்பட்ட பயனர்கள்",
"disconnect": "ஒன்றாக இசையை துண்டிக்கவும்",
"empty-user": "இணைக்கப்பட்ட பயனர்கள் இல்லை",
"host": "இசை ஒன்றாக புரவலன்",
"join": "ஒன்றாக இசையில் சேரவும்",
"permission": {
"all": "பிளேலிச்ட் மற்றும் பிளேயரைக் கட்டுப்படுத்த விருந்தினர்களை அனுமதிக்கவும்",
"host-only": "ஓச்டால் மட்டுமே பிளேலிச்ட் மற்றும் பிளேயரை கட்டுப்படுத்த முடியும்",
"playlist": "பிளேலிச்ட்டைக் கட்டுப்படுத்த விருந்தினர்களை அனுமதிக்கவும்"
},
"set-permission": "கட்டுப்பாட்டு அனுமதியை மாற்றவும்",
"status": {
"disconnected": "துண்டிக்கப்பட்டது",
"guest": "விருந்தினராக இணைக்கப்பட்டுள்ளது",
"host": "ஓச்டாக இணைக்கப்பட்டுள்ளது"
}
},
"name": "இசை ஒன்றாக [பீட்டா]",
"toast": {
"add-song-failed": "பாடல் சேர்க்கத் தவறிவிட்டது",
"closed": "இசை ஒன்றாக மூடப்பட்டது",
"disconnected": "இசை ஒன்றாக துண்டிக்கப்பட்டது",
"host-failed": "ஒன்றாக இசையை புரவலன் செய்யத் தவறிவிட்டது",
"id-copied": "இடைநிலைப்பலகைக்கு புரவலன் ஐடி நகலெடுக்கப்பட்டது",
"id-copy-failed": "புரவலன் ஐடியை இடைநிலைப்பலகைக்கு நகலெடுப்பதில் தோல்வி",
"join-failed": "ஒன்றாக இசையில் சேரத் தவறிவிட்டது",
"joined": "ஒன்றாக இசையில் சேர்ந்தார்",
"permission-changed": "இசை ஒன்றாக இசைவு \"{{permission}}\" என மாற்றப்பட்டது",
"remove-song-failed": "பாடலை அகற்றுவதில் தோல்வி",
"user-connected": "{{name}} ஒன்றாக இசையில் சேர்ந்தார்",
"user-disconnected": "{{name}}} இடது இசையை ஒன்றாக"
}
},
"navigation": {
"description": "உங்களுக்கு பிடித்த உலாவியைப் போலவே இடைமுகத்தில் நேரடியாக ஒருங்கிணைக்கப்பட்ட அடுத்த/பின் வழிசெலுத்தல் அம்புகள்",
"name": "வானோடல்",
"templates": {
"back": {
"title": "முந்தைய பக்கத்திற்குச் செல்"
},
"forward": {
"title": "அடுத்த பக்கத்திற்குச் செல்"
}
}
},
"no-google-login": {
"description": "இடைமுகத்திலிருந்து Google உள்நுழைவு பொத்தான்கள் மற்றும் இணைப்புகளை அகற்று",
"name": "கூகிள் உள்நுழைவு இல்லை"
},
"notifications": {
"description": "ஒரு பாடல் இயக்கத் தொடங்கும் போது அறிவிப்பைக் காண்பி (விண்டோசில் ஊடாடும் அறிவிப்புகள் கிடைக்கின்றன)",
"menu": {
"interactive": "ஊடாடும் அறிவிப்புகள்",
"interactive-settings": {
"label": "ஊடாடும் அமைப்புகள்",
"submenu": {
"hide-button-text": "பொத்தான் உரையை மறைக்கவும்",
"refresh-on-play-pause": "விளையாட்டு/இடைநிறுத்தத்தில் புதுப்பிக்கவும்",
"tray-controls": "தட்டு சொடுக்கு செய்யவும்/மூடு"
}
},
"priority": "அறிவிப்பு முன்னுரிமை",
"toast-style": "சிற்றுண்டி நடை",
"unpause-notification": "பொருத்தமற்ற அறிவிப்பைக் காட்டு"
},
"name": "அறிவிப்புகள்"
},
"performance-improvement": {
"description": "சோதனை ஸ்கிரிப்ட்களை இயக்குவதன் மூலம் செயல்திறனை மேம்படுத்தும்",
"name": "செயல்திறன் மேம்பாடு [பீட்டா]"
},
"picture-in-picture": {
"description": "பயன்பாட்டை பட-பட பயன்முறைக்கு மாற்ற அனுமதிக்கிறது",
"menu": {
"always-on-top": "எப்போதும் மேலே",
"hotkey": {
"label": "ஆட்ச்கி",
"prompt": {
"keybind-options": {
"hotkey": "ஆட்ச்கி"
},
"label": "படம்-இன்-படத்தை மாற்ற ஆட்கியைத் தேர்வுசெய்க",
"title": "படம்-இன்-பட ஆட்ச்கி"
}
},
"save-window-position": "சாளர நிலையை சேமி",
"save-window-size": "சாளர அளவை சேமி",
"use-native-pip": "உலாவி சொந்த பிப் பயன்படுத்தவும்"
},
"name": "படம்-படம்",
"templates": {
"button": "படம்-படம்"
}
},
"playback-speed": {
"description": "வேகமாக கேளுங்கள், மெதுவாக கேளுங்கள்! பாடல் வேகத்தைக் கட்டுப்படுத்தும் ச்லைடரைச் சேர்க்கிறது",
"name": "பின்னணி விரைவு",
"templates": {
"button": "வேகம்"
}
},
"precise-volume": {
"description": "தனிப்பயன் HUD மற்றும் தனிப்பயனாக்கக்கூடிய தொகுதி படிகளுடன், மவுச்வீல்/ஆட்கீசைப் பயன்படுத்தி துல்லியமாக அளவைக் கட்டுப்படுத்தவும்",
"menu": {
"arrows-shortcuts": "உள்ளக அம்பு-கீச் கட்டுப்பாடுகள்",
"custom-volume-steps": "தனிப்பயன் தொகுதி படிகளை அமைக்கவும்",
"global-shortcuts": "உலகளாவிய ஆட்கீச்"
},
"name": "துல்லியமான தொகுதி",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "அளவைக் குறைக்கவும்",
"increase": "அளவை அதிகரிக்கவும்"
},
"label": "உலகளாவிய தொகுதி விசைப்பலகைகளைத் தேர்வுசெய்க:",
"title": "உலகளாவிய தொகுதி கீபிண்ட்ச்"
},
"volume-steps": {
"label": "தொகுதி அதிகரிப்பு/குறைத்தல் படிகளைத் தேர்வுசெய்க",
"title": "தொகுதி படிகள்"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "தற்போதைய தரம்: {{quality}}",
"message": "வீடியோ தரத்தைத் தேர்வுசெய்க:",
"title": "வீடியோ தரத்தைத் தேர்வுசெய்க"
}
}
},
"description": "வீடியோ மேலடுக்கில் ஒரு பொத்தானைக் கொண்டு வீடியோ தரத்தை மாற்ற அனுமதிக்கிறது",
"name": "வீடியோ தர மாற்றி",
"renderer": {
"quality-settings-button": {
"label": "திறந்த பிளேயர் தர மாற்றி"
}
}
},
"scrobbler": {
"description": "ச்க்ரோப்ளிங் ஆதரவைச் சேர் (last.fm, Listenbrainz முதலியன)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm உடன் அங்கீகரிக்கத் தவறிவிட்டது\n அடுத்த மறுதொடக்கம் வரை பாப்அப்பை மறைக்கவும்.",
"title": "ஏற்பு தோல்வியடைந்தது"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm பநிஇ அமைப்புகள்"
},
"listenbrainz": {
"token": "WeskBrainz பயனர் கிள்ளாக்கை உள்ளிடவும்"
},
"scrobble-alternative-artist": "மாற்று கலைஞர்களை பயன்படுத்துங்கள்",
"scrobble-alternative-title": "மாற்று தலைப்புகளைப் பயன்படுத்தவும்",
"scrobble-other-media": "மற்ற ஊடகங்களை வெல்லுங்கள்"
},
"name": "ச்க்ரோபிளர்",
"prompt": {
"lastfm": {
"api-key": "Last.fm பநிஇ key",
"api-secret": "Last.fm பநிஇ மறைபொருள்"
},
"listenbrainz": {
"token": {
"label": "உங்கள் கயிட் பிரைன்ச் பயனர் கிள்ளாக்கை உள்ளிடவும்:",
"title": "கேளுங்கள் பிரெய்ன்ச் கிள்ளாக்கு"
}
}
}
},
"shortcuts": {
"description": "பிளேபேக்கிற்கான உலகளாவிய ஆட்கீசை அமைக்க அனுமதிக்கிறது (நாடகம்/இடைநிறுத்தம்/அடுத்த/முந்தைய) மீடியா விசைகளை மீறுவதன் மூலம் மீடியா ஓஎச்டி",
"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": "ம n னங்களைத் தவிர்க்கவும்"
},
"sponsorblock": {
"description": "அறிமுகம்/அவுட்ரோ போன்ற இசை அல்லாத பகுதிகள் அல்லது பாடல் இசைக்காத இசை வீடியோக்களின் பகுதிகளை தானாகவே தவிர்க்கிறது",
"name": "ஒப்புரவாளர் தொகுதி"
},
"synced-lyrics": {
"description": "எல்.ஆர்.சி.எல்.ஐ.பி போன்ற வழங்குநர்களைப் பயன்படுத்தி பாடல்களுக்கு ஒத்திசைக்கப்பட்ட பாடல்களை வழங்குகிறது.",
"errors": {
"fetch": "The பாடல் வரிகளைப் பெறும்போது பிழை ஏற்பட்டது.\n தயவுசெய்து பின்னர் மீண்டும் முயற்சிக்கவும்.",
"not-found": "Ling இந்த பாடலுக்கு வரிகள் எதுவும் கிடைக்கவில்லை."
},
"menu": {
"default-text-string": {
"label": "பாடல் வரிகளுக்கு இடையில் இயல்புநிலை எழுத்து",
"tooltip": "பாடல் வரிகளுக்கு இடையிலான இடைவெளியைப் பயன்படுத்த இயல்புநிலை எழுத்தைத் தேர்வுசெய்க"
},
"line-effect": {
"label": "வரி விளைவு",
"submenu": {
"fancy": {
"label": "ஆடம்பரமான",
"tooltip": "தற்போதைய வரியில் பெரிய, பயன்பாடு போன்ற விளைவுகளைப் பயன்படுத்தவும்"
},
"focus": {
"label": "குவி",
"tooltip": "தற்போதைய வரியை மட்டுமே வெள்ளை செய்யுங்கள்"
},
"offset": {
"label": "ஈடுசெய்யும்",
"tooltip": "வலதுபுறத்தில் தற்போதைய வரியை ஈடுசெய்யவும்"
},
"scale": {
"label": "அளவு",
"tooltip": "தற்போதைய வரியை அளவிடவும்"
}
},
"tooltip": "தற்போதைய வரிக்கு பொருந்தக்கூடிய விளைவைத் தேர்வுசெய்க"
},
"precise-timing": {
"label": "பாடல் வரிகளை சரியாக ஒத்திசைக்கவும்",
"tooltip": "அடுத்த வரியின் காட்சியைக் கணக்கிடுங்கள் (செயல்திறனில் ஒரு சிறிய தாக்கத்தை ஏற்படுத்தும்)"
},
"preferred-provider": {
"label": "விருப்பமான வழங்குநர்",
"none": {
"label": "இல்லை",
"tooltip": "விருப்பமான வழங்குநர் இல்லை"
},
"tooltip": "வழமையான வழங்குநரை தேர்ந்தெடுக்கவும்"
},
"romanization": {
"label": "பாடல் வரிகள் ரோமானியமாக்குங்கள்",
"tooltip": "பாடல் வரிகள் வேறு மொழியில் இருந்தால், லத்தீன் பதிப்பைக் காட்ட முயற்சிக்கவும்."
},
"show-lyrics-even-if-inexact": {
"label": "துல்லியமாக இருந்தாலும் பாடல்களைக் காட்டு",
"tooltip": "பாடல் காணப்படாவிட்டால், சொருகி மீண்டும் வேறு தேடல் வினவலுடன் முயற்சிக்கிறது.\n இரண்டாவது முயற்சியின் முடிவு துல்லியமாக இருக்காது."
},
"show-time-codes": {
"label": "நேரக் குறியீடுகளைக் காட்டு",
"tooltip": "பாடல் வரிகளுக்கு அடுத்த நேர குறியீடுகளைக் காட்டு"
}
},
"name": "ஒத்திசைக்கப்பட்ட பாடல்",
"refetch-btn": {
"fetching": "பெறுதல் ...",
"normal": "ரீஃபட்ச் பாடல்"
},
"warnings": {
"duration-mismatch": "⚠.எம் - கால பொருத்தமின்மை காரணமாக பாடல் வரிகள் ஒத்திசைக்கப்படாமல் இருக்கலாம்.",
"inexact": "- - இந்த பாடலுக்கான வரிகள் துல்லியமாக இருக்காது",
"instrumental": "- இது ஒரு கருவி பாடல்"
}
},
"taskbar-mediacontrol": {
"description": "உங்கள் சாளரங்கள் பணிப்பட்டியிலிருந்து பிளேபேக்கைக் கட்டுப்படுத்தவும்",
"name": "பணிப்பட்டு மீடியா கட்டுப்பாடு"
},
"touchbar": {
"description": "MACOS பயனர்களுக்கான டச்ச்பார் விட்செட்டை சேர்க்கிறது",
"name": "டக்பார்"
},
"transparent-player": {
"description": "பயன்பாட்டு சாளரத்தை வெளிப்படையானதாக மாற்றுக",
"menu": {
"opacity": {
"label": "ஒளிபுகாநிலை",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "வகை",
"submenu": {
"acrylic": "மங்கலான திரை தோற்றம்",
"mica": "மெல்லிய ஒளிவிடும் பின்புல தோற்றம் / மின்மினி கண்ணாடி",
"none": "மாற்றமில்லை அல்லது ஒன்றுமில்லை",
"tabbed": "ஒருங்கிணைக்கப்பற்ற பிரிவுகள்"
}
}
},
"name": "வெளிப்படையான சாதனம்"
},
"tuna-obs": {
"description": "OBS இன் சொருகி டுனாவுடன் ஒருங்கிணைப்பு",
"name": "டுனா குறிப்பு"
},
"unobtrusive-player": {
"description": "பாடலைப் பாடும்போது பாப் அப் செய்வதைத் தடுக்கிறது",
"name": "எளிதில் கேட்கக்கூடிய பாடல்"
},
"video-toggle": {
"description": "வீடியோ/பாடல் பயன்முறைக்கு இடையில் மாற ஒரு பொத்தானைச் சேர்க்கிறது. முழு வீடியோ தாவலையும் விருப்பமாக அகற்றலாம்",
"menu": {
"align": {
"label": "இருப்புவழி",
"submenu": {
"left": "இடது",
"middle": "நடுத்தர",
"right": "வலது"
}
},
"force-hide": "வீடியோ தாவலை அகற்றவும்",
"mode": {
"label": "பயன்முறை",
"submenu": {
"custom": "தனிப்பயன் மாற்று",
"disabled": "முடக்கப்பட்டது",
"native": "சொந்த மாற்று"
}
}
},
"name": "வீடியோ மாற்று",
"templates": {
"button-song": "பாடல்",
"button-video": "காணொளி"
}
},
"visualizer": {
"description": "பிளேயருக்கு ஒரு காட்சிப்படுத்தியைச் சேர்க்கிறது",
"menu": {
"visualizer-type": "விசுவலைசர் வகை"
},
"name": "காட்சிப்படுத்தல்"
}
}
}
================================================
FILE: src/i18n/resources/te.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "{{pluginName}}::{{contextName}} ప్లగిన్ను అమలు చేయడంలో విఫలమైంది",
"executed-at-ms": "{{pluginName}}::{{contextName}} ప్లగిన్ అమలు చేయబడిన సమయం {{ms}} మిల్లీసెకను",
"initialize-failed": "\"{{pluginName}}\" ప్లగిన్ను ప్రారంభించడంలో విఫలమైంది",
"load-all": "అన్నీ ప్లగిన్లను లోడ్ చెయ్యబడుతోంది",
"load-failed": "\"{{pluginName}}\" ప్లగిన్ లోడ్ చేయడంలో విఫలమైంది",
"loaded": "ప్లగిన్ \"{{pluginName}}\" లోడ్ చేయబడింది",
"unload-failed": "“{{pluginName}}” ప్లగిన్ను అన్లోడ్ చేయడంలో విఫలమైంది",
"unloaded": "\"{{pluginName}}\" ప్లగిన్ అన్లోడ్ చేయబడింది"
}
}
},
"language": {
"code": "te",
"local-name": "తెలుగు",
"name": "Telugu"
},
"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' ఉపయోగించండి (లేదా ఒకవేళ In-App Menu ఉపయోగిస్తుంటే 'Escape' ఉపయోగించండి)",
"message": "Hide Menu ఎనేబుల్ చేయబడింది",
"title": "Hide Menu ఎనేబుల్ అయిపోయింది"
},
"need-to-restart": {
"buttons": {
"later": "తర్వాత",
"restart-now": "ఇప్పుడే రీస్టార్ట్ చేయండి"
}
}
}
}
}
================================================
FILE: src/i18n/resources/th.json
================================================
{
"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}}, ขนาดหน้าจอ={{displaySize}}, ตำแหน่ง={{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": "เปิด/ปิดเครื่องมือสำหรับนักพัฒนา"
}
},
"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": {
"custom-window-title": {
"label": "ชื่อหน้าต่างกำหนดเอง",
"prompt": {
"label": "กำหนดชื่อหน้าต่างที่ต้องการ: (ปล่อยว่างเพื่อปิดใช้งาน)",
"placeholder": "ตัวอย่าง: {{applicationName}}"
}
},
"like-buttons": {
"default": "ค่าเริ่มต้น",
"force-show": "บังคับให้แสดง",
"hide": "ซ่อน",
"label": "ปุ่มถูกใจ"
},
"remove-upgrade-button": "ลบปุ่มอัปเกรด",
"theme": {
"dialog": {
"button": {
"cancel": "ยกเลิก",
"remove": "ลบ"
},
"remove-theme": "คุณแน่ใจหรือหรือไม่ที่จะลบธีม?",
"remove-theme-message": "สิ่งนี้จะลบธีมที่กำหนดเอง"
},
"label": "ธีม",
"submenu": {
"import-css-file": "นำเข้าไฟล์ CSS ที่กำหนดเอง",
"no-theme": "ไม่ใช้ธีม"
}
}
}
}
}
},
"plugins": {
"enabled": "เปิดใช้งาน",
"label": "ปลั๊กอิน",
"new": "ใหม่"
},
"view": {
"label": "มุมมอง",
"submenu": {
"force-reload": "บังคับโหลดใหม่",
"reload": "โหลดใหม่",
"reset-zoom": "ขนาดจริง",
"toggle-fullscreen": "เปิด/ปิดโหมดเต็มหน้าจอ",
"zoom-in": "ซูมเข้า",
"zoom-out": "ซูมออก"
}
}
},
"tray": {
"next": "ต่อไป",
"play-pause": "เล่น/หยุดชั่วคราว",
"previous": "ก่อนหน้า",
"quit": "ออก",
"restart": "รีสตาร์ตแอป",
"show": "แสดงหน้าต่าง",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "หากมีโฆษณาเล่นขึ้น เสียงจะถูกปิดและตั้งความเร็วในการเล่นเป็น 16 เท่า",
"name": "เพิ่มความเร็วโฆษณา"
},
"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": "โหมดสภาพแวดล้อม"
},
"amuse": {
"description": "เพิ่มการรองรับ {{applicationName}} สำหรับ Widget Amuse Now Playing ของ 6K Labs",
"name": "Amuse",
"response": {
"query": "API Server ของ Amuse ทำงานอยู่ ส่ง GET ไปที่ /query เพื่อขอข้อมูลเกี่ยวกับเพลง"
}
},
"api-server": {
"description": "เพิ่มเซิร์ฟเวอร์ API เพื่อควบคุมการเล่น",
"dialog": {
"request": {
"buttons": {
"allow": "อนุญาต",
"deny": "ปฏิเสธ"
},
"message": "อนุญาตให้ {{ID}} ({{origin}}) เข้าถึง API หรือไม่?",
"title": "คำขอลงชื่อเข้าใช้งาน API"
}
},
"menu": {
"auth-strategy": {
"label": "วิธีการลงชื่อเข้าใช้งาน",
"submenu": {
"auth-at-first": {
"label": "ลงชื่อเข้าใช้งานในคำขอแรก"
},
"none": {
"label": "ไม่ต้องลงชื่อเข้าใช้งาน"
}
}
},
"hostname": {
"label": "ชื่อโฮสต์ (Hostname)"
},
"port": {
"label": "พอร์ต (Port) ของ Server"
}
},
"name": "เซิร์ฟเวอร์ API [เบต้า]",
"prompt": {
"hostname": {
"label": "ใส่ชื่อโฮสต์ (Hostname) เช่น 0.0.0.0 สำหรับเซิร์ฟเวอร์ API:",
"title": "ชื่อโฮสต์ (Hostname)"
},
"port": {
"label": "ใส่พอร์ต (Port) ในการเข้าถึงเซิร์ฟเวอร์ API:",
"title": "พอร์ต (Port) ของ Server"
}
}
},
"audio-compressor": {
"description": "ใช้การบีบอัดเสียง (ลดระดับเสียงของส่วนที่ดังที่สุดของสัญญาณและเพิ่มระดับเสียงของส่วนที่เบาที่สุด)",
"name": "เครื่องมือบีบอัดเสียง"
},
"auth-proxy-adapter": {
"description": "รองรับการใช้พร็อกซีสำหรับการตรวจสอบสิทธิ์",
"menu": {
"disable": "เปิดใช้งาน ตัวเชื่อมต่อพร็อกซี",
"enable": "ปิดใช้งาน ตัวเชื่อมต่อพร็อกซี",
"hostname": {
"label": "ชื่อโฮสต์"
},
"port": {
"label": "พอร์ต"
}
},
"name": "ตัวเชื่อมต่อสำหรับตรวจสอบสิทธิ์ผ่านพร็อกซี",
"prompt": {
"hostname": {
"label": "ป้อนชื่อโฮสต์สำหรับพร็อกซีเซิร์ฟเวอร์ภายในเครื่อง (ต้องรีสตาร์ท):",
"title": "ชื่อโฮสต์พร็อกซี"
},
"port": {
"label": "ป้อนพอร์ตสำหรับพร็อกซีเซิร์ฟเวอร์ภายในเครื่อง (ต้องรีสตาร์ท):",
"title": "พอร์ตพร็อกซี"
}
}
},
"blur-nav-bar": {
"description": "ทำให้แถบนำทางโปร่งแสงและเบลอ",
"name": "เบลอแถบนำทาง"
},
"bypass-age-restrictions": {
"description": "ข้ามการตรวจสอบอายุของ Music Player",
"name": "ข้ามข้อจำกัดอายุ"
},
"captions-selector": {
"description": "ตัวเลือกคำบรรยายสำหรับเพลงใน {{applicationName}}",
"menu": {
"autoload": "เลือกคำบรรยายที่ใช้ครั้งล่าสุดโดยอัตโนมัติ",
"disable-captions": "ไม่มีคำบรรยายเป็นค่าเริ่มต้น"
},
"name": "ตัวเลือกคำบรรยาย",
"prompt": {
"selector": {
"label": "ภาษาคำบรรยายปัจจุบัน: {{language}}",
"none": "ไม่มี",
"title": "เลือกภาษาคำบรรยาย"
}
},
"templates": {
"title": "เปิดตัวเลือกคำบรรยาย"
},
"toast": {
"caption-changed": "เปลี่ยนคำบรรยายเป็นภาษา {{language}}",
"caption-disabled": "คำบรรยายถูกปิดใช้งาน",
"no-captions": "ไม่มีคำบรรยายสำหรับเพลงนี้"
}
},
"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": "ตัวเลือกเฟดเพลง"
}
}
},
"custom-output-device": {
"description": "ตั้งค่าอุปกรณ์เสียงออกสำหรับเพลง",
"menu": {
"device-selector": "เลือกอุปกรณ์"
},
"name": "อุปกรณ์เสียงออกที่กำหนดเอง",
"prompt": {
"device-selector": {
"label": "เลือกอุปกรณ์เสียงออกที่ต้องการใช้",
"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-application": "เล่นบน {{applicationName}}",
"set-inactivity-timeout": "ตั้งระยะเวลาไม่มีกิจกรรม",
"set-status-display-type": {
"label": "ข้อความสถานะ",
"submenu": {
"artist": "กำลังฟัง {ชื่อนักร้อง}",
"title": "กำลังฟัง {ชื่อเพลง}",
"application": "กำลังฟัง {{applicationName}}"
}
}
},
"name": "แสดงกิจกรรมบนดิสคอร์ด",
"prompt": {
"set-inactivity-timeout": {
"label": "ป้อนระยะเวลาไม่มีกิจกรรมเป็นวินาที:",
"title": "ตั้งระยะเวลาไม่มีกิจกรรม"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "ตกลง"
},
"message": "อ๊ะ! ขออภัย ดาวน์โหลดล้มเหลว…",
"title": "มีข้อผิดพลาดในการดาวน์โหลด!"
},
"start-download-playlist": {
"buttons": {
"ok": "ตกลง"
},
"detail": "({{playlistSize}} เพลง)",
"message": "กำลังดาวน์โหลดเพลย์ลิสต์ {{playlistTitle}}",
"title": "เริ่มต้นการดาวน์โหลดแล้ว"
}
},
"feedback": {
"conversion-progress": "การแปลง: {{percent}}%",
"converting": "กำลังแปลง…",
"done": "เสร็จสิ้น: {{filePath}}",
"download-info": "กำลังดาวน์โหลด {{artist}} - {{title}} [{{videoId}}",
"download-progress": "ดาวน์โหลด: {{percent}}%",
"downloading": "กำลังดาวน์โหลด…",
"downloading-counter": "กำลังดาวน์โหลด {{current}}/{{total}}…",
"downloading-playlist": "กำลังดาวน์โหลดเพลย์ลิสต์ \"{{playlistTitle}}\" - {{playlistSize}} เพลง ({{playlistId}})",
"error-while-downloading": "เกิดข้อผิดพลาดในการดาวน์โหลด \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "มีโฟลเดอร์ {{playlistFolder}} อยู่แล้ว",
"getting-playlist-info": "กำลังรับข้อมูลเพลย์ลิสต์…",
"loading": "กำลังโหลด…",
"playlist-has-only-one-song": "เพลย์ลิสต์มีเพียงเพลงเดียวเท่านั้น กำลังดาวน์โหลดเพลงนั้นโดยตรง",
"playlist-id-not-found": "ไม่พบ ID เพลย์ลิสต์",
"playlist-is-empty": "เพลย์ลิสต์ว่างเปล่า",
"playlist-is-mix-or-private": "เกิดข้อผิดพลาดในการรับข้อมูลเพลย์ลิสต์: ตรวจสอบให้แน่ใจว่าไม่ใช่เพลย์ลิสต์ส่วนตัวหรือเพลย์ลิสต์ \"มิกซ์สำหรับคุณ\"\n\n{{error}}",
"preparing-file": "กำลังเตรียมไฟล์…",
"saving": "กำลังบันทึก…",
"trying-to-get-playlist-id": "กำลังพยายามรับ ID เพลย์ลิสต์: {{playlistId}}",
"video-id-not-found": "ไม่พบวิดีโอ",
"writing-id3": "กำลังเขียนแท็ก ID3…"
}
},
"description": "ดาวน์โหลด MP3 / เสียงต้นฉบับโดยตรงจากอินเทอร์เฟซ",
"menu": {
"choose-download-folder": "เลือกโฟลเดอร์ดาวน์โหลด",
"download-finish-settings": {
"label": "ดาวน์โหลดเมื่อเล่นเสร็จ",
"prompt": {
"last-percent": "หลังจาก x เปอร์เซ็นต์",
"last-seconds": "x วินาทีล่าสุด",
"title": "ตั่งค่าเวลาที่จะดาวน์โหลด"
},
"submenu": {
"advanced": "การตั้งค่าขั้นสูง",
"enabled": "เปิดใช้งาน",
"mode": "โหมดเวลา",
"percent": "โหมดเปอร์เซ็นต์",
"seconds": "โหมดวินาที"
}
},
"download-playlist": "ดาวน์โหลดเพลย์ลิสต์",
"presets": "พรีเซ็ต",
"skip-existing": "ข้ามไฟล์ที่มีอยู่แล้ว"
},
"name": "ตัวดาวน์โหลด",
"renderer": {
"can-not-update-progress": "ไม่สามารถอัปเดตความคืบหน้าได้"
},
"templates": {
"button": "ดาวน์โหลด"
}
},
"equalizer": {
"description": "เพิ่มอีควอไลเซอร์ให้ที่เล่นเพลง",
"menu": {
"presets": {
"label": "การตั้งค่าล่วงหน้า",
"list": {
"bass-booster": "เพิ่มเบส"
}
}
},
"name": "อีควอไลเซอร์"
},
"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 แล้ว",
"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": "การนำทาง",
"templates": {
"back": {
"title": "ไปหน้าก่อนหน้า"
},
"forward": {
"title": "ไปหน้าถัดไป"
}
}
},
"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": "การแจ้งเตือน"
},
"performance-improvement": {
"description": "ปรับปรุงประสิทธิภาพโดยเปิดใช้งานสคริปต์ทดลอง",
"name": "ปรับปรุงประสิทธิภาพ [Beta]"
},
"picture-in-picture": {
"description": "อนุญาตให้สลับแอปเป็นโหมดภาพในภาพ",
"menu": {
"always-on-top": "อยู่ด้านบนเสมอ",
"hotkey": {
"label": "ปุ่มลัด",
"prompt": {
"keybind-options": {
"hotkey": "ปุ่มลัด"
},
"label": "เลือกปุ่มลัดเพื่อสลับโหมดภาพในภาพ",
"title": "ปุ่มลัดสำหรับโหมดภาพในภาพ"
}
},
"save-window-position": "บันทึกตำแหน่งหน้าต่าง",
"save-window-size": "บันทึกขนาดหน้าต่าง",
"use-native-pip": "ใช้โหมดภาพซ้อนภาพของเบราเซอร์"
},
"name": "โหมดภาพซ้อนภาพ",
"templates": {
"button": "เปิดโหมดภาพซ้อนภาพ"
}
},
"playback-speed": {
"description": "ฟังเพลงได้ทั้งช้าและเร็ว เพิ่มที่เลื่อนปรับความเร็วของเพลง",
"name": "ความเร็วเพลง",
"templates": {
"button": "ความเร็ว"
}
},
"precise-volume": {
"description": "ควบคุมระดับเสียงได้อย่างแม่นยำด้วยที่เลื่อนเมาส์หรือปุ่มลัด พร้อมด้วยหน้าจอแสดงข้อมูลและขั้นระดับเสียงที่ปรับแต่งได้",
"menu": {
"arrows-shortcuts": "ควบคุมด้วยปุ่มลูกศรเมื่ออยู่ในแอป",
"custom-volume-steps": "ตั้งขั้นการปรับระดับเสียง",
"global-shortcuts": "ตั้งปุ่มลัดที่ใช้ได้ทั้งระบบ"
},
"name": "ระดับเสียงแม่นยำ",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "ปุ่มลดระดับเสียง",
"increase": "ปุ่มเพิ่มระดับเสียง"
},
"label": "ตั้งค่าปุ่มลัดระดับเสียงที่ใช้ได้ทั้งระบบ:",
"title": "ปุ่มลัดระดับเสียงที่ใช้ได้ทั้งระบบ"
},
"volume-steps": {
"label": "ตั้งขั้นการเพิ่ม/ลดระดับเสียง",
"title": "ขั้นการปรับระดับเสียง"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "คุณภาพปัจจุบัน: {{quality}}",
"message": "เลือกคุณภาพวิดีโอ:",
"title": "เลือกคุณภาพวิดีโอ"
}
}
},
"description": "อนุญาตให้เปลี่ยนคุณภาพของวิดีโอด้วยปุ่มที่แสดงเหนือวิดีโอ",
"name": "ที่เปลี่ยนคุณภาพวิดีโอ",
"renderer": {
"quality-settings-button": {
"label": "เปิดตัวเปลี่ยนคุณภาพเครื่องเล่น"
}
}
},
"scrobbler": {
"description": "รองรับการบันทึกการเล่นเพลง (เช่น last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "เกิดปัญหาระหว่างเข้าใช้งาน Last.fm\nซ่อนข้อความนี้จนการรีสตาร์ตครั้งถัดไป",
"title": "เกิดปัญหาในการเข้าใช้งาน"
}
}
},
"menu": {
"lastfm": {
"api-settings": "การตั้งค่า API Last.fm"
},
"listenbrainz": {
"token": "ใส่ user token ของ ListenBrainz"
},
"scrobble-alternative-artist": "ใช้ชื่อศิลปินอื่น",
"scrobble-alternative-title": "ใช้ชื่ออื่น",
"scrobble-other-media": "บันทึกการเล่นสื่ออื่นๆ"
},
"name": "บันทึกการเล่น (Scrobbler)",
"prompt": {
"lastfm": {
"api-key": "API Key ของ Last.fm",
"api-secret": "API secret ของ Last.fm"
},
"listenbrainz": {
"token": {
"label": "ใส่ user token ListenBrainz ของคุณ:",
"title": "token ListenBrainz"
}
}
}
},
"shortcuts": {
"description": "อนุญาตให้ตั้งปุ่มลัดทั่วระบบสำหรับการเล่น (เล่น/หยุดชั่วคราว/ถัดไป/ก่อนหน้า) และปิดการแสดงผลสื่อโดยทับปุ่มควบคุมสื่อ เปิด Ctrl/CMD + F เพื่อค้นหา เปิดการใช้งานการควบคุมเพลงผ่าน MPRIS สำหรับ Linux และปุ่มลัดที่กำหนดเองได้สำหรับผู้ใช้ขั้นสูง",
"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": "ข้ามช่วงที่ไม่ใช่เพลงเช่น intro/outro หรือ ช่วงที่ไม่มีเพลงเล่นใน mv โดยอัตโนมัติ",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "ให้เนื้อเพลงที่ตรงกับเวลาของเพลง ผ่านผู้ให้บริการเช่น LRClib",
"errors": {
"fetch": "⚠️\tเกิดปัญหาในการดึงเนื้อเพลง\n\tกรุณาลองใหม่ภายหลัง",
"not-found": "⚠️ ไม่เจอเนื้อเพลงสำหรับเพลงนี้"
},
"menu": {
"default-text-string": {
"label": "อักขระคั่นระหว่างเนื้อเพลง",
"tooltip": "เลือกอักขระที่คั่นในช่วงที่อยู่ระหว่างเนื้อเพลง"
},
"line-effect": {
"label": "เอฟเฟกต์บรรทัด",
"submenu": {
"fancy": {
"label": "อลังการ",
"tooltip": "ใช้เอฟเฟกต์ใหญ่คล้ายบนแอพ บนบรรทัดปัจจุบัน"
},
"focus": {
"label": "เด่น",
"tooltip": "ทำให้แค่บรรทัดปัจจุบันสีขาว"
},
"offset": {
"label": "เลื่อน",
"tooltip": "เลื่อนบรรทัดปัจจุบันไปทางขวา"
},
"scale": {
"label": "ขยายขนาด",
"tooltip": "ขยายบรรทัดปัจจุบันให้ใหญ่ขึ้น"
}
},
"tooltip": "เลือกเอฟเฟกต์ที่จะใช้กับบรรทัดปัจจุบัน"
},
"precise-timing": {
"label": "ให้เนื้อเพลงตรงกับเพลงเป๊ะๆ",
"tooltip": "คำนวณมิลิวินาทีในการแสดงบรรทัดถัดไป (มีผลเล็กน้อยกับประสิทธิภาพการทำงาน)"
},
"preferred-provider": {
"label": "ผู้ให้บริการที่ต้องการ",
"none": {
"label": "ไม่มี",
"tooltip": "ไม่มีผู้ให้บริการที่ต้องการ"
},
"tooltip": "เลือกผู้ให้บริการที่ต้องการใช้งาน"
},
"romanization": {
"label": "เนื้อเพลงตัวด้วยอักษรโรมัน",
"tooltip": "ถ้าหากเนื้อเพลงอยู่ในภาษาอื่น ลองเปลี่ยนการแสดงผลโดยใช้เวอร์ชั่นลาติน"
},
"show-lyrics-even-if-inexact": {
"label": "แสดงเนื้อเพลงแม้ไม่ตรงเป๊ะ",
"tooltip": "ถ้าหาเนื้อเพลงไม่เจอจะลองหาด้วยคำค้นหาที่ต่างกัน\nอาจได้เนื้อเพลงไม่ตรง"
},
"show-time-codes": {
"label": "แสดงตำแหน่งเวลา",
"tooltip": "แสดงตำแหน่งเวลาข้างๆเนื้อเพลง"
}
},
"name": "เนื้อเพลงตรงกับเพลง",
"refetch-btn": {
"fetching": "กำลังดึงข้อมูล...",
"normal": "ดึงเนื้อเพลงใหม่"
},
"warnings": {
"duration-mismatch": "⚠️ - เนื้อเพลงอาจไม่ตรงกับเวลาเนื่องจากความยาวไม่ตรงกัน",
"inexact": "⚠️ - เนื้อเพลงอาจไม่ตรง",
"instrumental": "⚠️ - เพลงนี้เป็นเพลงบรรเลง"
}
},
"taskbar-mediacontrol": {
"description": "ควบคุมการเล่นผ่าน taskbar ของ Windows",
"name": "ควบคุมสื่อผ่าน Taskbar"
},
"touchbar": {
"description": "เพิ่ม Widget บน TouchBar สำหรับผู้ใช้ macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "ทำให้หน้าต่างของแอปโปร่งใส",
"menu": {
"opacity": {
"label": "ความทึบแสง",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "ประเภท",
"submenu": {
"acrylic": "อะคริลิก",
"mica": "ไมกา",
"none": "ไม่มี",
"tabbed": "Tabbed"
}
}
},
"name": "ที่เล่นเพลงโปร่งใส"
},
"tuna-obs": {
"description": "ใช้งานร่วมกันกับปลั้กอิน Tuna บน OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "ป้องกันเครื่องเล่นแสดงขึ้นเมื่อเริ่มเล่นเพลง",
"name": "เครื่องเล่นที่ไม่รบกวน"
},
"video-toggle": {
"description": "เพิ่มปุ่มสลับระหว่างโหมดเพลงกับโหมดวิดีโอ หรือลบแถบวิดีโอออกทั้งแถบ",
"menu": {
"align": {
"label": "ตำแหน่งปุ่ม",
"submenu": {
"left": "ซ้าย",
"middle": "กลาง",
"right": "ขวา"
}
},
"force-hide": "บังคับลบแถบวิดีโอ",
"mode": {
"label": "โหมด",
"submenu": {
"custom": "ปุ่มกำหนดเอง",
"disabled": "ปิด",
"native": "ปุ่มเริ่มต้น"
}
}
},
"name": "ปุ่มวิดีโอ",
"templates": {
"button-song": "เพลง",
"button-video": "วิดีโอ"
}
},
"visualizer": {
"description": "เพิ่มวิชวลไลเซอร์ให้ที่เล่นเพลง",
"menu": {
"visualizer-type": "ประเภทวิชวลไลเซอร์"
},
"name": "วิชวลไลเซอร์"
}
}
}
================================================
FILE: src/i18n/resources/tr.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "{{pluginName}}::{{contextName}} eklentisi çalıştırılamadı",
"executed-at-ms": "{{pluginName}}::{{contextName}} eklentisi {{ms}}ms'de çalıştırıldı",
"initialize-failed": "\"{{pluginName}}\" eklentisi başlatılamadı",
"load-all": "Tüm eklentiler yükleniyor",
"load-failed": "\"{{pluginName}}\" eklentisi yüklenemedi",
"loaded": "\"{{pluginName}}\" eklentisi yüklendi",
"unload-failed": "\"{{pluginName}}\" eklentisi çıkartılamadı",
"unloaded": "\"{{pluginName}}\" eklentisi çıkartıldı"
}
}
},
"language": {
"code": "tr",
"local-name": "Türkçe",
"name": "Turkish"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Yükleme tamamlandı. Geliştirici araçları açıldı"
},
"i18n": {
"loaded": "i18n yüklendi"
},
"second-instance": {
"receive-command": "Protokol üzerinden alınan komut: \"{{command}}\""
},
"theme": {
"css-file-not-found": "\"{{cssFile}}\" adlı CSS dosyası bulunamadı, yok sayılıyor"
},
"unresponsive": {
"details": "Yanıt verilmedi!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Uygulama ön belleği temizleniyor"
},
"window": {
"tried-to-render-offscreen": "Pencere ekranın dışında oluşturulmaya çalışıldı, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menü gizli, göstermek için 'Alt' tuşunu kullanın (veya Uygulama İçi Menüyü kullanıyorsanız 'Escape' tuşunu kullanın)",
"message": "Menüyü gizle etkinleştirildi",
"title": "Menüyü gizle etkinleştirildi"
},
"need-to-restart": {
"buttons": {
"later": "Daha Sonra",
"restart-now": "Şimdi yeniden başlat"
},
"detail": "\"{{pluginName}}\" eklentisinin çalışması için uygulamanın yeniden başlatılması gerekiyor",
"message": "\"{{pluginName}}\" eklentisi için uygulamanın yeniden başlatılması gerekiyor",
"title": "Uygulamanın yeniden başlatılması gerekiyor"
},
"unresponsive": {
"buttons": {
"quit": "Çıkış",
"relaunch": "Tekrar Başlat",
"wait": "Bekle"
},
"detail": "Rahatsızlık için özür dileriz! Lütfen ne yapacağınızı seçin:",
"message": "Uygulama yanıt vermiyor",
"title": "Pencere yanıt vermiyor"
},
"update-available": {
"buttons": {
"disable": "Güncellemeleri devre dışı bırak",
"download": "İndir",
"ok": "Tamam"
},
"detail": "Yeni bir sürüm mevcut. {{downloadLink}} adresi üzerinden indirebilirsin",
"message": "Yeni bir sürüm mevcut",
"title": "Güncelleme Mevcut"
}
},
"menu": {
"about": "Hakkında",
"navigation": {
"label": "Navigasyon",
"submenu": {
"copy-current-url": "Geçerli Url'yi kopyala",
"go-back": "Geri dön",
"go-forward": "İlerle",
"quit": "Çıkış",
"restart": "Uygulamayı Yeniden Başlat"
}
},
"options": {
"label": "Seçenekler",
"submenu": {
"advanced-options": {
"label": "Gelişmiş Seçenekler",
"submenu": {
"auto-reset-app-cache": "Uygulama başlatıldığında uygulama önbelleğini sıfırla",
"disable-hardware-acceleration": "Donanım hızlandırmayı devre dışı bırak",
"edit-config-json": "Düzenle config.json",
"override-user-agent": "\"User-Agent \"ı geçersiz kıl",
"restart-on-config-changes": "Yapılandırma değişikliğinde yeniden başlat",
"set-proxy": {
"label": "Proxy ayarla",
"prompt": {
"label": "Proxy Adresini Gir: (devre dışı bırakmak için boş bırakın)",
"placeholder": "Örnek: SOCKS5://127.0.0.1:9999",
"title": "Proxy ayarla"
}
},
"toggle-dev-tools": "DevTools'u Aç / Kapat"
}
},
"always-on-top": "Her zaman üstte",
"auto-update": "Otomatik Güncelleme",
"hide-menu": {
"dialog": {
"message": "Menü bir sonraki açılışta gizlenecektir, göstermek için [Alt] tuşunu kullanın (veya uygulama-içi-menü kullanıyorsanız [`] tuşuna geri basın)",
"title": "Gizli Menü Aktif"
},
"label": "Gizli Menü"
},
"language": {
"dialog": {
"message": "Dil değişikliği yeniden başlattıktan sonra etkinleşecektir",
"title": "Dil değiştirildi"
},
"label": "Dil",
"submenu": {
"to-help-translate": "Çeviriye yardım etmek ister misiniz? Buraya tıklayın"
}
},
"resume-on-start": "Uygulama başlatıldığında son şarkıyı devam ettir",
"single-instance-lock": "Tek Örnek Kilidi",
"start-at-login": "Başlangıçta çalıştır",
"starting-page": {
"label": "Başlangıç sayfası",
"unset": "Ayarlanmadı"
},
"tray": {
"label": "Tepsi",
"submenu": {
"disabled": "Devre Dışı",
"enabled-and-hide-app": "Uygulamayı etkinleştirin gizleyin",
"enabled-and-show-app": "Etkinleştir ve uygulamayı göster",
"play-pause-on-click": "Tıklandığında Oynat/Duraklat"
}
},
"visual-tweaks": {
"label": "Görsel İnce Ayarlar",
"submenu": {
"custom-window-title": {
"label": "Özel pencere başlığı",
"prompt": {
"label": "Özel pencere başlığı girin: (devre dışı bırakmak için boş bırakın)",
"placeholder": "Örnek: {{applicationName}}"
}
},
"like-buttons": {
"default": "Varsayılan",
"force-show": "Zorla göster",
"hide": "Gizle",
"label": "Beğenme düğmeleri",
"swap": "Beğenme butonlarının sıralamasını değiştir"
},
"remove-upgrade-button": "Yükseltme düğmesini kaldır",
"theme": {
"dialog": {
"button": {
"cancel": "İptal",
"remove": "Kaldır"
},
"remove-theme": "Özel temayı kaldırmak istediğinizden emin misiniz?",
"remove-theme-message": "Bu işlem özel temayı kaldıracaktır"
},
"label": "Tema",
"submenu": {
"import-css-file": "Özel CSS dosyanı içeri aktar",
"no-theme": "Tema Yok"
}
}
}
}
}
},
"plugins": {
"enabled": "Aktif",
"label": "Eklentiler",
"new": "YENİ"
},
"view": {
"label": "Görüntü",
"submenu": {
"force-reload": "Zorla yeniden başlat",
"reload": "Yeniden Başlat",
"reset-zoom": "Asıl Boyut",
"toggle-fullscreen": "Tam Ekran'a Geçiş",
"zoom-in": "Yakınlaştır",
"zoom-out": "Uzaklaştır"
}
}
},
"tray": {
"next": "Sonraki",
"play-pause": "Oynat/Durdur",
"previous": "Önceki",
"quit": "Çıkış",
"restart": "Yeniden başlat",
"show": "Pencereyi görüntüle",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Bir reklam oynatılırsa sesi kapatır ve oynatma hızını 16x olarak ayarlar",
"name": "Reklam Hızlandırma"
},
"adblocker": {
"description": "Tüm reklamları ve izleyicileri engelle",
"menu": {
"blocker": "Engelleyici"
},
"name": "Reklam Engelleyici"
},
"album-actions": {
"description": "Çalma listesindeki veya albümdeki tüm şarkılara Beğendim ve Beğenmedim düğmeleri ekler",
"name": "Albüm Eylemleri"
},
"album-color-theme": {
"description": "Albümün renk paletine dayalı dinamik bir tema ve efektler uygular",
"menu": {
"color-mix-ratio": {
"label": "Renk karışım oranı",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "Arama çubuğu temalarını etkinleştir"
},
"name": "Albüm Renk Teması"
},
"ambient-mode": {
"description": "Videodaki yumuşak renkleri ekranınızın arka planına yansıtarak bir ışık efekti uygular",
"menu": {
"blur-amount": {
"label": "Bulanıklık miktarı",
"submenu": {
"pixels": "{{blurAmount}} piksel"
}
},
"buffer": {
"label": "Önbellek",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Opaklık Miktarı",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kalite",
"submenu": {
"pixels": "{{quality}} piksel"
}
},
"size": {
"label": "Boyut",
"submenu": {
"percent": "{{size}}%"
}
},
"smoothness-transition": {
"label": "Yumuşak Geçiş",
"submenu": {
"during": "{{interpolationTime}} saniye boyunca"
}
},
"use-fullscreen": {
"label": "Tam ekran kullanılıyor"
}
},
"name": "Ambiyans Modu"
},
"amuse": {
"description": "6K Labs'ın Amuse oynatma widget'ı için {{applicationName}} desteği ekler",
"name": "Amuse",
"response": {
"query": "Amuse API sunucusu çalışıyor. Şarkı bilgilerini almak için GET /query kullanabilirsiniz."
}
},
"api-server": {
"description": "Oynatıcıyı kontrol etmek için API sunucusu ekler",
"dialog": {
"request": {
"buttons": {
"allow": "İzin ver",
"deny": "Reddet"
},
"message": "{{ID}} ({{origin}}) 'nın APIye erişmesine izin verilsin mi?",
"title": "APİ yetkilendirme isteği"
}
},
"menu": {
"auth-strategy": {
"label": "Yetkilendirme stratejisi",
"submenu": {
"auth-at-first": {
"label": "İlk istekte yetkilendir"
},
"none": {
"label": "Yetkilendirme Yok"
}
}
},
"hostname": {
"label": "Ana bilgisayar adı"
},
"https": {
"label": "HTTPS & Sertifikalar",
"submenu": {
"cert": {
"dialogTitle": "HTTPS sertifika dosyası seç",
"label": "Sertifika dosyası (.crt/.pem)"
},
"enable-https": {
"label": "HTTPS'i aktifleştir"
},
"key": {
"dialogTitle": "HTTPS özel anahtar dosyası seç",
"label": "Özel anahtar dosyası (.key/.pem)"
}
}
},
"port": {
"label": "Port"
}
},
"name": "API Sunucusu [Beta]",
"prompt": {
"hostname": {
"label": "API sunucusu için sunucu ismini (örneğin 0.0.0.0) girin:",
"title": "Sunucu ismi"
},
"port": {
"label": "API sunucusu için port girin:",
"title": "Bağlantı Noktası"
}
}
},
"audio-compressor": {
"description": "Ses sıkıştırma (dalganın en gürültülü bölümlerinin ses düzeyini azaltır ve daha yumuşak bölümlerin ses düzeyini artırır)",
"name": "Ses Sıkıştırma"
},
"auth-proxy-adapter": {
"description": "Kimlik doğrulama proxy hizmetlerinin kullanımına destek",
"menu": {
"disable": "Proxy Bağdaştırıcısını Devre Dışı Bırak",
"enable": "Proxy Bağdaştırıcısını Etkinleştir",
"hostname": {
"label": "Ana makine adı"
},
"port": {
"label": "Port"
}
},
"name": "Ara Sunucu Doğrulama Bağdaştırıcısı",
"prompt": {
"hostname": {
"label": "Yerel proxy sunucusu için ana makine adını girin (yeniden başlatma gerektirir):",
"title": "Proxy Ana Makine Adı"
},
"port": {
"label": "Yerel proxy sunucusu için bağlantı noktasını girin (yeniden başlatma gerektirir):",
"title": "Proxy Bağlantı Noktası"
}
}
},
"blur-nav-bar": {
"description": "Gezinme çubuğunu şeffaf ve bulanık yapar",
"name": "Navigasyon barını bulanıklaştır"
},
"bypass-age-restrictions": {
"description": "Music Player yaş doğrulamasını atla",
"name": "Yaş doğrulamasını atla"
},
"captions-selector": {
"description": "{{applicationName}} için altyazı seçici",
"menu": {
"autoload": "Son kullanılan altyazıyı otomatik olarak seç",
"disable-captions": "Varsayılan olarak altyazı yok"
},
"name": "Altyazı Seçici",
"prompt": {
"selector": {
"label": "Geçerli altyazı dili: {{language}}",
"none": "Hiçbiri",
"title": "Altyazı dilini seç"
}
},
"templates": {
"title": "Altyazı seçiciyi aç"
},
"toast": {
"caption-changed": "Başlık {{language}} olarak değiştirildi",
"caption-disabled": "Altyazılar devre dışı",
"no-captions": "Bu şarkı için altyazı mevcut değil"
}
},
"clock": {
"description": "Navigasyon barına bir saat ekle",
"menu": {
"format": {
"24-hour-format": "24-Saat Formatı",
"display-seconds": "Saniyeleri Göster",
"label": "Format"
}
},
"name": "Saat"
},
"compact-sidebar": {
"description": "Her zaman kompakt kenar çubugu",
"name": "Kompakt Kenar Çubuğu"
},
"crossfade": {
"description": "Şarkılar arasında Çapraz Geçiş",
"menu": {
"advanced": "Gelişmiş"
},
"name": "Çapraz Geçiş [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Güçlenme süresi (ms)",
"fade-out-duration": "Zayıflama süresi (ms)",
"fade-scaling": {
"label": "Zayıflama Ölçeği",
"linear": "Doğrusal",
"logarithmic": "Logaritmik"
},
"seconds-before-end": "Bitişten N saniye önce çapraz geçiş"
},
"title": "Çapraz Geçiş ayarları"
}
}
},
"custom-output-device": {
"description": "Şarkılar için özel bir medya çıkış aygıtı ayarlayın",
"menu": {
"device-selector": "Aygıt Seçin"
},
"name": "Özel Çıkış Aygıtı",
"prompt": {
"device-selector": {
"label": "Kullanılacak medya çıkış aygıtını seçin",
"title": "Çıkış Aygıtını Seçin"
}
}
},
"disable-autoplay": {
"description": "Şarkıların otomatik olarak duraklatılmasını sağlar",
"menu": {
"apply-once": "Yalnızca ilk şarkı için geçerlidir"
},
"name": "Otomatik oynatmayı devre dışı bırak"
},
"discord": {
"backend": {
"already-connected": "Aktif bağlantı olduğu halde bağlantı kurulmaya çalışıldı",
"connected": "Discord'a bağlandı",
"disconnected": "Discord ile bağlantı kesildi"
},
"description": "Rich Presence ile Discord'da ne dinlediğinizi gösterin",
"menu": {
"auto-reconnect": "Otomatik yeniden bağlan",
"clear-activity": "Etkinliği temizle",
"clear-activity-after-timeout": "Zaman aşımından sonra etkinliği temizle",
"connected": "Bağlı",
"disconnected": "Bağlantı kesildi",
"hide-duration-left": "Kalan süreyi gizle",
"hide-github-button": "GitHub bağlantısını gizle",
"play-on-application": "{{applicationName}} de oynat",
"set-inactivity-timeout": "Hareketsizlik zaman aşımını ayarla",
"set-status-display-type": {
"label": "Durum metni",
"submenu": {
"application": "{{applicationName}} Dinleniyor",
"artist": "{artist} Dinleniyor",
"title": "{song title} Dinleniyor"
}
}
},
"name": "Discord Etkinlik Durumu",
"prompt": {
"set-inactivity-timeout": {
"label": "Hareketsizlik zaman aşımını saniye cinsinden girin:",
"title": "Hareketsizlik zaman aşımını ayarla"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Tamam"
},
"message": "Argh! Özür dilerim, indirme başarısız oldu…",
"title": "İndirme sırasında bir hata meydana geldi!"
},
"start-download-playlist": {
"buttons": {
"ok": "Tamam"
},
"detail": "({{playlistSize}} şarkı)",
"message": "Oynatma listesini indir : {{playlistTitle}}",
"title": "İndirme Başladı"
}
},
"feedback": {
"conversion-progress": "Dönüştürme : {{percent}}%",
"converting": "Dönüştürülüyor…",
"done": "Tamamlandı: {{filePath}}",
"download-info": "{{artist}} - {{title}} [{{videoId}} indiriliyor",
"download-progress": "İndirme : {{percent}}%",
"downloading": "İndiriliyor…",
"downloading-counter": "İndiriliyor {{current}}/{{total}}…",
"downloading-playlist": "\"{{playlistTitle}}\" şarkı listesi indiriliyor - {{playlistSize}} şarkı ({{playlistId}})",
"error-while-downloading": "\"{{author}} - {{title}}\" indirilirken hata oluştu: {{error}}",
"folder-already-exists": "{{playlistFolder}} klasörü zaten mevcut",
"getting-playlist-info": "Oynatma listesi bilgisi alınıyor…",
"loading": "Yükleniyor…",
"playlist-has-only-one-song": "Oynatma listesinde yalnızca bir şarkı var, doğrudan indiriliyor",
"playlist-id-not-found": "Oynatma listesi ID'si bulunamadı",
"playlist-is-empty": "Oynatma listesi boş",
"playlist-is-mix-or-private": "Çalma listesi bilgisi alınırken hata oluştu: özel veya \"Size özel karışık\" bir çalma listesi olmadığından emin olun\n\n{{error}}",
"preparing-file": "Dosya Hazırlanıyor…",
"saving": "Kaydediliyor…",
"trying-to-get-playlist-id": "Çalma listesi ID'si alınmaya çalışılıyor: {{playlistId}}",
"video-id-not-found": "Video bulunamadı",
"writing-id3": "ID3 etiketleri yazılıyor…"
}
},
"description": "MP3 / kaynak sesini doğrudan arayüzden indir",
"menu": {
"choose-download-folder": "İndirme klasörünü seç",
"download-finish-settings": {
"label": "Bittiğinde indir",
"prompt": {
"last-percent": "Yüzde x'ten sonra",
"last-seconds": "Son x saniyede",
"title": "Ne zaman indirileceğini ayarla"
},
"submenu": {
"advanced": "Gelişmiş",
"enabled": "Etkin",
"mode": "Zaman türü",
"percent": "Yüzde",
"seconds": "Saniye"
}
},
"download-playlist": "Oynatma listesini indir",
"presets": "Hazır Ayarlar",
"skip-existing": "Mevcut dosyaları atla"
},
"name": "İndirici",
"renderer": {
"can-not-update-progress": "İlerleme güncellenemiyor"
},
"templates": {
"button": "İndir"
}
},
"equalizer": {
"description": "Oynatıcıya ekolayzer desteği ekler",
"menu": {
"presets": {
"label": "Ön Ayarlar",
"list": {
"bass-booster": "Bass güçlendirici"
}
}
},
"name": "Ekolayzer"
},
"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",
"templates": {
"back": {
"title": "Önceki sayfaya git"
},
"forward": {
"title": "Sonraki sayfaya geç"
}
}
},
"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"
},
"performance-improvement": {
"description": "Deneysel komut dosyalarını etkinleştirerek performansı artırın",
"name": "Performans iyileştirmesi [Beta]"
},
"picture-in-picture": {
"description": "Uygulamayı resim-içinde-resim moduna geçirmeye izin verir",
"menu": {
"always-on-top": "Her zaman üstte",
"hotkey": {
"label": "Kısayol",
"prompt": {
"keybind-options": {
"hotkey": "Kısayol"
},
"label": "Resim-içinde-resim arasında geçiş yapmak için bir kısayol tuşu seçin",
"title": "Resim-içinde-resim Kısayol Tuşu"
}
},
"save-window-position": "Pencere konumunu kaydet",
"save-window-size": "Pencere boyutunu kaydet",
"use-native-pip": "Tarayıcı yerel PiP'sini kullan"
},
"name": "Resim-içinde-resim",
"templates": {
"button": "Resim-içinde-resim"
}
},
"playback-speed": {
"description": "Hızlı dinle, yavaş dinle! Şarkı hızını kontrol eden bir kaydırıcı ekler",
"name": "Oynatma Hızı",
"templates": {
"button": "Hız"
}
},
"precise-volume": {
"description": "Özel bir HUD ve özelleştirilebilir ses seviyesi adımları ile fare tekerleği / kısayol tuşlarını kullanarak ses seviyesini hassas bir şekilde kontrol edin",
"menu": {
"arrows-shortcuts": "Yerel Ok Tuşu Kontrolleri",
"custom-volume-steps": "Özel Ses Seviyesi Adımlarını Ayarlama",
"global-shortcuts": "Genel Kısayol Tuşları"
},
"name": "Hassas Ses Seviyesi",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Ses Seviyesi Azaltma",
"increase": "Ses Seviyesi Yükseltme"
},
"label": "Genel Ses Tuş Atamalarını seçin:",
"title": "Genel Ses Tuş Atamaları"
},
"volume-steps": {
"label": "Ses Artırma/Azaltma Kademelerini Seçin",
"title": "Ses Kademeleri"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Mevcut Kalite: {{quality}}",
"message": "Video Kalitesini Seçin:",
"title": "Video Kalitesini Seçin"
}
}
},
"description": "Video katmanı üzerindeki bir düğme ile video kalitesinin değiştirilmesine izin verir",
"name": "Video Kalitesi Değiştirici",
"renderer": {
"quality-settings-button": {
"label": "Açık oynatıcı kalite 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-alternative-artist": "Alternatif sanatçıları kullan",
"scrobble-alternative-title": "Alternatif başlıklar kullan",
"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"
},
"synced-lyrics": {
"description": "LRClib gibi sağlayıcıları kullanarak şarkılara senkronize şarkı sözleri sağlar.",
"errors": {
"fetch": "⚠️ \tŞarkı sözleri alınırken bir hata oluştu.\n\tLütfen daha sonra tekrar deneyin.",
"not-found": "⚠️ Bu şarkı için şarkı sözleri bulunamadı."
},
"menu": {
"convert-chinese-character": {
"label": "Çince karakterleri çevir",
"submenu": {
"disabled": {
"label": "Devre dışı bırak",
"tooltip": "Çince karakterleri çevirmeyi devre dışı bırak"
},
"simplified-to-traditional": {
"label": "Basitleştirilmişten Geleneksele",
"tooltip": "Basitleştirilmiş Ç*nceyi Geleneksel Ç*nceye Çevir"
},
"traditional-to-simplified": {
"label": "Gelenekselden Basitleştirilmişe",
"tooltip": "Geleneksel Çince'yi Basitleştirilmiş Çince'ye dönüştür"
}
},
"tooltip": "Çince karakterleri Geleneksel veya Basitleştirilmiş Çinceye dönüştür"
},
"default-text-string": {
"label": "Şarkı sözleri arasında varsayılan karakter",
"tooltip": "Şarkı sözleri arasındaki boşluk için kullanılacak varsayılan karakteri seçin"
},
"line-effect": {
"label": "Çizgi etkisi",
"submenu": {
"fancy": {
"label": "Süslü",
"tooltip": "Mevcut satırda büyük, uygulama benzeri efektler kullan"
},
"focus": {
"label": "odak",
"tooltip": "Yalnızca geçerli satırı beyaz yapın"
},
"offset": {
"label": "telafi etmek,ofset",
"tooltip": "Geçerli satırın sağındaki ofset"
},
"scale": {
"label": "ölçek",
"tooltip": "Geçerli satırı ölçeklendirir"
}
},
"tooltip": "Geçerli satıra uygulanacak efekti seçin"
},
"precise-timing": {
"label": "Şarkı sözlerini mükemmel şekilde senkronize edin",
"tooltip": "Bir sonraki satırın görüntülenmesini milisaniyesine kadar hesaplayın (performans üzerinde küçük bir etkisi olabilir)"
},
"preferred-provider": {
"label": "Tercih edilen Sağlayıcı",
"none": {
"label": "Hiçbiri",
"tooltip": "Varsayılan sağlayıcı yok"
},
"tooltip": "Kullanmak İçin varsayılan sağlayıcıyı seçin"
},
"romanization": {
"label": "Sözleri Romanize Et",
"tooltip": "Sözler başka bir dilde gözüküyorsa, Latin versiyonunu dene."
},
"show-lyrics-even-if-inexact": {
"label": "Kesin olmasa bile şarkı sözlerini gösterin",
"tooltip": "Şarkı bulunamazsa, eklenti farklı bir arama sorgusuyla tekrar dener. \nİkinci denemenin sonucu tam olmayabilir."
},
"show-time-codes": {
"label": "Zaman kodlarını göster",
"tooltip": "Şarkı sözlerinin yanında zaman kodlarını gösterin"
}
},
"name": "Senkronize Şarkı Sözleri",
"refetch-btn": {
"fetching": "Getiriliyor...",
"normal": "Refetch şarkı sözleri"
},
"warnings": {
"duration-mismatch": "⚠️ - Süre uyuşmazlığı nedeniyle şarkı sözleri senkronize olmayabilir.",
"inexact": "⚠️ - Bu şarkının sözleri tam olmayabilir",
"instrumental": "⚠️ - Bu enstrümantal bir şarkıdır"
}
},
"taskbar-mediacontrol": {
"description": "Windows görev çubuğu üzerinden oynatmayı kontrol edebilmenize olanak sağlar",
"name": "Görev Çubuğu Medya Kontrolü"
},
"touchbar": {
"description": "macOS kullanıcıları için bir TouchBar widget'ı ekler",
"name": "TouchBar"
},
"transparent-player": {
"description": "Uygulama penceresini şeffaf yapar",
"menu": {
"opacity": {
"label": "Opaklık",
"submenu": {
"percent": "%{{opacity}}"
}
},
"type": {
"label": "Tür",
"submenu": {
"acrylic": "Akrilik",
"mica": "Mika",
"none": "Hiçbiri",
"tabbed": "Sekmeli"
}
}
},
"name": "Şeffaf Oynatıcı"
},
"tuna-obs": {
"description": "OBS eklentisi Tuna ile entegrasyon sağlar",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Müzik oynatıcının şarkı çalarken saçmalamasını engeller",
"name": "Göze Batmayan Çalar"
},
"video-toggle": {
"description": "Video/Şarkı modu arasında geçiş yapmak için bir düğme ekler. ayrıca isteğe bağlı olarak tüm video sekmesini kaldırabilir",
"menu": {
"align": {
"label": "Hizalama",
"submenu": {
"left": "Sol",
"middle": "Orta",
"right": "Sağ"
}
},
"force-hide": "Video sekmesini kaldırmaya zorla",
"mode": {
"label": "Mod",
"submenu": {
"custom": "Özel Ayar",
"disabled": "Devre dışı",
"native": "Yerel geçiş"
}
}
},
"name": "Video Geçiş",
"templates": {
"button-song": "Şarkı",
"button-video": "Video"
}
},
"visualizer": {
"description": "Oynatıcıya bir görselleştirici ekler",
"menu": {
"visualizer-type": "Görselleştirici Tipi"
},
"name": "Görselleştirici"
}
}
}
================================================
FILE: src/i18n/resources/uk.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Не вдалося виконати плагін {{pluginName}}::{{contextName}}",
"executed-at-ms": "Плагін {{pluginName}}::{{contextName}} запущено за {{ms}}ms",
"initialize-failed": "Не вдалося ініціалізувати плагін \"{{pluginName}}\"",
"load-all": "Завантаження всіх плагінів",
"load-failed": "Не вдалося завантажити плагін \"{{pluginName}}\"",
"loaded": "Плагін \"{{pluginName}}\" завантажено",
"unload-failed": "Не вдалося вивантажити плагін \"{{pluginName}}\"",
"unloaded": "Плагін \"{{pluginName}}\" вивантажено"
}
}
},
"language": {
"code": "uk",
"local-name": "Українська",
"name": "Ukrainian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Завантаження завершено. Відкрито DevTools"
},
"i18n": {
"loaded": "i18n завантажено"
},
"second-instance": {
"receive-command": "Отримано команду за протоколом: \"{{command}}\""
},
"theme": {
"css-file-not-found": "Файл CSS \"{{cssFile}}\" не існує, ігноруємо"
},
"unresponsive": {
"details": "Невідповідна помилка!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Очищення кешу програми"
},
"window": {
"tried-to-render-offscreen": "Вікно намагалися відобразитися поза екраном, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Меню приховано, використовуйте \"Alt\", щоб показати його (або \"Escape\", якщо ви використовуєте меню в додатку)",
"message": "Приховане меню увімкнено",
"title": "Увімкнути Приховане Меню"
},
"need-to-restart": {
"buttons": {
"later": "Пізніше",
"restart-now": "Перезапустити зараз"
},
"detail": "\"{{pluginName}}\" плагін потребує перезапуску, щоб набути чинності",
"message": "\"{{pluginName}}\" необхідно перезапустити",
"title": "Потрібен перезапуск"
},
"unresponsive": {
"buttons": {
"quit": "Вийти",
"relaunch": "Перезапустити",
"wait": "Чекати"
},
"detail": "Перепрошуємо за незручності! Будь ласка, оберіть, що робити:",
"message": "Програма не відповідає",
"title": "Вікно не відповідає"
},
"update-available": {
"buttons": {
"disable": "Вимкнути оновлення",
"download": "Завантажити",
"ok": "Так"
},
"detail": "Доступна нова версія, яку можна завантажити за посиланням {{downloadLink}}",
"message": "Доступна нова версія",
"title": "Доступне оновлення"
}
},
"menu": {
"about": "Про нас",
"navigation": {
"label": "Навігація",
"submenu": {
"copy-current-url": "Копіювати поточну URL-адресу",
"go-back": "Назад",
"go-forward": "Вперед",
"quit": "Вихід",
"restart": "Перезапустити програму"
}
},
"options": {
"label": "Параметри",
"submenu": {
"advanced-options": {
"label": "Додаткові опції",
"submenu": {
"auto-reset-app-cache": "Очищення кешу програми при її запуску",
"disable-hardware-acceleration": "Вимкнути апаратне прискорення",
"edit-config-json": "Редагувати config.json",
"override-user-agent": "Змінити User-Agent",
"restart-on-config-changes": "Перезапуск після зміни конфігурації",
"set-proxy": {
"label": "Задати проксі",
"prompt": {
"label": "Введіть адресу проксі-сервера: (залиште порожнім, щоб вимкнути)",
"placeholder": "Приклад: SOCKS5://127.0.0.1:9999",
"title": "Задати проксі"
}
},
"toggle-dev-tools": "Перемкнути DevTools"
}
},
"always-on-top": "Завжди зверху",
"auto-update": "Автооновлення",
"hide-menu": {
"dialog": {
"message": "Меню буде приховано при наступному запуску, використовуйте [Alt], щоб показати його (або клавішу [`], якщо ви використовуєте вбудоване меню)",
"title": "Приховане меню Увімкнено"
},
"label": "Приховане меню"
},
"language": {
"dialog": {
"message": "Мова буде змінена після перезапуску",
"title": "Мову змінено"
},
"label": "Мова",
"submenu": {
"to-help-translate": "Хочете допомогти з перекладом? Клацніть тут"
}
},
"resume-on-start": "Відновлювати останню пісню при запуску програми",
"single-instance-lock": "Заблокувати єдиним інстансом",
"start-at-login": "Запустити при вході в систему",
"starting-page": {
"label": "Початкова сторінка",
"unset": "Не задано"
},
"tray": {
"label": "Таця",
"submenu": {
"disabled": "Вимкнено",
"enabled-and-hide-app": "Запустити та приховати програму",
"enabled-and-show-app": "Запустити та відобразити додаток",
"play-pause-on-click": "Відтворення/Пауза за натисканням"
}
},
"visual-tweaks": {
"label": "Візуальні налаштування",
"submenu": {
"custom-window-title": {
"label": "Налаштування заголовка вікна",
"prompt": {
"label": "Введіть власний заголовок вікна: (залиште порожнім, щоб вимкнути)",
"placeholder": "Приклад: {{applicationName}}"
}
},
"like-buttons": {
"default": "За замовчуванням",
"force-show": "Завжди показувати",
"hide": "Приховати",
"label": "Як кнопки"
},
"remove-upgrade-button": "Прибрати кнопку оновлення",
"theme": {
"dialog": {
"button": {
"cancel": "Скасувати",
"remove": "Видалити"
},
"remove-theme": "Ви впевнені, що хочете видалити власну тему?",
"remove-theme-message": "Це видалить власну тему"
},
"label": "Тема",
"submenu": {
"import-css-file": "Імпортувати власний CSS файл",
"no-theme": "Без теми"
}
}
}
}
}
},
"plugins": {
"enabled": "Увімкнено",
"label": "Плагіни",
"new": "НОВЕ"
},
"view": {
"label": "Вид",
"submenu": {
"force-reload": "Примусове перезавантаження",
"reload": "Перезавантажити",
"reset-zoom": "Фактичний розмір",
"toggle-fullscreen": "Включити повноекранний режим",
"zoom-in": "Збільшити",
"zoom-out": "Зменшити"
}
}
},
"tray": {
"next": "Далі",
"play-pause": "Відтворення/Пауза",
"previous": "Попередній",
"quit": "Вихід",
"restart": "Перезапустити програму",
"show": "Показати вікно",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "При програванні реклами звук вимикається і встановлюється швидкість відтворення 16х",
"name": "Пришвидшення реклами"
},
"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": "Режим навколишнього середовища"
},
"amuse": {
"description": "Додає підтримку {{applicationName}} для віджета Amuse now playing від 6K Labs",
"name": "Amuse",
"response": {
"query": "Сервер Amuse API запущено. Запит GET /query для отримання інформації про пісню."
}
},
"api-server": {
"description": "Додає API сервер для контролю плеєра",
"dialog": {
"request": {
"buttons": {
"allow": "Дозволити",
"deny": "Відмінити"
},
"message": "Дозволити {{ID}} ({{origin}}) доступ до API?",
"title": "Запит авторизації до API"
}
},
"menu": {
"auth-strategy": {
"label": "Стратегія авторизації",
"submenu": {
"auth-at-first": {
"label": "Авторизувати при першому запиті"
},
"none": {
"label": "Немає авторизації"
}
}
},
"hostname": {
"label": "Назва серверу"
},
"port": {
"label": "Порт"
}
},
"name": "API сервер [Бета]",
"prompt": {
"hostname": {
"label": "Введіть ім'я хоста (наприклад 0.0.0.0) для API серверу:",
"title": "Ім'я хоста"
},
"port": {
"label": "Введіть порт API серверу:",
"title": "Порт"
}
}
},
"audio-compressor": {
"description": "Застосувати стиснення аудіо (зменшити гучність найгучніших фрагментів сигналу та збільшити гучність тихих фрагментів)",
"name": "Аудіокомпресор"
},
"auth-proxy-adapter": {
"description": "Підтримка використання проксі-сервісів з аутентифікацією",
"menu": {
"disable": "Вимкнути проксі-адаптер",
"enable": "Ввімкнути проксі-адаптер",
"hostname": {
"label": "Ім'я хоста"
},
"port": {
"label": "Порт"
}
},
"name": "Адаптер проксі-авторизації",
"prompt": {
"hostname": {
"label": "Введіть ім'я хоста для локального проксі сервера(вимагає перезапуск):",
"title": "Назва проксі-хоста"
},
"port": {
"label": "Введіть порт для локального проксі серверу (потрібен рестарт):",
"title": "Порт проксі"
}
}
},
"blur-nav-bar": {
"description": "Робить панель навігації прозорою та розмитою",
"name": "Розмиття панелі навігації"
},
"bypass-age-restrictions": {
"description": "Обхід перевірки віку на Music Player",
"name": "Обхід вікових обмежень"
},
"captions-selector": {
"description": "Вибір субтитрів для аудіодоріжок {{applicationName}}",
"menu": {
"autoload": "Автоматичний вибір останніх використаних субтитрів",
"disable-captions": "За замовчуванням субтитри відсутні"
},
"name": "Вибір субтитрів",
"prompt": {
"selector": {
"label": "Поточна мова субтитрів: {{language}}",
"none": "Нема",
"title": "Виберіть мову субтитрів"
}
},
"templates": {
"title": "Відкрити селектор субтитрів"
},
"toast": {
"caption-changed": "Субтитри змінені на {{language}}",
"caption-disabled": "Субтитри відключені",
"no-captions": "Субтитри не доступні для цієї пісні"
}
},
"compact-sidebar": {
"description": "Бічна панель завжди в компактному режимі",
"name": "Компактна бічна панель"
},
"crossfade": {
"description": "Плавний перехід між піснями",
"menu": {
"advanced": "Розширене"
},
"name": "Плавний перехід[Бета]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Тривалість згасання (мс)",
"fade-out-duration": "Тривалість згасання (ms)",
"fade-scaling": {
"label": "Згладжування масштабування",
"linear": "Лінійна",
"logarithmic": "Логарифмічна"
},
"seconds-before-end": "Плавний перехід за N секунд до кінця"
},
"title": "Параметри плавного переходу"
}
}
},
"custom-output-device": {
"description": "Налаштування власного пристрою для відтворення пісень",
"menu": {
"device-selector": "Обрати пристрій"
},
"name": "Спеціальний пристрій виводу",
"prompt": {
"device-selector": {
"label": "Оберіть пристрій для виводу медіа",
"title": "Оберіть пристрій виводу"
}
}
},
"disable-autoplay": {
"description": "Запуск пісні в режимі \"пауза\"",
"menu": {
"apply-once": "Застосовується тільки при запуску"
},
"name": "Вимкнути автовідтворення"
},
"discord": {
"backend": {
"already-connected": "Спроба підключення при активному з'єднанні",
"connected": "Під'єднано до Discord",
"disconnected": "Від'єднано від Discord"
},
"description": "Покажіть друзям, що ви слухаєте за допомогою Rich Presence",
"menu": {
"auto-reconnect": "Автоматичне перепідключення",
"clear-activity": "Очистити активність",
"clear-activity-after-timeout": "Очистити активність після тайм-ауту",
"connected": "Під'єднано",
"disconnected": "Від'єднано",
"hide-duration-left": "Приховати тривалість, яка залишилася",
"hide-github-button": "Приховати посилання на GitHub",
"play-on-application": "Слухати на {{applicationName}}",
"set-inactivity-timeout": "Встановити тайм-аут бездіяльності",
"set-status-display-type": {
"label": "Статус",
"submenu": {
"artist": "Ви слухаєте {artist}",
"application": "Відтворення з {{applicationName}}",
"title": "Ви слухаєте {song title}"
}
}
},
"name": "Активність Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Введіть тайм-аут бездіяльності в секундах:",
"title": "Встановлення тайм-ауту бездіяльності"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "Добре"
},
"message": "Йой! При завантаженні виникла помилка…",
"title": "Помилка при завантаженні!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} пісень)",
"message": "Завантаження списку відтворення {{playlistTitle}}",
"title": "Завантаження розпочато"
}
},
"feedback": {
"conversion-progress": "Конвертація: {{percent}}%",
"converting": "Конвертація…",
"done": "Готово: {{filePath}}",
"download-info": "Завантаження {{artist}} - {{title}} [{{videoId}}]",
"download-progress": "Завантажено: {{percent}}%",
"downloading": "Завантаження…",
"downloading-counter": "Завантажено {{current}}/{{total}}…",
"downloading-playlist": "Завантаження плейлисту \"{{playlistTitle}}\" - {{playlistSize}} пісень ({{playlistId}})",
"error-while-downloading": "Помилка завантаження \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Папка {{playlistFolder}} вже існує",
"getting-playlist-info": "Отримання інформації про плейлист…",
"loading": "Завантаження…",
"playlist-has-only-one-song": "Плейлист має лише один елемент, завантаження якого відбувається безпосередньо",
"playlist-id-not-found": "Ідентифікатор плейлиста не знайдено",
"playlist-is-empty": "Плейлист порожній",
"playlist-is-mix-or-private": "Помилка при отриманні інформації про плейлист: переконайтеся, що це не приватний плейлист або плейлист \"Мікс для вас\"\n\n{{error}}",
"preparing-file": "Готуємо файл…",
"saving": "Збереження…",
"trying-to-get-playlist-id": "Спроба отримати ідентифікатора плейлиста: {{playlistId}}",
"video-id-not-found": "Відео не знайдено",
"writing-id3": "Пишемо ID3-теги…"
}
},
"description": "Завантажує MP3 / джерело аудіо безпосередньо з інтерфейсу",
"menu": {
"choose-download-folder": "Оберіть папку для завантаження",
"download-finish-settings": {
"label": "Скачати по завершенню",
"prompt": {
"last-percent": "Після Х відсотків",
"last-seconds": "Останні Х секунд",
"title": "Налаштувати коли завантажувати"
},
"submenu": {
"advanced": "Розширені",
"enabled": "Увімкнено",
"mode": "Режим часу",
"percent": "Відсоток",
"seconds": "Секунди"
}
},
"download-playlist": "Завантажити плейлист",
"presets": "Попередні налаштування",
"skip-existing": "Пропустити наявні файли"
},
"name": "Завантажувач",
"renderer": {
"can-not-update-progress": "Неможливо оновити прогрес"
},
"templates": {
"button": "Завантажити"
}
},
"equalizer": {
"description": "Додає еквалайзер до програвача",
"menu": {
"presets": {
"label": "Шаблони",
"list": {
"bass-booster": "Підсилювач басів"
}
}
},
"name": "Еквалайзер"
},
"exponential-volume": {
"description": "Робить регулятор гучності експоненціальним, що полегшує вибір тихих рівнів гучності.",
"name": "Експоненціальна гучність"
},
"in-app-menu": {
"description": "Надає панелям меню вишуканий темний або кольоровий вигляд, схожий на альбом",
"menu": {
"hide-dom-window-controls": "Сховати елементи керування вікном DOM"
},
"name": "Меню в програмі"
},
"lumiastream": {
"description": "Додано підтримку для Lumia Stream",
"name": "Lumia Stream [Бета]"
},
"lyrics-genius": {
"description": "Додає підтримку текстів для більшості пісень",
"menu": {
"romanized-lyrics": "Романізовані тексти"
},
"name": "Тексти з Genius",
"renderer": {
"fetched-lyrics": "Тексти надано Genius"
}
},
"music-together": {
"description": "Поділитись музикою. Коли хост включає пісню, всі інші будуть чути ту ж пісню",
"dialog": {
"enter-host": "Введіть ID хоста"
},
"internal": {
"save": "Зберегти",
"track-source": "Джерело композиції",
"unknown-user": "Невідомий користувач"
},
"menu": {
"click-to-copy-id": "Скопіювати хост ID",
"close": "Вимкнути сумісне прослуховування",
"connected-users": "Підключені користувачі",
"disconnect": "Відключитись від Music Together",
"empty-user": "Немає підключених користувачів",
"host": "Хост Music Together",
"join": "Приєднатися до Music Together",
"permission": {
"all": "Дозволити гостям керувати списком відтворення та плеєром",
"host-only": "Лише хост може керувати списком відтворення та плеєром",
"playlist": "Дозволити гостям керувати списком відтворення"
},
"set-permission": "Змінити дозвіл на керування",
"status": {
"disconnected": "Відключено",
"guest": "Підключено як гість",
"host": "Підключено як хост"
}
},
"name": "Music Together [Бета]",
"toast": {
"add-song-failed": "Не вдалося додати пісню",
"closed": "Music Together закритий",
"disconnected": "Music Together відключено",
"host-failed": "Не вдалося увімкнути Music Together",
"id-copied": "ID хоста скопійовано в буфер обміну",
"id-copy-failed": "Не вдалося скопіювати ID хоста в буфер обміну",
"join-failed": "Не вдалося приєднатися до Music Together",
"joined": "Приєднано до Music Together",
"permission-changed": "Дозвіл Music Together змінено на \"{{permission}}\"",
"remove-song-failed": "Не вдалося видалити пісню",
"user-connected": "{{name}} приєднався до Music Together",
"user-disconnected": "{{name}} вийшов з Music Together"
}
},
"navigation": {
"description": "Стрілки навігації Вперед/Назад безпосередньо інтегровані в інтерфейс, як у вашому браузері, який ви використовуєте",
"name": "Навігація",
"templates": {
"back": {
"title": "Перейти на минулу сторінку"
},
"forward": {
"title": "Перейти на наступну сторінку"
}
}
},
"no-google-login": {
"description": "Видалити кнопки та посилання для входу через Google з інтерфейсу",
"name": "Без входу в Google"
},
"notifications": {
"description": "Відображати сповіщення, коли пісня починає грати (інтерактивні сповіщення доступні в Windows)",
"menu": {
"interactive": "Інтерактивні сповіщення",
"interactive-settings": {
"label": "Інтерактивні налаштування",
"submenu": {
"hide-button-text": "Сховати текст кнопки",
"refresh-on-play-pause": "Оновлення при відтворенні/паузі",
"tray-controls": "Відкриття/закриття при натисканні на значок в області повідомлень (tray)"
}
},
"priority": "Пріоритет повідомлень",
"toast-style": "Стиль спливаючих повідомлень",
"unpause-notification": "Показувати повідомлення при відновленні відтворення після паузи"
},
"name": "Сповіщення"
},
"performance-improvement": {
"description": "Поліпшіть продуктивність, ввімкнувши експериментальні сценарії",
"name": "Поліпшення продуктивності [Бета]"
},
"picture-in-picture": {
"description": "Дозволяє перемикати програму в режим «картинка в картинці»",
"menu": {
"always-on-top": "Завжди наверху",
"hotkey": {
"label": "Гаряча клавіша",
"prompt": {
"keybind-options": {
"hotkey": "Гаряча клавіша"
},
"label": "Оберіть гарячу клавішу для перемикання режиму зображення в зображенні",
"title": "Гаряча клавіша для режиму зображення в зображенні"
}
},
"save-window-position": "Зберегти положення вікна",
"save-window-size": "Зберегти розмір вікна",
"use-native-pip": "Використовувати вбудований режим \"картинка-у-картинці\" браузера"
},
"name": "Картинка-у-картинці",
"templates": {
"button": "Картинка-у-картинці"
}
},
"playback-speed": {
"description": "Додає слайдер, який керує швидкістю відтворення пісні",
"name": "Швидкість відтворення",
"templates": {
"button": "Швидкість"
}
},
"precise-volume": {
"description": "Точне керування гучністю за допомогою колеса миші/гарячих клавіш, з власним інтерфейсом користувача та настроюваними кроками гучності",
"menu": {
"arrows-shortcuts": "Локальне керування за допомогою клавіш зі стрілками",
"custom-volume-steps": "Встановити власні кроки гучності",
"global-shortcuts": "Глобальні гарячі клавіші"
},
"name": "Точна гучність",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Зменшити гучність",
"increase": "Збільшити гучність"
},
"label": "Вибрати глобальні комбінації клавіш для зміни гучності:",
"title": "Глобальні комбінації клавіш для регулювання гучності"
},
"volume-steps": {
"label": "Вибрати кроки збільшення/зменшення гучності",
"title": "Кроки гучності"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Поточна якість: {{quality}}",
"message": "Вибрати якість відео:",
"title": "Виберіть якість відео"
}
}
},
"description": "Дозволяє змінювати якість відео за допомогою кнопки на відео оверлеї",
"name": "Зміна якості відео",
"renderer": {
"quality-settings-button": {
"label": "Відкрити налаштування якості плеєру"
}
}
},
"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-alternative-artist": "Використати іншого виконавця",
"scrobble-alternative-title": "Використовувати альтернативні назви",
"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"
},
"synced-lyrics": {
"description": "Додає синхронізовані тексти до пісень використовуючи провайдери, такі як LRClib.",
"errors": {
"fetch": "⚠️ - При завантаженні слів пісні сталась помилка.\nСпробуйте будь ласка пізніше.",
"not-found": "⚠️ До цієї пісні текст не знайдено."
},
"menu": {
"default-text-string": {
"label": "Символ за замовчуванням між текстами пісень",
"tooltip": "Виберіть символ за замовчуванням, який буде використовуватися для проміжку між текстами пісень"
},
"line-effect": {
"label": "Лінійний ефект",
"submenu": {
"fancy": {
"label": "Fancy",
"tooltip": "Використовуйте великі, додаткоподібні ефекти на поточному рядку"
},
"focus": {
"label": "Зосереджитись",
"tooltip": "Зробити білим лише поточний рядок"
},
"offset": {
"label": "Офсет",
"tooltip": "Офсет з права від нинішньої лінії"
},
"scale": {
"label": "Масштабувати",
"tooltip": "Масштабувати поточну лінію"
}
},
"tooltip": "Виберіть ефект, який потрібно застосувати до поточної лінії"
},
"precise-timing": {
"label": "Зробити текст пісні ідеально синхронізованим",
"tooltip": "Обчисли до мілісекунд відображення наступного рядка (може мати невеликий вплив на продуктивність)"
},
"preferred-provider": {
"label": "Пріорітетний Провайдер",
"none": {
"label": "Жоден",
"tooltip": "Нема провайдера за замовчуванням"
},
"tooltip": "Оберіть якого провайдера використовувати за замовчуванням"
},
"romanization": {
"label": "Транслітерувати текст пісень",
"tooltip": "Якщо текст пісні іншою мовою, спробувати його відобразити латинською версією."
},
"show-lyrics-even-if-inexact": {
"label": "Показувати текст пісні, навіть якщо він неточний",
"tooltip": "Якщо пісня не знайдена, плагін повторює спробу з іншим пошуковим запитом.\nРезультат з другої спроби може бути не точним."
},
"show-time-codes": {
"label": "Відображати часові коди",
"tooltip": "Відображати часові коди біля тексту"
}
},
"name": "Синхронізовані тексти",
"refetch-btn": {
"fetching": "Завантаження...",
"normal": "Перезавантажити текст"
},
"warnings": {
"duration-mismatch": "⚠️ - Тексти цієї пісні можуть бути не синхронізовані через не співпадіння довжини пісні.",
"inexact": "⚠️ - Текст цієї пісні може не співпадати",
"instrumental": "⚠️ - Це інструментал"
}
},
"taskbar-mediacontrol": {
"description": "Керування відтворенням з панелі завдань Windows",
"name": "Керування медіа на панелі завдань"
},
"touchbar": {
"description": "Додає віджет TouchBar для користувачів macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Зробити вікно програми прозорим",
"menu": {
"opacity": {
"label": "Прозорість",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Тип",
"submenu": {
"acrylic": "Акриловий",
"mica": "Міка",
"none": "Жоден",
"tabbed": "З роздільниками"
}
}
},
"name": "Прозорий Плеєр"
},
"tuna-obs": {
"description": "Інтеграція з плагіном Tuna для OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Запобігання спливання плеєру під час відтворення пісні",
"name": "Ненав'язливий програвач"
},
"video-toggle": {
"description": "Додає кнопку для перемикання між режимом відео і режимом пісні. Також може опціонально видаляти вкладку відео",
"menu": {
"align": {
"label": "Вирівнювання",
"submenu": {
"left": "Зліва",
"middle": "По центру",
"right": "Справа"
}
},
"force-hide": "Примусово видалити вкладку відео",
"mode": {
"label": "Режим",
"submenu": {
"custom": "Власний перемикач",
"disabled": "Вимкнено",
"native": "Вбудований перемикач"
}
}
},
"name": "Перемикач відео",
"templates": {
"button-song": "Пісня",
"button-video": "Відео"
}
},
"visualizer": {
"description": "Додати візуалізацію до плеєра",
"menu": {
"visualizer-type": "Тип візуалізації"
},
"name": "Візуалізація"
}
}
}
================================================
FILE: src/i18n/resources/ur.json
================================================
{
"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": "ur",
"local-name": "اردو",
"name": "Urdu"
},
"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": "یوزر ایجنٹ کو اوور رائیڈ کریں",
"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] استعمال کریں (یا in-app-menu استعمال کرنے پر بیک ٹک [`] کریں)",
"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": {
"custom-window-title": {
"label": "اپنی مرضی کا ونڈو عنوان",
"prompt": {
"label": "اپنی مرضی کا ونڈو عنوان درج کریں: (بند کرنے کے لیے خالی چھوڑ دیں)",
"placeholder": "مثال: پیئر ڈیسک ٹاپ"
}
},
"like-buttons": {
"default": "پہلے سے طے شدہ",
"force-show": "زبردستی دکھائیں",
"hide": "چھپائیں",
"label": "لائیک بٹن"
},
"remove-upgrade-button": "اپ گریڈ بٹن ہٹائیں",
"theme": {
"dialog": {
"button": {
"cancel": "منسوخ کریں",
"remove": "ہٹائیں"
},
"remove-theme": "کیا آپ واقعی کسٹم تھیم کو ہٹانا چاہتے ہیں؟",
"remove-theme-message": "یہ کسٹم تھیم کو ہٹا دے گا"
},
"label": "تھیم",
"submenu": {
"import-css-file": "کسٹم CSS فائل درآمد کریں",
"no-theme": "کوئی تھیم نہیں"
}
}
}
}
}
},
"plugins": {
"enabled": "فعال",
"label": "پلگ انز",
"new": "نیا"
},
"view": {
"label": "دیکھیں",
"submenu": {
"force-reload": "زبردستی دوبارہ لوڈ کریں",
"reload": "دوبارہ لوڈ کریں",
"reset-zoom": "اصل سائز",
"toggle-fullscreen": "پوری سکرین ٹوگل کریں",
"zoom-in": "زوم ان کریں",
"zoom-out": "زوم آؤٹ کریں"
}
}
},
"tray": {
"next": "اگلا",
"play-pause": "چلائیں/روکیں",
"previous": "پچھلا",
"quit": "باہر نکلیں",
"restart": "ایپ دوبارہ شروع کریں",
"show": "ونڈو دکھائیں",
"tooltip": {
"default": "پیئر ڈیسک ٹاپ"
}
}
},
"plugins": {
"ad-speedup": {
"description": "اگر کوئی اشتہار چلے تو آواز بند کرکے پلے بیک کی رفتار 16 گناہ کردیں",
"name": "اشتہار کی رفتار"
},
"adblocker": {
"description": "شروغ سے تمام اشتہارات اور ٹریکنگ بلاک کردیں",
"menu": {
"blocker": "بلاکر"
},
"name": "ایڈ بلاکر"
}
}
}
================================================
FILE: src/i18n/resources/vi.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "Lỗi thực thi plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Phần mở rộng {{pluginName}}::{{contextName}} đã bắt đầu trong {{ms}}ms",
"initialize-failed": "Lỗi khi khởi động phần mở rộng \"{{pluginName}}\"",
"load-all": "Đang tải tất cả phần mở rộng",
"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 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 phím 'Esc' 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": "Phần 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ó một 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 mở ứ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 đặt 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 trong lần khởi chạy tiếp theo, 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 khởi động lại ứng dụng",
"title": "Ngôn ngữ đã thay đổi"
},
"label": "Ngôn ngữ",
"submenu": {
"to-help-translate": "— Bạn muốn hỗ trợ 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": {
"custom-window-title": {
"label": "Tiêu đề cửa sổ tùy chỉnh",
"prompt": {
"label": "Nhập tiêu đề cửa sổ tùy chỉnh: (để trống để vô hiệu hóa)",
"placeholder": "Ví dụ: {{applicationName}}"
}
},
"like-buttons": {
"default": "Mặc định",
"force-show": "Tập trung hiển thị",
"hide": "Ẩn",
"label": "Nút thích",
"swap": "Hoán đổi thứ tự nút Thích"
},
"remove-upgrade-button": "Xóa nút nâng cấp",
"theme": {
"dialog": {
"button": {
"cancel": "Hủy",
"remove": "Loại bỏ"
},
"remove-theme": "Bạn có chắc muốn loại bỏ chủ đề tùy chỉnh này không?",
"remove-theme-message": "Tùy chọn này sẽ loại bỏ chủ đề tùy chỉnh"
},
"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": "Đặt lại",
"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": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Nếu một quảng cáo được phát thì sẽ bị tắt tiếng và tăng tốc độ phát lên 16x",
"name": "Tăng tốc quảng cáo"
},
"adblocker": {
"description": "Chặn toàn bộ quảng cáo và trình theo dõi",
"menu": {
"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à Hủy thích để áp dụng cho tất cả bài hát trong 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}}%"
}
},
"enable-seekbar": "Bật tùy chỉnh giao diện cho thanh trượt"
},
"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"
},
"amuse": {
"description": "Thêm hỗ trợ {{applicationName}} cho tiện ích hiển thị bài hát đang phát Amuse của 6K Labs",
"name": "Amuse",
"response": {
"query": "Máy chủ API của Amuse đang chạy. GET /query để lấy thông tin về bài hát."
}
},
"api-server": {
"description": "Thêm máy chủ API để điều khiển trình phát",
"dialog": {
"request": {
"buttons": {
"allow": "Cho phép",
"deny": "Từ chối"
},
"message": "Cho phép {{ID}} ({{origin}}) truy cập API?",
"title": "Yêu cầu cho phép API"
}
},
"menu": {
"auth-strategy": {
"label": "Chiến thuật xác thực",
"submenu": {
"auth-at-first": {
"label": "Xác thực ngay yêu cầu đầu tiên"
},
"none": {
"label": "Không xác thực"
}
}
},
"hostname": {
"label": "Tên máy chủ"
},
"https": {
"label": "HTTPS & Chứng chỉ",
"submenu": {
"cert": {
"dialogTitle": "Chọn tệp chứng chỉ HTTPS",
"label": "Tệp chứng chỉ (.crt/.pem)"
},
"enable-https": {
"label": "Bật HTTPS"
},
"key": {
"dialogTitle": "Chọn tệp khóa riêng HTTPS",
"label": "Tệp khóa riêng (.key/.pem)"
}
}
},
"port": {
"label": "Cổng"
}
},
"name": "Máy chủ API [Thử nghiệm]",
"prompt": {
"hostname": {
"label": "Điền tên máy chủ (như 0.0.0.0) cho máy chủ API:",
"title": "Tên máy chủ"
},
"port": {
"label": "Nhập cổng cho máy chủ API:",
"title": "Cổng"
}
}
},
"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"
},
"auth-proxy-adapter": {
"description": "Ủng hộ cho mục đích duy trì dịch vụ xác minh proxy",
"menu": {
"disable": "Vô hiệu bộ chuyển đổi Proxy",
"enable": "Kích hoạt bộ chuyển đổi Proxy",
"hostname": {
"label": "Tên máy chủ"
},
"port": {
"label": "Cổng"
}
},
"name": "Bộ chuyển đổi xác minh máy chủ Proxy",
"prompt": {
"hostname": {
"label": "Nhập tên của máy chủ proxy lân cận (yêu cầu khởi động lại ứng dụng):",
"title": "Tên máy chủ Proxy"
},
"port": {
"label": "Nhập cổng của máy chủ proxy lận cận (bắt buộc khởi động lại ứng dụng):",
"title": "Cổng máy chủ Proxy"
}
}
},
"blur-nav-bar": {
"description": "Làm thanh điều hướng mờ và trong suốt",
"name": "Làm mờ thanh điều hướng"
},
"bypass-age-restrictions": {
"description": "Bỏ qua xác minh độ tuổi của Music Player",
"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 {{applicationName}}",
"menu": {
"autoload": "Tự động chọn phụ đề vừa sử dụng",
"disable-captions": "Không có phụ đề 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ụ đề"
},
"toast": {
"caption-changed": "Phụ đề đã chuyển sang {{language}}",
"caption-disabled": "Tắt phụ đề",
"no-captions": "Không có phụ đề nào cho bài hát này"
}
},
"clock": {
"description": "Thêm đồng hồ vào thanh điều hướng",
"menu": {
"format": {
"24-hour-format": "Định dạng 24h",
"display-seconds": "Hiển thị giây",
"label": "Định dạng"
}
},
"name": "Đồng hồ"
},
"compact-sidebar": {
"description": "Luôn đặt thanh bên ở 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": "Thời lượng mờ dần vào (ms)",
"fade-out-duration": "Thời lượng mờ dần ra (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ẽ"
}
}
},
"custom-output-device": {
"description": "Cài đặt cho thiết bị đầu ra tùy chỉnh cho bài hát",
"menu": {
"device-selector": "Chọn thiết bị"
},
"name": "Thiết bị đầu ra tùy chỉnh",
"prompt": {
"device-selector": {
"label": "Chọn thiết bị phát làm đầu ra để dùng",
"title": "Chọn thiết bị đầu ra"
}
}
},
"disable-autoplay": {
"description": "Làm nhạc bắt đầu ở chế độ \"tạm dừng\". Ngoài ra có thể dừng nhạc khi khởi động ứng dụng (nếu có bật tính năng \"Tiếp tục bài hát cuối cùng khi ứng dụng khởi động\")",
"menu": {
"apply-once": "Chỉ á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-application": "Phát trong {{applicationName}}",
"set-inactivity-timeout": "Đặt thời gian chờ không hoạt động",
"set-status-display-type": {
"label": "Văn bản trạng thái",
"submenu": {
"application": "Đang nghe {{applicationName}}",
"artist": "Đang nghe nhạc của {artist}",
"title": "Đang nghe nhạc {song title}"
}
}
},
"name": "Tích hợp trạng thái Discord",
"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: hãy đảm bảo danh sách phát không ở chế độ riêng tư hoặc danh sách phát \"Dành cho bạn\"\n\n{{error}}",
"preparing-file": "Đang chuẩn bị tệp…",
"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-finish-settings": {
"label": "Tải xuống khi hoàn tất",
"prompt": {
"last-percent": "Sau x phần trăm",
"last-seconds": "x giây cuối",
"title": "Định cấu hình thời điểm tải xuống"
},
"submenu": {
"advanced": "Nâng cao",
"enabled": "Đã kích hoạt",
"mode": "Chế độ thời gian",
"percent": "Phần trăm",
"seconds": "Giây"
}
},
"download-playlist": "Tải danh sách phát",
"presets": "Tùy chọn định dạng",
"skip-existing": "Bỏ qua các tập tin đã 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"
}
},
"equalizer": {
"description": "Thêm bộ chỉnh âm để điều chỉnh âm thanh cho trình phát nhạc",
"menu": {
"presets": {
"label": "Thiết lập có sẵn",
"list": {
"bass-booster": "Tăng âm trầm"
}
}
},
"name": "Bộ chỉnh âm"
},
"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": "Chuyển lời bài hát sang chữ Latin"
},
"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",
"templates": {
"back": {
"title": "Đi đến trang trước"
},
"forward": {
"title": "Đi đến trang tiếp theo"
}
}
},
"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"
},
"performance-improvement": {
"description": "Cải thiện hiệu suất thông qua kích hoạt scripts thử nghiệm",
"name": "Cải thiện hiệu năng [Thử nghiệm]"
},
"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 tắt",
"prompt": {
"keybind-options": {
"hotkey": "Phím tắt"
},
"label": "Chọn phím tắt để chuyển đổi ảnh trong ảnh",
"title": "Phím tắt ả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",
"renderer": {
"quality-settings-button": {
"label": "Mở trình thay đổi chất lượng"
}
}
},
"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 Last.fm \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-alternative-artist": "Dùng nghệ sĩ thay thế",
"scrobble-alternative-title": "Dùng tiêu đề thay thế",
"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": "Tự động bỏ qua những bài hát bạn nhấn không thích",
"name": "Bỏ qua 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 intro/outro hoặc các phần không được phát của video nhạc",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "Cung cấp lời được đồng bộ với bài hát, sử dụng các nhà cung cấp như LRClib.",
"errors": {
"fetch": "⚠️\t\tĐã xảy ra lỗi khi tìm lời bài hát.\n\tVui lòng thử lại sau.",
"not-found": "⚠️ Không tìm thấy lời cho bài hát này."
},
"menu": {
"convert-chinese-character": {
"label": "Chuyển đổi kí tự tiếng Trung",
"submenu": {
"disabled": {
"label": "Vô hiệu hóa",
"tooltip": "Vô hiệu hóa chuyển đổi kí tự tiếng Trung"
},
"simplified-to-traditional": {
"label": "Giản thể sang Phồn thể",
"tooltip": "Chuyển đổi tiếng Trung Giản thể sang tiếng Trung Phồn thể"
},
"traditional-to-simplified": {
"label": "Phồn thể sang Giản thể",
"tooltip": "Chuyển đổi tiếng Trung Phồn thể sang tiếng Trung Giản thể"
}
},
"tooltip": "Chuyển đổi kí tự tiếng Trung sang Phồn thể hoặc Giản thể"
},
"default-text-string": {
"label": "Kí tự giữa các lời bài hát",
"tooltip": "Chọn kí tự mặc định cho khoảng trống giữa các lời bài hát"
},
"line-effect": {
"label": "Kiểu lời nhạc",
"submenu": {
"fancy": {
"label": "Màu mè",
"tooltip": "Làm đoạn lời nhạc đang nghe to hơn và nổi bật hơn"
},
"focus": {
"label": "Tập trung",
"tooltip": "Chỉ làm cho dòng hiện tại có màu trắng"
},
"offset": {
"label": "Lệch",
"tooltip": "Làm dòng hiện tại lệch sang bên phải"
},
"scale": {
"label": "Phóng to",
"tooltip": "Làm dòng hiện tại to hơn các dòng khác"
}
},
"tooltip": "Chọn kiểu để áp dụng cho dòng hiện tại"
},
"precise-timing": {
"label": "Làm cho lời bài hát được đồng bộ hoàn hảo",
"tooltip": "Tính toán chính xác đến mili giây thời gian hiển thị dòng tiếp theo (có thể có tác động nhỏ đến hiệu suất)"
},
"preferred-provider": {
"label": "Nhà cung cấp ưa thích",
"none": {
"label": "Không có",
"tooltip": "Không có nhà cung cấp ưu thích"
},
"tooltip": "Chọn nhà cung cấp lời bài hát mặc định để sử dụng"
},
"romanization": {
"label": "Chuyển lời bài hát sang chữ Latin",
"tooltip": "Nếu lời bài hát đang ở ngôn ngữ khác, thử hiển thị phiên bản bảng chữ cái La-tinh."
},
"show-lyrics-even-if-inexact": {
"label": "Hiển thị lời bài hát ngay cả khi không chính xác",
"tooltip": "Nếu không tìm thấy bài hát, plugin sẽ thử lại bằng truy vấn tìm kiếm khác.\nKết quả từ lần thử thứ hai có thể không chính xác."
},
"show-time-codes": {
"label": "Hiện mốc thời gian",
"tooltip": "Hiện mốc thời gian bên cạnh lời bài hát"
}
},
"name": "Lời bài hát được đồng bộ hoá",
"refetch-btn": {
"fetching": "Đang tìm nạp...",
"normal": "Tải lại lời bài hát"
},
"warnings": {
"duration-mismatch": "⚠️ - Lời bài hát có thể không đồng bộ do thời lượng không khớp.",
"inexact": "⚠️ - Lời bài hát này có thể không chính xác",
"instrumental": "⚠️ - Đây là một bài hát không lời"
}
},
"taskbar-mediacontrol": {
"description": "Điều khiển nhạc từ cửa sổ xem trước trên thanh tác vụ Windows của bạn",
"name": "Điều khiển 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"
},
"transparent-player": {
"description": "Làm cho cửa sổ ứng dụng có hiệu ứng trong suốt",
"menu": {
"opacity": {
"label": "Độ mờ",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Kiểu nền",
"submenu": {
"acrylic": "Acrylic",
"mica": "Mica",
"none": "Không có",
"tabbed": "Tabbed"
}
}
},
"name": "Trình phát trong suốt"
},
"tuna-obs": {
"description": "Tích hợp với plugin Tuna của OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Ngăn trình phát nhạc xuất hiện đột ngột khi phát một bài hát",
"name": "Không hiện trình phát nhạc khi phát"
},
"video-toggle": {
"description": "Thêm nút để chuyển giữa chế độ Video/Bài hát. Cũng có thể ẩn toàn bộ video",
"menu": {
"align": {
"label": "Căn chỉnh",
"submenu": {
"left": "Trái",
"middle": "Giữa",
"right": "Phải"
}
},
"force-hide": "Buộc ẩn video",
"mode": {
"label": "Chế độ",
"submenu": {
"custom": "Tùy chỉnh",
"disabled": "Vô hiệu hoá",
"native": "Gốc"
}
}
},
"name": "Chuyển đổi video",
"templates": {
"button-song": "Bài hát",
"button-video": "Video"
}
},
"visualizer": {
"description": "Hiển thị sóng nhạc thay thế cho video (hay \"Music visualizer\")",
"menu": {
"visualizer-type": "Loại Sóng nhạc"
},
"name": "Sóng nhạc"
}
}
}
================================================
FILE: src/i18n/resources/zh-CN.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "执行插件 {{pluginName}}::{{contextName}} 失败",
"executed-at-ms": "插件 {{pluginName}}::{{contextName}} 已在 {{ms}} 毫秒内执行",
"initialize-failed": "初始化插件 “{{pluginName}}” 失败",
"load-all": "正在加载所有插件",
"load-failed": "加载插件 “{{pluginName}}” 失败",
"loaded": "插件 “{{pluginName}}” 已载入",
"unload-failed": "卸载插件 “{{pluginName}}” 失败",
"unloaded": "插件 “{{pluginName}}” 已卸载"
}
}
},
"language": {
"code": "zh-CN",
"local-name": "简体中文",
"name": "Simplified Chinese"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "加载完毕。开发人员工具已启动"
},
"i18n": {
"loaded": "i18n 已载入"
},
"second-instance": {
"receive-command": "已从协议接收到以下指令:“{{command}}”"
},
"theme": {
"css-file-not-found": "CSS 文件 “{{cssFile}}” 不存在,将被忽略"
},
"unresponsive": {
"details": "无响应错误!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "正在清理应用缓存"
},
"window": {
"tried-to-render-offscreen": "窗口试图于屏幕外绘制,窗口大小={{windowSize}},显示尺寸={{displaySize}},位置={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "菜单已隐藏,按下 Alt 键来显示(若使用应用内菜单则按 Esc 键)",
"message": "隐藏菜单已被启用",
"title": "隐藏菜单已启用"
},
"need-to-restart": {
"buttons": {
"later": "稍后",
"restart-now": "现在重启"
},
"detail": "“{{pluginName}}” 插件需要重启程序后生效",
"message": "“{{pluginName}}” 需要重启",
"title": "需要重启"
},
"unresponsive": {
"buttons": {
"quit": "退出",
"relaunch": "重启",
"wait": "等待"
},
"detail": "对产生的不便我们表示抱歉!请选择接下来要做什么:",
"message": "应用程序无响应",
"title": "窗口无响应"
},
"update-available": {
"buttons": {
"disable": "禁用更新",
"download": "下载",
"ok": "好的"
},
"detail": "新版本现已可用,可从 {{downloadLink}} 下载",
"message": "新版本可用",
"title": "更新可用"
}
},
"menu": {
"about": "关于",
"navigation": {
"label": "导航",
"submenu": {
"copy-current-url": "复制当前 URL",
"go-back": "后退",
"go-forward": "前进",
"quit": "退出",
"restart": "重启应用"
}
},
"options": {
"label": "选项",
"submenu": {
"advanced-options": {
"label": "高级选项",
"submenu": {
"auto-reset-app-cache": "启动时重置应用缓存",
"disable-hardware-acceleration": "禁用硬件加速",
"edit-config-json": "编辑 config.json",
"override-user-agent": "覆盖 User-Agent",
"restart-on-config-changes": "配置改变时重启",
"set-proxy": {
"label": "设置代理",
"prompt": {
"label": "输入代理地址(留空以禁用)",
"placeholder": "例如: SOCKS5://127.0.0.1:9999",
"title": "设置代理服务器"
}
},
"toggle-dev-tools": "切换开发人员工具"
}
},
"always-on-top": "保持置顶",
"auto-update": "自动更新",
"hide-menu": {
"dialog": {
"message": "菜单将在下次启动时隐藏,按下 Alt 键以显示(若使用应用内菜单则按 ` 键)",
"title": "隐藏菜单已启用"
},
"label": "隐藏菜单"
},
"language": {
"dialog": {
"message": "语言会在应用重启后更改",
"title": "语言已更改"
},
"label": "语言",
"submenu": {
"to-help-translate": "想要协助翻译?点击此处"
}
},
"resume-on-start": "应用启动时继续上次播放的歌曲",
"single-instance-lock": "单例模式",
"start-at-login": "系统登录时启动",
"starting-page": {
"label": "启动页面",
"unset": "取消设定"
},
"tray": {
"label": "托盘",
"submenu": {
"disabled": "已禁用",
"enabled-and-hide-app": "启用并隐藏应用",
"enabled-and-show-app": "启用并显示应用",
"play-pause-on-click": "点击时播放/暂停"
}
},
"visual-tweaks": {
"label": "视觉调整",
"submenu": {
"custom-window-title": {
"label": "自定义窗口标题",
"prompt": {
"label": "输入自定义窗口标题:(留空表示停用)",
"placeholder": "示例:{{applicationName}}"
}
},
"like-buttons": {
"default": "默认",
"force-show": "强制显示",
"hide": "隐藏",
"label": "点赞按钮",
"swap": "交换“点赞”按钮顺序"
},
"remove-upgrade-button": "移除升级按钮",
"theme": {
"dialog": {
"button": {
"cancel": "取消",
"remove": "移除"
},
"remove-theme": "您确定要移除自定义主题?",
"remove-theme-message": "此操作将移除自定义主题"
},
"label": "主题",
"submenu": {
"import-css-file": "导入自定义 CSS 文件",
"no-theme": "无主题"
}
}
}
}
}
},
"plugins": {
"enabled": "已启用",
"label": "插件",
"new": "新增"
},
"view": {
"label": "视图",
"submenu": {
"force-reload": "强制刷新",
"reload": "刷新",
"reset-zoom": "实际缩放大小",
"toggle-fullscreen": "切换全屏",
"zoom-in": "放大",
"zoom-out": "缩小"
}
}
},
"tray": {
"next": "下一首",
"play-pause": "播放/暂停",
"previous": "上一首",
"quit": "退出",
"restart": "重启应用",
"show": "显示窗口",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "使用静音以及 16 倍速播放跳过广告片段",
"name": "广告加速跳过"
},
"adblocker": {
"description": "屏蔽所有广告与跟踪器",
"menu": {
"blocker": "屏蔽器"
},
"name": "广告屏蔽器"
},
"album-actions": {
"description": "添加作用于播放列表或专辑中所有歌曲的全局“点赞/取消点赞”与“喜欢/取消喜欢”按钮",
"name": "专辑操作"
},
"album-color-theme": {
"description": "根据专辑封面配色动态改变主题与视觉效果",
"menu": {
"color-mix-ratio": {
"label": "颜色混合比例",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "启用进度条主题"
},
"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": "沉浸模式"
},
"amuse": {
"description": "为 6K Labs 的 Amuse 正在播放小部件添加 {{applicationName}} 支持",
"name": "Amuse",
"response": {
"query": "Amuse API服务器已在运行。使用 /query 以获取歌曲信息。"
}
},
"api-server": {
"description": "添加一个 API 服务器来控制播放器",
"dialog": {
"request": {
"buttons": {
"allow": "允许",
"deny": "拒绝"
},
"message": "允许 {{ID}} {{origin}} 访问该 API 吗?",
"title": "API 授权请求"
}
},
"menu": {
"auth-strategy": {
"label": "授权策略",
"submenu": {
"auth-at-first": {
"label": "首次请求时授权"
},
"none": {
"label": "无需授权"
}
}
},
"hostname": {
"label": "主机名"
},
"https": {
"label": "HTTPS & 数字证书",
"submenu": {
"cert": {
"dialogTitle": "选择 HTTPS 证书文件",
"label": "证书文件(.crt/.pem)"
},
"enable-https": {
"label": "启用 HTTPS"
},
"key": {
"dialogTitle": "选择 HTTPS 私钥文件",
"label": "私钥文件(.key/.pem)"
}
}
},
"port": {
"label": "端口号"
}
},
"name": "API 服务器 [测试]",
"prompt": {
"hostname": {
"label": "请输入 API 服务器的主机名(如 0.0.0.0):",
"title": "主机名"
},
"port": {
"label": "请输入 API 服务器的端口号:",
"title": "端口号"
}
}
},
"audio-compressor": {
"description": "对音频应用压缩(压低响亮部分,提升柔和部分)",
"name": "音频压缩器"
},
"auth-proxy-adapter": {
"description": "支持使用需要身份验证的代理",
"menu": {
"disable": "禁用代理适配",
"enable": "启用代理适配",
"hostname": {
"label": "主机名"
},
"port": {
"label": "端口"
}
},
"name": "认证代理适配",
"prompt": {
"hostname": {
"label": "请输入本地代理服务器的主机名(需要重启):",
"title": "代理主机名"
},
"port": {
"label": "请输入本地代理服务器的端口号(需要重启):",
"title": "代理端口"
}
}
},
"blur-nav-bar": {
"description": "让导航栏透明及模糊",
"name": "模糊导航栏"
},
"bypass-age-restrictions": {
"description": "绕过 Music Player 年龄验证",
"name": "绕过年龄验证"
},
"captions-selector": {
"description": "{{applicationName}} 音轨字幕选择器",
"menu": {
"autoload": "自动选择上次使用的字幕",
"disable-captions": "默认无字幕"
},
"name": "字幕选择器",
"prompt": {
"selector": {
"label": "当前字幕语言: {{language}}",
"none": "无",
"title": "选择字幕语言"
}
},
"templates": {
"title": "开启字幕选择器"
},
"toast": {
"caption-changed": "字幕语言更改为 {{language}}",
"caption-disabled": "停用了字幕",
"no-captions": "这首歌没有字幕"
}
},
"clock": {
"description": "添加时钟到导航栏",
"menu": {
"format": {
"24-hour-format": "24 小时格式",
"display-seconds": "显示秒数",
"label": "格式"
}
},
"name": "时钟"
},
"compact-sidebar": {
"description": "始终将侧边栏设为紧凑模式",
"name": "紧凑式侧边栏"
},
"crossfade": {
"description": "在歌曲间启用交叉淡化效果",
"menu": {
"advanced": "高级"
},
"name": "交叉淡化 [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "淡入持续时间(毫秒)",
"fade-out-duration": "淡出持续时间(毫秒)",
"fade-scaling": {
"label": "淡化尺度",
"linear": "线性",
"logarithmic": "对数"
},
"seconds-before-end": "歌曲结束前持续交叉淡化时长"
},
"title": "交叉淡化选项"
}
}
},
"custom-output-device": {
"description": "配置歌曲的自定义输出媒体设备",
"menu": {
"device-selector": "选择设备"
},
"name": "自定义输出设备",
"prompt": {
"device-selector": {
"label": "选择要使用的输出媒体设备",
"title": "选择输出设备"
}
}
},
"disable-autoplay": {
"description": "让曲目开始时处于 “暂停” 模式",
"menu": {
"apply-once": "仅在程序启动时应用"
},
"name": "禁用自动播放"
},
"discord": {
"backend": {
"already-connected": "已尝试使用活跃网络进行连接",
"connected": "已连接到 Discord",
"disconnected": "已和 Discord 断开连接"
},
"description": "使用 Rich Presence 与好友分享正在收听的音乐",
"menu": {
"auto-reconnect": "自动重连",
"clear-activity": "清除状态",
"clear-activity-after-timeout": "超时后清除状态",
"connected": "已连接",
"disconnected": "已断开连接",
"hide-duration-left": "隐藏剩余时长",
"hide-github-button": "隐藏 GitHub 链接按钮",
"play-on-application": "转至 {{applicationName}} 播放",
"set-inactivity-timeout": "设置非活跃时长",
"set-status-display-type": {
"label": "状态文本",
"submenu": {
"application": "在听 {{applicationName}}",
"artist": "在听 {artist}",
"title": "在听 {song title}"
}
}
},
"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-finish-settings": {
"label": "边播边下",
"prompt": {
"last-percent": "播放超过指定百分比时开始下载",
"last-seconds": "歌曲剩余指定秒数时开始下载",
"title": "配置在何时开始下载"
},
"submenu": {
"advanced": "高级",
"enabled": "已启用",
"mode": "激活时机",
"percent": "按播放百分比",
"seconds": "按播放秒数"
}
},
"download-playlist": "下载播放列表",
"presets": "预设",
"skip-existing": "跳过已存在的文件"
},
"name": "下载器",
"renderer": {
"can-not-update-progress": "无法更新进度"
},
"templates": {
"button": "下载"
}
},
"equalizer": {
"description": "为播放器添加均衡器",
"menu": {
"presets": {
"label": "预设",
"list": {
"bass-booster": "低音增强器"
}
}
},
"name": "均衡器"
},
"exponential-volume": {
"description": "让音量滑块指数化以便选择更低的音量。",
"name": "指数化音量"
},
"in-app-menu": {
"description": "为菜单栏启用更精美的外观,可选暗色或专辑配色",
"menu": {
"hide-dom-window-controls": "隐藏 DOM 窗口控件"
},
"name": "应用内菜单"
},
"lumiastream": {
"description": "添加 Lumia Stream 支持",
"name": "Lumia Stream [测试]"
},
"lyrics-genius": {
"description": "为大多数歌曲添加歌词支持",
"menu": {
"romanized-lyrics": "罗马化歌词"
},
"name": "Genius 歌词",
"renderer": {
"fetched-lyrics": "已从 Genius 获取歌词"
}
},
"music-together": {
"description": "与他人共享播放列表。当发起人播放歌曲时,其他人也会听到相同歌曲",
"dialog": {
"enter-host": "输入发起人 ID"
},
"internal": {
"save": "保存",
"track-source": "追踪来源",
"unknown-user": "未知用户"
},
"menu": {
"click-to-copy-id": "复制发起者 ID",
"close": "关闭一起听",
"connected-users": "已连接用户",
"disconnect": "断开一起听连接",
"empty-user": "没有已连接的用户",
"host": "一起听发起者",
"join": "加入一起听",
"permission": {
"all": "允许来宾控制播放列表与播放器",
"host-only": "仅发起人可以控制播放列表与播放器",
"playlist": "允许来宾控制播放列表"
},
"set-permission": "更改控制权限",
"status": {
"disconnected": "已断开连接",
"guest": "已作为来宾连接",
"host": "已作为发起人连接"
}
},
"name": "一起听 [测试]",
"toast": {
"add-song-failed": "添加歌曲失败",
"closed": "一起听已关闭",
"disconnected": "一起听已断开连接",
"host-failed": "发起一起听失败",
"id-copied": "已将发起者 ID 复制到剪切板",
"id-copy-failed": "复制发起者 ID 到剪贴板时失败",
"join-failed": "加入一起听失败",
"joined": "已加入一起听",
"permission-changed": "一起听权限已改为 \"{{permission}}\"",
"remove-song-failed": "移除歌曲失败",
"user-connected": "{{name}} 加入了一起听",
"user-disconnected": "{{name}} 离开了一起听"
}
},
"navigation": {
"description": "如同浏览器般,在应用界面内直接显示前进/后退导航按钮",
"name": "导航",
"templates": {
"back": {
"title": "转到上一页"
},
"forward": {
"title": "转到下一页"
}
}
},
"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": "通知"
},
"performance-improvement": {
"description": "通过开启实验性脚本改进性能",
"name": "性能改进 [公测]"
},
"picture-in-picture": {
"description": "允许应用切换到画中画模式",
"menu": {
"always-on-top": "保持置顶",
"hotkey": {
"label": "热键",
"prompt": {
"keybind-options": {
"hotkey": "热键"
},
"label": "选择切换画中画模式的热键",
"title": "画中画热键"
}
},
"save-window-position": "记住窗口位置",
"save-window-size": "记住窗口尺寸",
"use-native-pip": "使用浏览器原生画中画功能"
},
"name": "画中画",
"templates": {
"button": "画中画"
}
},
"playback-speed": {
"description": "快慢随心!为歌曲添加速度控制滑块",
"name": "回放速度",
"templates": {
"button": "速度"
}
},
"precise-volume": {
"description": "启用自定义的音量弹窗以及可变步长,以便采用滚轮/热键精确控制音量",
"menu": {
"arrows-shortcuts": "本地方向键控件",
"custom-volume-steps": "设置自定义音量步长",
"global-shortcuts": "全局热键"
},
"name": "精确音量",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "降低音量",
"increase": "提升音量"
},
"label": "选择全局音量键绑定:",
"title": "全局音量键绑定"
},
"volume-steps": {
"label": "选择音量增加/减弱步长",
"title": "音量步长"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "当前质量: {{quality}}",
"message": "选择视频画质:",
"title": "选择视频画质"
}
}
},
"description": "允许在视频上显示切换画质按钮",
"name": "视频画质切换器",
"renderer": {
"quality-settings-button": {
"label": "打开播放器音质更改程序"
}
}
},
"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-alternative-artist": "使用替代艺术家",
"scrobble-alternative-title": "使用替代标题",
"scrobble-other-media": "记录其他媒体文件"
},
"name": "歌曲记录器",
"prompt": {
"lastfm": {
"api-key": "Last.fm API 密钥(Key)",
"api-secret": "Last.fm API 密文(Secret)"
},
"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": "自动跳过非音乐部分,如 MV 的介绍/结语以及歌曲未开始的部分",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "透过 LRClib 等服务提供滚动歌词显示。",
"errors": {
"fetch": "⚠️ 获取歌词时发生错误。\n 请稍后再试。",
"not-found": "⚠️ 未找到此歌曲的歌词。"
},
"menu": {
"convert-chinese-character": {
"label": "转换中文字符",
"submenu": {
"disabled": {
"label": "已停用",
"tooltip": "禁用中文字符转换"
},
"simplified-to-traditional": {
"label": "简体到繁体",
"tooltip": "转换简体中文到繁体中文"
},
"traditional-to-simplified": {
"label": "繁体到简体",
"tooltip": "转换繁体中文到简体中文"
}
},
"tooltip": "转换简繁体中文字符"
},
"default-text-string": {
"label": "默认的歌词行间字符",
"tooltip": "选择在歌词间隙期间默认显示的字符"
},
"line-effect": {
"label": "歌词行特效",
"submenu": {
"fancy": {
"label": "Fancy",
"tooltip": "在当前行上使用大的、类似应用的效果"
},
"focus": {
"label": "高亮",
"tooltip": "仅将当前歌词行显示为白色"
},
"offset": {
"label": "偏移",
"tooltip": "将当前歌词行向右偏移"
},
"scale": {
"label": "放大",
"tooltip": "放大当前歌词行"
}
},
"tooltip": "选择当前歌词行应用的特效"
},
"precise-timing": {
"label": "让滚动歌词完全同步",
"tooltip": "以毫秒精度估算下句歌词的显示时间(可能对性能有小幅影响)"
},
"preferred-provider": {
"label": "首选歌词源",
"none": {
"label": "无",
"tooltip": "没有首选的歌词源"
},
"tooltip": "选择默认要用的源"
},
"romanization": {
"label": "将歌词罗马化",
"tooltip": "如果歌词以不同语言显示,试着展示拉丁字母版本。"
},
"show-lyrics-even-if-inexact": {
"label": "即使时值不精确依然显示歌词",
"tooltip": "若首次搜索未找到该歌曲的歌词,插件将尝试用不同的查询方式重新获取。\n重试查询的结果可能不精确。"
},
"show-time-codes": {
"label": "显示时值",
"tooltip": "在歌词旁显示时值"
}
},
"name": "滚动歌词",
"refetch-btn": {
"fetching": "正在获取…",
"normal": "重新获取歌词"
},
"warnings": {
"duration-mismatch": "⚠️ - 由于持续时间不对应,滚动歌词可能不同步。",
"inexact": "⚠️ - 此曲目的歌词可能不准确",
"instrumental": "⚠️ - 此曲目为纯音乐"
}
},
"taskbar-mediacontrol": {
"description": "从 Windows 任务栏控制音乐回放",
"name": "任务栏媒体控件"
},
"touchbar": {
"description": "为 macOS 用户启用 TouchBar 支持",
"name": "TouchBar"
},
"transparent-player": {
"description": "把应用窗口变透明",
"menu": {
"opacity": {
"label": "不透明",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "类型",
"submenu": {
"acrylic": "亚克力",
"mica": "云母",
"none": "无",
"tabbed": "标签"
}
}
},
"name": "透明播放器"
},
"tuna-obs": {
"description": "与 OBS 的 Tuna 插件集成",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "防止播放器在播放歌曲时弹出",
"name": "非侵扰式播放器"
},
"video-toggle": {
"description": "增加视频/音频模式间的切换按钮。兼具移除整个视频页面的功能",
"menu": {
"align": {
"label": "位置对齐",
"submenu": {
"left": "左对齐",
"middle": "居中",
"right": "右对齐"
}
},
"force-hide": "强制移除视频页",
"mode": {
"label": "模式",
"submenu": {
"custom": "自定义开关样式",
"disabled": "禁用",
"native": "原生开关样式"
}
}
},
"name": "视频切换开关",
"templates": {
"button-song": "歌曲",
"button-video": "视频"
}
},
"visualizer": {
"description": "在播放器中添加可视化效果",
"menu": {
"visualizer-type": "可视化类型"
},
"name": "可视化效果"
}
}
}
================================================
FILE: src/i18n/resources/zh-TW.json
================================================
{
"common": {
"console": {
"plugins": {
"execute-failed": "外掛 {{pluginName}} 無法執行::{{contextName}}",
"executed-at-ms": "外掛 {{pluginName}}::{{contextName}} 用了 {{ms}} 毫秒來執行",
"initialize-failed": "初始化外掛「{{pluginName}}」失敗",
"load-all": "正在載入所有外掛",
"load-failed": "載入外掛「{{pluginName}}」失敗",
"loaded": "外掛「{{pluginName}}」已載入",
"unload-failed": "移除外掛「{{pluginName}}」失敗",
"unloaded": "外掛「{{pluginName}}」已移除"
}
}
},
"language": {
"code": "zh-TW",
"local-name": "正體中文",
"name": "Traditional Chinese"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "載入完成。開發人員工具已開啟"
},
"i18n": {
"loaded": "i18n 已載入"
},
"second-instance": {
"receive-command": "使用通訊協定來接收指令:「{{command}}」"
},
"theme": {
"css-file-not-found": "CSS 檔案「{{cssFile}}」不存在,已忽略"
},
"unresponsive": {
"details": "沒有回應錯誤!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "清理程式的快取資料"
},
"window": {
"tried-to-render-offscreen": "視窗正嘗試在螢幕外繪製,視窗大小 = {{windowSize}},螢幕大小 = {{displaySize}},位置 = {{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "選單列已隱藏,使用 Alt 鍵來重新顯示(或是使用 Esc 鍵)",
"message": "隱藏選單列已經啟用",
"title": "隱藏選單列已啟用"
},
"need-to-restart": {
"buttons": {
"later": "稍後",
"restart-now": "立即重啟"
},
"detail": "外掛 \"{{pluginName}}\" 需要重啟應用才會生效",
"message": "\"{{pluginName}}\" 需要重啟應用",
"title": "需要重啟應用"
},
"unresponsive": {
"buttons": {
"quit": "離開",
"relaunch": "重啟應用",
"wait": "稍等"
},
"detail": "造成不便我們深表歉意!請選擇動作:",
"message": "應用程式沒有回應",
"title": "視窗沒有回應"
},
"update-available": {
"buttons": {
"disable": "停用新版本通知",
"download": "前往下載",
"ok": "略過"
},
"detail": "新版本已經推出,前往下載 {{downloadLink}}",
"message": "有新版本可用",
"title": "有可用的更新"
}
},
"menu": {
"about": "關於",
"navigation": {
"label": "導覽列",
"submenu": {
"copy-current-url": "複製當前頁面的網址",
"go-back": "返回上一頁",
"go-forward": "前往下一頁",
"quit": "退出",
"restart": "重啟應用"
}
},
"options": {
"label": "選項",
"submenu": {
"advanced-options": {
"label": "進階選項",
"submenu": {
"auto-reset-app-cache": "啟動時清除應用程式快取",
"disable-hardware-acceleration": "關閉硬體加速",
"edit-config-json": "編輯 config.json",
"override-user-agent": "覆寫使用者代理",
"restart-on-config-changes": "設定變更時自動重啟應用",
"set-proxy": {
"label": "設定代理伺服器",
"prompt": {
"label": "輸入代理伺服器位置:(留空以停用本設定)",
"placeholder": "例: SOCKS5://127.0.0.1:9999",
"title": "設定代理伺服器"
}
},
"toggle-dev-tools": "開發人員工具"
}
},
"always-on-top": "最上層顯示",
"auto-update": "自動更新",
"hide-menu": {
"dialog": {
"message": "選單列會在程式下次啟動時隱藏,使用 Alt 鍵來重新顯示(或是使用 ` 鍵)",
"title": "隱藏選單列已啟用"
},
"label": "隱藏選單列"
},
"language": {
"dialog": {
"message": "語言會在重啟應用後變更",
"title": "語言已變更"
},
"label": "語言",
"submenu": {
"to-help-translate": "想協助翻譯?按一下這裡"
}
},
"resume-on-start": "應用開啟時繼續播放上次的歌曲",
"single-instance-lock": "單實例模式",
"start-at-login": "開機時啟動",
"starting-page": {
"label": "啟動頁面",
"unset": "不指定"
},
"tray": {
"label": "系統匣",
"submenu": {
"disabled": "已停用",
"enabled-and-hide-app": "啟用並最小化應用程式",
"enabled-and-show-app": "啟用並顯示應用程式",
"play-pause-on-click": "點選時播放/暫停"
}
},
"visual-tweaks": {
"label": "介面設定",
"submenu": {
"custom-window-title": {
"label": "自訂窗口標題",
"prompt": {
"label": "輸入自訂視窗標題: (留空將其停用)",
"placeholder": "例如: {{applicationName}}"
}
},
"like-buttons": {
"default": "預設",
"force-show": "強制顯示",
"hide": "隱藏",
"label": "按讚按鈕",
"swap": "交換讚及倒讚的按鈕位置"
},
"remove-upgrade-button": "移除升級按鈕",
"theme": {
"dialog": {
"button": {
"cancel": "取消",
"remove": "確定移除"
},
"remove-theme": "確定要移除自訂主題嗎?",
"remove-theme-message": "這將會移除自訂主題"
},
"label": "主題",
"submenu": {
"import-css-file": "匯入自訂 CSS 檔案",
"no-theme": "無主題"
}
}
}
}
}
},
"plugins": {
"enabled": "已啟用",
"label": "外掛功能",
"new": "新的"
},
"view": {
"label": "檢視",
"submenu": {
"force-reload": "強制重新整理",
"reload": "重新整理",
"reset-zoom": "重設大小",
"toggle-fullscreen": "切換全螢幕",
"zoom-in": "放大",
"zoom-out": "縮小"
}
}
},
"tray": {
"next": "下一首",
"play-pause": "播放/暫停",
"previous": "上一首",
"quit": "關閉",
"restart": "重新啟動應用程式",
"show": "顯示視窗",
"tooltip": {
"default": "{{applicationName}}",
"with-song-info": "{{applicationName}}: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "使用 16 倍速播放及靜音來跳過廣告片段",
"name": "加速略過"
},
"adblocker": {
"description": "阻擋所有廣告",
"menu": {
"blocker": "阻擋方式"
},
"name": "廣告攔截器"
},
"album-actions": {
"description": "新增支援對整個播放清單或專輯\"喜歡/不喜歡\"\"取消喜歡/取消不喜歡\"的按鈕",
"name": "進階專輯操作"
},
"album-color-theme": {
"description": "根據專輯封面色調更改應用程式主題顏色",
"menu": {
"color-mix-ratio": {
"label": "顏色混合程度",
"submenu": {
"percent": "{{ratio}}%"
}
},
"enable-seekbar": "啟用進度條主題樣式"
},
"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": "微光效果"
},
"amuse": {
"description": "加入支援 6K Labs 的 Amuse OBS 外掛以取得 {{applicationName}} 現正播放資訊",
"name": "Amuse",
"response": {
"query": "Amuse API 伺服器正在運作中,使用 /query 以取得歌曲資訊。"
}
},
"api-server": {
"description": "新增伺服器以使用 API 控制播放器",
"dialog": {
"request": {
"buttons": {
"allow": "允許",
"deny": "拒絕"
},
"message": "允許 {{ID}} ({{origin}}) 存取 API 嗎?",
"title": "API 驗證請求"
}
},
"menu": {
"auth-strategy": {
"label": "驗證策略",
"submenu": {
"auth-at-first": {
"label": "首次請求時驗證"
},
"none": {
"label": "不要驗證"
}
}
},
"hostname": {
"label": "主機名稱"
},
"https": {
"label": "HTTPS 及 憑證",
"submenu": {
"cert": {
"dialogTitle": "選擇憑證檔案",
"label": "憑證檔案(.crt/.pem)"
},
"enable-https": {
"label": "啟用 HTTPS"
},
"key": {
"dialogTitle": "選擇私人金鑰檔案",
"label": "私人金鑰檔案(.key/.pem)"
}
}
},
"port": {
"label": "連接埠"
}
},
"name": "API 伺服器 [Beta]",
"prompt": {
"hostname": {
"label": "輸入 API 伺服器的主機名稱 例 (0.0.0.0):",
"title": "主機名稱"
},
"port": {
"label": "輸入 API 伺服器連接埠:",
"title": "連接埠"
}
}
},
"audio-compressor": {
"description": "使用音效壓縮 (大聲部份的音量降低,柔和部份的音量提高)",
"name": "音效壓縮器"
},
"auth-proxy-adapter": {
"description": "支援使用 Proxy 驗證服務",
"menu": {
"disable": "中斷 Proxy 連線",
"enable": "啟用 Proxy 連線",
"hostname": {
"label": "主機名稱"
},
"port": {
"label": "連接埠"
}
},
"name": "Proxy 連線驗證",
"prompt": {
"hostname": {
"label": "本地 Proxy 伺服器主機名稱(需要重啟應用):",
"title": "Proxy 主機名稱"
},
"port": {
"label": "本地 Proxy 伺服器連接埠(需要重啟應用):",
"title": "Proxy 連接埠"
}
}
},
"blur-nav-bar": {
"description": "使導覽列透明及模糊",
"name": "模糊導覽列"
},
"bypass-age-restrictions": {
"description": "繞過年齡驗證",
"name": "繞過年齡驗證"
},
"captions-selector": {
"description": "{{applicationName}} 音軌字幕選項",
"menu": {
"autoload": "自動選擇上次使用的字幕",
"disable-captions": "預設無字幕"
},
"name": "字幕選項",
"prompt": {
"selector": {
"label": "當前語言: {{language}}",
"none": "無",
"title": "選擇字幕語言"
}
},
"templates": {
"title": "開啟字幕選項"
},
"toast": {
"caption-changed": "字幕語言已更改至 {{language}}",
"caption-disabled": "字幕已停用",
"no-captions": "該首歌曲無可用的字幕"
}
},
"clock": {
"description": "新增時鐘至應用程式上方",
"menu": {
"format": {
"24-hour-format": "24 小時制",
"display-seconds": "顯示秒數",
"label": "時間格式"
}
},
"name": "時鐘"
},
"compact-sidebar": {
"description": "永遠讓側邊欄保持收合狀態",
"name": "收合側邊欄"
},
"crossfade": {
"description": "在歌曲之間使用交叉淡化",
"menu": {
"advanced": "進階"
},
"name": "交叉淡化 [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "淡入時間(毫秒)",
"fade-out-duration": "淡出時間(毫秒)",
"fade-scaling": {
"label": "淡化計算方式",
"linear": "線性",
"logarithmic": "對數"
},
"seconds-before-end": "交叉淡化持續時間 (秒)"
},
"title": "交叉淡化選項"
}
}
},
"custom-output-device": {
"description": "為歌曲設定自訂輸出媒體裝置",
"menu": {
"device-selector": "選擇裝置"
},
"name": "自訂輸出裝置",
"prompt": {
"device-selector": {
"label": "選擇要使用的輸出媒體裝置",
"title": "選擇輸出裝置"
}
}
},
"disable-autoplay": {
"description": "讓歌曲開始時為暫停模式",
"menu": {
"apply-once": "只在啟動程式時套用"
},
"name": "停用自動播放"
},
"discord": {
"backend": {
"already-connected": "已嘗試可用連線",
"connected": "已連線至 Discord",
"disconnected": "已與 Discord 中斷連線"
},
"description": "使用 Discord 狀態與你的好友分享你正在收聽的音樂",
"menu": {
"auto-reconnect": "自動重新連線",
"clear-activity": "清除狀態",
"clear-activity-after-timeout": "在音樂暫停後清除狀態",
"connected": "已連線",
"disconnected": "已斷開連線",
"hide-duration-left": "隱藏音樂剩餘時間狀態",
"hide-github-button": "隱藏 GitHub 頁面按鈕",
"play-on-application": "顯示 Play on {{applicationName}} 按鈕",
"set-inactivity-timeout": "設定閒置狀態時長",
"set-status-display-type": {
"label": "狀態樣式",
"submenu": {
"application": "正在聆聽 {{applicationName}}",
"artist": "正在聆聽 {artist} 的歌曲",
"title": "正在聆聽 {song title}"
}
}
},
"name": "Discord 狀態",
"prompt": {
"set-inactivity-timeout": {
"label": "設定多少秒後清除狀態:",
"title": "設定閒置狀態時長"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "完成"
},
"message": "啊!抱歉,下載失敗了…",
"title": "下載出現錯誤!"
},
"start-download-playlist": {
"buttons": {
"ok": "完成"
},
"detail": "({{playlistSize}} 首歌曲)",
"message": "正在下載播放清單 {{playlistTitle}}",
"title": "開始下載"
}
},
"feedback": {
"conversion-progress": "轉檔進度:{{percent}}%",
"converting": "轉檔中…",
"done": "完成下載:{{filePath}}",
"download-info": "正在下載 {{artist}} - {{title}} [{{videoId}}",
"download-progress": "下載進度:{{percent}}%",
"downloading": "下載中…",
"downloading-counter": "正在下載第 {{current}}/{{total}}…",
"downloading-playlist": "正在下載播放清單 \"{{playlistTitle}}\" - 共 {{playlistSize}} 首歌 ({{playlistId}})",
"error-while-downloading": "無法下載 \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "資料夾 {{playlistFolder}} 已經存在",
"getting-playlist-info": "正在取得播放清單資訊…",
"loading": "載入中…",
"playlist-has-only-one-song": "播放清單內只有一首歌曲,將直接下載",
"playlist-id-not-found": "沒有找到播放清單 ID",
"playlist-is-empty": "播放清單是空的",
"playlist-is-mix-or-private": "取得播放清單資訊時發生錯誤:請確認非私人播放清單或是\"為你推薦的合輯\"\n\n{{error}}",
"preparing-file": "正在準備檔案…",
"saving": "儲存中…",
"trying-to-get-playlist-id": "正在嘗試取得播放清單 ID:{{playlistId}}",
"video-id-not-found": "未能找到該影片",
"writing-id3": "正在寫入 ID3 標籤…"
}
},
"description": "開啟應用程式內下載 MP3/原始音檔功能",
"menu": {
"choose-download-folder": "選擇下載位置",
"download-finish-settings": {
"label": "智慧下載",
"prompt": {
"last-percent": "歌曲剩餘多少 % 時下載",
"last-seconds": "歌曲剩餘多少秒時下載",
"title": "智慧下載進階設定"
},
"submenu": {
"advanced": "進階",
"enabled": "啟用",
"mode": "判斷方式",
"percent": "百分比",
"seconds": "秒數"
}
},
"download-playlist": "下載播放清單",
"presets": "預設格式",
"skip-existing": "跳過已存在的檔案"
},
"name": "歌曲下載",
"renderer": {
"can-not-update-progress": "無法更新進度"
},
"templates": {
"button": "下載"
}
},
"equalizer": {
"description": "為播放器加入等化器",
"menu": {
"presets": {
"label": "預設格式",
"list": {
"bass-booster": "低音增強器"
}
}
},
"name": "等化器"
},
"exponential-volume": {
"description": "使音量滑桿指數化,以便更容易選擇較低的音量。",
"name": "指數化音量調整"
},
"in-app-menu": {
"description": "使選單列變更為黑色或隨主題變色",
"menu": {
"hide-dom-window-controls": "隱藏 DOM 視窗控制"
},
"name": "程式內選單列"
},
"lumiastream": {
"description": "新增對 Lumia Stream 的支援",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "為更多歌曲新增字幕支援",
"menu": {
"romanized-lyrics": "羅馬拼音字幕"
},
"name": "第三方字幕",
"renderer": {
"fetched-lyrics": "為 Genius 取得字幕"
}
},
"music-together": {
"description": "與他人共享播放清單。當主持人播放歌曲時,其他成員也會同步收聽",
"dialog": {
"enter-host": "輸入主持人 ID"
},
"internal": {
"save": "儲存",
"track-source": "追蹤來源",
"unknown-user": "未知使用者"
},
"menu": {
"click-to-copy-id": "複製主持人 ID",
"close": "同步關閉音樂",
"connected-users": "已連線的使用者",
"disconnect": "斷開連線共享音樂",
"empty-user": "無已連線的使用者",
"host": "發起共享音樂",
"join": "加入共享音樂",
"permission": {
"all": "允許加入的使用者控制播放清單及播放控制",
"host-only": "不允許加入的使用者控制播放清單及播放控制",
"playlist": "只允許加入的使用者控制播放清單"
},
"set-permission": "切換共享音樂播放許可權",
"status": {
"disconnected": "已斷開連線",
"guest": "以訪客身份加入",
"host": "以主持人身份加入"
}
},
"name": "共享音樂 [Beta]",
"toast": {
"add-song-failed": "歌曲加入失敗",
"closed": "關閉共享音樂",
"disconnected": "共享音樂已斷開連線",
"host-failed": "發起共享音樂失敗",
"id-copied": "已複製主持人 ID",
"id-copy-failed": "複製主持人 ID 失敗",
"join-failed": "加入共享音樂失敗",
"joined": "已加入共享音樂",
"permission-changed": "共享音樂播放權限已切換至 \"{{permission}}\"",
"remove-song-failed": "歌曲移除失敗",
"user-connected": "{{name}} 已加入共享音樂",
"user-disconnected": "{{name}} 離開了共享音樂"
}
},
"navigation": {
"description": "允許應用程式上方顯示上一頁/下一頁按鈕",
"name": "導覽列",
"templates": {
"back": {
"title": "回到上一頁"
},
"forward": {
"title": "前往下一頁"
}
}
},
"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": "歌曲播放通知"
},
"performance-improvement": {
"description": "使用實驗性的腳本以優化效能",
"name": "效能優化 [Beta]"
},
"picture-in-picture": {
"description": "允許應用程式切換至子母畫面模式",
"menu": {
"always-on-top": "最上層顯示",
"hotkey": {
"label": "快捷鍵",
"prompt": {
"keybind-options": {
"hotkey": "快捷鍵"
},
"label": "選擇一個快捷鍵來切換子母畫面模式",
"title": "子母畫面模式快捷鍵"
}
},
"save-window-position": "記住視窗位置",
"save-window-size": "記住視窗大小",
"use-native-pip": "使用瀏覽器原生子母畫面模式"
},
"name": "子母畫面",
"templates": {
"button": "子母畫面"
}
},
"playback-speed": {
"description": "傷心的人別聽慢歌,新增一個滑桿控制歌曲速度",
"name": "控制歌曲速度",
"templates": {
"button": "速度"
}
},
"precise-volume": {
"description": "讓你可使用滑鼠滾輪/快捷鍵控制音量,並顯示目前音量大小",
"menu": {
"arrows-shortcuts": "方向鍵音量控制",
"custom-volume-steps": "自訂音量調整時層級",
"global-shortcuts": "全域快捷鍵"
},
"name": "進階音量控制",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "降低音量",
"increase": "增加音量"
},
"label": "選擇全域音量控制快捷鍵:",
"title": "全域音量控制快捷鍵"
},
"volume-steps": {
"label": "設定音量每一次增加/降低的層級",
"title": "自訂音量調整時層級"
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "目前畫質:{{quality}}",
"message": "選擇影片畫質:",
"title": "選擇影片畫質"
}
}
},
"description": "允許在影片內進行畫質更改",
"name": "允許變更影片畫質",
"renderer": {
"quality-settings-button": {
"label": "開啟畫質調整器"
}
}
},
"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-alternative-artist": "使用替代的藝人",
"scrobble-alternative-title": "使用替代歌曲標題",
"scrobble-other-media": "紀錄其他媒體檔案"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last.fm API 金鑰",
"api-secret": "Last.fm API 密鑰"
},
"listenbrainz": {
"token": {
"label": "輸入您的 ListenBrainz 使用者憑證:",
"title": "ListenBrainz 使用者憑證"
}
}
}
},
"shortcuts": {
"description": "使用全域快捷鍵控制音樂 (播放/暫停/下一首/上一首) + 透過覆寫媒體快捷鍵停用媒體 OSD + 允許 Ctrl/CMD + F 來搜尋 + 支援 Linux MPRIS 媒體快捷鍵 + 更多自訂快捷鍵給進階使用者",
"menu": {
"override-media-keys": "覆寫媒體快捷鍵",
"set-keybinds": "設定全域歌曲控制"
},
"name": "快捷鍵 (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "下一首",
"play-pause": "播放/暫停",
"previous": "上一首"
},
"label": "選擇全域音樂控制快捷鍵:",
"title": "全域快捷鍵"
}
}
},
"skip-disliked-songs": {
"description": "自動跳過不喜歡的歌曲",
"name": "自動跳過不喜歡的歌曲"
},
"skip-silences": {
"description": "自動跳過音樂無聲片段",
"name": "自動跳過無聲片段"
},
"sponsorblock": {
"description": "自動跳過贊助片段",
"name": "SponsorBlock"
},
"synced-lyrics": {
"description": "使用 LRClib 等管道提供歌詞同步顯示。",
"errors": {
"fetch": "⚠️\t擷取歌詞時發生錯誤\n請稍後再試。",
"not-found": "⚠️未找到該首歌曲的歌詞。"
},
"menu": {
"convert-chinese-character": {
"label": "繁簡轉換",
"submenu": {
"disabled": {
"label": "已停用",
"tooltip": "停用繁簡轉換"
},
"simplified-to-traditional": {
"label": "簡體轉繁體",
"tooltip": "轉換簡體中文至繁體中文"
},
"traditional-to-simplified": {
"label": "繁體轉簡體",
"tooltip": "轉換繁體中文至簡體中文"
}
},
"tooltip": "轉換中文字符至繁體或簡體"
},
"default-text-string": {
"label": "預設歌詞中間隔的符號",
"tooltip": "選擇歌詞中間隔要使用的符號"
},
"line-effect": {
"label": "歌詞顯示效果",
"submenu": {
"fancy": {
"label": "絢麗",
"tooltip": "使用較為接近原生樣式並且放大目前該行歌詞"
},
"focus": {
"label": "聚焦",
"tooltip": "聚焦目前的歌詞"
},
"offset": {
"label": "凸行",
"tooltip": "凸行目前的歌詞"
},
"scale": {
"label": "放大",
"tooltip": "放大目前的歌詞"
}
},
"tooltip": "選擇要使用的歌詞顯示效果"
},
"precise-timing": {
"label": "使歌詞完美同步",
"tooltip": "更精確的計算下一行歌詞的顯示 (將會降低些許效能)"
},
"preferred-provider": {
"label": "偏好提供者",
"none": {
"label": "無",
"tooltip": "沒有偏好的提供者"
},
"tooltip": "選擇預設的歌詞提供者"
},
"romanization": {
"label": "羅馬拼音化歌詞",
"tooltip": "如果歌詞使用不同語言,嘗試使用拉丁文顯示。"
},
"show-lyrics-even-if-inexact": {
"label": "即使不精確依然強制顯示歌詞",
"tooltip": "當找不到符合該歌曲的歌詞時,該功能會嘗試不同的搜尋方式。\n使用不同的搜尋方式會導致不精確的結果。"
},
"show-time-codes": {
"label": "顯示時間線",
"tooltip": "在歌詞旁顯示時間線"
}
},
"name": "歌詞同步",
"refetch-btn": {
"fetching": "擷取中...",
"normal": "重新擷取歌詞"
},
"warnings": {
"duration-mismatch": "⚠️歌詞可能會出現不同步的情況。",
"inexact": "⚠️該歌曲的歌詞可能並不精確",
"instrumental": "⚠️該首歌曲為純音樂"
}
},
"taskbar-mediacontrol": {
"description": "允許工作列應用程式預覽介面顯示媒體控制相關按鈕",
"name": "工作列媒體控制"
},
"touchbar": {
"description": "為 macOS 使用者新增觸控列支援",
"name": "觸控列 (Touchbar) 支援"
},
"transparent-player": {
"description": "讓視窗背景變為透明糢糊",
"menu": {
"opacity": {
"label": "不透明度",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "樣式",
"submenu": {
"acrylic": "壓克力(Acrylic)",
"mica": "雲母(Mica)",
"none": "無",
"tabbed": "分頁式(Tabbed)"
}
}
},
"name": "透明糢糊效果"
},
"tuna-obs": {
"description": "與 OBS 的 Tuna 外掛連線",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "防止播放器介面在點選歌曲後彈出",
"name": "低干擾播放器介面"
},
"video-toggle": {
"description": "新增一個按鈕可以控制影片/歌曲切換和完全移除整個影片頁面的功能",
"menu": {
"align": {
"label": "按鈕位置",
"submenu": {
"left": "靠左",
"middle": "中間",
"right": "靠右"
}
},
"force-hide": "強制移除整個影片頁面",
"mode": {
"label": "模式",
"submenu": {
"custom": "自訂樣式",
"disabled": "停用",
"native": "原生樣式"
}
}
},
"name": "歌曲/影片切換",
"templates": {
"button-song": "歌曲",
"button-video": "影片"
}
},
"visualizer": {
"description": "在播放器新增視覺化效果",
"menu": {
"visualizer-type": "視覺化效果樣式"
},
"name": "視覺化效果"
}
}
}
================================================
FILE: src/index.html
================================================
================================================
FILE: src/index.ts
================================================
import path from 'node:path';
import url from 'node:url';
import fs from 'node:fs';
import {
BrowserWindow,
app,
screen,
globalShortcut,
session,
shell,
dialog,
ipcMain,
protocol,
type BrowserWindowConstructorOptions,
} from 'electron';
import {
enhanceWebRequest,
type BetterSession,
} from '@jellybrick/electron-better-web-request';
import is from 'electron-is';
import unhandled from 'electron-unhandled';
import { autoUpdater } from 'electron-updater';
import electronDebug from 'electron-debug';
import { parse } from 'node-html-parser';
import { deepmerge } from 'deepmerge-ts';
import { deepEqual } from 'fast-equals';
import { allPlugins, mainPlugins } from 'virtual:plugins';
import { languageResources } from 'virtual:i18n';
import * as config from '@/config';
import { refreshMenu, setApplicationMenu } from '@/menu';
import { fileExists, injectCSS, injectCSSAsFile } from '@/plugins/utils/main';
import { isTesting } from '@/utils/testing';
import { setUpTray } from '@/tray';
import { setupSongInfo } from '@/providers/song-info';
import { restart, setupAppControls } from '@/providers/app-controls';
import {
APP_PROTOCOL,
handleProtocol,
setupProtocolHandler,
} from '@/providers/protocol-handler';
import musicPlayerCss from '@/music-player.css?inline';
import {
forceLoadMainPlugin,
forceUnloadMainPlugin,
getAllLoadedMainPlugins,
loadAllMainPlugins,
} from '@/loader/main';
import { LoggerPrefix } from '@/utils';
import { APPLICATION_NAME, loadI18n, setLanguage, t } from '@/i18n';
import ErrorHtmlAsset from '@assets/error.html?asset';
import { defaultAuthProxyConfig } from '@/plugins/auth-proxy-adapter/config';
import type { PluginConfig } from '@/types/plugins';
// Catch errors and log them
unhandled({
logger: console.error,
showDialog: false,
});
// Prevent window being garbage collected
let mainWindow: Electron.BrowserWindow | null;
autoUpdater.autoDownload = false;
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.exit();
}
protocol.registerSchemesAsPrivileged([
{
scheme: 'http',
privileges: {
standard: true,
bypassCSP: true,
allowServiceWorkers: true,
supportFetchAPI: true,
corsEnabled: true,
stream: true,
codeCache: true,
},
},
{
scheme: 'https',
privileges: {
standard: true,
bypassCSP: true,
allowServiceWorkers: true,
supportFetchAPI: true,
corsEnabled: true,
stream: true,
codeCache: true,
},
},
{ scheme: 'mailto', privileges: { standard: true } },
]);
// 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,UseOzonePlatform,WaylandWindowDecorations',
);
// Disable Fluent Scrollbar (for OverlayScrollbar)
app.commandLine.appendSwitch('disable-features', 'FluentScrollbar');
if (config.get('options.disableHardwareAcceleration')) {
if (is.dev()) {
console.log('Disabling hardware acceleration');
}
app.disableHardwareAcceleration();
}
if (is.linux()) {
// Overrides WM_CLASS for X11 to correspond to icon filename
app.setName(
'com.github.th_ch.\u0079\u006f\u0075\u0074\u0075\u0062\u0065\u005f\u006d\u0075\u0073\u0069\u0063',
);
// Stops chromium from launching its own MPRIS service
if (await config.plugins.isEnabled('shortcuts')) {
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
}
}
if (config.get('options.proxy')) {
const authProxyEnabled = await config.plugins.isEnabled('auth-proxy-adapter');
let proxyToUse = '';
if (authProxyEnabled) {
// Use proxy from Auth-Proxy-Adapter plugin
const authProxyConfig = deepmerge(
defaultAuthProxyConfig,
config.get('plugins.auth-proxy-adapter') ?? {},
) as typeof defaultAuthProxyConfig;
const { hostname, port } = authProxyConfig;
proxyToUse = `socks5://${hostname}:${port}`;
} else if (config.get('options.proxy')) {
// Use global proxy settings
proxyToUse = config.get('options.proxy');
}
console.log(LoggerPrefix, `Using proxy: ${proxyToUse}`);
app.commandLine.appendSwitch('proxy-server', proxyToUse);
}
// Adds debug features like hotkeys for triggering dev tools and reload
electronDebug({
showDevTools: false, // Disable automatic devTools on new window
});
let icon = 'assets/icon.png';
if (process.platform === 'win32') {
icon = 'assets/generated/icons/win/icon.ico';
} else if (process.platform === 'darwin') {
icon = 'assets/generated/icons/mac/icon.icns';
}
function onClosed() {
// Dereference the window
// For multiple Windows store them in an array
mainWindow = null;
}
ipcMain.handle('peard:get-main-plugin-names', async () =>
Object.keys(await mainPlugins()),
);
const initHook = async (win: BrowserWindow) => {
const allPluginStubs = await allPlugins();
ipcMain.handle(
'peard:get-config',
(_, id: string) =>
deepmerge(
allPluginStubs[id].config ?? { enabled: false },
config.get(`plugins.${id}`) ?? {},
) as PluginConfig,
);
ipcMain.handle('peard:set-config', (_, name: string, obj: object) =>
config.setPartial(`plugins.${name}`, obj, allPluginStubs[name].config),
);
config.watch((newValue, oldValue) => {
const newPluginConfigList = (newValue?.plugins ?? {}) as Record<
string,
unknown
>;
const oldPluginConfigList = (oldValue?.plugins ?? {}) as Record<
string,
unknown
>;
Object.entries(newPluginConfigList).forEach(([id, newPluginConfig]) => {
const isEqual = deepEqual(oldPluginConfigList[id], newPluginConfig);
if (!isEqual) {
const oldConfig = oldPluginConfigList[id] as PluginConfig;
const config = deepmerge(
allPluginStubs[id].config ?? { enabled: false },
newPluginConfig ?? {},
) as PluginConfig;
if (config.enabled !== oldConfig?.enabled) {
if (config.enabled) {
win.webContents.send('plugin:enable', id);
ipcMain.emit('plugin:enable', id);
forceLoadMainPlugin(id, win);
} else {
win.webContents.send('plugin:unload', id);
ipcMain.emit('plugin:unload', id);
forceUnloadMainPlugin(id, win);
}
if (allPluginStubs[id]?.restartNeeded) {
showNeedToRestartDialog(id);
}
}
const mainPlugin = getAllLoadedMainPlugins()[id];
if (mainPlugin) {
if (config.enabled && typeof mainPlugin.backend !== 'function') {
mainPlugin.backend?.onConfigChange?.call(
mainPlugin.backend,
config,
);
}
}
win.webContents.send('config-changed', id, config);
}
});
});
};
const showNeedToRestartDialog = async (id: string) => {
const plugin = (await allPlugins())[id];
const dialogOptions: Electron.MessageBoxOptions = {
type: 'info',
buttons: [
t('main.dialog.need-to-restart.buttons.restart-now'),
t('main.dialog.need-to-restart.buttons.later'),
],
title: t('main.dialog.need-to-restart.title'),
message: t('main.dialog.need-to-restart.message', {
pluginName: plugin?.name?.() ?? id,
}),
detail: t('main.dialog.need-to-restart.detail', {
pluginName: plugin?.name?.() ?? id,
}),
defaultId: 0,
cancelId: 1,
};
let dialogPromise: Promise;
if (mainWindow) {
dialogPromise = dialog.showMessageBox(mainWindow, dialogOptions);
} else {
dialogPromise = dialog.showMessageBox(dialogOptions);
}
dialogPromise.then((dialogOutput) => {
switch (dialogOutput.response) {
case 0: {
restart();
break;
}
// Ignore
default: {
break;
}
}
});
};
function initTheme(win: BrowserWindow) {
injectCSS(win.webContents, musicPlayerCss);
// Load user CSS
const themes: string[] = config.get('options.themes');
if (Array.isArray(themes)) {
for (const cssFile of themes) {
fileExists(
cssFile,
() => {
injectCSSAsFile(win.webContents, cssFile);
},
() => {
console.warn(
LoggerPrefix,
t('main.console.theme.css-file-not-found', { cssFile }),
);
},
);
}
}
win.webContents.once('did-finish-load', () => {
if (is.dev()) {
console.debug(LoggerPrefix, t('main.console.did-finish-load.dev-tools'));
win.webContents.openDevTools();
}
});
}
async function createMainWindow() {
const windowSize = config.get('window-size');
const windowMaximized = config.get('window-maximized');
const windowPosition: Electron.Point = config.get('window-position');
const useInlineMenu = await config.plugins.isEnabled('in-app-menu');
const defaultTitleBarOverlayOptions: Electron.TitleBarOverlay = {
color: '#00000000',
symbolColor: '#ffffff',
height: 32,
};
const decorations: Partial = {
frame: !is.macOS() && !useInlineMenu,
titleBarOverlay: defaultTitleBarOverlayOptions,
titleBarStyle: useInlineMenu
? 'hidden'
: is.macOS()
? 'hiddenInset'
: 'default',
autoHideMenuBar: config.get('options.hideMenu'),
};
// Note: on linux, for some weird reason, having these extra properties with 'frame: false' does not work
if (is.linux() && useInlineMenu) {
delete decorations.titleBarOverlay;
delete decorations.titleBarStyle;
}
const electronWindowSettings: Electron.BrowserWindowConstructorOptions = {
icon,
width: windowSize.width,
height: windowSize.height,
minWidth: 325,
minHeight: 425,
backgroundColor: '#000',
show: false,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, '..', 'preload', 'preload.cjs'),
...(isTesting()
? undefined
: {
// Sandbox is only enabled in tests for now
// See https://www.electronjs.org/docs/latest/tutorial/sandbox#preload-scripts
sandbox: false,
}),
},
...decorations,
};
const win = new BrowserWindow(electronWindowSettings);
await initHook(win);
initTheme(win);
await loadAllMainPlugins(win);
if (windowPosition) {
const { x: windowX, y: windowY } = windowPosition;
const winSize = win.getSize();
const display = screen.getDisplayNearestPoint(windowPosition);
const primaryDisplay = screen.getPrimaryDisplay();
const scaleFactor = is.windows()
? primaryDisplay.scaleFactor / display.scaleFactor
: 1;
const scaledWidth = Math.floor(windowSize.width * scaleFactor);
const scaledHeight = Math.floor(windowSize.height * scaleFactor);
const scaledX = windowX;
const scaledY = windowY;
if (
scaledX + scaledWidth / 2 < display.bounds.x - 8 || // Left
scaledX + scaledWidth / 2 > display.bounds.x + display.bounds.width || // Right
scaledY < display.bounds.y - 8 || // Top
scaledY + scaledHeight / 2 > display.bounds.y + display.bounds.height // Bottom
) {
// Window is offscreen
if (is.dev()) {
console.warn(
LoggerPrefix,
t('main.console.window.tried-to-render-offscreen', {
windowSize: String(winSize),
displaySize: JSON.stringify(display.bounds),
position: JSON.stringify(windowPosition),
}),
);
}
} else {
win.setSize(scaledWidth, scaledHeight);
win.setPosition(scaledX, scaledY);
}
}
if (windowMaximized) {
win.maximize();
}
if (config.get('options.alwaysOnTop')) {
win.setAlwaysOnTop(true);
}
const urlToLoad = config.get('options.resumeOnStart')
? config.get('url')
: config.defaultConfig.url;
win.on('closed', onClosed);
win.on('move', () => {
if (win.isMaximized()) {
return;
}
const [x, y] = win.getPosition();
lateSave('window-position', { x, y });
});
let winWasMaximized: boolean;
win.on('resize', () => {
const [width, height] = win.getSize();
const isMaximized = win.isMaximized();
if (winWasMaximized !== isMaximized) {
winWasMaximized = isMaximized;
config.set('window-maximized', isMaximized);
}
if (isMaximized) {
return;
}
lateSave('window-size', {
width,
height,
});
});
const savedTimeouts: Record = {};
function lateSave(
key: string,
value: unknown,
fn: (key: string, value: unknown) => void = config.set,
) {
if (savedTimeouts[key]) {
clearTimeout(savedTimeouts[key]);
}
savedTimeouts[key] = setTimeout(() => {
fn(key, value);
savedTimeouts[key] = undefined;
}, 600);
}
app.on('render-process-gone', (_event, _webContents, details) => {
showUnresponsiveDialog(win, details);
});
win.once('ready-to-show', () => {
if (config.get('options.appVisible')) {
win.show();
}
});
removeContentSecurityPolicy();
win.webContents.on('dom-ready', () => {
if (useInlineMenu && is.windows()) {
win.setTitleBarOverlay({
...defaultTitleBarOverlayOptions,
height: Math.floor(
defaultTitleBarOverlayOptions.height! *
win.webContents.getZoomFactor(),
),
});
}
});
win.webContents.on('will-redirect', (event) => {
const url = new URL(event.url);
// Workarounds for regions where YTM is restricted
if (
url.hostname.endsWith('\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com') &&
url.pathname === '/premium'
) {
event.preventDefault();
win.webContents.loadURL(
'https://accounts.google.com/ServiceLogin?ltmpl=music&service=\u0079\u006f\u0075\u0074\u0075\u0062\u0065&continue=https%3A%2F%2Fwww.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com%2Fsignin%3Faction_handle_signin%3Dtrue%26next%3Dhttps%253A%252F%252Fmusic.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com%252F',
);
}
});
win.webContents.loadURL(urlToLoad);
return win;
}
app.once('browser-window-created', (_event, win) => {
if (config.get('options.overrideUserAgent')) {
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
const originalUserAgent = win.webContents.userAgent;
const userAgents = {
mac: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.152 Safari/537.36',
windows:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.152 Safari/537.36',
linux:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.152 Safari/537.36',
};
const updatedUserAgent = is.macOS()
? userAgents.mac
: is.windows()
? userAgents.windows
: userAgents.linux;
win.webContents.userAgent = updatedUserAgent;
app.userAgentFallback = updatedUserAgent;
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
// This will only happen if login failed, and "retry" was pressed
if (
win.webContents.getURL().startsWith('https://accounts.google.com') &&
details.url.startsWith('https://accounts.google.com')
) {
details.requestHeaders['User-Agent'] = originalUserAgent;
}
cb({ requestHeaders: details.requestHeaders });
});
}
setupSongInfo(win);
setupAppControls();
win.webContents.on(
'did-fail-load',
(
_event,
errorCode,
errorDescription,
validatedURL,
isMainFrame,
frameProcessId,
frameRoutingId,
) => {
const log = JSON.stringify(
{
error: 'did-fail-load',
errorCode,
errorDescription,
validatedURL,
isMainFrame,
frameProcessId,
frameRoutingId,
},
null,
'\t',
);
if (is.dev()) {
console.log(log);
}
if (
errorCode !== -3 &&
// Workaround for #2435
!new URL(validatedURL).hostname.includes('doubleclick.net')
) {
// -3 is a false positive
win.webContents.send('log', log);
win.webContents.loadFile(ErrorHtmlAsset);
}
},
);
win.webContents.on('will-prevent-unload', (event) => {
event.preventDefault();
});
const customWindowTitle = config.get('options.customWindowTitle');
if (customWindowTitle) {
win.on('page-title-updated', (event) => {
event.preventDefault();
win.setTitle(customWindowTitle);
});
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
// Unregister all shortcuts.
globalShortcut.unregisterAll();
});
app.on('activate', async () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
mainWindow = await createMainWindow();
} else if (!mainWindow.isVisible()) {
mainWindow.show();
}
});
const getDefaultLocale = async (locale: string) =>
Object.keys(await languageResources()).includes(locale) ? locale : null;
app.whenReady().then(async () => {
if (!config.get('options.language')) {
const locale = await getDefaultLocale(app.getLocale());
if (locale) {
config.set('options.language', locale);
}
}
await loadI18n().then(async () => {
await setLanguage(config.get('options.language') ?? 'en');
console.log(LoggerPrefix, t('main.console.i18n.loaded'));
});
if (config.get('options.autoResetAppCache')) {
// Clear cache after 20s
const clearCacheTimeout = setTimeout(() => {
if (is.dev()) {
console.log(
LoggerPrefix,
t('main.console.when-ready.clearing-cache-after-20s'),
);
}
session.defaultSession.clearCache();
clearTimeout(clearCacheTimeout);
}, 20_000);
}
// Register appID on windows
if (is.windows()) {
const appID =
'com.github.th-ch.\u0079\u006f\u0075\u0074\u0075\u0062\u0065\u002d\u006d\u0075\u0073\u0069\u0063';
app.setAppUserModelId(appID);
const appLocation = process.execPath;
const appData = app.getPath('appData');
// Check shortcut validity if not in dev mode / running portable app
if (
!is.dev() &&
!appLocation.startsWith(path.join(appData, '..', 'Local', 'Temp'))
) {
const shortcutPath = path.join(
appData,
'Microsoft',
'Windows',
'Start Menu',
'Programs',
`${APPLICATION_NAME}.lnk`,
);
try {
// Check if shortcut is registered and valid
const shortcutDetails = shell.readShortcutLink(shortcutPath); // Throw error if it doesn't exist yet
if (
shortcutDetails.target !== appLocation ||
shortcutDetails.appUserModelId !== appID
) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw 'needUpdate';
}
} catch (error) {
// If not valid -> Register shortcut
shell.writeShortcutLink(
shortcutPath,
error === 'needUpdate' ? 'update' : 'create',
{
target: appLocation,
cwd: path.dirname(appLocation),
description: `${APPLICATION_NAME} Desktop App - including custom plugins`,
appUserModelId: appID,
},
);
}
}
}
ipcMain.on('get-renderer-script', (event) => {
// Inject index.html file as string using insertAdjacentHTML
// In dev mode, get string from process.env.VITE_DEV_SERVER_URL, else use fs.readFileSync
if (is.dev() && process.env.ELECTRON_RENDERER_URL) {
// HACK: to make vite work with electron renderer (supports hot reload)
event.returnValue = [
null,
`
console.log('${LoggerPrefix}', 'Loading vite from dev server');
(async () => {
await new Promise((resolve) => {
if (document.readyState === 'loading') {
console.log('${LoggerPrefix}', 'Waiting for DOM to load');
document.addEventListener('DOMContentLoaded', () => resolve(), { once: true });
} else {
resolve();
}
});
const viteScript = document.createElement('script');
viteScript.type = 'module';
viteScript.src = '${process.env.ELECTRON_RENDERER_URL}/@vite/client';
const rendererScript = document.createElement('script');
rendererScript.type = 'module';
rendererScript.src = '${process.env.ELECTRON_RENDERER_URL}/renderer.ts';
document.body.appendChild(viteScript);
document.body.appendChild(rendererScript);
})();
0
`,
];
} else {
const rendererPath = path.join(__dirname, '..', 'renderer');
const indexHTML = parse(
fs.readFileSync(path.join(rendererPath, 'index.html'), 'utf-8'),
);
const scriptSrc = indexHTML.querySelector('script')!;
const scriptPath = path.join(
rendererPath,
scriptSrc.getAttribute('src')!,
);
const scriptString = fs.readFileSync(scriptPath, 'utf-8');
event.returnValue = [
url.pathToFileURL(scriptPath).toString(),
scriptString + ';0',
];
}
});
mainWindow = await createMainWindow();
await setApplicationMenu(mainWindow);
await refreshMenu(mainWindow);
setUpTray(app, mainWindow);
setupProtocolHandler(mainWindow);
app.on('second-instance', (_, commandLine) => {
const uri = `${APP_PROTOCOL}://`;
const protocolArgv = commandLine.find((arg) => arg.startsWith(uri));
if (protocolArgv) {
const lastIndex = protocolArgv.endsWith('/') ? -1 : undefined;
const command = protocolArgv.slice(uri.length, lastIndex);
if (is.dev()) {
console.debug(
LoggerPrefix,
t('main.console.second-instance.receive-command', { command }),
);
}
const splited = decodeURIComponent(command).split(' ');
handleProtocol(splited.shift()!, ...splited);
return;
}
if (!mainWindow) {
return;
}
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
if (!mainWindow.isVisible()) {
mainWindow.show();
}
mainWindow.focus();
});
// Autostart at login
app.setLoginItemSettings({
openAtLogin: config.get('options.startAtLogin'),
});
if (!is.dev() && config.get('options.autoUpdates')) {
const updateTimeout = setTimeout(() => {
autoUpdater.checkForUpdatesAndNotify();
clearTimeout(updateTimeout);
}, 2000);
autoUpdater.on('update-available', () => {
const downloadLink =
'https://github.com/pear-devs/pear-desktop/releases/latest';
const dialogOptions: Electron.MessageBoxOptions = {
type: 'info',
buttons: [
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;
if (mainWindow) {
dialogPromise = dialog.showMessageBox(mainWindow, dialogOptions);
} else {
dialogPromise = dialog.showMessageBox(dialogOptions);
}
dialogPromise.then((dialogOutput) => {
switch (dialogOutput.response) {
// Download
case 1: {
shell.openExternal(downloadLink);
break;
}
// Disable updates
case 2: {
config.set('options.autoUpdates', false);
break;
}
case 0: {
break;
}
}
});
});
}
if (config.get('options.hideMenu') && !config.get('options.hideMenuWarned')) {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: t('main.dialog.hide-menu-enabled.title'),
message: t('main.dialog.hide-menu-enabled.message'),
});
config.set('options.hideMenuWarned', true);
}
// Optimized for Mac OS X
if (is.macOS() && !config.get('options.appVisible')) {
app.dock?.hide();
}
let forceQuit = false;
app.on('before-quit', () => {
forceQuit = true;
});
if (is.macOS() || config.get('options.tray')) {
mainWindow.on('close', (event) => {
// Hide the window instead of quitting (quit is available in tray options)
if (!forceQuit) {
event.preventDefault();
mainWindow!.hide();
}
});
}
});
function showUnresponsiveDialog(
win: BrowserWindow,
details: Electron.RenderProcessGoneDetails,
) {
if (details) {
console.error(
LoggerPrefix,
t('main.console.unresponsive.details', {
error: JSON.stringify(details, null, '\t'),
}),
);
}
dialog
.showMessageBox(win, {
type: 'error',
title: t('main.dialog.unresponsive.title'),
message: t('main.dialog.unresponsive.message'),
detail: t('main.dialog.unresponsive.detail'),
buttons: [
t('main.dialog.unresponsive.buttons.wait'),
t('main.dialog.unresponsive.buttons.relaunch'),
t('main.dialog.unresponsive.buttons.quit'),
],
cancelId: 0,
})
.then((result) => {
switch (result.response) {
case 1: {
restart();
break;
}
case 2: {
app.quit();
break;
}
}
});
}
function removeContentSecurityPolicy(
betterSession: BetterSession = session.defaultSession as BetterSession,
) {
// Allows defining multiple "onHeadersReceived" listeners
// by enhancing the session.
// Some plugins (e.g. adblocker) also define a "onHeadersReceived" listener
enhanceWebRequest(betterSession);
// Custom listener to tweak the content security policy
betterSession.webRequest.onHeadersReceived((details, callback) => {
details.responseHeaders ??= {};
// prettier-ignore
if (new URL(details.url).protocol === 'https:') {
// Remove the content security policy
delete details.responseHeaders['content-security-policy-report-only'];
delete details.responseHeaders['Content-Security-Policy-Report-Only'];
delete details.responseHeaders['content-security-policy'];
delete details.responseHeaders['Content-Security-Policy'];
if (
!details.responseHeaders['access-control-allow-origin'] &&
!details.responseHeaders['Access-Control-Allow-Origin']
) {
details.responseHeaders['access-control-allow-origin'] = ['https://music.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com'];
}
}
callback({ cancel: false, responseHeaders: details.responseHeaders });
});
// When multiple listeners are defined, apply them all
betterSession.webRequest.setResolver(
'onHeadersReceived',
async (listeners) => {
return listeners.reduce(
async (accumulator, listener) => {
const acc = await accumulator;
if (acc.cancel) {
return acc;
}
const result = await listener.apply();
return { ...accumulator, ...result };
},
Promise.resolve({ cancel: false }),
);
},
);
}
================================================
FILE: src/loader/main.ts
================================================
import { type BrowserWindow, ipcMain } from 'electron';
import { deepmerge } from 'deepmerge-ts';
import { allPlugins, mainPlugins } from 'virtual:plugins';
import * as config from '@/config';
import { LoggerPrefix, startPlugin, stopPlugin } from '@/utils';
import { t } from '@/i18n';
import type { PluginConfig, PluginDef } from '@/types/plugins';
import type { BackendContext } from '@/types/contexts';
const loadedPluginMap: Record<
string,
PluginDef
> = {};
const createContext = (
id: string,
win: BrowserWindow,
): BackendContext => ({
getConfig: async () =>
deepmerge(
(await allPlugins())[id].config ?? { enabled: false },
config.get(`plugins.${id}`) ?? {},
) as PluginConfig,
setConfig: async (newConfig) => {
config.setPartial(
`plugins.${id}`,
newConfig,
(await allPlugins())[id].config,
);
},
ipc: {
send: (event: string, ...args: unknown[]) => {
win.webContents.send(event, ...args);
},
handle: (event: string, listener: CallableFunction) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call
ipcMain.handle(event, (_, ...args: unknown[]) => listener(...args));
},
on: (event: string, listener: CallableFunction) => {
ipcMain.on(event, (_, ...args: unknown[]) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
listener(...args);
});
},
removeHandler: (event: string) => {
ipcMain.removeHandler(event);
},
},
window: win,
});
export const forceUnloadMainPlugin = async (
id: string,
win: BrowserWindow,
): Promise => {
const plugin = loadedPluginMap[id];
if (!plugin) return;
try {
const hasStopped = await stopPlugin(id, plugin, {
ctx: 'backend',
context: createContext(id, win),
});
if (
hasStopped ||
(hasStopped === null &&
typeof plugin.backend !== 'function' &&
plugin.backend)
) {
delete loadedPluginMap[id];
console.log(
LoggerPrefix,
t('common.console.plugins.unloaded', { pluginName: id }),
);
return;
} else {
const message = t('common.console.plugins.unload-failed', {
pluginName: id,
});
console.log(LoggerPrefix, message);
return Promise.reject(new Error(message));
}
} catch (err) {
console.error(
LoggerPrefix,
t('common.console.plugins.unload-failed', { pluginName: id }),
);
console.trace(err);
return Promise.reject(err as Error);
}
};
export const forceLoadMainPlugin = async (
id: string,
win: BrowserWindow,
): Promise => {
const plugin = (await mainPlugins())[id];
if (!plugin) return;
try {
const hasStarted = await startPlugin(id, plugin, {
ctx: 'backend',
context: createContext(id, win),
});
if (
hasStarted ||
(hasStarted === null &&
typeof plugin.backend !== 'function' &&
plugin.backend)
) {
loadedPluginMap[id] = plugin;
} else {
const message = t('common.console.plugins.load-failed', {
pluginName: id,
});
console.log(LoggerPrefix, message);
return Promise.reject(new Error(message));
}
} catch (err) {
console.error(
LoggerPrefix,
t('common.console.plugins.initialize-failed', { pluginName: id }),
);
console.trace(err);
return Promise.reject(err as Error);
}
};
export const loadAllMainPlugins = async (win: BrowserWindow) => {
console.log(LoggerPrefix, t('common.console.plugins.load-all'));
const pluginConfigs = config.plugins.getPlugins();
const queue: Promise[] = [];
for (const [plugin, pluginDef] of Object.entries(await mainPlugins())) {
const config = deepmerge(pluginDef.config, pluginConfigs[plugin] ?? {});
if (config.enabled) {
queue.push(forceLoadMainPlugin(plugin, win));
} else if (loadedPluginMap[plugin]) {
queue.push(forceUnloadMainPlugin(plugin, win));
}
}
await Promise.allSettled(queue);
};
export const unloadAllMainPlugins = async (win: BrowserWindow) => {
for (const id of Object.keys(loadedPluginMap)) {
await forceUnloadMainPlugin(id, win);
}
};
export const getLoadedMainPlugin = (
id: string,
): PluginDef | undefined => {
return loadedPluginMap[id];
};
export const getAllLoadedMainPlugins = () => {
return loadedPluginMap;
};
================================================
FILE: src/loader/menu.ts
================================================
import { deepmerge } from 'deepmerge-ts';
import { allPlugins } from 'virtual:plugins';
import * as config from '@/config';
import { setApplicationMenu } from '@/menu';
import { LoggerPrefix } from '@/utils';
import { t } from '@/i18n';
import type { MenuContext } from '@/types/contexts';
import type { BrowserWindow, MenuItemConstructorOptions } from 'electron';
import type { PluginConfig } from '@/types/plugins';
const menuTemplateMap: Record = {};
const createContext = (
id: string,
win: BrowserWindow,
): MenuContext => ({
getConfig: async () =>
deepmerge(
(await allPlugins())[id].config ?? { enabled: false },
config.get(`plugins.${id}`) ?? {},
) as PluginConfig,
setConfig: async (newConfig) => {
config.setPartial(
`plugins.${id}`,
newConfig,
(await allPlugins())[id].config,
);
},
window: win,
refresh: async () => {
await setApplicationMenu(win);
if (await config.plugins.isEnabled('in-app-menu')) {
win.webContents.send('refresh-in-app-menu');
}
},
});
export const forceLoadMenuPlugin = async (id: string, win: BrowserWindow) => {
try {
const plugin = (await allPlugins())[id];
if (!plugin) return;
const menu = plugin.menu?.(createContext(id, win));
if (menu) {
const result = await menu;
if (result.length > 0) {
menuTemplateMap[id] = result;
} else {
return;
}
} else return;
console.log(
LoggerPrefix,
t('common.console.plugins.loaded', { pluginName: `${id}::menu` }),
);
} catch (err) {
console.error(
LoggerPrefix,
t('common.console.plugins.initialize-failed', {
pluginName: `${id}::menu`,
}),
);
console.trace(err);
}
};
export const loadAllMenuPlugins = async (win: BrowserWindow) => {
const pluginConfigs = config.plugins.getPlugins();
for (const [pluginId, pluginDef] of Object.entries(await allPlugins())) {
const config = deepmerge(
pluginDef.config ?? { enabled: false },
pluginConfigs[pluginId] ?? {},
);
if (config.enabled) {
await forceLoadMenuPlugin(pluginId, win);
}
}
};
export const getMenuTemplate = (
id: string,
): MenuItemConstructorOptions[] | undefined => {
return menuTemplateMap[id];
};
export const getAllMenuTemplate = () => {
return menuTemplateMap;
};
================================================
FILE: src/loader/preload.ts
================================================
import { deepmerge } from 'deepmerge-ts';
import { allPlugins, preloadPlugins } from 'virtual:plugins';
import { LoggerPrefix, startPlugin, stopPlugin } from '@/utils';
import * as config from '@/config';
import { t } from '@/i18n';
import type { PreloadContext } from '@/types/contexts';
import type { PluginConfig, PluginDef } from '@/types/plugins';
const loadedPluginMap: Record<
string,
PluginDef
> = {};
const createContext = (id: string): PreloadContext => ({
getConfig: async () =>
deepmerge(
(await allPlugins())[id].config ?? { enabled: false },
config.get(`plugins.${id}`) ?? {},
) as PluginConfig,
setConfig: async (newConfig) => {
config.setPartial(
`plugins.${id}`,
newConfig,
(await allPlugins())[id].config,
);
},
});
export const forceUnloadPreloadPlugin = async (id: string) => {
if (!loadedPluginMap[id]) return;
const hasStopped = await stopPlugin(id, loadedPluginMap[id], {
ctx: 'preload',
context: createContext(id),
});
if (hasStopped || (hasStopped === null && loadedPluginMap[id].preload)) {
console.log(
LoggerPrefix,
t('common.console.plugins.unloaded', { pluginName: id }),
);
delete loadedPluginMap[id];
} else {
console.error(
LoggerPrefix,
t('common.console.plugins.unload-failed', { pluginName: id }),
);
}
};
export const forceLoadPreloadPlugin = async (id: string) => {
try {
const plugin = (await preloadPlugins())[id];
if (!plugin) return;
const hasStarted = await startPlugin(id, plugin, {
ctx: 'preload',
context: createContext(id),
});
if (
hasStarted ||
(hasStarted === null &&
typeof plugin.preload !== 'function' &&
plugin.preload)
) {
loadedPluginMap[id] = plugin;
}
console.log(
LoggerPrefix,
t('common.console.plugins.loaded', { pluginName: id }),
);
} catch (err) {
console.error(
LoggerPrefix,
t('common.console.plugins.initialize-failed', { pluginName: id }),
);
console.trace(err);
}
};
export const loadAllPreloadPlugins = async () => {
const pluginConfigs = config.plugins.getPlugins();
for (const [pluginId, pluginDef] of Object.entries(await preloadPlugins())) {
const config = deepmerge(
pluginDef.config ?? { enable: false },
pluginConfigs[pluginId] ?? {},
);
if (config.enabled) {
forceLoadPreloadPlugin(pluginId);
} else {
if (loadedPluginMap[pluginId]) {
forceUnloadPreloadPlugin(pluginId);
}
}
}
};
export const unloadAllPreloadPlugins = async () => {
for (const id of Object.keys(loadedPluginMap)) {
await forceUnloadPreloadPlugin(id);
}
};
export const getLoadedPreloadPlugin = (
id: string,
): PluginDef | undefined => {
return loadedPluginMap[id];
};
export const getAllLoadedPreloadPlugins = () => {
return loadedPluginMap;
};
================================================
FILE: src/loader/renderer.ts
================================================
import { deepmerge } from 'deepmerge-ts';
import { rendererPlugins } from 'virtual:plugins';
import { LoggerPrefix, startPlugin, stopPlugin } from '@/utils';
import { t } from '@/i18n';
import type { RendererContext } from '@/types/contexts';
import type { PluginConfig, PluginDef } from '@/types/plugins';
const unregisterStyleMap: Record void)[]> = {};
const loadedPluginMap: Record<
string,
PluginDef
> = {};
export const createContext = (
id: string,
): RendererContext => ({
getConfig: () =>
window.ipcRenderer.invoke('peard:get-config', id) as Promise,
setConfig: async (newConfig) => {
await window.ipcRenderer.invoke('peard:set-config', id, newConfig);
},
ipc: {
send: (event: string, ...args: unknown[]) => {
window.ipcRenderer.send(event, ...args);
},
invoke: (event: string, ...args: unknown[]) =>
window.ipcRenderer.invoke(event, ...args),
on: (event: string, listener: CallableFunction) => {
window.ipcRenderer.on(event, (_, ...args: unknown[]) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
listener(...args);
});
},
removeAllListeners: (event: string) => {
window.ipcRenderer.removeAllListeners(event);
},
},
});
export const forceUnloadRendererPlugin = async (id: string) => {
unregisterStyleMap[id]?.forEach((unregister) => unregister());
delete unregisterStyleMap[id];
delete loadedPluginMap[id];
const plugin = (await rendererPlugins())[id];
if (!plugin) return;
const hasStopped = await stopPlugin(id, plugin, {
ctx: 'renderer',
context: createContext(id),
});
if (plugin?.stylesheets) {
document.querySelector(`style#plugin-${id}`)?.remove();
}
if (hasStopped || (hasStopped === null && plugin?.renderer)) {
console.log(
LoggerPrefix,
t('common.console.plugins.unloaded', { pluginName: id }),
);
} else {
console.error(
LoggerPrefix,
t('common.console.plugins.unload-failed', { pluginName: id }),
);
}
};
export const forceLoadRendererPlugin = async (id: string) => {
const plugin = (await rendererPlugins())[id];
if (!plugin) return;
const hasEvaled = await startPlugin(id, plugin, {
ctx: 'renderer',
context: createContext(id),
});
if (
hasEvaled ||
plugin?.stylesheets ||
(hasEvaled === null &&
typeof plugin?.renderer !== 'function' &&
plugin?.renderer)
) {
loadedPluginMap[id] = plugin;
if (plugin?.stylesheets) {
const styleSheetList = plugin.stylesheets.map((style) => {
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(style);
return styleSheet;
});
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
...styleSheetList,
];
}
console.log(
LoggerPrefix,
t('common.console.plugins.loaded', { pluginName: id }),
);
} else {
console.log(
LoggerPrefix,
t('common.console.plugins.initialize-failed', { pluginName: id }),
);
}
};
export const loadAllRendererPlugins = async () => {
const pluginConfigs = window.mainConfig.plugins.getPlugins();
for (const [pluginId, pluginDef] of Object.entries(await rendererPlugins())) {
const config = deepmerge(pluginDef.config, pluginConfigs[pluginId] ?? {});
if (config.enabled) {
await forceLoadRendererPlugin(pluginId);
} else {
if (loadedPluginMap[pluginId]) {
await forceUnloadRendererPlugin(pluginId);
}
}
}
};
export const unloadAllRendererPlugins = async () => {
for (const id of Object.keys(loadedPluginMap)) {
await forceUnloadRendererPlugin(id);
}
};
export const getLoadedRendererPlugin = (
id: string,
): PluginDef | undefined => {
return loadedPluginMap[id];
};
export const getAllLoadedRendererPlugins = () => {
return loadedPluginMap;
};
================================================
FILE: src/menu.ts
================================================
import is from 'electron-is';
import {
app,
type BrowserWindow,
clipboard,
dialog,
Menu,
type MenuItem,
shell,
} from 'electron';
import prompt from 'custom-electron-prompt';
import { satisfies } from 'semver';
import { allPlugins } from 'virtual:plugins';
import { languageResources } from 'virtual:i18n';
import * as config from './config';
import { restart } from './providers/app-controls';
import { startingPages } from './providers/extracted-data';
import promptOptions from './providers/prompt-options';
import { getAllMenuTemplate, loadAllMenuPlugins } from './loader/menu';
import { APPLICATION_NAME, setLanguage, t } from '@/i18n';
import packageJson from '../package.json';
export type MenuTemplate = Electron.MenuItemConstructorOptions[];
// True only if in-app-menu was loaded on launch
const inAppMenuActive = await config.plugins.isEnabled('in-app-menu');
const pluginEnabledMenu = async (
plugin: string,
label = '',
description: string | undefined = undefined,
isNew = false,
hasSubmenu = false,
refreshMenu: (() => void) | undefined = undefined,
): Promise => ({
label: label || plugin,
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
toolTip: description,
type: 'checkbox',
checked: await config.plugins.isEnabled(plugin),
click(item: Electron.MenuItem) {
if (item.checked) {
config.plugins.enable(plugin);
} else {
config.plugins.disable(plugin);
}
if (hasSubmenu) {
refreshMenu?.();
}
},
});
export const refreshMenu = async (win: BrowserWindow) => {
await setApplicationMenu(win);
if (inAppMenuActive) {
win.webContents.send('refresh-in-app-menu');
}
};
export const mainMenuTemplate = async (
win: BrowserWindow,
): Promise => {
const innerRefreshMenu = () => refreshMenu(win);
const { navigationHistory } = win.webContents;
await loadAllMenuPlugins(win);
const allPluginsStubs = await allPlugins();
const menuResult = await Promise.all(
Object.entries(getAllMenuTemplate()).map(async ([id, template]) => {
const plugin = allPluginsStubs[id];
const pluginLabel = plugin?.name?.() ?? id;
const pluginDescription = plugin?.description?.() ?? undefined;
const isNew = plugin?.addedVersion
? satisfies(packageJson.version, plugin.addedVersion)
: false;
if (!(await config.plugins.isEnabled(id))) {
return [
id,
await pluginEnabledMenu(
id,
pluginLabel,
pluginDescription,
isNew,
true,
innerRefreshMenu,
),
] as const;
}
return [
id,
{
label: pluginLabel,
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
toolTip: pluginDescription,
submenu: [
await pluginEnabledMenu(
id,
t('main.menu.plugins.enabled'),
undefined,
false,
true,
innerRefreshMenu,
),
{ type: 'separator' },
...template,
],
} satisfies Electron.MenuItemConstructorOptions,
] as const;
}),
);
const availablePlugins = Object.keys(await allPlugins());
const pluginMenus = await Promise.all(
availablePlugins
.sort((a, b) => {
const aPluginLabel = allPluginsStubs[a]?.name?.() ?? a;
const bPluginLabel = allPluginsStubs[b]?.name?.() ?? b;
return aPluginLabel.localeCompare(bPluginLabel);
})
.map((id) => {
const predefinedTemplate = menuResult.find((it) => it[0] === id);
if (predefinedTemplate) return predefinedTemplate[1];
const plugin = allPluginsStubs[id];
const pluginLabel = plugin?.name?.() ?? id;
const pluginDescription = plugin?.description?.() ?? undefined;
const isNew = plugin?.addedVersion
? satisfies(packageJson.version, plugin.addedVersion)
: false;
return pluginEnabledMenu(
id,
pluginLabel,
pluginDescription,
isNew,
true,
innerRefreshMenu,
);
}),
);
const langResources = await languageResources();
const availableLanguages = Object.keys(langResources);
return [
{
label: t('main.menu.plugins.label'),
submenu: pluginMenus,
},
{
label: t('main.menu.options.label'),
submenu: [
{
label: t('main.menu.options.submenu.auto-update'),
type: 'checkbox',
checked: config.get('options.autoUpdates'),
click(item: MenuItem) {
config.setMenuOption('options.autoUpdates', item.checked);
},
},
{
label: t('main.menu.options.submenu.resume-on-start'),
type: 'checkbox',
checked: config.get('options.resumeOnStart'),
click(item: MenuItem) {
config.setMenuOption('options.resumeOnStart', item.checked);
},
},
{
label: t('main.menu.options.submenu.starting-page.label'),
submenu: (() => {
const subMenuArray: Electron.MenuItemConstructorOptions[] =
Object.keys(startingPages).map((name) => ({
label: name,
type: 'radio',
checked: config.get('options.startingPage') === name,
click() {
config.set('options.startingPage', name);
},
}));
subMenuArray.unshift({
label: t('main.menu.options.submenu.starting-page.unset'),
type: 'radio',
checked: config.get('options.startingPage') === '',
click() {
config.set('options.startingPage', '');
},
});
return subMenuArray;
})(),
},
{
label: t('main.menu.options.submenu.visual-tweaks.label'),
submenu: [
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.remove-upgrade-button',
),
type: 'checkbox',
checked: config.get('options.removeUpgradeButton'),
click(item: MenuItem) {
config.setMenuOption(
'options.removeUpgradeButton',
item.checked,
);
},
},
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.custom-window-title.label',
),
async click() {
const output = await prompt(
{
title: t(
'main.menu.options.submenu.visual-tweaks.submenu.custom-window-title.label',
),
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.custom-window-title.prompt.label',
),
value: config.get('options.customWindowTitle') || '',
type: 'input',
inputAttrs: {
type: 'text',
placeholder: t(
'main.menu.options.submenu.visual-tweaks.submenu.custom-window-title.prompt.placeholder',
{
applicationName: APPLICATION_NAME,
},
),
},
width: 500,
...promptOptions(),
},
win,
);
if (typeof output === 'string') {
config.setMenuOption('options.customWindowTitle', output);
}
},
},
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.like-buttons.label',
),
submenu: [
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.like-buttons.default',
),
type: 'radio',
checked: !config.get('options.likeButtons'),
click() {
config.set('options.likeButtons', '');
},
},
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.like-buttons.force-show',
),
type: 'radio',
checked: config.get('options.likeButtons') === 'force',
click() {
config.set('options.likeButtons', 'force');
},
},
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.like-buttons.hide',
),
type: 'radio',
checked: config.get('options.likeButtons') === 'hide',
click() {
config.set('options.likeButtons', 'hide');
},
},
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.like-buttons.swap',
),
type: 'checkbox',
checked: config.get('options.swapLikeButtonsOrder'),
click(item: MenuItem) {
config.setMenuOption(
'options.swapLikeButtonsOrder',
item.checked,
);
},
},
],
},
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.theme.label',
),
submenu: [
...((config.get('options.themes')?.length ?? 0) === 0
? [
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.theme.submenu.no-theme',
),
},
]
: []),
...(config.get('options.themes')?.map((theme: string) => ({
type: 'normal' as const,
label: theme,
async click() {
const { response } = await dialog.showMessageBox(win, {
type: 'question',
defaultId: 1,
title: t(
'main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.remove-theme',
),
message: t(
'main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.remove-theme-message',
{ theme },
),
buttons: [
t(
'main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.button.cancel',
),
t(
'main.menu.options.submenu.visual-tweaks.submenu.theme.dialog.button.remove',
),
],
});
if (response === 1) {
config.set(
'options.themes',
config
.get('options.themes')
?.filter((t) => t !== theme) ?? [],
);
innerRefreshMenu();
}
},
})) ?? []),
{ type: 'separator' },
{
label: t(
'main.menu.options.submenu.visual-tweaks.submenu.theme.submenu.import-css-file',
),
type: 'normal',
async click() {
const { filePaths } = await dialog.showOpenDialog({
filters: [{ name: 'CSS Files', extensions: ['css'] }],
properties: ['openFile', 'multiSelections'],
});
if (filePaths) {
config.set('options.themes', filePaths);
innerRefreshMenu();
}
},
},
],
},
],
},
{
label: t('main.menu.options.submenu.single-instance-lock'),
type: 'checkbox',
checked: true,
click(item: MenuItem) {
if (!item.checked && app.hasSingleInstanceLock()) {
app.releaseSingleInstanceLock();
} else if (item.checked && !app.hasSingleInstanceLock()) {
app.requestSingleInstanceLock();
}
},
},
{
label: t('main.menu.options.submenu.always-on-top'),
type: 'checkbox',
checked: config.get('options.alwaysOnTop'),
click(item: MenuItem) {
config.setMenuOption('options.alwaysOnTop', item.checked);
win.setAlwaysOnTop(item.checked);
},
},
...((is.windows() || is.linux()
? [
{
label: t('main.menu.options.submenu.hide-menu.label'),
type: 'checkbox',
checked: config.get('options.hideMenu'),
click(item) {
config.setMenuOption('options.hideMenu', item.checked);
if (item.checked && !config.get('options.hideMenuWarned')) {
dialog.showMessageBox(win, {
type: 'info',
title: t(
'main.menu.options.submenu.hide-menu.dialog.title',
),
message: t(
'main.menu.options.submenu.hide-menu.dialog.message',
),
});
}
},
},
]
: []) satisfies Electron.MenuItemConstructorOptions[]),
...((is.windows() || is.macOS()
? // Only works on Win/Mac
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
[
{
label: t('main.menu.options.submenu.start-at-login'),
type: 'checkbox',
checked: config.get('options.startAtLogin'),
click(item) {
config.setMenuOption('options.startAtLogin', item.checked);
},
},
]
: []) satisfies Electron.MenuItemConstructorOptions[]),
{
label: t('main.menu.options.submenu.tray.label'),
submenu: [
{
label: t('main.menu.options.submenu.tray.submenu.disabled'),
type: 'radio',
checked: !config.get('options.tray'),
click() {
config.setMenuOption('options.tray', false);
config.setMenuOption('options.appVisible', true);
},
},
{
label: t(
'main.menu.options.submenu.tray.submenu.enabled-and-show-app',
),
type: 'radio',
checked:
config.get('options.tray') && config.get('options.appVisible'),
click() {
config.setMenuOption('options.tray', true);
config.setMenuOption('options.appVisible', true);
},
},
{
label: t(
'main.menu.options.submenu.tray.submenu.enabled-and-hide-app',
),
type: 'radio',
checked:
config.get('options.tray') && !config.get('options.appVisible'),
click() {
config.setMenuOption('options.tray', true);
config.setMenuOption('options.appVisible', false);
},
},
{ type: 'separator' },
{
label: t(
'main.menu.options.submenu.tray.submenu.play-pause-on-click',
),
type: 'checkbox',
checked: config.get('options.trayClickPlayPause'),
click(item: MenuItem) {
config.setMenuOption(
'options.trayClickPlayPause',
item.checked,
);
},
},
],
},
{
label: t('main.menu.options.submenu.language.label') + ' (Language)',
submenu: [
{
label: t(
'main.menu.options.submenu.language.submenu.to-help-translate',
),
type: 'normal',
click() {
const url = 'https://bit.ly/48n5YF7';
shell.openExternal(url);
},
} as Electron.MenuItemConstructorOptions,
].concat(
availableLanguages
.map(
(lang): Electron.MenuItemConstructorOptions => ({
label: `${langResources[lang].translation.language?.name ?? 'Unknown'} (${langResources[lang].translation.language?.['local-name'] ?? 'Unknown'})`,
type: 'checkbox',
checked: (config.get('options.language') ?? 'en') === lang,
click() {
config.setMenuOption('options.language', lang);
refreshMenu(win);
setLanguage(lang);
dialog.showMessageBox(win, {
title: t(
'main.menu.options.submenu.language.dialog.title',
),
message: t(
'main.menu.options.submenu.language.dialog.message',
),
});
},
}),
)
.sort((a, b) => a.label!.localeCompare(b.label!)),
),
},
{ type: 'separator' },
{
label: t('main.menu.options.submenu.advanced-options.label'),
submenu: [
{
label: t(
'main.menu.options.submenu.advanced-options.submenu.set-proxy.label',
),
type: 'normal',
async click(item: MenuItem) {
await setProxy(item, win);
},
},
{
label: t(
'main.menu.options.submenu.advanced-options.submenu.override-user-agent',
),
type: 'checkbox',
checked: config.get('options.overrideUserAgent'),
click(item: MenuItem) {
config.setMenuOption('options.overrideUserAgent', item.checked);
},
},
{
label: t(
'main.menu.options.submenu.advanced-options.submenu.disable-hardware-acceleration',
),
type: 'checkbox',
checked: config.get('options.disableHardwareAcceleration'),
click(item: MenuItem) {
config.setMenuOption(
'options.disableHardwareAcceleration',
item.checked,
);
},
},
{
label: t(
'main.menu.options.submenu.advanced-options.submenu.restart-on-config-changes',
),
type: 'checkbox',
checked: config.get('options.restartOnConfigChanges'),
click(item: MenuItem) {
config.setMenuOption(
'options.restartOnConfigChanges',
item.checked,
);
},
},
{
label: t(
'main.menu.options.submenu.advanced-options.submenu.auto-reset-app-cache',
),
type: 'checkbox',
checked: config.get('options.autoResetAppCache'),
click(item: MenuItem) {
config.setMenuOption('options.autoResetAppCache', item.checked);
},
},
{ type: 'separator' },
is.macOS()
? {
label: t(
'main.menu.options.submenu.advanced-options.submenu.toggle-dev-tools',
),
// Cannot use "toggleDevTools" role in macOS
click() {
const { webContents } = win;
if (webContents.isDevToolsOpened()) {
webContents.closeDevTools();
} else {
webContents.openDevTools();
}
},
}
: {
label: t(
'main.menu.options.submenu.advanced-options.submenu.toggle-dev-tools',
),
role: 'toggleDevTools',
},
{
label: t(
'main.menu.options.submenu.advanced-options.submenu.edit-config-json',
),
click() {
config.edit();
},
},
],
},
],
},
{
label: t('main.menu.view.label'),
submenu: [
{
label: t('main.menu.view.submenu.reload'),
role: 'reload',
},
{
label: t('main.menu.view.submenu.force-reload'),
role: 'forceReload',
},
{ type: 'separator' },
{
label: t('main.menu.view.submenu.zoom-in'),
role: 'zoomIn',
accelerator: 'CmdOrCtrl+=',
visible: false,
},
{
label: t('main.menu.view.submenu.zoom-in'),
role: 'zoomIn',
accelerator: 'CmdOrCtrl+Plus',
},
{
label: t('main.menu.view.submenu.zoom-out'),
role: 'zoomOut',
accelerator: 'CmdOrCtrl+-',
},
{
label: t('main.menu.view.submenu.zoom-out'),
role: 'zoomOut',
accelerator: 'CmdOrCtrl+Shift+-',
visible: false,
},
{
label: t('main.menu.view.submenu.reset-zoom'),
role: 'resetZoom',
},
{ type: 'separator' },
{
label: t('main.menu.view.submenu.toggle-fullscreen'),
role: 'togglefullscreen',
},
],
},
{
label: t('main.menu.navigation.label'),
submenu: [
{
label: t('main.menu.navigation.submenu.go-back'),
click() {
if (navigationHistory.canGoBack()) {
navigationHistory.goBack();
}
},
},
{
label: t('main.menu.navigation.submenu.go-forward'),
click() {
if (navigationHistory.canGoForward()) {
navigationHistory.goForward();
}
},
},
{
label: t('main.menu.navigation.submenu.copy-current-url'),
click() {
const currentURL = win.webContents.getURL();
clipboard.writeText(currentURL);
},
},
{
label: t('main.menu.navigation.submenu.restart'),
click: restart,
},
{
label: t('main.menu.navigation.submenu.quit'),
role: 'quit',
},
],
},
{
label: t('main.menu.about'),
submenu: [{ role: 'about' }],
},
];
};
export const setApplicationMenu = async (win: Electron.BrowserWindow) => {
const menuTemplate: MenuTemplate = [...(await mainMenuTemplate(win))];
if (process.platform === 'darwin') {
const { name } = app;
menuTemplate.unshift({
label: name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'selectAll' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ type: 'separator' },
{ role: 'minimize' },
{ role: 'close' },
{ role: 'quit' },
],
});
}
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
};
async function setProxy(item: Electron.MenuItem, win: BrowserWindow) {
const output = await prompt(
{
title: t(
'main.menu.options.submenu.advanced-options.submenu.set-proxy.prompt.title',
),
label: t(
'main.menu.options.submenu.advanced-options.submenu.set-proxy.prompt.label',
),
value: config.get('options.proxy'),
type: 'input',
inputAttrs: {
type: 'url',
placeholder: t(
'main.menu.options.submenu.advanced-options.submenu.set-proxy.prompt.placeholder',
),
},
width: 450,
...promptOptions(),
},
win,
);
if (typeof output === 'string') {
config.setMenuOption('options.proxy', output);
item.checked = output !== '';
} else {
// User pressed cancel
item.checked = !item.checked; // Reset checkbox
}
}
================================================
FILE: src/music-player.css
================================================
/**
* Overriding default style
*/
/* Allow window dragging */
ytmusic-nav-bar {
position: relative;
}
ytmusic-nav-bar::before {
content: '';
position: absolute;
inset: 0;
-webkit-user-select: none;
-webkit-app-region: drag;
}
ytmusic-nav-bar > .left-content > *,
ytmusic-nav-bar > .center-content > *,
ytmusic-nav-bar > .right-content > * {
-webkit-app-region: no-drag;
}
iron-icon,
ytmusic-pivot-bar-item-renderer,
.tab-title,
a {
-webkit-app-region: no-drag;
}
/* custom style for navbar */
ytmusic-app-layout {
--ytmusic-nav-bar-height: 90px;
}
/* Disable Image Selection */
img {
-webkit-user-select: none;
user-select: none;
}
/* Hide cast button which doesn't work */
ytmusic-cast-button {
display: none !important;
}
/* Remove useless inaccessible button on top-right corner of the video player */
.ytp-chrome-top-buttons {
display: none !important;
}
/* Make the logo un-draggable */
ytmusic-nav-bar > div.left-content > a,
ytmusic-nav-bar > div.left-content > a > picture > img {
-webkit-user-drag: none;
}
/* yt-music bugs */
tp-yt-paper-item.ytmusic-guide-entry-renderer::before {
border-radius: 8px !important;
}
/* fix video player align */
#av-id {
padding-bottom: 0;
}
#av-id ~ #player.ytmusic-player-page:not([player-ui-state='FULLSCREEN']) {
margin-top: auto !important;
margin-bottom: auto !important;
margin-left: var(--ytmusic-player-page-vertical-padding);
margin-right: var(--ytmusic-player-page-vertical-padding);
max-height: calc(100% - (var(--ytmusic-player-page-vertical-padding) * 2));
max-width: calc(100% - var(--ytmusic-player-page-vertical-padding) * 2);
}
/* macos traffic lights fix */
:where([data-os*='Macintosh']) ytmusic-app-layout#layout ytmusic-nav-bar {
padding-top: var(--ytmusic-nav-bar-offset, 0);
}
:where([data-os*='Macintosh']) ytmusic-app-layout#layout {
--ytmusic-nav-bar-offset: 24px;
--ytmusic-nav-bar-height: calc(90px + var(--ytmusic-nav-bar-offset, 0));
}
tp-yt-iron-dropdown,
tp-yt-paper-dialog {
app-region: no-drag;
}
================================================
FILE: src/navigation.d.ts
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */
interface NavigationOptions {
info: any;
}
interface NavigationHistoryEntry extends EventTarget {
readonly url?: string;
readonly key: string;
readonly id: string;
readonly index: number;
readonly sameDocument: boolean;
getState(): any;
ondispose: ((this: NavigationHistoryEntry, ev: Event) => any) | null;
}
interface NavigationTransition {
readonly navigationType: NavigationType;
readonly from: NavigationHistoryEntry;
readonly finished: Promise;
}
interface NavigationResult {
committed: Promise;
finished: Promise;
}
interface NavigationNavigateOptions extends NavigationOptions {
state: any;
history?: NavigationHistoryBehavior;
}
interface NavigationReloadOptions extends NavigationOptions {
state: any;
}
interface NavigationUpdateCurrentEntryOptions {
state: any;
}
interface NavigationEventsMap {
currententrychange: NavigateEvent;
navigate: NavigateEvent;
navigateerror: NavigateEvent;
navigatesuccess: NavigateEvent;
}
interface Navigation extends EventTarget {
entries(): Array;
readonly currentEntry?: NavigationHistoryEntry;
updateCurrentEntry(options: NavigationUpdateCurrentEntryOptions): undefined;
readonly transition?: NavigationTransition;
readonly canGoBack: boolean;
readonly canGoForward: boolean;
navigate(url: string, options?: NavigationNavigateOptions): NavigationResult;
reload(options?: NavigationReloadOptions): NavigationResult;
traverseTo(key: string, options?: NavigationOptions): NavigationResult;
back(options?: NavigationOptions): NavigationResult;
forward(options?: NavigationOptions): NavigationResult;
onnavigate: ((this: Navigation, ev: Event) => any) | null;
onnavigatesuccess: ((this: Navigation, ev: Event) => any) | null;
onnavigateerror: ((this: Navigation, ev: Event) => any) | null;
oncurrententrychange: ((this: Navigation, ev: Event) => any) | null;
addEventListener(
name: K,
listener: (event: NavigationEventsMap[K]) => void,
);
}
declare class NavigateEvent extends Event {
canIntercept: boolean;
destination: NavigationHistoryEntry;
downloadRequest: string | null;
formData: FormData;
hashChange: boolean;
info: Record;
navigationType: 'push' | 'reload' | 'replace' | 'traverse';
signal: AbortSignal;
userInitiated: boolean;
intercept(options?: Record): void;
scroll(): void;
}
type NavigationHistoryBehavior = 'auto' | 'push' | 'replace';
declare const Navigation: {
prototype: Navigation;
new (): Navigation;
};
================================================
FILE: src/pear-desktop.ts
================================================
///
declare module '*.html' {
const html: string;
export default html;
}
declare module '*.html?raw' {
const html: string;
export default html;
}
declare module '*.svg?inline' {
const base64: string;
export default base64;
}
declare module '*.svg?raw' {
const html: string;
export default html;
}
declare module '*.png' {
const element: HTMLImageElement;
export default element;
}
declare module '*.jpg' {
const element: HTMLImageElement;
export default element;
}
declare module '*.css' {
const css: string;
export default css;
}
declare module '*.css?inline' {
const css: string;
export default css;
}
================================================
FILE: src/plugins/album-actions/index.tsx
================================================
import { render } from 'solid-js/web';
import { createSignal, Show } from 'solid-js';
import { t } from '@/i18n';
import { createPlugin } from '@/utils';
import { waitForElement } from '@/utils/wait-for-element';
import {
DislikeButton,
LikeButton,
UnDislikeButton,
UnLikeButton,
} from './templates';
export default createPlugin<
unknown,
unknown,
{
observer?: MutationObserver;
loadObserver?: MutationObserver;
changeObserver?: MutationObserver;
waiting: boolean;
onPageChange(): void;
loadFullList: (event: MouseEvent) => void;
applyToList(id: string, loader: HTMLElement): void;
start(): void;
stop(): void;
}
>({
name: () => t('plugins.album-actions.name'),
description: () => t('plugins.album-actions.description'),
restartNeeded: false,
addedVersion: '3.2.X',
config: {
enabled: false,
},
renderer: {
waiting: false,
start() {
// Waits for pagechange
this.onPageChange();
this.observer = new MutationObserver(() => {
this.onPageChange();
});
this.observer.observe(document.querySelector('#browse-page')!, {
attributes: false,
childList: true,
subtree: true,
});
},
async onPageChange() {
if (this.waiting) {
return;
} else {
this.waiting = true;
}
const continuations = await waitForElement('#continuations');
this.waiting = false;
const [showUnDislike, setShowUnDislike] = createSignal(true);
const [showDislike, setShowDislike] = createSignal(true);
const [showLike, setShowLike] = createSignal(true);
const [showUnLike, setShowUnLike] = createSignal(true);
const DEFAULT_MASK_SIZE = '100% 50%';
const [unDislikeMaskSize, setUnDislikeMaskSize] =
createSignal(DEFAULT_MASK_SIZE);
const [dislikeMaskSize, setDislikeMaskSize] =
createSignal(DEFAULT_MASK_SIZE);
const [likeMaskSize, setLikeMaskSize] = createSignal(DEFAULT_MASK_SIZE);
const [unLikeMaskSize, setUnLikeMaskSize] =
createSignal(DEFAULT_MASK_SIZE);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.flexDirection = 'row';
render(
() => (
<>
>
),
buttonContainer,
);
//Finds the playlist
const playlist =
document.querySelector('ytmusic-playlist-shelf-renderer') ??
document.querySelector(':nth-last-child(1 of ytmusic-shelf-renderer)');
if (!playlist) {
return;
}
// Adds an observer for every button, so it gets updated when one is clicked
this.changeObserver?.disconnect();
this.changeObserver = new MutationObserver(() => {
this.stop();
this.start();
});
const allButtons = playlist.querySelectorAll(
'yt-button-shape.ytmusic-like-button-renderer',
);
for (const btn of allButtons) {
this.changeObserver.observe(btn, {
attributes: true,
childList: false,
subtree: false,
});
}
//Determine if button is needed and colors the percentage
const listsLength = playlist.querySelectorAll(
'#button-shape-dislike > button',
).length;
if (continuations.children.length == 0 && listsLength > 0) {
const counts = {
dislike: playlist?.querySelectorAll(
'#button-shape-dislike > button[aria-pressed=true]',
).length,
undislike: playlist?.querySelectorAll(
'#button-shape-dislike > button[aria-pressed=false]',
).length,
unlike: playlist?.querySelectorAll(
'#button-shape-like > button[aria-pressed=false]',
).length,
like: playlist?.querySelectorAll(
'#button-shape-like > button[aria-pressed=true]',
).length,
};
for (const [name, size] of Object.entries(counts)) {
switch (name) {
case 'dislike':
if (size > 0) {
setShowDislike(true);
setDislikeMaskSize(`100% ${100 - (size / listsLength) * 100}%`);
} else {
setShowDislike(false);
}
break;
case 'undislike':
if (size > 0) {
setShowUnDislike(true);
setUnDislikeMaskSize(
`100% ${100 - (size / listsLength) * 100}%`,
);
} else {
setShowUnDislike(false);
}
break;
case 'like':
if (size > 0) {
setShowLike(true);
setLikeMaskSize(`100% ${100 - (size / listsLength) * 100}%`);
} else {
setShowLike(false);
}
break;
case 'unlike':
if (size > 0) {
setShowUnLike(true);
setUnLikeMaskSize(`100% ${100 - (size / listsLength) * 100}%`);
} else {
setShowUnLike(false);
}
break;
}
}
}
const menuParent =
document.querySelector('#action-buttons')?.parentElement;
if (menuParent && !document.querySelector('.like-menu')) {
const menu = document.createElement('div');
menu.id = 'ytmd-album-action-buttons';
menu.className =
'action-buttons style-scope ytmusic-responsive-header-renderer';
menuParent.insertBefore(
menu,
menuParent.children[menuParent.children.length - 1],
);
menu.appendChild(buttonContainer);
}
},
loadFullList(event: MouseEvent) {
if (event.target instanceof Element) {
event.stopPropagation();
const button = event.target.closest('button') as HTMLElement;
if (!button?.id) return;
const id = button.id;
const loader = document.getElementById('continuations')!;
this.loadObserver = new MutationObserver(() => {
this.applyToList(id, loader);
});
this.applyToList(id, loader);
this.loadObserver.observe(loader, {
attributes: true,
childList: true,
subtree: true,
});
loader?.style.setProperty('top', '0');
loader?.style.setProperty('left', '50%');
loader?.style.setProperty('position', 'absolute');
}
},
applyToList(id: string, loader: HTMLElement) {
if (loader.children.length != 0) return;
this.loadObserver?.disconnect();
let playlistButtons: NodeListOf | undefined;
const playlist =
document.querySelector('ytmusic-playlist-shelf-renderer') ??
document.querySelector(':nth-last-child(1 of ytmusic-shelf-renderer)');
switch (id) {
case 'allundislike':
playlistButtons = playlist?.querySelectorAll(
'#button-shape-dislike > button[aria-pressed=true]',
);
break;
case 'alldislike':
playlistButtons = playlist?.querySelectorAll(
'#button-shape-dislike > button[aria-pressed=false]',
);
break;
case 'alllike':
playlistButtons = playlist?.querySelectorAll(
'#button-shape-like > button[aria-pressed=false]',
);
break;
case 'allunlike':
playlistButtons = playlist?.querySelectorAll(
'#button-shape-like > button[aria-pressed=true]',
);
break;
default:
}
playlistButtons?.forEach((elem) => elem.click());
},
stop() {
this.observer?.disconnect();
this.changeObserver?.disconnect();
for (const button of document.querySelectorAll('.like-menu')) {
button.remove();
}
},
},
});
================================================
FILE: src/plugins/album-actions/templates/dislike-button.tsx
================================================
export interface DislikeButtonProps {
onClick?: (e: MouseEvent) => void;
maskSize: string;
}
export const DislikeButton = (props: DislikeButtonProps) => (
);
================================================
FILE: src/plugins/album-actions/templates/index.ts
================================================
export * from './like-button';
export * from './dislike-button';
export * from './undislike-button';
export * from './unlike-button';
================================================
FILE: src/plugins/album-actions/templates/like-button.tsx
================================================
export interface LikeButtonProps {
onClick?: (e: MouseEvent) => void;
maskSize: string;
}
export const LikeButton = (props: LikeButtonProps) => (
);
================================================
FILE: src/plugins/album-actions/templates/undislike-button.tsx
================================================
export interface UnDislikeButtonProps {
onClick?: (e: MouseEvent) => void;
maskSize: string;
}
export const UnDislikeButton = (props: UnDislikeButtonProps) => (
);
================================================
FILE: src/plugins/album-actions/templates/unlike-button.tsx
================================================
export interface UnLikeButtonProps {
onClick?: (e: MouseEvent) => void;
maskSize: string;
}
export const UnLikeButton = (props: UnLikeButtonProps) => (
);
================================================
FILE: src/plugins/album-color-theme/index.ts
================================================
import { FastAverageColor } from 'fast-average-color';
import Color, { type ColorInstance } from 'color';
import style from './style.css?inline';
import { createPlugin } from '@/utils';
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';
type Config = {
enabled: boolean;
ratio: number;
enableSeekbar: boolean;
};
type Renderer = {
getMixedColor(
color: string,
key: string,
alpha?: number,
ratioMultiply?: number,
): string;
updateColor(alpha: number): void;
onConfigChange(newConfig: Config): void;
};
export default createPlugin({
name: () => t('plugins.album-color-theme.name'),
description: () => t('plugins.album-color-theme.description'),
restartNeeded: false,
config: {
enabled: false,
ratio: 0.5,
enableSeekbar: true,
} satisfies Config as Config,
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 });
},
})),
},
{
label: t('plugins.album-color-theme.menu.enable-seekbar'),
type: 'checkbox',
checked: config.enableSeekbar,
click(item) {
setConfig({ enableSeekbar: item.checked });
},
},
];
},
renderer: {
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,
color: null as ColorInstance | null,
darkColor: null as ColorInstance | null,
start() {
this.playerPage = document.querySelector('#player-page');
this.navBarBackground = document.querySelector(
'#nav-bar-background',
);
this.ytmusicPlayerBar =
document.querySelector('ytmusic-player-bar');
this.playerBarBackground = document.querySelector(
'#player-bar-background',
);
this.sidebarBig = document.querySelector('#guide-wrapper');
this.sidebarSmall = document.querySelector(
'#mini-guide-background',
);
this.ytmusicAppLayout = document.querySelector('#layout');
},
async onPlayerApiReady(playerApi, { getConfig }) {
const config = await getConfig();
(this as Renderer).onConfigChange(config);
const fastAverageColor = new FastAverageColor();
document.addEventListener('videodatachange', async (event) => {
if (event.detail.name !== 'dataloaded') return;
const playerResponse = playerApi.getPlayerResponse();
const thumbnail =
playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
if (!thumbnail) return;
const albumColor = await fastAverageColor
.getColorAsync(thumbnail.url)
.catch((err) => {
console.error(err);
return null;
});
if (albumColor) {
const target = Color(albumColor.hex);
this.darkColor = target.darken(0.3).rgb();
this.color = target.darken(0.15).rgb();
while (this.color.luminosity() > 0.5) {
this.color = this.color?.darken(0.05);
this.darkColor = this.darkColor?.darken(0.05);
}
document.documentElement.style.setProperty(
COLOR_KEY,
`${~~this.color.red()}, ${~~this.color.green()}, ${~~this.color.blue()}`,
);
document.documentElement.style.setProperty(
DARK_COLOR_KEY,
`${~~this.darkColor.red()}, ${~~this.darkColor.green()}, ${~~this.darkColor.blue()}`,
);
} else {
document.documentElement.style.setProperty(COLOR_KEY, '0, 0, 0');
document.documentElement.style.setProperty(DARK_COLOR_KEY, '0, 0, 0');
}
let alpha: number | null = null;
if (await window.mainConfig.plugins.isEnabled('transparent-player')) {
const value: unknown = window.mainConfig.get(
'plugins.transparent-player.opacity',
);
if (typeof value === 'number' && value >= 0 && value <= 1) {
alpha = value;
}
}
(this as Renderer).updateColor(alpha ?? 1);
});
},
onConfigChange(config) {
document.documentElement.style.setProperty(
RATIO_KEY,
`${~~(config.ratio * 100)}%`,
);
if (config.enableSeekbar) document.body.classList.add('seekbar-theme');
else document.body.classList.remove('seekbar-theme');
},
getMixedColor(
color: string,
key: string,
alpha = 1,
ratioMultiply?: number,
) {
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(alpha: number) {
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)',
'--paper-toast-background-color': '#323232',
'--ytmusic-search-background': '#030303',
'--paper-slider-knob-color': '#f03',
'--paper-dialog-background-color': '#212121',
'--paper-progress-active-color-1': '#f03',
'--paper-progress-active-color-2': '#ff2791',
'--yt-spec-inverted-background': '#f3f3f3',
'background': 'rgba(3, 3, 3)',
'--ytmusic-background': 'rgba(3, 3, 3)',
};
const colorKeyMap: Record = {
'background': DARK_COLOR_KEY,
'--ytmusic-background': DARK_COLOR_KEY,
};
const ratioMap: Record = {
'--paper-progress-active-color-1': 1.75,
'--paper-progress-active-color-2': 1.75,
'--yt-spec-inverted-background': 1.75,
};
const getMixedColor = (this as Renderer).getMixedColor.bind(this);
Object.entries(variableMap).map(([variable, color]) => {
const key = colorKeyMap[variable] ?? COLOR_KEY;
const ratio = ratioMap[variable] ?? undefined;
document.documentElement.style.setProperty(
variable,
getMixedColor(color, key, alpha, ratio),
'important',
);
});
},
},
});
================================================
FILE: src/plugins/album-color-theme/style.css
================================================
yt-page-navigation-progress {
--yt-page-navigation-container-color: #00000046 !important;
--yt-page-navigation-progress-color: white !important;
}
#player-page {
transition: transform 300ms,
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
}
#nav-bar-background {
transition: opacity 200ms,
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
}
#mini-guide-background {
transition: opacity 200ms,
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
border-right: 0px !important;
}
#guide-wrapper {
transition: opacity 200ms,
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) !important;
}
#items {
border-radius: 10px !important;
}
/* fix blur navigation bar */
ytmusic-app-layout > [slot="player-page"]:not([is-mweb-modernization-enabled]):not(:has(ytmusic-player[player-ui-state=FULLSCREEN])) {
padding-top: 90px;
margin-top: calc(-90px + var(--menu-bar-height, 0px)) !important;
}
/* fix icon color */
.duration.ytmusic-player-queue-item, .byline.ytmusic-player-queue-item {
color: rgba(255, 255, 255, 0.5) !important;
--yt-endpoint-color: rgba(255, 255, 255, 0.5) !important;
--yt-endpoint-hover-color: rgba(255, 255, 255, 0.5) !important;
--yt-endpoint-visited-color: rgba(255, 255, 255, 0.5) !important;
}
.icon.ytmusic-menu-navigation-item-renderer {
color: rgba(255, 255, 255, 0.5) !important;
}
.menu.ytmusic-player-bar {
--iron-icon-fill-color: rgba(255, 255, 255, 0.5) !important;
}
ytmusic-player-bar {
color: rgba(255, 255, 255, 0.5) !important;
}
.time-info.ytmusic-player-bar {
color: rgba(255, 255, 255, 0.5) !important;
}
.volume-slider.ytmusic-player-bar, .expand-volume-slider.ytmusic-player-bar {
--paper-slider-container-color: rgba(255, 255, 255, 0.5) !important;
}
/* fix background image */
ytmusic-fullbleed-thumbnail-renderer img {
mask: linear-gradient(to bottom, #000 0%, #000 50%, transparent 100%);
}
.background-gradient.style-scope,
ytmusic-app-layout[is-bauhaus-sidenav-enabled] #mini-guide-background.ytmusic-app-layout {
background: var(--ytmusic-background) !important;
}
ytmusic-browse-response[has-background]:not([disable-gradient]) .background-gradient.ytmusic-browse-response {
background: unset !important;
}
#background.immersive-background.style-scope.ytmusic-browse-response {
opacity: 0.6;
}
ytmusic-search-box[is-bauhaus-sidenav-enabled] {
--ytmusic-search-background: var(--ytmusic-color-black3) !important;
}
.seekbar-theme #progress-bar.ytmusic-player-bar {
--paper-slider-active-color: linear-gradient(to right, var(--paper-progress-active-color-1) 80%, var(--paper-progress-active-color-2) 100%) !important;
--paper-slider-knob-color: var(--paper-progress-active-color-1) !important;
--paper-slider-knob-start-color: var(--paper-progress-active-color-2) !important;
}
================================================
FILE: src/plugins/ambient-mode/index.ts
================================================
import style from './style.css?inline';
import { t } from '@/i18n';
import { createPlugin } from '@/utils';
import { menu } from './menu';
import { type AmbientModePluginConfig } from './types';
import { waitForElement } from '@/utils/wait-for-element';
const defaultConfig: AmbientModePluginConfig = {
enabled: false,
quality: 50,
buffer: 30,
interpolationTime: 1500,
blur: 100,
size: 100,
opacity: 1,
fullscreen: false,
};
export default createPlugin({
name: () => t('plugins.ambient-mode.name'),
description: () => t('plugins.ambient-mode.description'),
restartNeeded: false,
config: defaultConfig,
stylesheets: [style],
menu: menu,
renderer: {
interpolationTime: defaultConfig.interpolationTime,
buffer: defaultConfig.buffer,
qualityRatio: defaultConfig.quality,
size: defaultConfig.size,
blur: defaultConfig.blur,
opacity: defaultConfig.opacity,
isFullscreen: defaultConfig.fullscreen,
unregister: null as (() => void) | null,
update: null as (() => void) | 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('#song-image');
const songVideo = document.querySelector('#song-video');
const image = songImage?.querySelector(
'yt-img-shadow > img',
);
const video = await waitForElement(
'.html5-video-container > video',
);
const videoWrapper = document.querySelector(
'#song-video > .player-wrapper',
);
const injectBlurImage = () => {
if (!songImage || !image) return null;
this.lastImageSource = image.src;
const blurImage = document.createElement('img');
blurImage.classList.add('html5-blur-image');
blurImage.src = image.src;
this.update = () => {
if (this.isFullscreen) blurImage.classList.add('fullscreen');
else blurImage.classList.remove('fullscreen');
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();
/* injecting */
songImage.prepend(blurImage);
/* cleanup */
return () => {
if (blurImage.isConnected) blurImage.remove();
};
};
const injectBlurVideo = () => {
if (!songVideo || !video || !videoWrapper) return null;
this.lastVideoSource = video.src;
const blurCanvas = document.createElement('canvas');
blurCanvas.classList.add('html5-blur-canvas');
const context = blurCanvas.getContext('2d', {
willReadFrequently: true,
});
/* effect */
let lastEffectWorkId: number | null = null;
let lastImageData: ImageData | null = null;
const onSync = () => {
if (typeof lastEffectWorkId === 'number')
cancelAnimationFrame(lastEffectWorkId);
lastEffectWorkId = requestAnimationFrame(() => {
if (!context) return;
const width = this.qualityRatio;
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);
context.globalAlpha = 1 - frameOffset * 2; // because of alpha value must be < 1
context.putImageData(lastImageData, 0, 0);
context.globalAlpha = frameOffset;
}
context.drawImage(video, 0, 0, width, height);
lastImageData = context.getImageData(0, 0, width, height); // current image data
lastEffectWorkId = null;
});
};
this.update = () => {
const rect = video.getBoundingClientRect();
const newWidth = Math.floor(video.width || rect.width);
const newHeight = Math.floor(video.height || rect.height);
if (newWidth === 0 || newHeight === 0) return;
blurCanvas.width = this.qualityRatio;
blurCanvas.height = Math.floor(
(newHeight / newWidth) * this.qualityRatio,
);
if (this.isFullscreen) blurCanvas.classList.add('fullscreen');
else blurCanvas.classList.remove('fullscreen');
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();
/* hooking */
let canvasInterval: NodeJS.Timeout | null = null;
canvasInterval = setInterval(
onSync,
Math.max(1, Math.ceil(1000 / this.buffer)),
);
const onPause = () => {
if (canvasInterval) clearInterval(canvasInterval);
canvasInterval = null;
};
const onPlay = () => {
if (canvasInterval) clearInterval(canvasInterval);
canvasInterval = setInterval(
onSync,
Math.max(1, Math.ceil(1000 / this.buffer)),
);
};
songVideo.addEventListener('pause', onPause);
songVideo.addEventListener('play', onPlay);
/* injecting */
videoWrapper.prepend(blurCanvas);
/* cleanup */
return () => {
if (canvasInterval) clearInterval(canvasInterval);
songVideo.removeEventListener('pause', onPause);
songVideo.removeEventListener('play', onPlay);
if (blurCanvas.isConnected) blurCanvas.remove();
};
};
const isVideoMode = () => {
const songVideo = document.querySelector('#song-video');
if (!songVideo) {
this.lastMediaType = 'image';
return false;
}
const isVideo = getComputedStyle(songVideo).display !== 'none';
this.lastMediaType = isVideo ? 'video' : 'image';
return isVideo;
};
const playerPage = document.querySelector('#player-page');
const ytmusicAppLayout = document.querySelector('#layout');
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') {
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.size = newConfig.size;
this.blur = newConfig.blur;
this.opacity = newConfig.opacity;
this.isFullscreen = newConfig.fullscreen;
this.update?.();
},
stop() {
this.update = null;
this.unregister?.();
if (this.interval) clearInterval(this.interval);
},
},
});
================================================
FILE: src/plugins/ambient-mode/menu.ts
================================================
import { type MenuItemConstructorOptions } from 'electron';
import { t } from '@/i18n';
import { type MenuContext } from '@/types/contexts';
import { type AmbientModePluginConfig } from './types';
export interface menuParameters {
getConfig: () => AmbientModePluginConfig | Promise;
setConfig: (
conf: Partial>,
) => void | Promise;
}
export const menu: (
ctx: MenuContext,
) =>
| MenuItemConstructorOptions[]
| Promise = 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 });
},
},
];
};
================================================
FILE: src/plugins/ambient-mode/style.css
================================================
#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-image .html5-blur-image {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
#song-video canvas.html5-blur-canvas.fullscreen {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#song-video .html5-video-container {
height: 100%;
}
#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;
}
/* Fix ambient mode overlapping other elements #2520 */
.song-button.ytmusic-av-toggle, .video-button.ytmusic-av-toggle {
z-index: 1;
background-color: transparent;
}
#side-panel.side-panel.ytmusic-player-page {
z-index: 0;
}
================================================
FILE: src/plugins/ambient-mode/types.ts
================================================
export type AmbientModePluginConfig = {
enabled: boolean;
quality: number;
buffer: number;
interpolationTime: number;
blur: number;
size: number;
opacity: number;
fullscreen: boolean;
};
================================================
FILE: src/plugins/amuse/backend.ts
================================================
import { t } from 'i18next';
import { type Context, Hono } from 'hono';
import { cors } from 'hono/cors';
import { serve } from '@hono/node-server';
import { registerCallback, type SongInfo } from '@/providers/song-info';
import { createBackend } from '@/utils';
import type { AmuseSongInfo } from './types';
const amusePort = 9863;
const formatSongInfo = (info: SongInfo) => {
const formattedSongInfo: AmuseSongInfo = {
player: {
hasSong: !!(info.artist && info.title),
isPaused: info.isPaused ?? false,
seekbarCurrentPosition: info.elapsedSeconds ?? 0,
},
track: {
duration: info.songDuration,
title: info.title,
author: info.artist,
cover: info.imageSrc ?? '',
url: info.url ?? '',
id: info.videoId,
isAdvertisement: false,
},
};
return formattedSongInfo;
};
export default createBackend({
currentSongInfo: {} as SongInfo,
app: null as Hono | null,
server: null as ReturnType | null,
start() {
registerCallback((songInfo) => {
this.currentSongInfo = songInfo;
});
this.app = new Hono();
this.app.use('*', cors());
this.app.get('/', (ctx) =>
ctx.body(t('plugins.amuse.response.query'), 200),
);
const queryAndApiHandler = (ctx: Context) => {
return ctx.json(formatSongInfo(this.currentSongInfo), 200);
};
this.app.get('/query', queryAndApiHandler);
this.app.get('/api', queryAndApiHandler);
try {
this.server = serve({
fetch: this.app.fetch.bind(this.app),
port: amusePort,
});
} catch (err) {
console.error(err);
}
},
stop() {
if (this.server) {
this.server?.close();
}
},
});
================================================
FILE: src/plugins/amuse/index.ts
================================================
import { createPlugin } from '@/utils';
import backend from './backend';
import { APPLICATION_NAME, t } from '@/i18n';
export interface MusicWidgetConfig {
enabled: boolean;
}
export const defaultConfig: MusicWidgetConfig = {
enabled: false,
};
export default createPlugin({
name: () => t('plugins.amuse.name'),
description: () =>
t('plugins.amuse.description', {
applicationName: APPLICATION_NAME,
}),
addedVersion: '3.7.X',
restartNeeded: true,
config: defaultConfig,
backend,
});
================================================
FILE: src/plugins/amuse/types.ts
================================================
export interface PlayerInfo {
hasSong: boolean;
isPaused: boolean;
seekbarCurrentPosition: number;
}
export interface TrackInfo {
author: string;
title: string;
cover: string;
duration: number;
url: string;
id: string;
isAdvertisement: boolean;
}
export interface AmuseSongInfo {
player: PlayerInfo;
track: TrackInfo;
}
================================================
FILE: src/plugins/api-server/backend/api-version.ts
================================================
export const API_VERSION = 'v1';
================================================
FILE: src/plugins/api-server/backend/index.ts
================================================
export * from './main';
================================================
FILE: src/plugins/api-server/backend/main.ts
================================================
import { createServer as createHttpServer } from 'node:http';
import { createServer as createHttpsServer } from 'node:https';
import { readFileSync } from 'node:fs';
import { jwt } from 'hono/jwt';
import { OpenAPIHono as Hono } from '@hono/zod-openapi';
import { cors } from 'hono/cors';
import { swaggerUI } from '@hono/swagger-ui';
import { serve } from '@hono/node-server';
import { createNodeWebSocket } from '@hono/node-ws';
import { registerCallback } from '@/providers/song-info';
import { createBackend } from '@/utils';
import { JWTPayloadSchema } from './scheme';
import { registerAuth, registerControl, registerWebsocket } from './routes';
import { APPLICATION_NAME } from '@/i18n';
import { type APIServerConfig, AuthStrategy } from '../config';
import type { BackendType } from './types';
import type {
LikeType,
RepeatMode,
VolumeState,
} from '@/types/datahost-get-state';
export const backend = createBackend({
async start(ctx) {
const config = await ctx.getConfig();
this.init(ctx);
registerCallback((songInfo) => {
this.songInfo = songInfo;
});
ctx.ipc.on('peard:player-api-loaded', () => {
ctx.ipc.send('peard:setup-seeked-listener');
ctx.ipc.send('peard:setup-time-changed-listener');
ctx.ipc.send('peard:setup-repeat-changed-listener');
ctx.ipc.send('peard:setup-like-changed-listener');
ctx.ipc.send('peard:setup-volume-changed-listener');
ctx.ipc.send('peard:setup-shuffle-changed-listener');
});
ctx.ipc.on(
'peard:repeat-changed',
(mode: RepeatMode) => (this.currentRepeatMode = mode),
);
ctx.ipc.on(
'peard:volume-changed',
(newVolumeState: VolumeState) => (this.volumeState = newVolumeState),
);
this.run(config);
},
stop() {
this.end();
},
onConfigChange(config) {
const old = this.oldConfig;
if (
old?.hostname === config.hostname &&
old?.port === config.port &&
old?.useHttps === config.useHttps &&
old?.certPath === config.certPath &&
old?.keyPath === config.keyPath
) {
this.oldConfig = config;
return;
}
this.end();
this.run(config);
this.oldConfig = config;
},
// Custom
init(backendCtx) {
this.app = new Hono();
const ws = createNodeWebSocket({
app: this.app,
});
this.app.use('*', cors());
// for web remote control
this.app.use('*', async (ctx, next) => {
ctx.header('Access-Control-Request-Private-Network', 'true');
await next();
});
// middlewares
this.app.use('/api/*', async (ctx, next) => {
const config = await backendCtx.getConfig();
if (config.authStrategy !== AuthStrategy.NONE) {
return await jwt({
secret: config.secret,
alg: 'HS256',
})(ctx, next);
}
await next();
});
this.app.use('/api/*', async (ctx, next) => {
const result = await JWTPayloadSchema.spa(await ctx.get('jwtPayload'));
const config = await backendCtx.getConfig();
const isAuthorized =
config.authStrategy === AuthStrategy.NONE ||
(result.success && config.authorizedClients.includes(result.data.id));
if (!isAuthorized) {
ctx.status(401);
return ctx.body('Unauthorized');
}
return await next();
});
// routes
registerControl(
this.app,
backendCtx,
() => this.songInfo,
() => this.currentRepeatMode,
() =>
backendCtx.window.webContents.executeJavaScript(
'document.querySelector("#like-button-renderer")?.likeStatus',
) as Promise,
() => this.volumeState,
);
registerAuth(this.app, backendCtx);
registerWebsocket(this.app, backendCtx, ws);
// swagger
this.app.openAPIRegistry.registerComponent(
'securitySchemes',
'bearerAuth',
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
);
this.app.doc('/doc', {
openapi: '3.1.0',
info: {
version: '1.0.0',
title: `${APPLICATION_NAME} API Server`,
description:
'Note: You need to get an access token using the `/auth/{id}` endpoint first to call any API endpoints under `/api`.',
},
security: [
{
bearerAuth: [],
},
],
});
this.app.get('/swagger', swaggerUI({ url: '/doc' }));
this.injectWebSocket = ws.injectWebSocket.bind(this);
},
run(config) {
if (!this.app) return;
try {
const serveOptions =
config.useHttps && config.certPath && config.keyPath
? {
fetch: this.app.fetch.bind(this.app),
port: config.port,
hostname: config.hostname,
createServer: createHttpsServer,
serverOptions: {
key: readFileSync(config.keyPath),
cert: readFileSync(config.certPath),
},
}
: {
fetch: this.app.fetch.bind(this.app),
port: config.port,
hostname: config.hostname,
createServer: createHttpServer,
};
this.server = serve(serveOptions);
if (this.injectWebSocket && this.server) {
this.injectWebSocket(this.server);
}
} catch (err) {
console.error(err);
}
},
end() {
this.server?.close();
this.server = undefined;
},
});
================================================
FILE: src/plugins/api-server/backend/routes/auth.ts
================================================
import { createRoute, z } from '@hono/zod-openapi';
import { dialog } from 'electron';
import { sign } from 'hono/jwt';
import { getConnInfo } from '@hono/node-server/conninfo';
import { t } from '@/i18n';
import { type APIServerConfig, AuthStrategy } from '../../config';
import type { JWTPayload } from '../scheme';
import type { HonoApp } from '../types';
import type { BackendContext } from '@/types/contexts';
const routes = {
request: createRoute({
method: 'post',
path: '/auth/{id}',
summary: '',
description: '',
security: [],
request: {
params: z.object({
id: z.string(),
}),
},
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
accessToken: z.string(),
}),
},
},
},
403: {
description: 'Forbidden',
},
},
}),
};
export const register = (
app: HonoApp,
{ getConfig, setConfig }: BackendContext,
) => {
app.openapi(routes.request, async (ctx) => {
const config = await getConfig();
const { id } = ctx.req.param();
if (config.authorizedClients.includes(id)) {
// SKIP CHECK
} else if (config.authStrategy === AuthStrategy.AUTH_AT_FIRST) {
const result = await dialog.showMessageBox({
title: t('plugins.api-server.dialog.request.title'),
message: t('plugins.api-server.dialog.request.message', {
origin: getConnInfo(ctx).remote.address,
ID: id,
}),
buttons: [
t('plugins.api-server.dialog.request.buttons.allow'),
t('plugins.api-server.dialog.request.buttons.deny'),
],
defaultId: 1,
cancelId: 1,
});
if (result.response === 1) {
ctx.status(403);
return ctx.body(null);
}
} else if (config.authStrategy === AuthStrategy.NONE) {
// SKIP CHECK
}
if (!config.authorizedClients.includes(id)) {
setConfig({
authorizedClients: [...config.authorizedClients, id],
});
}
const token = await sign(
{
id,
iat: ~~(Date.now() / 1000),
} satisfies JWTPayload,
config.secret,
);
ctx.status(200);
return ctx.json({
accessToken: token,
});
});
};
================================================
FILE: src/plugins/api-server/backend/routes/control.ts
================================================
import { createRoute, z } from '@hono/zod-openapi';
import { ipcMain } from 'electron';
import { getSongControls } from '@/providers/song-controls';
import {
LikeType,
type RepeatMode,
type VolumeState,
} from '@/types/datahost-get-state';
import {
AddSongToQueueSchema,
GoBackSchema,
GoForwardScheme,
MoveSongInQueueSchema,
QueueParamsSchema,
SearchSchema,
SeekSchema,
SetFullscreenSchema,
SetQueueIndexSchema,
SetVolumeSchema,
SongInfoSchema,
SwitchRepeatSchema,
type ResponseSongInfo,
} from '../scheme';
import { API_VERSION } from '../api-version';
import type { SongInfo } from '@/providers/song-info';
import type { BackendContext } from '@/types/contexts';
import type { APIServerConfig } from '../../config';
import type { HonoApp } from '../types';
import type { QueueResponse } from '@/types/music-player-desktop-internal';
import type { Context } from 'hono';
const routes = {
previous: createRoute({
method: 'post',
path: `/api/${API_VERSION}/previous`,
summary: 'play previous song',
description: 'Plays the previous song in the queue',
responses: {
204: {
description: 'Success',
},
},
}),
next: createRoute({
method: 'post',
path: `/api/${API_VERSION}/next`,
summary: 'play next song',
description: 'Plays the next song in the queue',
responses: {
204: {
description: 'Success',
},
},
}),
play: createRoute({
method: 'post',
path: `/api/${API_VERSION}/play`,
summary: 'Play',
description: 'Change the state of the player to play',
responses: {
204: {
description: 'Success',
},
},
}),
pause: createRoute({
method: 'post',
path: `/api/${API_VERSION}/pause`,
summary: 'Pause',
description: 'Change the state of the player to pause',
responses: {
204: {
description: 'Success',
},
},
}),
togglePlay: createRoute({
method: 'post',
path: `/api/${API_VERSION}/toggle-play`,
summary: 'Toggle play/pause',
description:
'Change the state of the player to play if paused, or pause if playing',
responses: {
204: {
description: 'Success',
},
},
}),
getLikeState: createRoute({
method: 'get',
path: `/api/${API_VERSION}/like-state`,
summary: 'get like state',
description: 'Get the current like state',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
state: z.enum(LikeType).nullable(),
}),
},
},
},
},
}),
like: createRoute({
method: 'post',
path: `/api/${API_VERSION}/like`,
summary: 'like song',
description: 'Set the current song as liked',
responses: {
204: {
description: 'Success',
},
},
}),
dislike: createRoute({
method: 'post',
path: `/api/${API_VERSION}/dislike`,
summary: 'dislike song',
description: 'Set the current song as disliked',
responses: {
204: {
description: 'Success',
},
},
}),
seekTo: createRoute({
method: 'post',
path: `/api/${API_VERSION}/seek-to`,
summary: 'seek',
description: 'Seek to a specific time in the current song',
request: {
body: {
description: 'seconds to seek to',
content: {
'application/json': {
schema: SeekSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
goBack: createRoute({
method: 'post',
path: `/api/${API_VERSION}/go-back`,
summary: 'go back',
description: 'Move the current song back by a number of seconds',
request: {
body: {
description: 'seconds to go back',
content: {
'application/json': {
schema: GoBackSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
goForward: createRoute({
method: 'post',
path: `/api/${API_VERSION}/go-forward`,
summary: 'go forward',
description: 'Move the current song forward by a number of seconds',
request: {
body: {
description: 'seconds to go forward',
content: {
'application/json': {
schema: GoForwardScheme,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
getShuffleState: createRoute({
method: 'get',
path: `/api/${API_VERSION}/shuffle`,
summary: 'get shuffle state',
description: 'Get the current shuffle state',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
state: z.boolean().nullable(),
}),
},
},
},
},
}),
shuffle: createRoute({
method: 'post',
path: `/api/${API_VERSION}/shuffle`,
summary: 'shuffle',
description: 'Shuffle the queue',
responses: {
204: {
description: 'Success',
},
},
}),
repeatMode: createRoute({
method: 'get',
path: `/api/${API_VERSION}/repeat-mode`,
summary: 'get current repeat mode',
description: 'Get the current repeat mode (NONE, ALL, ONE)',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
mode: z.enum(['ONE', 'NONE', 'ALL']).nullable(),
}),
},
},
},
},
}),
switchRepeat: createRoute({
method: 'post',
path: `/api/${API_VERSION}/switch-repeat`,
summary: 'switch repeat',
description: 'Switch the repeat mode',
request: {
body: {
description: 'number of times to click the repeat button',
content: {
'application/json': {
schema: SwitchRepeatSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
setVolume: createRoute({
method: 'post',
path: `/api/${API_VERSION}/volume`,
summary: 'set volume',
description: 'Set the volume of the player',
request: {
body: {
description: 'volume to set',
content: {
'application/json': {
schema: SetVolumeSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
getVolumeState: createRoute({
method: 'get',
path: `/api/${API_VERSION}/volume`,
summary: 'get volume state',
description: 'Get the current volume state of the player',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
state: z.number(),
isMuted: z.boolean(),
}),
},
},
},
},
}),
setFullscreen: createRoute({
method: 'post',
path: `/api/${API_VERSION}/fullscreen`,
summary: 'set fullscreen',
description: 'Set the fullscreen state of the player',
request: {
body: {
description: 'fullscreen state',
content: {
'application/json': {
schema: SetFullscreenSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
toggleMute: createRoute({
method: 'post',
path: `/api/${API_VERSION}/toggle-mute`,
summary: 'toggle mute',
description: 'Toggle the mute state of the player',
responses: {
204: {
description: 'Success',
},
},
}),
getFullscreenState: createRoute({
method: 'get',
path: `/api/${API_VERSION}/fullscreen`,
summary: 'get fullscreen state',
description: 'Get the current fullscreen state',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
state: z.boolean(),
}),
},
},
},
},
}),
oldQueueInfo: createRoute({
deprecated: true,
method: 'get',
path: `/api/${API_VERSION}/queue-info`,
summary: 'get current queue info',
description: 'Get the current queue info',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({}),
},
},
},
204: {
description: 'No queue info',
},
},
}),
oldSongInfo: createRoute({
deprecated: true,
method: 'get',
path: `/api/${API_VERSION}/song-info`,
summary: 'get current song info',
description: 'Get the current song info',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: SongInfoSchema,
},
},
},
204: {
description: 'No song info',
},
},
}),
songInfo: createRoute({
method: 'get',
path: `/api/${API_VERSION}/song`,
summary: 'get current song info',
description: 'Get the current song info',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: SongInfoSchema,
},
},
},
204: {
description: 'No song info',
},
},
}),
nextSongInfo: createRoute({
method: 'get',
path: `/api/${API_VERSION}/queue/next`,
summary: 'get next song info',
description:
'Get information about the next song in the queue (relative index +1)',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: SongInfoSchema,
},
},
},
204: {
description: 'No next song in queue',
},
},
}),
queueInfo: createRoute({
method: 'get',
path: `/api/${API_VERSION}/queue`,
summary: 'get current queue info',
description: 'Get the current queue info',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({}),
},
},
},
204: {
description: 'No queue info',
},
},
}),
addSongToQueue: createRoute({
method: 'post',
path: `/api/${API_VERSION}/queue`,
summary: 'add song to queue',
description: 'Add a song to the queue',
request: {
body: {
description: 'video id of the song to add',
content: {
'application/json': {
schema: AddSongToQueueSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
moveSongInQueue: createRoute({
method: 'patch',
path: `/api/${API_VERSION}/queue/{index}`,
summary: 'move song in queue',
description: 'Move a song in the queue',
request: {
params: QueueParamsSchema,
body: {
description: 'index to move the song to',
content: {
'application/json': {
schema: MoveSongInQueueSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
removeSongFromQueue: createRoute({
method: 'delete',
path: `/api/${API_VERSION}/queue/{index}`,
summary: 'remove song from queue',
description: 'Remove a song from the queue',
request: {
params: QueueParamsSchema,
},
responses: {
204: {
description: 'Success',
},
},
}),
setQueueIndex: createRoute({
method: 'patch',
path: `/api/${API_VERSION}/queue`,
summary: 'set queue index',
description: 'Set the current index of the queue',
request: {
body: {
description: 'index to move the song to',
content: {
'application/json': {
schema: SetQueueIndexSchema,
},
},
},
},
responses: {
204: {
description: 'Success',
},
},
}),
clearQueue: createRoute({
method: 'delete',
path: `/api/${API_VERSION}/queue`,
summary: 'clear queue',
description: 'Clear the queue',
responses: {
204: {
description: 'Success',
},
},
}),
search: createRoute({
method: 'post',
path: `/api/${API_VERSION}/search`,
summary: 'search for a song',
description: 'search for a song',
request: {
body: {
description: 'search query',
content: {
'application/json': {
schema: SearchSchema,
},
},
},
},
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({}),
},
},
},
},
}),
};
type PromiseOrValue = T | Promise;
export const register = (
app: HonoApp,
{ window }: BackendContext,
songInfoGetter: () => PromiseOrValue,
repeatModeGetter: () => PromiseOrValue,
likeTypeGetter: () => PromiseOrValue,
volumeStateGetter: () => PromiseOrValue,
) => {
const controller = getSongControls(window);
app.openapi(routes.previous, (ctx) => {
controller.previous();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.next, (ctx) => {
controller.next();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.play, (ctx) => {
controller.play();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.pause, (ctx) => {
controller.pause();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.togglePlay, (ctx) => {
controller.playPause();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.getLikeState, async (ctx) => {
ctx.status(200);
return ctx.json({ state: (await likeTypeGetter()) ?? null });
});
app.openapi(routes.like, (ctx) => {
controller.like();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.dislike, (ctx) => {
controller.dislike();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.seekTo, (ctx) => {
const { seconds } = ctx.req.valid('json');
controller.seekTo(seconds);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.goBack, (ctx) => {
const { seconds } = ctx.req.valid('json');
controller.goBack(seconds);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.goForward, (ctx) => {
const { seconds } = ctx.req.valid('json');
controller.goForward(seconds);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.getShuffleState, async (ctx) => {
const stateResponsePromise = new Promise((resolve) => {
ipcMain.once(
'peard:get-shuffle-response',
(_, isShuffled: boolean | undefined) => {
return resolve(!!isShuffled);
},
);
controller.requestShuffleInformation();
});
const isShuffled = await stateResponsePromise;
ctx.status(200);
return ctx.json({ state: isShuffled });
});
app.openapi(routes.shuffle, (ctx) => {
controller.shuffle();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.repeatMode, async (ctx) => {
ctx.status(200);
return ctx.json({ mode: (await repeatModeGetter()) ?? null });
});
app.openapi(routes.switchRepeat, (ctx) => {
const { iteration } = ctx.req.valid('json');
controller.switchRepeat(iteration);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.setVolume, (ctx) => {
const { volume } = ctx.req.valid('json');
controller.setVolume(volume);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.getVolumeState, async (ctx) => {
ctx.status(200);
return ctx.json(
(await volumeStateGetter()) ?? { state: 0, isMuted: false },
);
});
app.openapi(routes.setFullscreen, (ctx) => {
const { state } = ctx.req.valid('json');
controller.setFullscreen(state);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.toggleMute, (ctx) => {
controller.muteUnmute();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.getFullscreenState, async (ctx) => {
const stateResponsePromise = new Promise((resolve) => {
ipcMain.once(
'peard:set-fullscreen',
(_, isFullscreen: boolean | undefined) => {
return resolve(!!isFullscreen);
},
);
controller.requestFullscreenInformation();
});
const fullscreen = await stateResponsePromise;
ctx.status(200);
return ctx.json({ state: fullscreen });
});
const songInfo = async (ctx: Context) => {
const info = await songInfoGetter();
if (!info) {
ctx.status(204);
return ctx.body(null);
}
const body = { ...info };
delete body.image;
ctx.status(200);
return ctx.json(body satisfies ResponseSongInfo);
};
app.openapi(routes.oldSongInfo, songInfo);
app.openapi(routes.songInfo, songInfo);
// Queue
const queueInfo = async (ctx: Context) => {
const queueResponsePromise = new Promise((resolve) => {
ipcMain.once('peard:get-queue-response', (_, queue: QueueResponse) => {
return resolve(queue);
});
controller.requestQueueInformation();
});
const info = await queueResponsePromise;
if (!info) {
ctx.status(204);
return ctx.body(null);
}
ctx.status(200);
return ctx.json(info);
};
app.openapi(routes.oldQueueInfo, queueInfo);
app.openapi(routes.queueInfo, queueInfo);
app.openapi(routes.nextSongInfo, async (ctx) => {
const queueResponsePromise = new Promise((resolve) => {
ipcMain.once('peard:get-queue-response', (_, queue: QueueResponse) => {
return resolve(queue);
});
controller.requestQueueInformation();
});
const queue = await queueResponsePromise;
if (!queue?.items || queue.items.length === 0) {
ctx.status(204);
return ctx.body(null);
}
// Find the currently selected song
const currentIndex = queue.items.findIndex((item) => {
const renderer =
item.playlistPanelVideoRenderer ||
item.playlistPanelVideoWrapperRenderer?.primaryRenderer
?.playlistPanelVideoRenderer;
return renderer?.selected === true;
});
// Get the next song (currentIndex + 1)
const nextIndex = currentIndex + 1;
if (nextIndex >= queue.items.length) {
// No next song available
ctx.status(204);
return ctx.body(null);
}
const nextItem = queue.items[nextIndex];
const nextRenderer =
nextItem.playlistPanelVideoRenderer ||
nextItem.playlistPanelVideoWrapperRenderer?.primaryRenderer
?.playlistPanelVideoRenderer;
if (!nextRenderer) {
ctx.status(204);
return ctx.body(null);
}
// Extract relevant information similar to SongInfo format
const nextSongInfo = {
title: nextRenderer.title?.runs?.[0]?.text,
videoId: nextRenderer.videoId,
thumbnail: nextRenderer.thumbnail,
lengthText: nextRenderer.lengthText,
shortBylineText: nextRenderer.shortBylineText,
};
ctx.status(200);
return ctx.json(nextSongInfo);
});
app.openapi(routes.addSongToQueue, (ctx) => {
const { videoId, insertPosition } = ctx.req.valid('json');
controller.addSongToQueue(videoId, insertPosition);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.moveSongInQueue, (ctx) => {
const index = Number(ctx.req.param('index'));
const { toIndex } = ctx.req.valid('json');
controller.moveSongInQueue(index, toIndex);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.removeSongFromQueue, (ctx) => {
const index = Number(ctx.req.param('index'));
controller.removeSongFromQueue(index);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.setQueueIndex, (ctx) => {
const { index } = ctx.req.valid('json');
controller.setQueueIndex(index);
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.clearQueue, (ctx) => {
controller.clearQueue();
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.search, async (ctx) => {
const { query, params, continuation } = ctx.req.valid('json');
const response = await controller.search(query, params, continuation);
ctx.status(200);
return ctx.json(response as object);
});
};
================================================
FILE: src/plugins/api-server/backend/routes/index.ts
================================================
export { register as registerControl } from './control';
export { register as registerAuth } from './auth';
export { register as registerWebsocket } from './websocket';
================================================
FILE: src/plugins/api-server/backend/routes/websocket.ts
================================================
import { createRoute } from '@hono/zod-openapi';
import { type NodeWebSocket } from '@hono/node-ws';
import {
registerCallback,
type SongInfo,
SongInfoEvent,
} from '@/providers/song-info';
import { API_VERSION } from '../api-version';
import type { WSContext } from 'hono/ws';
import type { Context, Next } from 'hono';
import type { RepeatMode, VolumeState } from '@/types/datahost-get-state';
import type { HonoApp } from '../types';
import type { BackendContext } from '@/types/contexts';
import type { APIServerConfig } from '@/plugins/api-server/config';
enum DataTypes {
PlayerInfo = 'PLAYER_INFO',
VideoChanged = 'VIDEO_CHANGED',
PlayerStateChanged = 'PLAYER_STATE_CHANGED',
PositionChanged = 'POSITION_CHANGED',
VolumeChanged = 'VOLUME_CHANGED',
RepeatChanged = 'REPEAT_CHANGED',
ShuffleChanged = 'SHUFFLE_CHANGED',
}
type PlayerState = {
song?: SongInfo;
isPlaying: boolean;
muted: boolean;
position: number;
volume: number;
repeat: RepeatMode;
shuffle: boolean;
};
export const register = (
app: HonoApp,
{ ipc }: BackendContext,
{ upgradeWebSocket }: NodeWebSocket,
) => {
let volumeState: VolumeState | undefined = undefined;
let repeat: RepeatMode = 'NONE';
let shuffle = false;
let lastSongInfo: SongInfo | undefined = undefined;
const sockets = new Set>();
const send = (type: DataTypes, state: Partial) => {
sockets.forEach((socket) =>
socket.send(JSON.stringify({ type, ...state })),
);
};
const createPlayerState = ({
songInfo,
volumeState,
repeat,
shuffle,
}: {
songInfo?: SongInfo;
volumeState?: VolumeState;
repeat: RepeatMode;
shuffle: boolean;
}): PlayerState => ({
song: songInfo,
isPlaying: songInfo ? !songInfo.isPaused : false,
muted: volumeState?.isMuted ?? false,
position: songInfo?.elapsedSeconds ?? 0,
volume: volumeState?.state ?? 100,
repeat,
shuffle,
});
registerCallback((songInfo, event) => {
if (event === SongInfoEvent.VideoSrcChanged) {
send(DataTypes.VideoChanged, { song: songInfo, position: 0 });
}
if (event === SongInfoEvent.PlayOrPaused) {
send(DataTypes.PlayerStateChanged, {
isPlaying: !(songInfo?.isPaused ?? true),
position: songInfo.elapsedSeconds,
});
}
if (event === SongInfoEvent.TimeChanged) {
send(DataTypes.PositionChanged, { position: songInfo.elapsedSeconds });
}
lastSongInfo = { ...songInfo };
});
ipc.on('peard:volume-changed', (newVolumeState: VolumeState) => {
volumeState = newVolumeState;
send(DataTypes.VolumeChanged, {
volume: volumeState.state,
muted: volumeState.isMuted,
});
});
ipc.on('peard:repeat-changed', (mode: RepeatMode) => {
repeat = mode;
send(DataTypes.RepeatChanged, { repeat });
});
ipc.on('peard:seeked', (t: number) => {
send(DataTypes.PositionChanged, { position: t });
});
ipc.on('peard:shuffle-changed', (newShuffle: boolean) => {
shuffle = newShuffle;
send(DataTypes.ShuffleChanged, { shuffle });
});
app.openapi(
createRoute({
method: 'get',
path: `/api/${API_VERSION}/ws`,
summary: 'websocket endpoint',
description: 'WebSocket endpoint for real-time updates',
responses: {
101: {
description: 'Switching Protocols',
},
},
}),
upgradeWebSocket(() => ({
onOpen(_, ws) {
// "Unsafe argument of type `WSContext` assigned to a parameter of type `WSContext`. (@typescript-eslint/no-unsafe-argument)" ????? what?
sockets.add(ws as WSContext);
ws.send(
JSON.stringify({
type: DataTypes.PlayerInfo,
...createPlayerState({
songInfo: lastSongInfo,
volumeState,
repeat,
shuffle,
}),
}),
);
},
onClose(_, ws) {
sockets.delete(ws as WSContext);
},
})) as (ctx: Context, next: Next) => Promise,
);
};
================================================
FILE: src/plugins/api-server/backend/scheme/auth.ts
================================================
import { z } from '@hono/zod-openapi';
export type JWTPayload = z.infer;
export const JWTPayloadSchema = z.object({
id: z.string(),
iat: z.number(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/go-back.ts
================================================
import { z } from '@hono/zod-openapi';
export const GoBackSchema = z.object({
seconds: z.number(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/go-forward.ts
================================================
import { z } from '@hono/zod-openapi';
export const GoForwardScheme = z.object({
seconds: z.number(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/index.ts
================================================
export * from './auth';
export * from './song-info';
export * from './seek';
export * from './go-back';
export * from './go-forward';
export * from './switch-repeat';
export * from './set-volume';
export * from './set-fullscreen';
export * from './queue';
export * from './search';
================================================
FILE: src/plugins/api-server/backend/scheme/queue.ts
================================================
import { z } from '@hono/zod-openapi';
export const QueueParamsSchema = z.object({
index: z.coerce.number().int().nonnegative(),
});
export const AddSongToQueueSchema = z.object({
videoId: z.string(),
insertPosition: z
.enum(['INSERT_AT_END', 'INSERT_AFTER_CURRENT_VIDEO'])
.optional()
.default('INSERT_AT_END'),
});
export const MoveSongInQueueSchema = z.object({
toIndex: z.number(),
});
export const SetQueueIndexSchema = z.object({
index: z.number().int().nonnegative(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/search.ts
================================================
import { z } from '@hono/zod-openapi';
export const SearchSchema = z.object({
query: z.string(),
params: z.string().optional(),
continuation: z.string().optional(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/seek.ts
================================================
import { z } from '@hono/zod-openapi';
export const SeekSchema = z.object({
seconds: z.number(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/set-fullscreen.ts
================================================
import { z } from '@hono/zod-openapi';
export const SetFullscreenSchema = z.object({
state: z.boolean(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/set-volume.ts
================================================
import { z } from '@hono/zod-openapi';
export const SetVolumeSchema = z.object({
volume: z.number(),
});
================================================
FILE: src/plugins/api-server/backend/scheme/song-info.ts
================================================
import { z } from '@hono/zod-openapi';
import { MediaType } from '@/providers/song-info';
export type ResponseSongInfo = z.infer;
export const SongInfoSchema = z.object({
title: z.string(),
artist: z.string(),
views: z.number(),
uploadDate: z.string().optional(),
imageSrc: z.string().nullable().optional(),
isPaused: z.boolean().optional(),
songDuration: z.number(),
elapsedSeconds: z.number().optional(),
url: z.string().optional(),
album: z.string().nullable().optional(),
videoId: z.string(),
playlistId: z.string().optional(),
mediaType: z.enum([
MediaType.Audio,
MediaType.OriginalMusicVideo,
MediaType.UserGeneratedContent,
MediaType.PodcastEpisode,
MediaType.OtherVideo,
]),
});
================================================
FILE: src/plugins/api-server/backend/scheme/switch-repeat.ts
================================================
import { z } from '@hono/zod-openapi';
export const SwitchRepeatSchema = z.object({
iteration: z.number(),
});
================================================
FILE: src/plugins/api-server/backend/types.ts
================================================
import { type OpenAPIHono as Hono } from '@hono/zod-openapi';
import { type serve } from '@hono/node-server';
import type { RepeatMode, VolumeState } from '@/types/datahost-get-state';
import type { BackendContext } from '@/types/contexts';
import type { SongInfo } from '@/providers/song-info';
import type { APIServerConfig } from '../config';
export type HonoApp = Hono;
export type BackendType = {
app?: HonoApp;
server?: ReturnType;
oldConfig?: APIServerConfig;
songInfo?: SongInfo;
currentRepeatMode?: RepeatMode;
volumeState?: VolumeState;
injectWebSocket?: (server: ReturnType) => void;
init: (ctx: BackendContext) => void;
run: (config: APIServerConfig) => void;
end: () => void;
};
================================================
FILE: src/plugins/api-server/config.ts
================================================
export enum AuthStrategy {
AUTH_AT_FIRST = 'AUTH_AT_FIRST',
NONE = 'NONE',
}
export interface APIServerConfig {
enabled: boolean;
hostname: string;
port: number;
authStrategy: AuthStrategy;
secret: string;
authorizedClients: string[];
useHttps: boolean;
certPath: string;
keyPath: string;
}
export const defaultAPIServerConfig: APIServerConfig = {
enabled: false,
hostname: '0.0.0.0',
port: 26538,
authStrategy: AuthStrategy.AUTH_AT_FIRST,
secret: Date.now().toString(36),
authorizedClients: [],
useHttps: false,
certPath: '',
keyPath: '',
};
================================================
FILE: src/plugins/api-server/index.ts
================================================
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import { defaultAPIServerConfig } from './config';
import { onMenu } from './menu';
import { backend } from './backend';
export default createPlugin({
name: () => t('plugins.api-server.name'),
description: () => t('plugins.api-server.description'),
restartNeeded: false,
config: defaultAPIServerConfig,
addedVersion: '3.6.X',
menu: onMenu,
backend,
});
================================================
FILE: src/plugins/api-server/menu.ts
================================================
import { dialog } from 'electron';
import prompt from 'custom-electron-prompt';
import { t } from '@/i18n';
import promptOptions from '@/providers/prompt-options';
import {
type APIServerConfig,
AuthStrategy,
defaultAPIServerConfig,
} from './config';
import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';
export const onMenu = async ({
getConfig,
setConfig,
window,
}: MenuContext): Promise => {
const config = await getConfig();
return [
{
label: t('plugins.api-server.menu.hostname.label'),
type: 'normal',
async click() {
const config = await getConfig();
const newHostname =
(await prompt(
{
title: t('plugins.api-server.prompt.hostname.title'),
label: t('plugins.api-server.prompt.hostname.label'),
value: config.hostname,
type: 'input',
width: 380,
...promptOptions(),
},
window,
)) ??
config.hostname ??
defaultAPIServerConfig.hostname;
setConfig({ ...config, hostname: newHostname });
},
},
{
label: t('plugins.api-server.menu.port.label'),
type: 'normal',
async click() {
const config = await getConfig();
const newPort =
(await prompt(
{
title: t('plugins.api-server.prompt.port.title'),
label: t('plugins.api-server.prompt.port.label'),
value: config.port,
type: 'counter',
counterOptions: { minimum: 0, maximum: 65565 },
width: 380,
...promptOptions(),
},
window,
)) ??
config.port ??
defaultAPIServerConfig.port;
setConfig({ ...config, port: newPort });
},
},
{
label: t('plugins.api-server.menu.auth-strategy.label'),
type: 'submenu',
submenu: [
{
label: t(
'plugins.api-server.menu.auth-strategy.submenu.auth-at-first.label',
),
type: 'radio',
checked: config.authStrategy === AuthStrategy.AUTH_AT_FIRST,
click() {
setConfig({ ...config, authStrategy: AuthStrategy.AUTH_AT_FIRST });
},
},
{
label: t('plugins.api-server.menu.auth-strategy.submenu.none.label'),
type: 'radio',
checked: config.authStrategy === AuthStrategy.NONE,
click() {
setConfig({ ...config, authStrategy: AuthStrategy.NONE });
},
},
],
},
{
label: t('plugins.api-server.menu.https.label'),
type: 'submenu',
submenu: [
{
label: t('plugins.api-server.menu.https.submenu.enable-https.label'),
type: 'checkbox',
checked: config.useHttps,
click(menuItem) {
setConfig({ ...config, useHttps: menuItem.checked });
},
},
{
label: t('plugins.api-server.menu.https.submenu.cert.label'),
type: 'normal',
async click() {
const config = await getConfig();
const result = await dialog.showOpenDialog(window, {
title: t(
'plugins.api-server.menu.https.submenu.cert.dialogTitle',
),
filters: [{ name: 'Certificate', extensions: ['crt', 'pem'] }],
properties: ['openFile'],
});
if (!result.canceled && result.filePaths.length > 0) {
setConfig({ ...config, certPath: result.filePaths[0] });
}
},
},
{
label: t('plugins.api-server.menu.https.submenu.key.label'),
type: 'normal',
async click() {
const config = await getConfig();
const result = await dialog.showOpenDialog(window, {
title: t('plugins.api-server.menu.https.submenu.key.dialogTitle'),
filters: [{ name: 'Private Key', extensions: ['key', 'pem'] }],
properties: ['openFile'],
});
if (!result.canceled && result.filePaths.length > 0) {
setConfig({ ...config, keyPath: result.filePaths[0] });
}
},
},
],
},
];
};
================================================
FILE: src/plugins/audio-compressor.ts
================================================
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import { type MusicPlayer } from '@/types/music-player';
const lazySafeTry = (...fns: (() => void)[]) => {
for (const fn of fns) {
try {
fn();
} catch {}
}
};
const createCompressorNode = (
audioContext: AudioContext,
): DynamicsCompressorNode => {
const compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.ratio.value = 12;
compressor.knee.value = 40;
compressor.attack.value = 0;
compressor.release.value = 0.25;
return compressor;
};
class Storage {
lastSource: MediaElementAudioSourceNode | null = null;
lastContext: AudioContext | null = null;
lastCompressor: DynamicsCompressorNode | null = null;
connected: WeakMap =
new WeakMap();
connectToCompressor = (
source: MediaElementAudioSourceNode | null = null,
audioContext: AudioContext | null = null,
compressor: DynamicsCompressorNode | null = null,
): boolean => {
if (!(source && audioContext && compressor)) return false;
const current = this.connected.get(source);
if (current === compressor) return false;
this.lastSource = source;
this.lastContext = audioContext;
this.lastCompressor = compressor;
if (current) {
lazySafeTry(
() => source.disconnect(current),
() => current.disconnect(audioContext.destination),
);
} else {
lazySafeTry(() => source.disconnect(audioContext.destination));
}
try {
source.connect(compressor);
compressor.connect(audioContext.destination);
this.connected.set(source, compressor);
return true;
} catch (error) {
console.error('connectToCompressor failed', error);
return false;
}
};
disconnectCompressor = (): boolean => {
const source = this.lastSource;
const audioContext = this.lastContext;
if (!(source && audioContext)) return false;
const current = this.connected.get(source);
if (!current) return false;
lazySafeTry(
() => source.connect(audioContext.destination),
() => source.disconnect(current),
() => current.disconnect(audioContext.destination),
);
this.connected.delete(source);
return true;
};
}
const storage = new Storage();
const audioCanPlayHandler = ({
detail: { audioSource, audioContext },
}: CustomEvent) => {
storage.connectToCompressor(
audioSource,
audioContext,
createCompressorNode(audioContext),
);
};
const ensureAudioContextLoad = (playerApi: MusicPlayer) => {
if (playerApi.getPlayerState() !== 1 || storage.lastContext) return;
playerApi.loadVideoById(
playerApi.getPlayerResponse().videoDetails.videoId,
playerApi.getCurrentTime(),
playerApi.getUserPlaybackQualityPreference(),
);
};
export default createPlugin({
name: () => t('plugins.audio-compressor.name'),
description: () => t('plugins.audio-compressor.description'),
renderer: {
onPlayerApiReady(playerApi) {
ensureAudioContextLoad(playerApi);
},
start() {
document.addEventListener('peard:audio-can-play', audioCanPlayHandler, {
passive: true,
});
storage.connectToCompressor(
storage.lastSource,
storage.lastContext,
storage.lastCompressor,
);
},
stop() {
document.removeEventListener('peard:audio-can-play', audioCanPlayHandler);
storage.disconnectCompressor();
},
},
});
================================================
FILE: src/plugins/auth-proxy-adapter/backend/index.ts
================================================
import * as net from 'node:net';
import { SocksClient, type SocksClientOptions } from 'socks';
import is from 'electron-is';
import { createBackend, LoggerPrefix } from '@/utils';
import * as config from '@/config';
import { type AuthProxyConfig, defaultAuthProxyConfig } from '../config';
import type { BackendType } from './types';
import type { BackendContext } from '@/types/contexts';
// Parsing the upstream authentication SOCK proxy URL
const parseSocksUrl = (socksUrl: string) => {
// Format: socks5://username:password@your_server_ip:port
const url = new URL(socksUrl);
return {
host: url.hostname,
port: parseInt(url.port, 10),
type: url.protocol === 'socks5:' ? 5 : 4,
username: url.username,
password: url.password,
};
};
export const backend = createBackend({
async start(ctx: BackendContext) {
const pluginConfig = await ctx.getConfig();
this.startServer(pluginConfig);
},
stop() {
this.stopServer();
},
onConfigChange(config: AuthProxyConfig) {
if (
this.oldConfig?.hostname === config.hostname &&
this.oldConfig?.port === config.port
) {
this.oldConfig = config;
return;
}
this.stopServer();
this.startServer(config);
this.oldConfig = config;
},
// Custom
// Start proxy server - SOCKS5
startServer(serverConfig: AuthProxyConfig) {
if (this.server) {
this.stopServer();
}
const { port, hostname } = serverConfig;
// Upstream proxy from system settings
const upstreamProxyUrl = config.get('options.proxy');
// Create SOCKS proxy server
const socksServer = net.createServer((socket) => {
socket.once('data', (chunk) => {
if (chunk[0] === 0x05) {
// SOCKS5
this.handleSocks5(socket, chunk, upstreamProxyUrl);
} else {
socket.end();
}
});
socket.on('error', (err) => {
console.error(LoggerPrefix, '[SOCKS] Socket error:', err.message);
});
});
// Listen for errors
socksServer.on('error', (err) => {
console.error(LoggerPrefix, '[SOCKS Server Error]', err.message);
});
// Start server
socksServer.listen(port, hostname, () => {
console.log(LoggerPrefix, '===========================================');
console.log(
LoggerPrefix,
`[Auth-Proxy-Adapter] Enable SOCKS proxy at socks5://${hostname}:${port}`,
);
console.log(
LoggerPrefix,
`[Auth-Proxy-Adapter] Using upstream proxy: ${upstreamProxyUrl}`,
);
console.log(LoggerPrefix, '===========================================');
});
this.server = socksServer;
},
// Handle SOCKS5 request
handleSocks5(
clientSocket: net.Socket,
chunk: Buffer,
upstreamProxyUrl: string,
) {
// Handshake phase
const numMethods = chunk[1];
const methods = chunk.subarray(2, 2 + numMethods);
// Check if client supports no authentication method (0x00)
if (methods.includes(0x00)) {
// Reply to client, choose no authentication method
clientSocket.write(Buffer.from([0x05, 0x00]));
// Wait for client's connection request
clientSocket.once('data', (data) => {
this.processSocks5Request(clientSocket, data, upstreamProxyUrl);
});
} else {
// Authentication methods not supported by the client
clientSocket.write(Buffer.from([0x05, 0xff]));
clientSocket.end();
}
},
// Handle SOCKS5 connection request
processSocks5Request(
clientSocket: net.Socket,
data: Buffer,
upstreamProxyUrl: string,
) {
// Parse target address and port
let targetHost, targetPort;
const cmd = data[1]; // Command: 0x01=CONNECT, 0x02=BIND, 0x03=UDP
const atyp = data[3]; // Address type: 0x01=IPv4, 0x03=Domain, 0x04=IPv6
if (cmd !== 0x01) {
// Currently only support CONNECT command
clientSocket.write(
Buffer.from([0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0]),
);
clientSocket.end();
return;
}
if (atyp === 0x01) {
// IPv4
targetHost = `${data[4]}.${data[5]}.${data[6]}.${data[7]}`;
targetPort = data.readUInt16BE(8);
} else if (atyp === 0x03) {
// Domain
const hostLen = data[4];
targetHost = data.subarray(5, 5 + hostLen).toString();
targetPort = data.readUInt16BE(5 + hostLen);
} else if (atyp === 0x04) {
// IPv6
const ipv6Buffer = data.subarray(4, 20);
targetHost = Array.from(new Array(8), (_, i) =>
ipv6Buffer.readUInt16BE(i * 2).toString(16),
).join(':');
targetPort = data.readUInt16BE(20);
}
if (is.dev()) {
console.debug(
LoggerPrefix,
`[SOCKS5] Request to connect to ${targetHost}:${targetPort}`,
);
}
const socksProxy = parseSocksUrl(upstreamProxyUrl);
if (!socksProxy) {
// Failed to parse proxy URL
clientSocket.write(
Buffer.from([0x05, 0x01, 0x00, 0x01, 0, 0, 0, 0, 0, 0]),
);
clientSocket.end();
return;
}
const options: SocksClientOptions = {
proxy: {
host: socksProxy.host,
port: socksProxy.port,
type: socksProxy.type as 4 | 5,
userId: socksProxy.username,
password: socksProxy.password,
},
command: 'connect',
destination: {
host: targetHost || defaultAuthProxyConfig.hostname,
port: targetPort || defaultAuthProxyConfig.port,
},
};
SocksClient.createConnection(options)
.then((info) => {
const { socket: proxySocket } = info;
// Connection successful, send success response to client
const responseBuffer = Buffer.from([
0x05, // VER: SOCKS5
0x00, // REP: Success
0x00, // RSV: Reserved field
0x01, // ATYP: IPv4
0,
0,
0,
0, // BND.ADDR: 0.0.0.0 (Bound address, usually not important)
0,
0, // BND.PORT: 0 (Bound port, usually not important)
]);
clientSocket.write(responseBuffer);
// Establish bidirectional data stream
proxySocket.pipe(clientSocket);
clientSocket.pipe(proxySocket);
proxySocket.on('error', (error) => {
console.error(LoggerPrefix, '[SOCKS5] Proxy socket error:', error);
if (clientSocket.writable) clientSocket.end();
});
clientSocket.on('error', (error) => {
console.error(LoggerPrefix, '[SOCKS5] Client socket error:', error);
if (proxySocket.writable) proxySocket.end();
});
})
.catch((error) => {
console.error(LoggerPrefix, '[SOCKS5] Connection error:', error);
// Send failure response to client
clientSocket.write(
Buffer.from([0x05, 0x05, 0x00, 0x01, 0, 0, 0, 0, 0, 0]),
);
clientSocket.end();
});
},
// Stop proxy server
stopServer() {
if (this.server) {
this.server.close();
this.server = undefined;
}
},
});
================================================
FILE: src/plugins/auth-proxy-adapter/backend/types.ts
================================================
import type net from 'net';
import type { AuthProxyConfig } from '../config';
import type { Server } from 'http';
export type BackendType = {
server?: Server | net.Server;
oldConfig?: AuthProxyConfig;
startServer: (serverConfig: AuthProxyConfig) => void;
stopServer: () => void;
handleSocks5: (
clientSocket: net.Socket,
chunk: Buffer,
upstreamProxyUrl: string,
) => void;
processSocks5Request: (
clientSocket: net.Socket,
data: Buffer,
upstreamProxyUrl: string,
) => void;
};
================================================
FILE: src/plugins/auth-proxy-adapter/config.ts
================================================
export interface AuthProxyConfig {
enabled: boolean;
hostname: string;
port: number;
}
export const defaultAuthProxyConfig: AuthProxyConfig = {
enabled: false,
hostname: '127.0.0.1',
port: 4545,
};
================================================
FILE: src/plugins/auth-proxy-adapter/index.ts
================================================
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import { defaultAuthProxyConfig } from './config';
import { onMenu } from './menu';
import { backend } from './backend';
export default createPlugin({
name: () => t('plugins.auth-proxy-adapter.name'),
description: () => t('plugins.auth-proxy-adapter.description'),
restartNeeded: true,
config: defaultAuthProxyConfig,
addedVersion: '3.10.X',
menu: onMenu,
backend,
});
================================================
FILE: src/plugins/auth-proxy-adapter/menu.ts
================================================
import prompt from 'custom-electron-prompt';
import { t } from '@/i18n';
import promptOptions from '@/providers/prompt-options';
import { type AuthProxyConfig, defaultAuthProxyConfig } from './config';
import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';
export const onMenu = async ({
getConfig,
setConfig,
window,
}: MenuContext): Promise => {
await getConfig();
return [
{
label: t('plugins.auth-proxy-adapter.menu.hostname.label'),
type: 'normal',
async click() {
const config = await getConfig();
const newHostname =
(await prompt(
{
title: t('plugins.auth-proxy-adapter.prompt.hostname.title'),
label: t('plugins.auth-proxy-adapter.prompt.hostname.label'),
value: config.hostname,
type: 'input',
width: 380,
...promptOptions(),
},
window,
)) ??
config.hostname ??
defaultAuthProxyConfig.hostname;
setConfig({ ...config, hostname: newHostname });
},
},
{
label: t('plugins.auth-proxy-adapter.menu.port.label'),
type: 'normal',
async click() {
const config = await getConfig();
const newPort =
(await prompt(
{
title: t('plugins.auth-proxy-adapter.prompt.port.title'),
label: t('plugins.auth-proxy-adapter.prompt.port.label'),
value: config.port,
type: 'counter',
counterOptions: { minimum: 0, maximum: 65535 },
width: 380,
...promptOptions(),
},
window,
)) ??
config.port ??
defaultAuthProxyConfig.port;
setConfig({ ...config, port: newPort });
},
},
];
};
================================================
FILE: src/plugins/blur-nav-bar/index.ts
================================================
import { createPlugin } from '@/utils';
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: 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('');
},
},
});
================================================
FILE: src/plugins/blur-nav-bar/style.css
================================================
#nav-bar-background,
#header.ytmusic-item-section-renderer {
background: rgba(0, 0, 0, 0.3) !important;
backdrop-filter: blur(8px) !important;
}
ytmusic-tabs {
backdrop-filter: blur(8px) !important;
}
ytmusic-tabs.stuck {
background: rgba(0, 0, 0, 0.3) !important;
}
#nav-bar-divider {
display: none !important;
}
================================================
FILE: src/plugins/captions-selector/back.ts
================================================
import prompt from 'custom-electron-prompt';
import promptOptions from '@/providers/prompt-options';
import { createBackend } from '@/utils';
import { t } from '@/i18n';
export default createBackend({
start({ ipc: { handle }, window }) {
handle(
'peard:captions-selector',
async (captionLabels: Record, currentIndex: string) =>
await prompt(
{
title: t('plugins.captions-selector.prompt.selector.title'),
label: t('plugins.captions-selector.prompt.selector.label', {
language:
captionLabels[currentIndex] ||
t('plugins.captions-selector.prompt.selector.none'),
}),
type: 'select',
value: currentIndex,
selectOptions: captionLabels,
resizable: true,
...promptOptions(),
},
window,
),
);
},
stop({ ipc: { removeHandler } }) {
removeHandler('captionsSelector');
},
});
================================================
FILE: src/plugins/captions-selector/index.ts
================================================
import { createPlugin } from '@/utils';
import { APPLICATION_NAME, t } from '@/i18n';
import backend from './back';
import renderer, {
type CaptionsSelectorConfig,
type LanguageOptions,
} from './renderer';
import type { MusicPlayer } from '@/types/music-player';
export default createPlugin<
unknown,
unknown,
{
captionsSettingsButton?: HTMLElement;
captionTrackList: LanguageOptions[] | null;
api: MusicPlayer | null;
config: CaptionsSelectorConfig | null;
videoChangeListener: () => void;
},
CaptionsSelectorConfig
>({
name: () => t('plugins.captions-selector.name'),
description: () =>
t('plugins.captions-selector.description', {
applicationName: APPLICATION_NAME,
}),
config: {
enabled: false,
disableCaptions: false,
autoload: false,
lastCaptionsCode: '',
},
async menu({ getConfig, setConfig }) {
const config = await getConfig();
return [
{
label: t('plugins.captions-selector.menu.autoload'),
type: 'checkbox',
checked: config.autoload,
click(item) {
setConfig({ autoload: item.checked });
},
},
{
label: t('plugins.captions-selector.menu.disable-captions'),
type: 'checkbox',
checked: config.disableCaptions,
click(item) {
setConfig({ disableCaptions: item.checked });
},
},
];
},
backend,
renderer,
});
================================================
FILE: src/plugins/captions-selector/renderer.tsx
================================================
import { render } from 'solid-js/web';
import { createSignal, Show } from 'solid-js';
import { createRenderer } from '@/utils';
import { t } from '@/i18n';
import { CaptionsSettingButton } from './templates/captions-settings-template';
import type { MusicPlayer } from '@/types/music-player';
import type { AppElement } from '@/types/queue';
export interface LanguageOptions {
displayName: string;
id: string | null;
is_default: boolean;
is_servable: boolean;
is_translateable: boolean;
kind: string;
languageCode: string; // 2 length
languageName: string;
name: string | null;
vss_id: string;
}
export interface CaptionsSelectorConfig {
enabled: boolean;
disableCaptions: boolean;
autoload: boolean;
lastCaptionsCode: string;
}
const [hidden, setHidden] = createSignal(false);
export default createRenderer<
{
captionsSettingsButton?: HTMLElement;
captionTrackList: LanguageOptions[] | null;
api: MusicPlayer | null;
config: CaptionsSelectorConfig | null;
videoChangeListener: () => void;
},
CaptionsSelectorConfig
>({
captionTrackList: null,
api: null,
config: null,
videoChangeListener() {
if (this.config?.disableCaptions) {
setTimeout(() => this.api!.unloadModule('captions'), 100);
setHidden(true);
return;
}
this.api!.loadModule('captions');
setTimeout(() => {
this.captionTrackList =
this.api!.getOption('captions', 'tracklist') ?? [];
if (this.config!.autoload && this.config!.lastCaptionsCode) {
this.api?.setOption('captions', 'track', {
languageCode: this.config!.lastCaptionsCode,
});
}
setHidden(!this.captionTrackList?.length);
}, 250);
},
async start({ getConfig }) {
this.config = await getConfig();
},
stop() {
this.api?.unloadModule('captions');
document
.querySelector('video')
?.removeEventListener('peard:src-changed', this.videoChangeListener);
if (this.captionsSettingsButton) {
document
.querySelector('.right-controls-buttons')
?.removeChild(this.captionsSettingsButton);
}
},
onPlayerApiReady(playerApi, { ipc, setConfig }) {
this.api = playerApi;
render(
() => (
{
const appApi = document.querySelector('ytmusic-app');
if (this.captionTrackList?.length) {
const currentCaptionTrack =
playerApi.getOption('captions', 'track');
let currentIndex = currentCaptionTrack
? this.captionTrackList.indexOf(
this.captionTrackList.find(
(track) =>
track.languageCode ===
currentCaptionTrack.languageCode,
)!,
)
: null;
const captionLabels = [
...this.captionTrackList.map((track) => track.displayName),
'None',
];
currentIndex = (await ipc.invoke(
'peard:captions-selector',
captionLabels,
currentIndex,
)) as number;
if (currentIndex === null) {
return;
}
const newCaptions = this.captionTrackList[currentIndex];
setConfig({ lastCaptionsCode: newCaptions?.languageCode });
if (newCaptions) {
playerApi.setOption('captions', 'track', {
languageCode: newCaptions.languageCode,
});
appApi?.toastService?.show(
t('plugins.captions-selector.toast.caption-changed', {
language: newCaptions.displayName,
}),
);
} else {
playerApi.setOption('captions', 'track', {});
appApi?.toastService?.show(
t('plugins.captions-selector.toast.caption-disabled'),
);
}
setTimeout(() => playerApi.playVideo());
} else {
appApi?.toastService?.show(
t('plugins.captions-selector.toast.no-captions'),
);
}
}}
ref={this.captionsSettingsButton}
/>
),
document.querySelector('.right-controls-buttons')!,
);
this.captionTrackList =
this.api.getOption('captions', 'tracklist') ?? [];
document
.querySelector('video')
?.addEventListener('peard:src-changed', this.videoChangeListener);
},
onConfigChange(newConfig) {
this.config = newConfig;
},
});
================================================
FILE: src/plugins/captions-selector/templates/captions-settings-template.tsx
================================================
export interface CaptionsSettingsButtonProps {
label: string;
onClick: (event: MouseEvent) => void;
}
export const CaptionsSettingButton = (props: CaptionsSettingsButtonProps) => (
props.onClick(e)}
role={'button'}
tabindex={0}
title={props.label}
>
);
================================================
FILE: src/plugins/clock/index.tsx
================================================
import { render } from 'solid-js/web';
import { createSignal, onMount } from 'solid-js';
import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { type MenuTemplate } from '@/menu';
import { t } from '@/i18n';
import { type ClockPluginConfig } from './types';
const defaultConfig: ClockPluginConfig = {
enabled: false,
displaySeconds: false,
hour12: false,
};
export default createPlugin({
name: () => t('plugins.clock.name'),
description: () => t('plugins.clock.description'),
restartNeeded: false,
config: defaultConfig,
stylesheets: [style],
menu: async ({ getConfig, setConfig }): Promise => {
const config = await getConfig();
return [
{
label: t('plugins.clock.menu.format.label'),
submenu: [
{
label: t('plugins.clock.menu.format.display-seconds'),
type: 'checkbox',
checked: config.displaySeconds,
click(item) {
setConfig({ displaySeconds: item.checked });
},
},
{
label: t('plugins.clock.menu.format.24-hour-format'),
type: 'checkbox',
checked: !config.hour12,
click(item) {
setConfig({ hour12: !item.checked });
},
},
],
},
];
},
renderer: {
displaySeconds: defaultConfig.displaySeconds,
hour12: defaultConfig.hour12,
interval: null as NodeJS.Timeout | null,
clockContainer: document.createElement('div'),
updateTime: null as unknown as () => void,
async start({ getConfig }) {
const config = await getConfig();
this.displaySeconds = config.displaySeconds;
this.hour12 = config.hour12;
if (!this.clockContainer) {
this.clockContainer = document.createElement('div');
}
const [time, setTime] = createSignal();
const updateTime = () => {
const timeFormat: Intl.DateTimeFormatOptions = {
hour12: this.hour12,
hour: 'numeric',
minute: 'numeric',
second: this.displaySeconds ? 'numeric' : undefined,
};
const now = new Date();
setTime(now.toLocaleTimeString('en', timeFormat));
};
this.updateTime = updateTime;
onMount(() => {
this.interval = setInterval(updateTime, 1000);
});
render(
() => (
<>
{time()}
>
),
this.clockContainer,
);
const menu = document.querySelector('.center-content');
menu?.append(this.clockContainer);
},
onConfigChange(newConfig) {
this.displaySeconds = newConfig.displaySeconds;
this.hour12 = newConfig.hour12;
this.updateTime();
},
stop() {
this.clockContainer.remove();
this.clockContainer.replaceChildren();
if (this.interval) {
clearInterval(this.interval);
}
},
},
});
================================================
FILE: src/plugins/clock/style.css
================================================
.clock {
position: absolute;
left: 50%;
transform: translateX(-50%);
align-self: center;
}
================================================
FILE: src/plugins/clock/types.ts
================================================
export type ClockPluginConfig = {
enabled: boolean;
displaySeconds: boolean;
hour12: boolean;
};
================================================
FILE: src/plugins/compact-sidebar/index.ts
================================================
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export default createPlugin<
unknown,
unknown,
{
getCompactSidebar: () => HTMLElement | null;
isCompactSidebarDisabled: () => boolean;
}
>({
name: () => t('plugins.compact-sidebar.name'),
description: () => t('plugins.compact-sidebar.description'),
restartNeeded: false,
config: {
enabled: false,
},
renderer: {
getCompactSidebar: () => document.querySelector('#mini-guide'),
isCompactSidebarDisabled() {
const compactSidebar = this.getCompactSidebar();
return (
compactSidebar === null ||
window.getComputedStyle(compactSidebar).display === 'none'
);
},
start() {
if (this.isCompactSidebarDisabled()) {
document.querySelector('#button')?.click();
}
},
stop() {
if (this.isCompactSidebarDisabled()) {
document.querySelector('#button')?.click();
}
},
onConfigChange() {
if (this.isCompactSidebarDisabled()) {
document.querySelector('#button')?.click();
}
},
},
});
================================================
FILE: src/plugins/crossfade/fader.ts
================================================
/**
* VolumeFader
* Sophisticated Media Volume Fading
*
* Requires browser support for:
* - HTMLMediaElement
* - requestAnimationFrame()
* - ES6
*
* Does not depend on any third-party library.
*
* License: MIT
*
* Nick Schwarzenberg
* v0.2.0, 07/2016
*/
// Internal utility: check if value is a valid volume level and throw if not
const validateVolumeLevel = (value: number) => {
// Number between 0 and 1?
if (!Number.isNaN(value) && value >= 0 && value <= 1) {
// Yup, that's fine
} else {
// Abort and throw an exception
throw new TypeError('Number between 0 and 1 expected as volume!');
}
};
type VolumeLogger = (
message: string,
...args: Params
) => void;
interface VolumeFaderOptions {
/**
* logging `function(stuff, …)` for execution information (default: no logging)
*/
logger?: VolumeLogger;
/**
* either 'linear', 'logarithmic' or a positive number in dB (default: logarithmic)
*/
fadeScaling?: string | number;
/**
* media volume 0…1 to apply during setup (volume not touched by default)
*/
initialVolume?: number;
/**
* time in milliseconds to complete a fade (default: 1000 ms)
*/
fadeDuration?: number;
}
interface VolumeFade {
volume: {
start: number;
end: number;
};
time: {
start: number;
end: number;
};
callback?: () => void;
}
// Main class
export class VolumeFader {
private readonly media: HTMLMediaElement;
private readonly logger: VolumeLogger | null;
private scale: {
internalToVolume: (level: number) => number;
volumeToInternal: (level: number) => number;
};
private fadeDuration: number = 1000;
private active: boolean = false;
private fade: VolumeFade | undefined;
/**
* VolumeFader Constructor
*
* @param media {HTMLMediaElement} - audio or video element to be controlled
* @param options {Object} - an object with optional settings
* @throws {TypeError} if options.initialVolume or options.fadeDuration are invalid
*
*/
constructor(media: HTMLMediaElement, options: VolumeFaderOptions) {
// Passed media element of correct type?
if (media instanceof HTMLMediaElement) {
// Save reference to media element
this.media = media;
} else {
// Abort and throw an exception
throw new TypeError('Media element expected!');
}
// Make sure options is an object
options = options || {};
// Log function passed?
if (typeof options.logger === 'function') {
// Set log function to the one specified
this.logger = options.logger;
} else {
// Set log function explicitly to false
this.logger = null;
}
// Linear volume fading?
if (options.fadeScaling === 'linear') {
// Pass levels unchanged
this.scale = {
internalToVolume: (level: number) => level,
volumeToInternal: (level: number) => level,
};
// Log setting
this.logger?.('Using linear fading.');
}
// No linear, but logarithmic fading…
else {
let dynamicRange: number;
// Default dynamic range?
if (
options.fadeScaling === undefined ||
options.fadeScaling === 'logarithmic'
) {
// Set default of 60 dB
dynamicRange = 3;
}
// Custom dynamic range?
else if (
typeof options.fadeScaling === 'number' &&
!Number.isNaN(options.fadeScaling) &&
options.fadeScaling > 0
) {
// Turn amplitude dB into a multiple of 10 power dB
dynamicRange = options.fadeScaling / 2 / 10;
}
// Unsupported value
else {
// Abort and throw exception
throw new TypeError(
"Expected 'linear', 'logarithmic' or a positive number as fade scaling preference!",
);
}
// Use exponential/logarithmic scaler for expansion/compression
this.scale = {
internalToVolume: (level: number) =>
this.exponentialScaler(level, dynamicRange),
volumeToInternal: (level: number) =>
this.logarithmicScaler(level, dynamicRange),
};
// Log setting if not default
if (options.fadeScaling)
this.logger?.(
'Using logarithmic fading with ' +
String(10 * dynamicRange) +
' dB dynamic range.',
);
}
// Set initial volume?
if (options.initialVolume !== undefined) {
// Validate volume level and throw if invalid
validateVolumeLevel(options.initialVolume);
// Set initial volume
this.media.volume = options.initialVolume;
// Log setting
this.logger?.('Set initial volume to ' + String(this.media.volume) + '.');
}
// Fade duration given?
if (options.fadeDuration === undefined) {
// Set default fade duration (1000 ms)
this.fadeDuration = 1000;
} else {
// Try to set given fade duration (will log if successful and throw if not)
this.setFadeDuration(options.fadeDuration);
}
// Indicate that fader is not active yet
this.active = false;
// Initialization done
this.logger?.('Initialized for', this.media);
}
/**
* Re(start) the update cycle.
* (this.active must be truthy for volume updates to take effect)
*
* @return {Object} VolumeFader instance for chaining
*/
start() {
// Set fader to be active
this.active = true;
// Start by running the update method
this.updateVolume();
// Return instance for chaining
return this;
}
/**
* Stop the update cycle.
* (interrupting any fade)
*
* @return {Object} VolumeFader instance for chaining
*/
stop() {
// Set fader to be inactive
this.active = false;
// Return instance for chaining
return this;
}
/**
* Set fade duration.
* (used for future calls to fadeTo)
*
* @param {Number} fadeDuration - fading length in milliseconds
* @throws {TypeError} if fadeDuration is not a number greater than zero
* @return {Object} VolumeFader instance for chaining
*/
setFadeDuration(fadeDuration: number) {
// If duration is a valid number > 0…
if (!Number.isNaN(fadeDuration) && fadeDuration > 0) {
// Set fade duration
this.fadeDuration = fadeDuration;
// Log setting
this.logger?.('Set fade duration to ' + String(fadeDuration) + ' ms.');
} else {
// Abort and throw an exception
throw new TypeError('Positive number expected as fade duration!');
}
// Return instance for chaining
return this;
}
/**
* Define a new fade and start fading.
*
* @param {Number} targetVolume - level to fade to in the range 0…1
* @param {Function} callback - (optional) function to be called when fade is complete
* @throws {TypeError} if targetVolume is not in the range 0…1
* @return {Object} VolumeFader instance for chaining
*/
fadeTo(targetVolume: number, callback?: () => void) {
// Validate volume and throw if invalid
validateVolumeLevel(targetVolume);
// Define new fade
this.fade = {
// Volume start and end point on internal fading scale
volume: {
start: this.scale.volumeToInternal(this.media.volume),
end: this.scale.volumeToInternal(targetVolume),
},
// Time start and end point
time: {
start: Date.now(),
end: Date.now() + this.fadeDuration,
},
// Optional callback function
callback,
};
// Start fading
this.start();
// Log new fade
this.logger?.('New fade started:', this.fade);
// Return instance for chaining
return this;
}
// Convenience shorthand methods for common fades
fadeIn(callback: () => void) {
this.fadeTo(1, callback);
}
fadeOut(callback: () => void) {
this.fadeTo(0, callback);
}
/**
* Internal: Update media volume.
* (calls itself through requestAnimationFrame)
*/
updateVolume() {
// Fader active and fade available to process?
if (this.active && this.fade) {
// Get current time
const now = Date.now();
// Time left for fading?
if (now < this.fade.time.end) {
// Compute current fade progress
const progress =
(now - this.fade.time.start) /
(this.fade.time.end - this.fade.time.start);
// Compute current level on internal scale
const level =
progress * (this.fade.volume.end - this.fade.volume.start) +
this.fade.volume.start;
// Map fade level to volume level and apply it to media element
this.media.volume = this.scale.internalToVolume(level);
// Schedule next update
window.requestAnimationFrame(this.updateVolume.bind(this));
} else {
// Log end of fade
this.logger?.('Fade to ' + String(this.fade.volume.end) + ' complete.');
// Time is up, jump to target volume
this.media.volume = this.scale.internalToVolume(this.fade.volume.end);
// Set fader to be inactive
this.active = false;
// Done, call back (if callable)
if (typeof this.fade.callback === 'function') this.fade.callback();
// Clear fade
this.fade = undefined;
}
}
}
/**
* Internal: Exponential scaler with dynamic range limit.
*
* @param {Number} input - logarithmic input level to be expanded (float, 0…1)
* @param {Number} dynamicRange - expanded output range, in multiples of 10 dB (float, 0…∞)
* @return {Number} - expanded level (float, 0…1)
*/
exponentialScaler(input: number, dynamicRange: number) {
// Special case: make zero (or any falsy input) return zero
if (input === 0) {
// Since the dynamic range is limited,
// allow a zero to produce a plain zero instead of a small faction
// (audio would not be recognized as silent otherwise)
return 0;
}
// Scale 0…1 to minus something × 10 dB
input = (input - 1) * dynamicRange;
// Compute power of 10
return 10 ** input;
}
/**
* Internal: Logarithmic scaler with dynamic range limit.
*
* @param {Number} input - exponential input level to be compressed (float, 0…1)
* @param {Number} dynamicRange - coerced input range, in multiples of 10 dB (float, 0…∞)
* @return {Number} - compressed level (float, 0…1)
*/
logarithmicScaler(input: number, dynamicRange: number) {
// Special case: make zero (or any falsy input) return zero
if (input === 0) {
// Logarithm of zero would be -∞, which would map to zero anyway
return 0;
}
// Compute base-10 logarithm
input = Math.log10(input);
// Scale minus something × 10 dB to 0…1 (clipping at 0)
return Math.max(1 + input / dynamicRange, 0);
}
}
export default {
VolumeFader,
};
================================================
FILE: src/plugins/crossfade/index.ts
================================================
import { Innertube } from '\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js';
import prompt from 'custom-electron-prompt';
import { Howl } from 'howler';
import promptOptions from '@/providers/prompt-options';
import { getNetFetchAsFetch } from '@/plugins/utils/main';
import { createPlugin } from '@/utils';
import { VolumeFader } from './fader';
import { t } from '@/i18n';
import type { BrowserWindow } from 'electron';
import type { RendererContext } from '@/types/contexts';
export type CrossfadePluginConfig = {
enabled: boolean;
fadeInDuration: number;
fadeOutDuration: number;
secondsBeforeEnd: number;
fadeScaling: 'linear' | 'logarithmic' | number;
};
export default createPlugin<
unknown,
unknown,
{
config?: CrossfadePluginConfig;
ipc?: RendererContext['ipc'];
},
CrossfadePluginConfig
>({
name: () => t('plugins.crossfade.name'),
description: () => t('plugins.crossfade.description'),
restartNeeded: true,
config: {
enabled: false,
/**
* The duration of the fade in and fade out in milliseconds.
*
* @default 1500ms
*/
fadeInDuration: 1500,
/**
* The duration of the fade in and fade out in milliseconds.
*
* @default 5000ms
*/
fadeOutDuration: 5000,
/**
* The duration of the fade in and fade out in seconds.
*
* @default 10s
*/
secondsBeforeEnd: 10,
/**
* The scaling algorithm to use for the fade.
* (or a positive number in dB)
*
* @default 'linear'
*/
fadeScaling: 'linear',
},
menu({ window, getConfig, setConfig }) {
const promptCrossfadeValues = async (
win: BrowserWindow,
options: CrossfadePluginConfig,
): Promise | undefined> => {
const res = await prompt(
{
title: t('plugins.crossfade.prompt.options'),
type: 'multiInput',
multiInputOptions: [
{
label: t(
'plugins.crossfade.prompt.options.multi-input.fade-in-duration',
),
value: options.fadeInDuration,
inputAttrs: {
type: 'number',
required: true,
min: '0',
step: '100',
},
},
{
label: t(
'plugins.crossfade.prompt.options.multi-input.fade-out-duration',
),
value: options.fadeOutDuration,
inputAttrs: {
type: 'number',
required: true,
min: '0',
step: '100',
},
},
{
label: t(
'plugins.crossfade.prompt.options.multi-input.seconds-before-end',
),
value: options.secondsBeforeEnd,
inputAttrs: {
type: 'number',
required: true,
min: '0',
},
},
{
label: t(
'plugins.crossfade.prompt.options.multi-input.fade-scaling.label',
),
selectOptions: {
linear: t(
'plugins.crossfade.prompt.options.multi-input.fade-scaling.linear',
),
logarithmic: t(
'plugins.crossfade.prompt.options.multi-input.fade-scaling.logarithmic',
),
},
value: options.fadeScaling,
},
],
resizable: true,
height: 360,
...promptOptions(),
},
win,
).catch(console.error);
if (!res) {
return undefined;
}
let fadeScaling: 'linear' | 'logarithmic' | number;
if (res[3] === 'linear' || res[3] === 'logarithmic') {
fadeScaling = res[3];
} else if (isFinite(Number(res[3]))) {
fadeScaling = Number(res[3]);
} else {
fadeScaling = options.fadeScaling;
}
return {
fadeInDuration: Number(res[0]),
fadeOutDuration: Number(res[1]),
secondsBeforeEnd: Number(res[2]),
fadeScaling,
};
};
return [
{
label: t('plugins.crossfade.menu.advanced'),
async click() {
const newOptions = await promptCrossfadeValues(
window,
await getConfig(),
);
if (newOptions) {
setConfig(newOptions);
}
},
},
];
},
async backend({ ipc }) {
const yt = await Innertube.create({
fetch: getNetFetchAsFetch(),
});
ipc.handle('audio-url', async (videoID: string) => {
const info = await yt.getBasicInfo(videoID);
return info.streaming_data?.formats[0].decipher(yt.session.player);
});
},
renderer: {
async start({ ipc, getConfig }) {
this.config = await getConfig();
this.ipc = ipc;
},
onConfigChange(newConfig) {
this.config = newConfig;
},
onPlayerApiReady() {
let transitionAudio: Howl; // Howler audio used to fade out the current music
let firstVideo = true;
let waitForTransition: Promise;
const getStreamURL = async (videoID: string): Promise =>
this.ipc?.invoke('audio-url', videoID) as Promise;
const getVideoIDFromURL = (url: string) =>
new URLSearchParams(url.split('?')?.at(-1)).get('v');
const isReadyToCrossfade = () =>
transitionAudio && transitionAudio.state() === 'loaded';
const watchVideoIDChanges = (cb: (id: string) => void) => {
window.navigation.addEventListener('navigate', (event) => {
const currentVideoID = getVideoIDFromURL(
(event.currentTarget as Navigation).currentEntry?.url ?? '',
);
const nextVideoID = getVideoIDFromURL(event.destination.url ?? '');
if (
nextVideoID &&
currentVideoID &&
(firstVideo || nextVideoID !== currentVideoID)
) {
if (isReadyToCrossfade()) {
crossfade(() => {
cb(nextVideoID);
});
} else {
cb(nextVideoID);
firstVideo = false;
}
}
});
};
const createAudioForCrossfade = (url: string) => {
if (transitionAudio) {
transitionAudio.unload();
}
transitionAudio = new Howl({
src: url,
html5: true,
volume: 0,
});
syncVideoWithTransitionAudio();
};
const syncVideoWithTransitionAudio = () => {
const video = document.querySelector('video')!;
const videoFader = new VolumeFader(video, {
fadeScaling: this.config?.fadeScaling,
fadeDuration: this.config?.fadeInDuration,
});
transitionAudio.play();
transitionAudio.seek(video.currentTime);
video.addEventListener('seeking', () => {
transitionAudio.seek(video.currentTime);
});
video.addEventListener('pause', () => {
transitionAudio.pause();
});
video.addEventListener('play', () => {
transitionAudio.play();
transitionAudio.seek(video.currentTime);
// Fade in
const videoVolume = video.volume;
video.volume = 0;
videoFader.fadeTo(videoVolume);
});
// Exit just before the end for the transition
const transitionBeforeEnd = () => {
if (
video.currentTime >=
video.duration - (this.config?.secondsBeforeEnd ?? 0) &&
isReadyToCrossfade()
) {
video.removeEventListener('timeupdate', transitionBeforeEnd);
// Go to next video - XXX: does not support "repeat 1" mode
document.querySelector('.next-button')?.click();
}
};
video.addEventListener('timeupdate', transitionBeforeEnd);
};
const crossfade = (cb: () => void) => {
if (!isReadyToCrossfade()) {
cb();
return;
}
let resolveTransition: () => void;
waitForTransition = new Promise((resolve) => {
resolveTransition = resolve;
});
const video = document.querySelector('video')!;
const fader = new VolumeFader(transitionAudio._sounds[0]._node, {
initialVolume: video.volume,
fadeScaling: this.config?.fadeScaling,
fadeDuration: this.config?.fadeOutDuration,
});
// Fade out the music
video.volume = 0;
fader.fadeOut(() => {
resolveTransition();
cb();
});
};
watchVideoIDChanges(async (videoID) => {
await waitForTransition;
const url = await getStreamURL(videoID);
if (!url) {
return;
}
createAudioForCrossfade(url);
});
},
},
});
================================================
FILE: src/plugins/custom-output-device/index.ts
================================================
import prompt from 'custom-electron-prompt';
import { t } from '@/i18n';
import promptOptions from '@/providers/prompt-options';
import { createPlugin } from '@/utils';
import { renderer } from './renderer';
export interface CustomOutputPluginConfig {
enabled: boolean;
output: string;
devices: Record;
}
export default createPlugin({
name: () => t('plugins.custom-output-device.name'),
description: () => t('plugins.custom-output-device.description'),
restartNeeded: true,
config: {
enabled: false,
output: 'default',
devices: {},
} as CustomOutputPluginConfig,
menu: ({ setConfig, getConfig, window }) => {
const promptDeviceSelector = async () => {
const options = await getConfig();
const response = await prompt(
{
title: t('plugins.custom-output-device.prompt.device-selector.title'),
label: t('plugins.custom-output-device.prompt.device-selector.label'),
value: options.output || 'default',
type: 'select',
selectOptions: options.devices,
width: 500,
...promptOptions(),
},
window,
).catch(console.error);
if (!response) return;
options.output = response;
setConfig(options);
};
return [
{
label: t('plugins.custom-output-device.menu.device-selector'),
click: promptDeviceSelector,
},
];
},
renderer,
});
================================================
FILE: src/plugins/custom-output-device/renderer.ts
================================================
import { createRenderer } from '@/utils';
import type { MusicPlayer } from '@/types/music-player';
import type { RendererContext } from '@/types/contexts';
import type { CustomOutputPluginConfig } from './index';
const updateDeviceList = async (
context: RendererContext,
) => {
const newDevices: Record = {};
const devices = await navigator.mediaDevices
.enumerateDevices()
.then((devices) =>
devices.filter((device) => device.kind === 'audiooutput'),
);
for (const device of devices) {
newDevices[device.deviceId] = device.label;
}
const options = await context.getConfig();
options.devices = newDevices;
context.setConfig(options);
};
const updateSinkId = async (
audioContext?: AudioContext & {
setSinkId?: (sinkId: string) => Promise;
},
sinkId?: string,
) => {
if (!audioContext || !sinkId) return;
if (!('setSinkId' in audioContext)) return;
if (typeof audioContext.setSinkId === 'function') {
await audioContext.setSinkId(sinkId);
}
};
export const renderer = createRenderer<
{
options?: CustomOutputPluginConfig;
audioContext?: AudioContext;
audioCanPlayHandler: (event: CustomEvent) => Promise;
},
CustomOutputPluginConfig
>({
async audioCanPlayHandler({ detail: { audioContext } }) {
this.audioContext = audioContext;
await updateSinkId(audioContext, this.options!.output);
},
async onPlayerApiReady(_: MusicPlayer, context) {
this.options = await context.getConfig();
await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
navigator.mediaDevices.ondevicechange = async () =>
await updateDeviceList(context);
document.addEventListener('peard:audio-can-play', this.audioCanPlayHandler, {
once: true,
passive: true,
});
await updateDeviceList(context);
},
stop() {
document.removeEventListener(
'peard:audio-can-play',
this.audioCanPlayHandler,
);
navigator.mediaDevices.ondevicechange = null;
},
async onConfigChange(config) {
this.options = config;
await updateSinkId(this.audioContext, config.output);
},
});
================================================
FILE: src/plugins/disable-autoplay/index.ts
================================================
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import type { VideoDataChanged } from '@/types/video-data-changed';
import type { MusicPlayer } from '@/types/music-player';
export type DisableAutoPlayPluginConfig = {
enabled: boolean;
applyOnce: boolean;
};
export default createPlugin<
unknown,
unknown,
{
config: DisableAutoPlayPluginConfig | null;
api: MusicPlayer | null;
eventListener: (event: CustomEvent) => void;
timeUpdateListener: (e: Event) => void;
},
DisableAutoPlayPluginConfig
>({
name: () => t('plugins.disable-autoplay.name'),
description: () => t('plugins.disable-autoplay.description'),
restartNeeded: false,
config: {
enabled: false,
applyOnce: false,
},
menu: async ({ getConfig, setConfig }) => {
const config = await getConfig();
return [
{
label: t('plugins.disable-autoplay.menu.apply-once'),
type: 'checkbox',
checked: config.applyOnce,
async click() {
const nowConfig = await getConfig();
setConfig({
applyOnce: !nowConfig.applyOnce,
});
},
},
];
},
renderer: {
config: null,
api: null,
eventListener(event: CustomEvent) {
if (this.config?.applyOnce) {
document.removeEventListener('videodatachange', this.eventListener);
}
if (event.detail.name === 'dataloaded') {
this.api?.pauseVideo();
document
.querySelector('video')
?.addEventListener('timeupdate', this.timeUpdateListener, {
once: true,
});
}
},
timeUpdateListener(e: Event) {
if (e.target instanceof HTMLVideoElement) {
e.target.pause();
}
},
async start({ getConfig }) {
this.config = await getConfig();
},
onPlayerApiReady(api) {
this.api = api;
document.addEventListener('videodatachange', this.eventListener);
},
stop() {
document.removeEventListener('videodatachange', this.eventListener);
},
onConfigChange(newConfig) {
this.config = newConfig;
},
},
});
================================================
FILE: src/plugins/discord/constants.ts
================================================
/**
* Application ID registered by @th-ch/pear-desktop dev team
*/
export const clientId = '1177081335727267940';
/**
* Throttle time for progress updates in milliseconds
*/
export const PROGRESS_THROTTLE_MS = 15_000;
/**
* Time in milliseconds to wait before sending a time update
*/
export const TIME_UPDATE_DEBOUNCE_MS = 5000;
/**
* Filler character for padding short Hangul strings (Discord requires min 2 chars)
*/
export const HANGUL_FILLER = '\u3164';
/**
* Enum for keys used in TimerManager.
*/
export enum TimerKey {
ClearActivity = 'clearActivity', // Timer to clear activity when paused
UpdateTimeout = 'updateTimeout', // Timer for throttled activity updates
DiscordConnectRetry = 'discordConnectRetry', // Timer for Discord connection retries
}
================================================
FILE: src/plugins/discord/discord-service.ts
================================================
import { Client as DiscordClient } from '@xhayper/discord-rpc';
import { dev } from 'electron-is';
import { ActivityType } from 'discord-api-types/v10';
import { t } from '@/i18n';
import { LoggerPrefix } from '@/utils';
import { clientId, PROGRESS_THROTTLE_MS, TimerKey } from './constants';
import { TimerManager } from './timer-manager';
import {
buildDiscordButtons,
padHangulFields,
sanitizeActivityText,
isSeek,
} from './utils';
import type { DiscordPluginConfig } from './index';
import type { SongInfo } from '@/providers/song-info';
import type { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
// Public API definition for the Discord Service
export class DiscordService {
/**
* Discord RPC client instance.
*/
rpc!: DiscordClient;
/**
* Indicates if the service is ready to send activity updates.
*/
ready = false;
/**
* Indicates if the service should attempt to reconnect automatically.
*/
autoReconnect = true;
/**
* Cached song information from the last activity update.
*/
lastSongInfo?: SongInfo;
/**
* Timestamp of the last progress update sent to Discord.
*/
lastProgressUpdate = 0;
/**
* Plugin configuration.
*/
config?: DiscordPluginConfig;
refreshCallbacks: (() => void)[] = [];
timerManager = new TimerManager();
mainWindow: Electron.BrowserWindow;
/**
* Initializes the Discord service with configuration and main window reference.
* Sets up RPC event listeners.
* @param mainWindow - Electron BrowserWindow instance.
* @param config - Plugin configuration.
*/
constructor(
mainWindow: Electron.BrowserWindow,
config?: DiscordPluginConfig,
) {
this.config = config;
this.mainWindow = mainWindow;
this.autoReconnect = config?.autoReconnect ?? true; // Default autoReconnect to true
this.initializeRpc();
}
private initializeRpc() {
if (this.rpc) {
try {
this.rpc.destroy();
} catch {
// ignored
}
this.rpc.removeAllListeners();
}
this.rpc = new DiscordClient({ clientId });
this.rpc.on('connected', () => {
if (dev()) {
console.log(LoggerPrefix, t('plugins.discord.backend.connected'));
}
this.refreshCallbacks.forEach((cb) => cb());
});
this.rpc.on('ready', () => {
this.ready = true;
if (this.lastSongInfo && this.config) {
this.updateActivity(this.lastSongInfo);
}
});
this.rpc.on('disconnected', () => {
this.resetInfo();
if (this.autoReconnect) {
this.connectRecursive();
}
});
}
/**
* Builds the SetActivity payload for Discord Rich Presence.
* @param songInfo - Current song information.
* @param config - Plugin configuration.
* @returns The SetActivity object.
*/
private buildActivityInfo(
songInfo: SongInfo,
config: DiscordPluginConfig,
): SetActivity {
padHangulFields(songInfo);
const activityInfo: SetActivity = {
type: ActivityType.Listening,
statusDisplayType: config.statusDisplayType,
details: sanitizeActivityText(
songInfo.alternativeTitle ?? songInfo.title
), // Song title
detailsUrl: songInfo.url ?? undefined,
state: sanitizeActivityText(
songInfo.tags?.at(0) ?? songInfo.artist
), // Artist name
stateUrl: songInfo.artistUrl,
largeImageKey: songInfo.imageSrc ?? undefined,
largeImageText: songInfo.album
? sanitizeActivityText(songInfo.album)
: undefined,
buttons: buildDiscordButtons(config, songInfo),
};
// Handle paused state display
if (songInfo.isPaused) {
activityInfo.largeImageText = '⏸︎';
} else if (
!config.hideDurationLeft &&
songInfo.songDuration > 0 &&
typeof songInfo.elapsedSeconds === 'number'
) {
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
activityInfo.startTimestamp = Math.floor(songStartTime / 1000);
activityInfo.endTimestamp = Math.floor(
(songStartTime + songInfo.songDuration * 1000) / 1000,
);
}
return activityInfo;
}
/**
* Sets a timer to clear Discord activity if the music is paused for too long,
* based on the plugin configuration.
*/
private setActivityTimeout() {
this.timerManager.clear(TimerKey.ClearActivity); // Clear any existing timeout
if (
this.lastSongInfo?.isPaused === true && // Music must be paused
this.config?.activityTimeoutEnabled && // Timeout must be enabled in config
this.config?.activityTimeoutTime && // Timeout duration must be set
this.config.activityTimeoutTime > 0 // Timeout duration must be positive
) {
this.timerManager.set(
TimerKey.ClearActivity,
() => {
this.clearActivity();
},
this.config.activityTimeoutTime,
);
}
}
/**
* Resets the internal state (except config and mainWindow), clears timers, and logs disconnection.
*/
private resetInfo() {
this.ready = false;
this.lastSongInfo = undefined;
this.lastProgressUpdate = 0;
this.timerManager.clearAll();
if (dev()) {
console.log(LoggerPrefix, t('plugins.discord.backend.disconnected'));
}
}
/**
* Attempts to connect to Discord RPC after a delay, used for retries.
* @returns Promise that resolves on successful login or rejects on failure/cancellation.
*/
private connectWithRetry(): Promise {
return new Promise((resolve, reject) => {
this.timerManager.set(
TimerKey.DiscordConnectRetry,
() => {
// Stop retrying if auto-reconnect is disabled or already connected
if (!this.autoReconnect || this.rpc.isConnected) {
this.timerManager.clear(TimerKey.DiscordConnectRetry);
if (this.rpc.isConnected) resolve();
else
reject(
new Error('Auto-reconnect disabled or already connected.'),
);
return;
}
// Attempt to login
this.rpc
.login()
.then(() => {
this.timerManager.clear(TimerKey.DiscordConnectRetry); // Success, stop retrying
resolve();
})
.catch(() => {
this.initializeRpc();
this.connectRecursive();
});
},
5000, // 5-second delay before retrying
);
});
}
/**
* Recursively attempts to connect to Discord RPC if auto-reconnect is enabled and not connected.
*/
private connectRecursive = () => {
if (!this.autoReconnect || this.rpc.isConnected) {
this.timerManager.clear(TimerKey.DiscordConnectRetry);
return;
}
this.connectWithRetry();
};
/**
* Connects to Discord RPC. Shows an error dialog on failure if specified and not auto-reconnecting.
* @param showErrorDialog - Whether to show an error dialog on initial connection failure.
*/
connect(showErrorDialog = false): void {
if (this.rpc.isConnected) {
if (dev()) {
console.log(
LoggerPrefix,
t('plugins.discord.backend.already-connected'),
);
}
return;
}
if (!this.config) {
return;
}
this.ready = false;
this.timerManager.clear(TimerKey.DiscordConnectRetry);
this.rpc.login().catch(() => {
this.resetInfo();
if (this.autoReconnect) {
// For some reason @xhayper/discord-rpc leaves a dangling listener on connection failure
// so we destroy and recreate the RPC client before reconnecting.
this.initializeRpc();
this.connectRecursive();
} else if (showErrorDialog && this.mainWindow) {
// connection failed
}
});
}
/**
* Disconnects from Discord RPC, prevents auto-reconnection, and clears timers.
*/
disconnect(): void {
this.autoReconnect = false;
this.timerManager.clear(TimerKey.DiscordConnectRetry);
this.timerManager.clear(TimerKey.ClearActivity);
try {
this.rpc.removeAllListeners();
this.rpc.destroy();
} catch {
// ignored
}
this.resetInfo(); // Reset internal state
}
/**
* Updates the Discord Rich Presence based on the current song information.
* Handles throttling logic to avoid excessive updates.
* Detects changes in song, pause state, or seeks for immediate updates.
* @param songInfo - The current song information.
*/
updateActivity(songInfo: SongInfo): void {
if (!this.config) return;
if (!songInfo.title && !songInfo.artist) {
if (this.lastSongInfo?.videoId) {
this.clearActivity();
this.lastSongInfo = undefined;
}
return;
}
// Cache the latest song info
this.timerManager.clear(TimerKey.ClearActivity);
if (!this.rpc || !this.ready) {
// skip update if not ready
return;
}
const now = Date.now();
const elapsedSeconds = songInfo.elapsedSeconds ?? 0;
const songChanged = songInfo.videoId !== this.lastSongInfo?.videoId;
const pauseChanged = songInfo.isPaused !== this.lastSongInfo?.isPaused;
const seeked =
!songChanged &&
isSeek(this.lastSongInfo?.elapsedSeconds ?? 0, elapsedSeconds);
if (
(songChanged || pauseChanged || seeked) &&
this.lastSongInfo !== undefined
) {
this.timerManager.clear(TimerKey.UpdateTimeout);
const activityInfo = this.buildActivityInfo(songInfo, this.config);
this.rpc.user
?.setActivity(activityInfo)
.catch((err) =>
console.error(LoggerPrefix, 'Failed to set activity:', err),
);
this.lastSongInfo.videoId = songInfo.videoId;
this.lastSongInfo.isPaused = songInfo.isPaused ?? false;
this.lastSongInfo.elapsedSeconds = elapsedSeconds;
this.lastProgressUpdate = now;
this.setActivityTimeout();
} else if (now - this.lastProgressUpdate > PROGRESS_THROTTLE_MS) {
this.timerManager.clear(TimerKey.UpdateTimeout);
const activityInfo = this.buildActivityInfo(songInfo, this.config);
this.rpc.user
?.setActivity(activityInfo)
.catch((err) =>
console.error(LoggerPrefix, 'Failed to set throttled activity:', err),
);
this.lastProgressUpdate = now;
this.setActivityTimeout();
} else {
const remainingThrottle =
PROGRESS_THROTTLE_MS - (now - this.lastProgressUpdate);
const songInfoSnapshot = { ...songInfo };
this.timerManager.set(
TimerKey.UpdateTimeout,
() => {
if (
this.lastSongInfo?.videoId === songInfoSnapshot.videoId &&
this.lastSongInfo?.isPaused === songInfoSnapshot.isPaused &&
this.config
) {
const activityInfo = this.buildActivityInfo(
songInfoSnapshot,
this.config,
);
this.rpc.user?.setActivity(activityInfo);
this.lastProgressUpdate = Date.now();
this.lastSongInfo.elapsedSeconds =
songInfoSnapshot.elapsedSeconds ?? 0;
this.setActivityTimeout();
}
},
remainingThrottle,
);
}
this.lastSongInfo = { ...songInfo };
}
/**
* Clears the Discord Rich Presence activity.
*/
clearActivity(): void {
if (this.rpc.isConnected && this.ready) {
this.rpc.user?.clearActivity();
}
this.lastProgressUpdate = 0;
this.lastSongInfo = undefined;
this.timerManager.clear(TimerKey.ClearActivity);
this.timerManager.clear(TimerKey.UpdateTimeout);
}
/**
* Updates the configuration used by the service and re-evaluates activity/timeouts.
* @param newConfig - The new plugin configuration.
*/
onConfigChange(newConfig: DiscordPluginConfig): void {
this.config = newConfig;
this.autoReconnect = newConfig.autoReconnect ?? true;
if (this.lastSongInfo && this.ready && this.rpc.isConnected) {
this.updateActivity(this.lastSongInfo);
}
this.setActivityTimeout();
}
/**
* Registers a callback function to be called when the RPC connection status changes (connected/disconnected).
* @param cb - The callback function.
*/
registerRefreshCallback(cb: () => void): void {
this.refreshCallbacks.push(cb);
}
/**
* Checks if the Discord RPC client is currently connected and ready.
* @returns True if connected and ready, false otherwise.
*/
isConnected(): boolean {
// Consider both connection and readiness state
return this.rpc.isConnected && this.ready;
}
/**
* Cleans up resources: disconnects RPC, clears all timers, and clears callbacks.
* Should be called when the plugin stops or the application quits.
*/
cleanup(): void {
this.disconnect();
this.refreshCallbacks = [];
}
}
================================================
FILE: src/plugins/discord/index.ts
================================================
import { StatusDisplayType } from 'discord-api-types/v10';
import { createPlugin } from '@/utils';
import { backend } from './main';
import { onMenu } from './menu';
import { t } from '@/i18n';
export type DiscordPluginConfig = {
'enabled': boolean;
/**
* If enabled, will try to reconnect to discord every 5 seconds after disconnecting or failing to connect
*
* @default true
*/
'autoReconnect': boolean;
/**
* If enabled, the discord rich presence gets cleared when music paused after the time specified below
*/
'activityTimeoutEnabled': boolean;
/**
* The time in milliseconds after which the discord rich presence gets cleared when music paused
*
* @default 10 * 60 * 1000 (10 minutes)
*/
'activityTimeoutTime': number;
/**
* Add a "Play on $APPLICATION_NAME" button to rich presence
*/
'playOn\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063': boolean;
/**
* Hide the "View App On GitHub" button in the rich presence
*/
'hideGitHubButton': boolean;
/**
* Hide the "duration left" in the rich presence
*/
'hideDurationLeft': boolean;
/**
* Controls which field is displayed in the Discord status text
*/
'statusDisplayType': (typeof StatusDisplayType)[keyof typeof StatusDisplayType];
};
export default createPlugin({
name: () => t('plugins.discord.name'),
description: () => t('plugins.discord.description'),
restartNeeded: false,
config: {
'enabled': false,
'autoReconnect': true,
'activityTimeoutEnabled': true,
'activityTimeoutTime': 10 * 60 * 1000,
'playOn\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063': true,
'hideGitHubButton': false,
'hideDurationLeft': false,
'statusDisplayType': StatusDisplayType.Details,
} as DiscordPluginConfig,
menu: onMenu,
backend,
});
================================================
FILE: src/plugins/discord/main.ts
================================================
import { app } from 'electron';
import { registerCallback, SongInfoEvent } from '@/providers/song-info';
import { createBackend } from '@/utils';
import { DiscordService } from './discord-service';
import { TIME_UPDATE_DEBOUNCE_MS } from './constants';
import type { DiscordPluginConfig } from './index';
export let discordService = null as DiscordService | null;
export const backend = createBackend<
{
config?: DiscordPluginConfig;
lastTimeUpdateSent: number;
},
DiscordPluginConfig
>({
lastTimeUpdateSent: 0,
async start(ctx) {
// Get initial configuration from the context
const config = await ctx.getConfig();
discordService = new DiscordService(ctx.window, config);
if (config.enabled) {
ctx.window.once('ready-to-show', () => {
discordService?.connect(!config.autoReconnect);
registerCallback((songInfo, event) => {
if (!discordService?.isConnected()) return;
if (event !== SongInfoEvent.TimeChanged) {
discordService?.updateActivity(songInfo);
this.lastTimeUpdateSent = Date.now();
} else {
const now = Date.now();
if (now - this.lastTimeUpdateSent > TIME_UPDATE_DEBOUNCE_MS) {
discordService?.updateActivity(songInfo);
this.lastTimeUpdateSent = now; // Record the time of this debounced update
}
}
});
});
}
ctx.ipc.on('peard:player-api-loaded', () => {
ctx.ipc.send('peard:setup-time-changed-listener');
});
app.on('before-quit', () => {
discordService?.cleanup();
});
},
stop() {
discordService?.cleanup();
},
onConfigChange(newConfig) {
discordService?.onConfigChange(newConfig);
const currentlyConnected = discordService?.isConnected() ?? false;
if (newConfig.enabled && !currentlyConnected) {
discordService?.connect(!newConfig.autoReconnect);
} else if (!newConfig.enabled && currentlyConnected) {
discordService?.disconnect();
}
},
});
================================================
FILE: src/plugins/discord/menu.ts
================================================
import prompt from 'custom-electron-prompt';
import { StatusDisplayType } from 'discord-api-types/v10';
import { discordService } from './main';
import { singleton } from '@/providers/decorators';
import promptOptions from '@/providers/prompt-options';
import { setMenuOptions } from '@/config/plugins';
import { APPLICATION_NAME, t } from '@/i18n';
import type { MenuContext } from '@/types/contexts';
import type { DiscordPluginConfig } from './index';
import type { MenuTemplate } from '@/menu';
const registerRefreshOnce = singleton((refreshMenu: () => void) => {
discordService?.registerRefreshCallback(refreshMenu);
});
const DiscordStatusDisplayTypeLabels: Record = {
[StatusDisplayType.Name]:
'plugins.discord.menu.set-status-display-type.submenu.application',
[StatusDisplayType.State]:
'plugins.discord.menu.set-status-display-type.submenu.artist',
[StatusDisplayType.Details]:
'plugins.discord.menu.set-status-display-type.submenu.title',
};
export const onMenu = async ({
window,
getConfig,
setConfig,
refresh,
}: MenuContext): Promise => {
const config = await getConfig();
registerRefreshOnce(refresh);
return [
{
label: discordService?.isConnected()
? t('plugins.discord.menu.connected')
: t('plugins.discord.menu.disconnected'),
enabled: !discordService?.isConnected(),
click: () => discordService?.connect(true),
},
{
label: t('plugins.discord.menu.auto-reconnect'),
type: 'checkbox',
checked: config.autoReconnect,
click(item: Electron.MenuItem) {
setConfig({
autoReconnect: item.checked,
});
},
},
{
label: t('plugins.discord.menu.clear-activity'),
click: () => discordService?.clearActivity(),
},
{
label: t('plugins.discord.menu.clear-activity-after-timeout'),
type: 'checkbox',
checked: config.activityTimeoutEnabled,
click(item: Electron.MenuItem) {
setConfig({
activityTimeoutEnabled: item.checked,
});
},
},
{
label: t('plugins.discord.menu.play-on-application'),
type: 'checkbox',
checked:
config[
'playOn\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063'
],
click(item: Electron.MenuItem) {
setConfig({
'playOn\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063':
item.checked,
});
},
},
{
label: t('plugins.discord.menu.hide-github-button'),
type: 'checkbox',
checked: config.hideGitHubButton,
click(item: Electron.MenuItem) {
setConfig({
hideGitHubButton: item.checked,
});
},
},
{
label: t('plugins.discord.menu.hide-duration-left'),
type: 'checkbox',
checked: config.hideDurationLeft,
click(item: Electron.MenuItem) {
setConfig({
hideDurationLeft: item.checked,
});
},
},
{
label: t('plugins.discord.menu.set-inactivity-timeout'),
click: () => setInactivityTimeout(window, config),
},
{
label: t('plugins.discord.menu.set-status-display-type.label'),
submenu: Object.values(StatusDisplayType)
.filter(
(v) => typeof StatusDisplayType[v as StatusDisplayType] !== 'number',
)
.map((statusDisplayType) => ({
label: t(
DiscordStatusDisplayTypeLabels[
statusDisplayType as StatusDisplayType
],
{
applicationName: APPLICATION_NAME,
},
),
type: 'radio',
checked: config.statusDisplayType === statusDisplayType,
click() {
setConfig({
statusDisplayType: statusDisplayType as StatusDisplayType,
});
},
})),
},
];
};
async function setInactivityTimeout(
win: Electron.BrowserWindow,
options: DiscordPluginConfig,
) {
const output = await prompt(
{
title: t('plugins.discord.prompt.set-inactivity-timeout.title'),
label: t('plugins.discord.prompt.set-inactivity-timeout.label'),
value: String(Math.round((options.activityTimeoutTime ?? 0) / 1e3)),
type: 'counter',
counterOptions: { minimum: 0, multiFire: true },
width: 450,
...promptOptions(),
},
win,
);
if (output) {
options.activityTimeoutTime = Math.round(~~output * 1e3);
setMenuOptions('discord', options);
}
}
================================================
FILE: src/plugins/discord/timer-manager.ts
================================================
import type { TimerKey } from './constants';
/**
* Manages NodeJS Timers, ensuring only one timer exists per key.
*/
export class TimerManager {
timers = new Map();
/**
* Sets a timer for a given key, clearing any existing timer with the same key.
* @param key - The unique key for the timer (using TimerKey enum).
* @param fn - The function to execute after the delay.
* @param delay - The delay in milliseconds.
*/
set(key: TimerKey, fn: () => void, delay: number): void {
this.clear(key);
this.timers.set(key, setTimeout(fn, delay));
}
/**
* Clears the timer associated with the given key.
* @param key - The key of the timer to clear (using TimerKey enum).
*/
clear(key: TimerKey): void {
const timer = this.timers.get(key);
if (timer) {
clearTimeout(timer);
this.timers.delete(key);
}
}
/**
* Clears all managed timers.
*/
clearAll(): void {
for (const timer of this.timers.values()) {
clearTimeout(timer);
}
this.timers.clear();
}
}
================================================
FILE: src/plugins/discord/utils.ts
================================================
import { HANGUL_FILLER } from './constants';
import { APPLICATION_NAME } from '@/i18n';
import type { GatewayActivityButton } from 'discord-api-types/v10';
import type { SongInfo } from '@/providers/song-info';
import type { DiscordPluginConfig } from './index';
/**
* Truncates a string to a specified length, adding ellipsis if truncated.
* @param str - The string to truncate.
* @param length - The maximum allowed length.
* @returns The truncated string.
*/
export const truncateString = (str: string, length: number): string => {
if (str.length > length) {
return `${str.substring(0, length - 3)}...`;
}
return str;
};
/**
* Sanitizes a string for Discord Rich Presence activity, ensuring it meets length requirements.
* @param input - The string to sanitize.
* @param fallback - A fallback string to use if the input is empty or whitespace. Defaults to 'undefined'.
* @returns The sanitized string, compliant with Discord's requirements.
*/
export function sanitizeActivityText(input: string | undefined, fallback: string = 'undefined'): string {
const text = (input && input.trim()) ? input.trim() : fallback.trim();
let safeString = truncateString(text, 128);
if (safeString.length === 0) {
return fallback;
}
if (safeString.length < 2) {
safeString = safeString.padEnd(2, '⠀'); // change if necessary
}
return safeString;
}
/**
* Builds the array of buttons for the Discord Rich Presence activity.
* @param config - The plugin configuration.
* @param songInfo - The current song information.
* @returns An array of buttons or undefined if no buttons are configured.
*/
export const buildDiscordButtons = (
config: DiscordPluginConfig,
songInfo: SongInfo,
): GatewayActivityButton[] | undefined => {
const buttons: GatewayActivityButton[] = [];
if (
config[
'playOn\u0059\u006f\u0075\u0054\u0075\u0062\u0065\u004d\u0075\u0073\u0069\u0063'
] &&
songInfo.url
) {
buttons.push({
label: `Play on ${APPLICATION_NAME}`,
url: songInfo.url,
});
}
if (!config.hideGitHubButton) {
buttons.push({
label: 'View App On GitHub',
url: 'https://github.com/pear-devs/pear-desktop',
});
}
return buttons.length ? buttons : undefined;
};
/**
* Pads Hangul fields (title, artist, album) in SongInfo if they are less than 2 characters long.
* Discord requires fields to be at least 2 characters.
* @param songInfo - The song information object (will be mutated).
*/
export const padHangulFields = (songInfo: SongInfo): void => {
(['title', 'artist', 'album'] as const).forEach((key) => {
const value = songInfo[key];
if (typeof value === 'string' && value.length > 0 && value.length < 2) {
songInfo[key] = value + HANGUL_FILLER.repeat(2 - value.length);
}
});
};
/**
* Checks if the difference between two time values indicates a seek operation.
* @param oldSeconds - The previous elapsed time in seconds.
* @param newSeconds - The current elapsed time in seconds.
* @returns True if the time difference suggests a seek, false otherwise.
*/
export const isSeek = (oldSeconds: number, newSeconds: number): boolean => {
// Consider it a seek if the time difference is greater than 2 seconds
// (allowing for minor discrepancies in reporting)
return Math.abs(newSeconds - oldSeconds) > 2;
};
================================================
FILE: src/plugins/downloader/index.ts
================================================
import { DefaultPresetList, type Preset } from './types';
import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { onConfigChange, onMainLoad } from './main';
import { onPlayerApiReady, onRendererLoad } from './renderer';
import { onMenu } from './menu';
import { t } from '@/i18n';
export type DownloaderPluginConfig = {
enabled: boolean;
downloadFolder?: string;
downloadOnFinish?: {
enabled: boolean;
seconds: number;
percent: number;
mode: 'percent' | 'seconds';
folder?: string;
};
selectedPreset: string;
customPresetSetting: Preset;
skipExisting: boolean;
playlistMaxItems?: number;
};
export const defaultConfig: DownloaderPluginConfig = {
enabled: false,
downloadFolder: undefined,
downloadOnFinish: {
enabled: false,
seconds: 20,
percent: 10,
mode: 'seconds',
folder: undefined,
},
selectedPreset: 'mp3 (256kbps)', // Selected preset
customPresetSetting: DefaultPresetList['mp3 (256kbps)'], // Presets
skipExisting: false,
playlistMaxItems: undefined,
};
export default createPlugin({
name: () => t('plugins.downloader.name'),
description: () => t('plugins.downloader.description'),
restartNeeded: true,
config: defaultConfig,
stylesheets: [style],
menu: onMenu,
backend: {
start: onMainLoad,
onConfigChange,
},
renderer: {
start: onRendererLoad,
onPlayerApiReady,
},
});
================================================
FILE: src/plugins/downloader/main/index.ts
================================================
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { randomBytes } from 'node:crypto';
import { app, type BrowserWindow, dialog, ipcMain } from 'electron';
import {
Innertube,
UniversalCache,
Utils,
YTNodes,
Platform,
} from '\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js';
import is from 'electron-is';
import filenamify from 'filenamify';
import { Mutex } from 'async-mutex';
import * as NodeID3 from 'node-id3';
import { BG, type BgConfig } from 'bgutils-js';
import { lazy } from 'lazy-var';
import {
cropMaxWidth,
getFolder,
sendFeedback as sendFeedback_,
setBadge,
} from './utils';
import {
registerCallback,
cleanupName,
getImage,
MediaType,
type SongInfo,
SongInfoEvent,
} from '@/providers/song-info';
import { getNetFetchAsFetch } from '@/plugins/utils/main';
import { t } from '@/i18n';
import { DefaultPresetList, type Preset, VideoFormatList } from '../types';
import type { DownloaderPluginConfig } from '../index';
import type { BackendContext } from '@/types/contexts';
import type { GetPlayerResponse } from '@/types/get-player-response';
import type { FormatOptions } from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/types';
import type { VideoInfo } from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/parser/\u0079\u006f\u0075\u0074\u0075\u0062\u0065';
import type { PlayerErrorMessage } from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/parser/nodes';
import type {
TrackInfo,
Playlist,
} from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/parser/ytmusic';
type CustomSongInfo = SongInfo & { trackId?: string };
const ffmpeg = lazy(async () =>
(await import('@ffmpeg.wasm/main')).createFFmpeg({
log: false,
logger() {}, // Console.log,
progress() {}, // Console.log,
}),
);
const ffmpegMutex = new Mutex();
Platform.shim.eval = async (data: Types.BuildScriptResult, env: Record) => {
const properties = [];
if(env.n) {
properties.push(`n: exportedVars.nFunction("${env.n}")`)
}
if (env.sig) {
properties.push(`sig: exportedVars.sigFunction("${env.sig}")`)
}
const code = `${data.output}\nreturn { ${properties.join(', ')} }`;
return new Function(code)();
}
let yt: Innertube;
let win: BrowserWindow;
let playingUrl: string;
const isPremium = async () => {
// If signed out, it is understood as non-premium
const isSignedIn = (await win.webContents.executeJavaScript(
'!!yt.config_.LOGGED_IN',
)) as boolean;
if (!isSignedIn) return false;
// If signed in, check if the upgrade button is present
const upgradeBtnIconPathData = (await win.webContents.executeJavaScript(
'document.querySelector(\'iron-iconset-svg[name="yt-sys-icons"] #\u0079\u006f\u0075\u0074\u0075\u0062\u0065_music_monochrome\')?.firstChild?.getAttribute("d")?.substring(0, 15)',
)) as string | null;
// Fallback to non-premium if the icon is not found
if (!upgradeBtnIconPathData) return false;
const upgradeButton = `ytmusic-guide-entry-renderer:has(> tp-yt-paper-item > yt-icon path[d^="${upgradeBtnIconPathData}"])`;
return (await win.webContents.executeJavaScript(
`!document.querySelector('${upgradeButton}')`,
)) as boolean;
};
const sendError = (error: Error, source?: string) => {
win.setProgressBar(-1); // Close progress bar
setBadge(0); // Close badge
sendFeedback_(win); // Reset feedback
const songNameMessage = source ? `\nin ${source}` : '';
const cause = error.cause
? `\n\n${
// eslint-disable-next-line @typescript-eslint/no-base-to-string,@typescript-eslint/restrict-template-expressions
error.cause instanceof Error ? error.cause.toString() : error.cause
}`
: '';
const message = `${error.toString()}${songNameMessage}${cause}`;
console.error(message);
console.trace(error);
dialog.showMessageBox(win, {
type: 'info',
buttons: [t('plugins.downloader.backend.dialog.error.buttons.ok')],
title: t('plugins.downloader.backend.dialog.error.title'),
message: t('plugins.downloader.backend.dialog.error.message'),
detail: message,
});
};
export const getCookieFromWindow = async (win: BrowserWindow) => {
return (
await win.webContents.session.cookies.get({
url: 'https://music.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com',
})
)
.map((it) => it.name + '=' + it.value)
.join(';');
};
let config: DownloaderPluginConfig;
export const onMainLoad = async ({
window: _win,
getConfig,
ipc,
}: BackendContext) => {
win = _win;
config = await getConfig();
yt = await Innertube.create({
cache: new UniversalCache(false),
cookie: await getCookieFromWindow(win),
generate_session_locally: true,
fetch: getNetFetchAsFetch(),
});
const requestKey = 'O43z0dpjhgX20SCx4KAo';
const visitorData = yt.session.context.client.visitorData;
if (visitorData) {
const cleanUp = (context: Partial) => {
delete context.window;
delete context.document;
};
try {
const [width, height] = win.getSize();
// emulate jsdom using linkedom
const window = new (await import('happy-dom')).Window({
width,
height,
console,
});
const document = window.document;
Object.assign(globalThis, {
window,
document,
});
const bgConfig: BgConfig = {
fetch: getNetFetchAsFetch(),
globalObj: globalThis,
identifier: visitorData,
requestKey,
};
const bgChallenge = await BG.Challenge.create(bgConfig);
const interpreterJavascript =
bgChallenge?.interpreterJavascript
.privateDoNotAccessOrElseSafeScriptWrappedValue;
if (interpreterJavascript) {
// This is a workaround to run the interpreterJavascript code
// Maybe there is a better way to do this (e.g. https://github.com/Siubaak/sval ?)
// eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-call
new Function(interpreterJavascript)();
const poTokenResult = await BG.PoToken.generate({
program: bgChallenge.program,
globalName: bgChallenge.globalName,
bgConfig,
}).finally(() => {
cleanUp(globalThis);
});
yt.session.po_token = poTokenResult.poToken;
} else {
cleanUp(globalThis);
}
} catch {
cleanUp(globalThis);
}
}
ipc.handle('download-song', (url: string) => downloadSong(url));
ipc.on('peard:video-src-changed', (data: GetPlayerResponse) => {
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
});
ipc.handle('download-playlist-request', async (url: string) =>
downloadPlaylist(url),
);
downloadSongOnFinishSetup({ ipc, getConfig });
};
export const onConfigChange = (newConfig: DownloaderPluginConfig) => {
config = newConfig;
};
export async function downloadSong(
url: string,
playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => {},
) {
let resolvedName;
try {
await downloadSongUnsafe(
false,
url,
(name: string) => (resolvedName = name),
playlistFolder,
trackId,
increasePlaylistProgress,
);
} catch (error: unknown) {
sendError(error as Error, resolvedName || url);
}
}
export async function downloadSongFromId(
id: string,
playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => {},
) {
let resolvedName;
try {
await downloadSongUnsafe(
true,
id,
(name: string) => (resolvedName = name),
playlistFolder,
trackId,
increasePlaylistProgress,
);
} catch (error: unknown) {
sendError(error as Error, resolvedName || id);
}
}
function downloadSongOnFinishSetup({
ipc,
}: Pick, 'ipc' | 'getConfig'>) {
let currentUrl: string | undefined;
let duration: number | undefined;
let time = 0;
const defaultDownloadFolder = app.getPath('downloads');
registerCallback((songInfo: SongInfo, event) => {
if (event === SongInfoEvent.TimeChanged) {
const elapsedSeconds = songInfo.elapsedSeconds ?? 0;
if (elapsedSeconds > time) time = elapsedSeconds;
return;
}
if (
!songInfo.isPaused &&
songInfo.url !== currentUrl &&
config.downloadOnFinish?.enabled
) {
if (typeof currentUrl === 'string' && duration && duration > 0) {
if (
config.downloadOnFinish.mode === 'seconds' &&
duration - time <= config.downloadOnFinish.seconds
) {
downloadSong(
currentUrl,
config.downloadOnFinish.folder ??
config.downloadFolder ??
defaultDownloadFolder,
);
} else if (
config.downloadOnFinish.mode === 'percent' &&
time >= duration * (config.downloadOnFinish.percent / 100)
) {
downloadSong(
currentUrl,
config.downloadOnFinish.folder ??
config.downloadFolder ??
defaultDownloadFolder,
);
}
}
currentUrl = songInfo.url;
duration = songInfo.songDuration;
time = 0;
}
});
ipcMain.on('peard:player-api-loaded', () => {
ipc.send('peard:setup-time-changed-listener');
});
}
async function downloadSongUnsafe(
isId: boolean,
idOrUrl: string,
setName: (name: string) => void,
playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => {},
) {
const sendFeedback = (message: unknown, progress?: number) => {
if (!playlistFolder) {
sendFeedback_(win, message);
if (progress && !isNaN(progress)) {
win.setProgressBar(progress);
}
}
};
sendFeedback(t('plugins.downloader.backend.feedback.downloading'), 2);
let id: string | null;
if (isId) {
id = idOrUrl;
} else {
id = getVideoId(idOrUrl);
if (typeof id !== 'string')
throw new Error(
t('plugins.downloader.backend.feedback.video-id-not-found'),
);
}
let info: TrackInfo | VideoInfo = await yt.music.getInfo(id);
if (!info) {
throw new Error(
t('plugins.downloader.backend.feedback.video-id-not-found'),
);
}
const metadata = getMetadata(info);
if (metadata.album === 'N/A') {
metadata.album = '';
}
metadata.trackId = trackId;
const dir =
playlistFolder || config.downloadFolder || app.getPath('downloads');
const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${
metadata.title
}`;
setName(name);
let playabilityStatus = info.playability_status;
let bypassedResult = null;
if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
// Try to bypass the age restriction
bypassedResult = await getAndroidTvInfo(id);
playabilityStatus = bypassedResult.playability_status;
if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
throw new Error(
`[${playabilityStatus.status}] ${playabilityStatus.reason}`,
);
}
info = bypassedResult;
}
if (playabilityStatus?.status === 'UNPLAYABLE') {
const errorScreen =
playabilityStatus.error_screen as PlayerErrorMessage | null;
throw new Error(
`[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`,
);
}
const selectedPreset = config.selectedPreset ?? 'mp3 (256kbps)';
let presetSetting: Preset;
if (selectedPreset === 'Custom') {
presetSetting = config.customPresetSetting ?? DefaultPresetList['Custom'];
} else if (selectedPreset === 'Source') {
presetSetting = DefaultPresetList['Source'];
} else {
presetSetting = DefaultPresetList['mp3 (256kbps)'];
}
const downloadOptions: FormatOptions = {
type: (await isPremium()) ? 'audio' : 'video+audio', // Audio, video or video+audio
quality: 'best', // Best, bestefficiency, 144p, 240p, 480p, 720p and so on.
format: 'any', // Media container format
};
const format = info.chooseFormat(downloadOptions);
let targetFileExtension: string;
if (!presetSetting?.extension) {
targetFileExtension =
VideoFormatList.find((it) => it.itag === format.itag)?.container ??
'mp3';
} else {
targetFileExtension = presetSetting?.extension ?? 'mp3';
}
let filename = filenamify(`${name}.${targetFileExtension}`, {
replacement: '_',
maxLength: 255,
});
if (!is.macOS()) {
filename = filename.normalize('NFC');
}
const filePath = join(dir, filename);
if (config.skipExisting && existsSync(filePath)) {
sendFeedback(null, -1);
return;
}
const stream = await info.download(downloadOptions);
console.info(
t('plugins.downloader.backend.feedback.download-info', {
artist: metadata.artist,
title: metadata.title,
videoId: metadata.videoId,
}),
);
const iterableStream = Utils.streamToIterable(stream);
if (!existsSync(dir)) {
mkdirSync(dir);
}
let fileBuffer = await iterableStreamToProcessedUint8Array(
iterableStream,
targetFileExtension,
metadata,
presetSetting?.ffmpegArgs ?? [],
format.content_length ?? 0,
sendFeedback,
increasePlaylistProgress,
);
if (fileBuffer && targetFileExtension === 'mp3') {
fileBuffer = await writeID3(
Buffer.from(fileBuffer),
metadata,
sendFeedback,
);
}
if (fileBuffer) {
writeFileSync(filePath, fileBuffer);
}
sendFeedback(null, -1);
console.info(
t('plugins.downloader.backend.feedback.done', {
filePath,
}),
);
}
async function downloadChunks(
stream: AsyncGenerator,
contentLength: number,
sendFeedback: (str: string, value?: number) => void,
increasePlaylistProgress: (value: number) => void = () => {},
) {
const chunks = [];
let downloaded = 0;
for await (const chunk of stream) {
downloaded += chunk.length;
chunks.push(chunk);
const ratio = downloaded / contentLength;
const progress = Math.floor(ratio * 100);
sendFeedback(
t('plugins.downloader.backend.feedback.download-progress', {
percent: progress,
}),
ratio,
);
// 15% for download, 85% for conversion
// This is a very rough estimate, trying to make the progress bar look nice
increasePlaylistProgress(ratio * 0.15);
}
return chunks;
}
async function iterableStreamToProcessedUint8Array(
stream: AsyncGenerator,
extension: string,
metadata: CustomSongInfo,
presetFfmpegArgs: string[],
contentLength: number,
sendFeedback: (str: string, value?: number) => void,
increasePlaylistProgress: (value: number) => void = () => {},
): Promise {
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 {
const ffmpegInstance = await ffmpeg.get();
if (!ffmpegInstance.isLoaded()) {
await ffmpegInstance.load();
}
sendFeedback(t('plugins.downloader.backend.feedback.preparing-file'));
ffmpegInstance.FS(
'writeFile',
safeVideoName,
Buffer.concat(
await downloadChunks(
stream,
contentLength,
sendFeedback,
increasePlaylistProgress,
),
),
);
sendFeedback(t('plugins.downloader.backend.feedback.converting'));
ffmpegInstance.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 ffmpegInstance.run(
'-i',
safeVideoName,
...presetFfmpegArgs,
...getFFmpegMetadataArgs(metadata),
safeVideoNameWithExtension,
);
} finally {
ffmpegInstance.FS('unlink', safeVideoName);
}
sendFeedback(t('plugins.downloader.backend.feedback.saving'));
try {
return ffmpegInstance.FS('readFile', safeVideoNameWithExtension);
} finally {
ffmpegInstance.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,
metadata: CustomSongInfo,
sendFeedback: (str: string, value?: number) => void,
) {
try {
sendFeedback(t('plugins.downloader.backend.feedback.writing-id3'));
const tags: NodeID3.Tags = {};
// Create the metadata tags
tags.title = metadata.title;
tags.artist = metadata.artist;
if (metadata.album) {
tags.album = metadata.album;
}
const coverBuffer = await getCoverBuffer(metadata.imageSrc ?? '');
if (coverBuffer) {
tags.image = {
mime: 'image/png',
type: {
id: NodeID3.TagConstants.AttachedPicture.PictureType.FRONT_COVER,
},
description: 'thumbnail',
imageBuffer: coverBuffer,
};
}
if (metadata.trackId) {
tags.trackNumber = metadata.trackId;
}
return NodeID3.write(tags, buffer);
} catch (error: unknown) {
sendError(error as Error, `${metadata.artist} - ${metadata.title}`);
return null;
}
}
export async function downloadPlaylist(givenUrl?: string | URL) {
try {
givenUrl = new URL(givenUrl ?? '');
} catch {
givenUrl = new URL(win.webContents.getURL());
}
const playlistId =
getPlaylistID(givenUrl) || getPlaylistID(new URL(playingUrl));
if (!playlistId) {
sendError(
new Error(t('plugins.downloader.backend.feedback.playlist-id-not-found')),
);
return;
}
const sendFeedback = (message?: unknown) => sendFeedback_(win, message);
console.log(
t('plugins.downloader.backend.feedback.trying-to-get-playlist-id', {
playlistId,
}),
);
sendFeedback(t('plugins.downloader.backend.feedback.getting-playlist-info'));
let playlist: Playlist;
const items: YTNodes.MusicResponsiveListItem[] = [];
try {
playlist = await yt.music.getPlaylist(playlistId);
if (playlist?.items) {
const filteredItems = playlist.items.filter(
(item): item is YTNodes.MusicResponsiveListItem =>
item instanceof YTNodes.MusicResponsiveListItem,
);
items.push(...filteredItems);
}
} catch (error: unknown) {
sendError(
Error(
t('plugins.downloader.backend.feedback.playlist-is-mix-or-private', {
error: String(error),
}),
),
);
return;
}
if (!playlist || !playlist.items || playlist.items.length === 0) {
sendError(
new Error(t('plugins.downloader.backend.feedback.playlist-is-empty')),
);
return;
}
const normalPlaylistTitle =
playlist.header && 'title' in playlist.header
? playlist.header?.title?.text
: undefined;
const playlistTitle =
normalPlaylistTitle ??
playlist.page.contents_memo
?.get('MusicResponsiveListItemFlexColumn')
?.at(2)
?.as(YTNodes.MusicResponsiveListItemFlexColumn)?.title?.text ??
'NO_TITLE';
const isAlbum = !normalPlaylistTitle;
while (playlist.has_continuation) {
playlist = await playlist.getContinuation();
const filteredItems = playlist.items.filter(
(item): item is YTNodes.MusicResponsiveListItem =>
item instanceof YTNodes.MusicResponsiveListItem,
);
items.push(...filteredItems);
}
if (items.length === 1) {
sendFeedback(
t('plugins.downloader.backend.feedback.playlist-has-only-one-song'),
);
await downloadSongFromId(items.at(0)!.id!);
return;
}
let safePlaylistTitle = filenamify(playlistTitle, { replacement: ' ' });
if (!is.macOS()) {
safePlaylistTitle = safePlaylistTitle.normalize('NFC');
}
const folder = getFolder(config.downloadFolder ?? '');
const playlistFolder = join(folder, safePlaylistTitle);
if (existsSync(playlistFolder)) {
if (!config.skipExisting) {
sendError(
new Error(
t('plugins.downloader.backend.feedback.folder-already-exists', {
playlistFolder,
}),
),
);
return;
}
} else {
mkdirSync(playlistFolder, { recursive: true });
}
dialog.showMessageBox(win, {
type: 'info',
buttons: [
t('plugins.downloader.backend.dialog.start-download-playlist.buttons.ok'),
],
title: t('plugins.downloader.backend.dialog.start-download-playlist.title'),
message: t(
'plugins.downloader.backend.dialog.start-download-playlist.message',
{
playlistTitle,
},
),
detail: t(
'plugins.downloader.backend.dialog.start-download-playlist.detail',
{
playlistSize: items.length,
},
),
});
if (is.dev()) {
console.log(
t('plugins.downloader.backend.feedback.downloading-playlist', {
playlistTitle,
playlistSize: items.length,
playlistId,
}),
);
}
win.setProgressBar(2); // Starts with indefinite bar
setBadge(items.length);
let counter = 1;
const progressStep = 1 / items.length;
const increaseProgress = (itemPercentage: number) => {
const currentProgress = (counter - 1) / (items.length ?? 1);
const newProgress = currentProgress + progressStep * itemPercentage;
win.setProgressBar(newProgress);
};
try {
for (const song of items) {
sendFeedback(
t('plugins.downloader.backend.feedback.downloading-counter', {
current: counter,
total: items.length,
}),
);
const trackId = isAlbum ? counter : undefined;
await downloadSongFromId(
song.id!,
playlistFolder,
trackId?.toString(),
increaseProgress,
).catch((error) =>
sendError(
new Error(
t('plugins.downloader.backend.feedback.error-while-downloading', {
author: song.author!.name,
title: song.title!,
error: String(error),
}),
),
),
);
win.setProgressBar(counter / items.length);
setBadge(items.length - counter);
counter++;
}
} catch (error: unknown) {
sendError(error as Error);
} finally {
win.setProgressBar(-1); // Close progress bar
setBadge(0); // Close badge counter
sendFeedback(); // Clear feedback
}
}
function getFFmpegMetadataArgs(metadata: CustomSongInfo) {
if (!metadata) {
return [];
}
return [
...(metadata.title ? ['-metadata', `title=${metadata.title}`] : []),
...(metadata.artist ? ['-metadata', `artist=${metadata.artist}`] : []),
...(metadata.album ? ['-metadata', `album=${metadata.album}`] : []),
...(metadata.trackId ? ['-metadata', `track=${metadata.trackId}`] : []),
];
}
// Playlist radio modifier needs to be cut from playlist ID
const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
const getPlaylistID = (aURL?: URL): string | null | undefined => {
const result =
aURL?.searchParams.get('list') || aURL?.searchParams.get('playlist');
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
return result.slice(INVALID_PLAYLIST_MODIFIER.length);
}
return result;
};
const getVideoId = (url: URL | string): string | null => {
return new URL(url).searchParams.get('v');
};
const getMetadata = (info: TrackInfo): CustomSongInfo => ({
videoId: info.basic_info.id!,
title: cleanupName(info.basic_info.title!),
artist: cleanupName(info.basic_info.author!),
album: info.player_overlays?.browser_media_session?.as(
YTNodes.BrowserMediaSession,
).album?.text,
imageSrc: info.basic_info.thumbnail?.find((t) => !t.url.endsWith('.webp'))
?.url,
views: info.basic_info.view_count!,
songDuration: info.basic_info.duration!,
mediaType: MediaType.Audio,
});
// This is used to bypass age restrictions
const getAndroidTvInfo = async (id: string): Promise => {
// GetInfo 404s with the bypass, so we use getBasicInfo instead
// that's fine as we only need the streaming data
return await yt.getBasicInfo(id, {
client: 'TV_EMBEDDED',
});
};
================================================
FILE: src/plugins/downloader/main/utils.ts
================================================
import { app, type BrowserWindow } from 'electron';
import is from 'electron-is';
export const getFolder = (customFolder?: string) =>
customFolder ?? app.getPath('downloads');
export const sendFeedback = (win: BrowserWindow, message?: unknown) => {
win.webContents.send('downloader-feedback', message);
};
export const cropMaxWidth = (image: Electron.NativeImage) => {
const imageSize = image.getSize();
// Standard artwork width with margins from both sides is 280 + 720 + 280
if (imageSize.width === 1280 && imageSize.height === 720) {
return image.crop({
x: 280,
y: 0,
width: 720,
height: 720,
});
}
return image;
};
export const setBadge = (n: number) => {
if (is.linux() || is.macOS()) {
app.setBadgeCount(n);
}
};
================================================
FILE: src/plugins/downloader/menu.ts
================================================
import { dialog } from 'electron';
import prompt from 'custom-electron-prompt';
import { deepmerge } from 'deepmerge-ts';
import { downloadPlaylist } from './main';
import { getFolder } from './main/utils';
import { DefaultPresetList } from './types';
import { t } from '@/i18n';
import promptOptions from '@/providers/prompt-options';
import { type DownloaderPluginConfig, defaultConfig } from './index';
import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';
export const onMenu = async ({
getConfig,
setConfig,
}: MenuContext): Promise => {
const config = await getConfig();
return [
{
label: t('plugins.downloader.menu.download-finish-settings.label'),
type: 'submenu',
submenu: [
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.enabled',
),
type: 'checkbox',
checked: config.downloadOnFinish?.enabled ?? false,
click(item) {
setConfig({
downloadOnFinish: {
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
),
enabled: item.checked,
},
});
},
},
{
type: 'separator',
},
{
label: t('plugins.downloader.menu.choose-download-folder'),
click() {
const result = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory'],
defaultPath: getFolder(
config.downloadOnFinish?.folder ?? config.downloadFolder,
),
});
if (result) {
setConfig({
downloadOnFinish: {
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
),
folder: result[0],
},
});
}
},
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.mode',
),
type: 'submenu',
submenu: [
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.seconds',
),
type: 'radio',
checked: config.downloadOnFinish?.mode === 'seconds',
click() {
setConfig({
downloadOnFinish: {
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
),
mode: 'seconds',
},
});
},
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.percent',
),
type: 'radio',
checked: config.downloadOnFinish?.mode === 'percent',
click() {
setConfig({
downloadOnFinish: {
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
),
mode: 'percent',
},
});
},
},
],
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.advanced',
),
async click() {
const res = await prompt({
title: t(
'plugins.downloader.menu.download-finish-settings.prompt.title',
),
type: 'multiInput',
multiInputOptions: [
{
label: t(
'plugins.downloader.menu.download-finish-settings.prompt.last-seconds',
),
inputAttrs: {
type: 'number',
required: true,
min: '0',
step: '1',
},
value:
config.downloadOnFinish?.seconds ??
defaultConfig.downloadOnFinish!.seconds,
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.prompt.last-percent',
),
inputAttrs: {
type: 'number',
required: true,
min: '1',
max: '100',
step: '1',
},
value:
config.downloadOnFinish?.percent ??
defaultConfig.downloadOnFinish!.percent,
},
],
...promptOptions(),
height: 240,
resizable: true,
}).catch(console.error);
if (!res) {
return undefined;
}
setConfig({
downloadOnFinish: {
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
),
seconds: Number(res[0]),
percent: Number(res[1]),
},
});
return;
},
},
],
},
{
label: t('plugins.downloader.menu.download-playlist'),
click: () => downloadPlaylist(),
},
{
label: t('plugins.downloader.menu.choose-download-folder'),
click() {
const result = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory'],
defaultPath: getFolder(config.downloadFolder ?? ''),
});
if (result) {
setConfig({ downloadFolder: result[0] });
} // Else = user pressed cancel
},
},
{
label: t('plugins.downloader.menu.presets'),
submenu: Object.keys(DefaultPresetList).map((preset) => ({
label: preset,
type: 'radio',
checked: config.selectedPreset === preset,
click() {
setConfig({ selectedPreset: preset });
},
})),
},
{
label: t('plugins.downloader.menu.skip-existing'),
type: 'checkbox',
checked: config.skipExisting,
click(item) {
setConfig({ skipExisting: item.checked });
},
},
];
};
================================================
FILE: src/plugins/downloader/renderer.tsx
================================================
import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';
import { defaultConfig } from '@/config/defaults';
import { getSongMenu } from '@/providers/dom-elements';
import { getSongInfo } from '@/providers/song-info-front';
import { t } from '@/i18n';
import {
isAlbumOrPlaylist,
isMusicOrVideoTrack,
} from '@/plugins/utils/renderer/check';
import { DownloadButton } from './templates/download';
import type { RendererContext } from '@/types/contexts';
import type { DownloaderPluginConfig } from './index';
let download: () => void;
const [downloadButtonText, setDownloadButtonText] = createSignal('');
let buttonContainer: HTMLDivElement | null = null;
const menuObserver = new MutationObserver(() => {
const menu = getSongMenu();
if (
!menu ||
menu.contains(buttonContainer) ||
!(isMusicOrVideoTrack() || isAlbumOrPlaylist()) ||
!buttonContainer
) {
return;
}
menu.prepend(buttonContainer);
});
export const onRendererLoad = ({
ipc,
}: RendererContext) => {
download = () => {
const songMenu = getSongMenu();
let videoUrl = songMenu
?.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;
}
} else {
videoUrl = getSongInfo().url || window.location.href;
}
ipc.invoke('download-song', videoUrl);
};
ipc.on('downloader-feedback', (feedback: string) => {
const targetHtml = feedback || t('plugins.downloader.templates.button');
setDownloadButtonText(targetHtml);
});
};
export const onPlayerApiReady = () => {
setDownloadButtonText(t('plugins.downloader.templates.button'));
buttonContainer = document.createElement('div');
buttonContainer.classList.add(
'style-scope',
'menu-item',
'ytmusic-menu-popup-renderer',
);
buttonContainer.setAttribute('aria-disabled', 'false');
buttonContainer.setAttribute('aria-selected', 'false');
buttonContainer.setAttribute('role', 'option');
buttonContainer.setAttribute('tabindex', '-1');
render(
() => ,
buttonContainer,
);
menuObserver.observe(document.querySelector('ytmusic-popup-container')!, {
childList: true,
subtree: true,
});
};
================================================
FILE: src/plugins/downloader/style.css
================================================
.ytmd-menu-item {
display: var(--ytmusic-menu-item_-_display);
height: var(--ytmusic-menu-item_-_height);
align-items: var(--ytmusic-menu-item_-_align-items);
padding: var(--ytmusic-menu-item_-_padding);
cursor: pointer;
}
.ytmd-menu-item > .yt-simple-endpoint:hover {
background-color: var(--ytmusic-menu-item-hover-background-color);
}
.ytmd-menu-item {
flex: var(--ytmusic-menu-item-icon_-_flex);
margin: var(--ytmusic-menu-item-icon_-_margin);
fill: var(--ytmusic-menu-item-icon_-_fill);
stroke: var(--iron-icon-stroke-color, none);
width: var(--iron-icon-width, 24px);
height: var(--iron-icon-height, 24px);
animation: var(--iron-icon_-_animation);
}
================================================
FILE: src/plugins/downloader/templates/download.tsx
================================================
export const DownloadButton = (props: {
onClick: () => void;
text: string;
}) => (
);
================================================
FILE: src/plugins/downloader/types.ts
================================================
export interface Preset {
extension?: string | null;
ffmpegArgs: string[];
}
// Presets for FFmpeg
export const DefaultPresetList: Record = {
'mp3 (256kbps)': {
extension: 'mp3',
ffmpegArgs: ['-b:a', '256k'],
},
'Source': {
extension: undefined,
ffmpegArgs: ['-acodec', 'copy'],
},
'Custom': {
extension: null,
ffmpegArgs: [],
},
};
export interface VideoFormat {
itag: number;
container: string;
content: string;
resolution: string;
bitrate: string;
range: string;
vrOr3D: string;
}
// converted from https://gist.github.com/sidneys/7095afe4da4ae58694d128b1034e01e2
// and https://gist.github.com/MartinEesmaa/2f4b261cb90a47e9c41ba115a011a4aa
export const VideoFormatList: VideoFormat[] = [
{
itag: 5,
container: 'flv',
content: 'audio/video',
resolution: '240p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 6,
container: 'flv',
content: 'audio/video',
resolution: '270p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 17,
container: '3gp',
content: 'audio/video',
resolution: '144p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 18,
container: 'mp4',
content: 'audio/video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 22,
container: 'mp4',
content: 'audio/video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 34,
container: 'flv',
content: 'audio/video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 35,
container: 'flv',
content: 'audio/video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 36,
container: '3gp',
content: 'audio/video',
resolution: '180p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 37,
container: 'mp4',
content: 'audio/video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 38,
container: 'mp4',
content: 'audio/video',
resolution: '3072p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 43,
container: 'webm',
content: 'audio/video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 44,
container: 'webm',
content: 'audio/video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 45,
container: 'webm',
content: 'audio/video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 46,
container: 'webm',
content: 'audio/video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 82,
container: 'mp4',
content: 'audio/video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 83,
container: 'mp4',
content: 'audio/video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 84,
container: 'mp4',
content: 'audio/video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 85,
container: 'mp4',
content: 'audio/video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 91,
container: 'hls',
content: 'audio/video',
resolution: '144p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 92,
container: 'hls',
content: 'audio/video',
resolution: '240p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 93,
container: 'hls',
content: 'audio/video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 94,
container: 'hls',
content: 'audio/video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 95,
container: 'hls',
content: 'audio/video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 96,
container: 'hls',
content: 'audio/video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '-',
},
{
itag: 100,
container: 'webm',
content: 'audio/video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 101,
container: 'webm',
content: 'audio/video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 102,
container: 'webm',
content: 'audio/video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '3D',
},
{
itag: 132,
container: 'hls',
content: 'audio/video',
resolution: '240p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 133,
container: 'mp4',
content: 'video',
resolution: '240p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 134,
container: 'mp4',
content: 'video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 135,
container: 'mp4',
content: 'video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 136,
container: 'mp4',
content: 'video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 137,
container: 'mp4',
content: 'video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 138,
container: 'mp4',
content: 'video',
resolution: '2160p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 139,
container: 'm4a',
content: 'audio',
resolution: '-',
bitrate: '48k',
range: '-',
vrOr3D: '',
},
{
itag: 140,
container: 'm4a',
content: 'audio',
resolution: '-',
bitrate: '128k',
range: '-',
vrOr3D: '',
},
{
itag: 141,
container: 'm4a',
content: 'audio',
resolution: '-',
bitrate: '256k',
range: '-',
vrOr3D: '',
},
{
itag: 151,
container: 'hls',
content: 'audio/video',
resolution: '72p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 160,
container: 'mp4',
content: 'video',
resolution: '144p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 167,
container: 'webm',
content: 'video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 168,
container: 'webm',
content: 'video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 169,
container: 'webm',
content: 'video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 171,
container: 'webm',
content: 'audio',
resolution: '-',
bitrate: '128k',
range: '-',
vrOr3D: '',
},
{
itag: 218,
container: 'webm',
content: 'video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 219,
container: 'webm',
content: 'video',
resolution: '144p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 242,
container: 'webm',
content: 'video',
resolution: '240p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 243,
container: 'webm',
content: 'video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 244,
container: 'webm',
content: 'video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 245,
container: 'webm',
content: 'video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 246,
container: 'webm',
content: 'video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 247,
container: 'webm',
content: 'video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 248,
container: 'webm',
content: 'video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 249,
container: 'webm',
content: 'audio',
resolution: '-',
bitrate: '50k',
range: '-',
vrOr3D: '',
},
{
itag: 250,
container: 'webm',
content: 'audio',
resolution: '-',
bitrate: '70k',
range: '-',
vrOr3D: '',
},
{
itag: 251,
container: 'webm',
content: 'audio',
resolution: '-',
bitrate: '160k',
range: '-',
vrOr3D: '',
},
{
itag: 264,
container: 'mp4',
content: 'video',
resolution: '1440p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 266,
container: 'mp4',
content: 'video',
resolution: '2160p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 271,
container: 'webm',
content: 'video',
resolution: '1440p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 272,
container: 'webm',
content: 'video',
resolution: '4320p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 278,
container: 'webm',
content: 'video',
resolution: '144p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 298,
container: 'mp4',
content: 'video',
resolution: '720p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 299,
container: 'mp4',
content: 'video',
resolution: '1080p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 302,
container: 'webm',
content: 'video',
resolution: '720p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 303,
container: 'webm',
content: 'video',
resolution: '1080p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 308,
container: 'webm',
content: 'video',
resolution: '1440p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 313,
container: 'webm',
content: 'video',
resolution: '2160p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 315,
container: 'webm',
content: 'video',
resolution: '2160p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 330,
container: 'webm',
content: 'video',
resolution: '144p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 331,
container: 'webm',
content: 'video',
resolution: '240p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 332,
container: 'webm',
content: 'video',
resolution: '360p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 333,
container: 'webm',
content: 'video',
resolution: '480p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 334,
container: 'webm',
content: 'video',
resolution: '720p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 335,
container: 'webm',
content: 'video',
resolution: '1080p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 336,
container: 'webm',
content: 'video',
resolution: '1440p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 337,
container: 'webm',
content: 'video',
resolution: '2160p60',
bitrate: '-',
range: 'hdr',
vrOr3D: '',
},
{
itag: 272,
container: 'webm',
content: 'video',
resolution: '2880p/4320p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 399,
container: 'mp4',
content: 'video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 400,
container: 'mp4',
content: 'video',
resolution: '1440p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 401,
container: 'mp4',
content: 'video',
resolution: '2160p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 402,
container: 'mp4',
content: 'video',
resolution: '2880p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 571,
container: 'mp4',
content: 'video',
resolution: '3840p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 702,
container: 'mp4',
content: 'video',
resolution: '3840p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 571,
container: 'mp4',
content: 'video',
resolution: '3840p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 694,
container: 'mp4',
content: 'video',
resolution: '144p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 695,
container: 'mp4',
content: 'video',
resolution: '240p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 696,
container: 'mp4',
content: 'video',
resolution: '360p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 697,
container: 'mp4',
content: 'video',
resolution: '480p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 698,
container: 'mp4',
content: 'video',
resolution: '720p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 699,
container: 'mp4',
content: 'video',
resolution: '1080p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 700,
container: 'mp4',
content: 'video',
resolution: '1440p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 701,
container: 'mp4',
content: 'video',
resolution: '2160p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 702,
container: 'mp4',
content: 'video',
resolution: '3840p',
bitrate: '-',
range: '-',
vrOr3D: '',
},
// Audio formats
{
itag: 599,
container: 'mp4',
content: 'audio',
resolution: '-',
bitrate: '30k',
range: '-',
vrOr3D: '',
},
{
itag: 600,
container: 'webm',
content: 'audio',
resolution: '-',
bitrate: '35k',
range: '-',
vrOr3D: '',
},
{
itag: 774,
container: 'webm',
content: 'audio',
resolution: '-',
bitrate: '256k',
range: '-',
vrOr3D: '',
},
// Livestream formats
{
itag: 300,
container: 'ts',
content: 'audio/video',
resolution: '720p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
{
itag: 301,
container: 'ts',
content: 'audio/video',
resolution: '1080p60',
bitrate: '-',
range: '-',
vrOr3D: '',
},
];
================================================
FILE: src/plugins/equalizer/index.ts
================================================
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import {
defaultPresets,
presetConfigs,
type Preset,
type FilterConfig,
} from './presets';
import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';
export type EqualizerPluginConfig = {
enabled: boolean;
filters: FilterConfig[];
presets: { [preset in Preset]: boolean };
};
let appliedFilters: BiquadFilterNode[] = [];
export default createPlugin({
name: () => t('plugins.equalizer.name'),
description: () => t('plugins.equalizer.description'),
restartNeeded: false,
addedVersion: '3.7.X',
config: {
enabled: false,
filters: [],
presets: { 'bass-booster': false },
} as EqualizerPluginConfig,
menu: async ({
getConfig,
setConfig,
}: MenuContext): Promise => {
const config = await getConfig();
return [
{
label: t('plugins.equalizer.menu.presets.label'),
type: 'submenu',
submenu: defaultPresets.map((preset) => ({
label: t(`plugins.equalizer.menu.presets.list.${preset}`),
type: 'radio',
checked: config.presets[preset],
click() {
setConfig({
presets: { ...config.presets, [preset]: !config.presets[preset] },
});
},
})),
},
];
},
renderer: {
async start({ getConfig }) {
const config = await getConfig();
document.addEventListener(
'peard:audio-can-play',
({ detail: { audioSource, audioContext } }) => {
const filtersToApply = config.filters.concat(
defaultPresets
.filter((preset) => config.presets[preset])
.map((preset) => presetConfigs[preset]),
);
filtersToApply.forEach((filter) => {
const biquadFilter = audioContext.createBiquadFilter();
biquadFilter.type = filter.type;
biquadFilter.frequency.value = filter.frequency; // filter frequency in Hz
biquadFilter.Q.value = filter.Q;
biquadFilter.gain.value = filter.gain; // filter gain in dB
audioSource.connect(biquadFilter);
biquadFilter.connect(audioContext.destination);
appliedFilters.push(biquadFilter);
});
},
{ once: true, passive: true },
);
},
stop() {
appliedFilters.forEach((filter) => filter.disconnect());
appliedFilters = [];
},
},
});
================================================
FILE: src/plugins/equalizer/presets.ts
================================================
export const defaultPresets = ['bass-booster'] as const;
export type Preset = (typeof defaultPresets)[number];
export type FilterConfig = {
type: BiquadFilterType;
frequency: number;
Q: number;
gain: number;
};
export const presetConfigs: Record = {
'bass-booster': {
type: 'lowshelf',
frequency: 80,
Q: 100,
gain: 12.0,
},
};
================================================
FILE: src/plugins/exponential-volume/index.ts
================================================
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import type { MusicPlayer } from '@/types/music-player';
export default createPlugin({
name: () => t('plugins.exponential-volume.name'),
description: () => t('plugins.exponential-volume.description'),
restartNeeded: true,
config: {
enabled: false,
},
renderer: {
onPlayerApiReady(playerApi) {
const syncVolume = (playerApi: MusicPlayer) => {
if (playerApi.getPlayerState() === 3) {
setTimeout(() => syncVolume(playerApi), 0);
return;
}
playerApi.setVolume(playerApi.getVolume());
};
// "fix volume ratio 0.4" by Marco Pfeiffer
// https://bit.ly/4nMu2WF
// Manipulation exponent, higher value = lower volume
// 3 is the value used by pulseaudio, which Barteks2x figured out this gist here: https://gist.github.com/Barteks2x/a4e189a36a10c159bb1644ffca21c02a
// 0.05 (or 5%) is the lowest you can select in the UI which with an exponent of 3 becomes 0.000125 or 0.0125%
const EXPONENT = 3;
const storedOriginalVolumes = new WeakMap();
const propertyDescriptor = Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
'volume',
);
Object.defineProperty(HTMLMediaElement.prototype, 'volume', {
get(this: HTMLMediaElement) {
const lowVolume =
(propertyDescriptor?.get?.call(this) as number) ?? 0;
const calculatedOriginalVolume = lowVolume ** (1 / EXPONENT);
// The calculated value has some accuracy issues which can lead to problems for implementations that expect exact values.
// To avoid this, I'll store the unmodified volume to return it when read here.
// This mostly solves the issue, but the initial read has no stored value and the volume can also change though external influences.
// To avoid ill effects, I check if the stored volume is somewhere in the same range as the calculated volume.
const storedOriginalVolume = storedOriginalVolumes.get(this) ?? 0;
const storedDeviation = Math.abs(
storedOriginalVolume - calculatedOriginalVolume,
);
return storedDeviation < 0.01
? storedOriginalVolume
: calculatedOriginalVolume;
},
set(this: HTMLMediaElement, originalVolume: number) {
const lowVolume = originalVolume ** EXPONENT;
storedOriginalVolumes.set(this, originalVolume);
propertyDescriptor?.set?.call(this, lowVolume);
},
});
syncVolume(playerApi);
},
},
});
================================================
FILE: src/plugins/in-app-menu/constants.ts
================================================
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,
};
================================================
FILE: src/plugins/in-app-menu/index.ts
================================================
import titlebarStyle from './titlebar.css?inline';
import { createPlugin } from '@/utils';
import { onMainLoad } from './main';
import { onMenu } from './menu';
import { onConfigChange, onPlayerApiReady, onRendererLoad } from './renderer';
import { t } from '@/i18n';
import { defaultInAppMenuConfig } from './constants';
export default createPlugin({
name: () => t('plugins.in-app-menu.name'),
description: () => t('plugins.in-app-menu.description'),
restartNeeded: true,
config: defaultInAppMenuConfig,
stylesheets: [titlebarStyle],
menu: onMenu,
backend: onMainLoad,
renderer: {
start: onRendererLoad,
onPlayerApiReady,
onConfigChange,
},
});
================================================
FILE: src/plugins/in-app-menu/main.ts
================================================
import { register } from 'electron-localshortcut';
import {
BrowserWindow,
Menu,
type MenuItem,
ipcMain,
nativeImage,
type WebContents,
} from 'electron';
import type { BackendContext } from '@/types/contexts';
import type { InAppMenuConfig } from './constants';
export const onMainLoad = ({
window: win,
ipc: { handle, send },
}: BackendContext) => {
win.on('close', () => {
send('close-all-in-app-menu-panel');
});
win.once('ready-to-show', () => {
register(win, '`', () => {
send('toggle-in-app-menu');
});
});
handle('get-menu', () =>
JSON.parse(
JSON.stringify(
Menu.getApplicationMenu(),
(key: string, value: unknown) =>
key !== 'commandsMap' && key !== 'menu' ? value : undefined,
),
),
);
const getMenuItemById = (commandId: number): MenuItem | null => {
const menu = Menu.getApplicationMenu();
let target: MenuItem | null = null;
const stack = [...(menu?.items ?? [])];
while (stack.length > 0) {
const now = stack.shift();
now?.submenu?.items.forEach((item) => stack.push(item));
if (now?.commandId === commandId) {
target = now;
break;
}
}
return target;
};
ipcMain.handle('peard:menu-event', (event, commandId: number) => {
const target = getMenuItemById(commandId);
if (target)
(
target.click as (
args0: unknown,
args1: BrowserWindow | null,
args3: WebContents,
) => void
)(undefined, BrowserWindow.fromWebContents(event.sender), event.sender);
});
handle('get-menu-by-id', (commandId: number) => {
const result = getMenuItemById(commandId);
return JSON.parse(
JSON.stringify(result, (key: string, value: unknown) =>
key !== 'commandsMap' && key !== 'menu' ? value : undefined,
),
);
});
handle('window-is-maximized', () => win.isMaximized());
handle('window-close', () => win.close());
handle('window-minimize', () => win.minimize());
handle('window-maximize', () => win.maximize());
win.on('maximize', () => send('window-maximize'));
handle('window-unmaximize', () => win.unmaximize());
win.on('unmaximize', () => send('window-unmaximize'));
handle('image-path-to-data-url', (imagePath: string) => {
const nativeImageIcon = nativeImage.createFromPath(imagePath);
return nativeImageIcon?.toDataURL();
});
};
================================================
FILE: src/plugins/in-app-menu/menu.ts
================================================
import is from 'electron-is';
import { t } from '@/i18n';
import type { InAppMenuConfig } from './constants';
import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';
export const onMenu = async ({
getConfig,
setConfig,
}: MenuContext): Promise => {
const config = await getConfig();
if (is.linux()) {
return [
{
label: t('plugins.in-app-menu.menu.hide-dom-window-controls'),
type: 'checkbox',
checked: config.hideDOMWindowControls,
click(item) {
config.hideDOMWindowControls = item.checked;
setConfig(config);
},
},
];
}
return [];
};
================================================
FILE: src/plugins/in-app-menu/renderer/IconButton.tsx
================================================
import { type JSX } from 'solid-js';
import { css } from 'solid-styled-components';
import { cacheNoArgs } from '@/providers/decorators';
const iconButton = cacheNoArgs(
() => 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;
export const IconButton = (props: CollapseIconButtonProps) => {
return (
{props.children}
);
};
================================================
FILE: src/plugins/in-app-menu/renderer/MenuButton.tsx
================================================
import { type JSX, splitProps } from 'solid-js';
import { css } from 'solid-styled-components';
import { cacheNoArgs } from '@/providers/decorators';
const menuStyle = cacheNoArgs(
() => 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 & {
text?: string;
selected?: boolean;
};
export const MenuButton = (props: MenuButtonProps) => {
const [local, leftProps] = splitProps(props, ['text']);
return (
);
};
================================================
FILE: src/plugins/in-app-menu/renderer/Panel.tsx
================================================
import { createSignal, type JSX, Show, splitProps, mergeProps } from 'solid-js';
import { Portal } from 'solid-js/web';
import { css } from 'solid-styled-components';
import { Transition } from 'solid-transition-group';
import {
autoUpdate,
flip,
offset,
type OffsetOptions,
size,
} from '@floating-ui/dom';
import { useFloating } from 'solid-floating-ui';
import { cacheNoArgs } from '@/providers/decorators';
const panelStyle = cacheNoArgs(
() => 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 = cacheNoArgs(() => ({
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 & {
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(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 (
);
};
================================================
FILE: src/plugins/in-app-menu/renderer/PanelItem.tsx
================================================
import { createSignal, Match, Show, Switch } from 'solid-js';
import { type 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 { cacheNoArgs } from '@/providers/decorators';
const itemStyle = cacheNoArgs(
() => 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 = cacheNoArgs(
() => css`
height: 32px;
padding: 4px;
color: white;
`,
);
const itemLabelStyle = cacheNoArgs(
() => css`
font-size: 12px;
color: white;
`,
);
const itemChipStyle = cacheNoArgs(
() => 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 = cacheNoArgs(
() => 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 = cacheNoArgs(
() => 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 = cacheNoArgs(() => ({
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(null);
const [anchor, setAnchor] = createSignal(null);
const [child, setChild] = createSignal(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('peard: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 (
}>
{props.name}
} when={props.chip}>
{props.chip}
{props.type === 'submenu' && props.children}
);
};
================================================
FILE: src/plugins/in-app-menu/renderer/TitleBar.tsx
================================================
import { type Menu, type 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 { cacheNoArgs } from '@/providers/decorators';
import type { RendererContext } from '@/types/contexts';
import type { InAppMenuConfig } from '../constants';
const titleStyle = cacheNoArgs(
() => 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 = cacheNoArgs(
() => css`
min-height: 1px;
height: 1px;
margin: 4px 0;
background-color: rgba(255, 255, 255, 0.2);
`,
);
const animationStyle = cacheNoArgs(() => ({
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 (
{(subItem) => (
props.onClick?.(subItem().commandId)}
toolTip={subItem().toolTip}
type={'normal'}
/>
props.onClick?.(subItem().commandId)}
toolTip={subItem().toolTip}
type={'checkbox'}
/>
props.onClick?.(subItem().commandId, radioGroup())
}
toolTip={subItem().toolTip}
type={'radio'}
/>
)}
);
};
export type TitleBarProps = {
ipc: RendererContext['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(null);
const [menu, setMenu] = createSignal(null);
const [mouseY, setMouseY] = createSignal(0);
const [data, { refetch }] = createResource(
async () => (await props.ipc.invoke('get-menu')) as Promise,
);
const [isMaximized, { refetch: refetchMaximize }] = createResource(
async () =>
(await props.ipc.invoke('window-is-maximized')) as Promise,
);
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 (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);
const ytmusicAppLayout = document.querySelector('#layout');
ytmusicAppLayout?.addEventListener('scroll', () => {
const scrollValue = ytmusicAppLayout.scrollTop;
if (scrollValue > 20) {
ytmusicAppLayout.classList.add('content-scrolled');
} else {
ytmusicAppLayout.classList.remove('content-scrolled');
}
});
});
createEffect(() => {
if (!menu() && data()) {
setMenu(data() ?? null);
}
});
onCleanup(() => {
window.removeEventListener('mousemove', listener);
});
return (
setCollapsed(!collapsed())}
style={{
'border-top-left-radius': '4px',
}}
>
{
(element as HTMLElement).style.removeProperty('transition-delay');
}}
onBeforeEnter={(element) => {
if (ignoreTransition()) return;
const index = Number(element.getAttribute('data-index') ?? 0);
(element as HTMLElement).style.setProperty(
'transition-delay',
`${index * 0.025}s`,
);
}}
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`,
);
}}
>
{(item, index) => {
const [anchor, setAnchor] = createSignal(
null,
);
const handleClick = () => {
if (openTarget() === anchor()) {
setOpenTarget(null);
} else {
setOpenTarget(anchor());
}
};
return (
<>
>
);
}}
);
};
================================================
FILE: src/plugins/in-app-menu/renderer/WindowController.tsx
================================================
import { css } from 'solid-styled-components';
import { Show } from 'solid-js';
import { IconButton } from './IconButton';
import { cacheNoArgs } from '@/providers/decorators';
const containerStyle = cacheNoArgs(
() => 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 (
}
when={props.isMaximize}
>
);
};
================================================
FILE: src/plugins/in-app-menu/renderer.tsx
================================================
import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';
import { TitleBar } from './renderer/TitleBar';
import { defaultInAppMenuConfig, type InAppMenuConfig } from './constants';
import { APPLICATION_NAME } from '@/i18n';
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(
defaultInAppMenuConfig,
);
export const onRendererLoad = async ({
getConfig,
ipc,
}: RendererContext) => {
setConfig(await getConfig());
document.title = APPLICATION_NAME;
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(scrollStyle);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
render(
() => (
),
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);
};
================================================
FILE: src/plugins/in-app-menu/titlebar.css
================================================
:root {
--titlebar-background-color: var(--ytmusic-color-black3);
--menu-bar-height: 32px;
}
/* original style */
ytmusic-app-layout {
overflow: auto scroll;
height: calc(100vh - var(--menu-bar-height, 36px));
/* fixes laggy list scrolling in large playlists */
backface-visibility: hidden;
}
ytmusic-app-layout#layout {
--ytmusic-nav-bar-offset: 0px;
}
ytmusic-app-layout > #content {
padding-top: var(--menu-bar-height, 36px);
}
ytmusic-app-layout::-webkit-scrollbar{
width: var(--ytmusic-scrollbar-width);
}
ytmusic-app-layout::-webkit-scrollbar-thumb{
background-color: rgb(126, 126, 126);
}
ytmusic-app-layout > [slot='nav-bar'],
#nav-bar-background.ytmusic-app-layout {
top: var(--menu-bar-height, 36px) !important;
}
#nav-bar-divider.ytmusic-app-layout {
top: calc(
var(--ytmusic-nav-bar-height) + 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(
100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height) -
var(--ytmusic-player-bar-height)
) !important;
}
ytmusic-guide-renderer {
height: calc(
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/pear-devs/pear-desktop/issues/1737 */
html {
scrollbar-color: unset;
}
/* fixes scrollbar lagging behind in large playlists */
ytmusic-browse-response .ytmusic-responsive-list-item-renderer {
will-change: transform;
}
/* fix fullscreen style */
ytmusic-player[player-ui-state='FULLSCREEN'] {
margin-top: calc(var(--menu-bar-height, 32px) * -1) !important;
}
================================================
FILE: src/plugins/lumiastream/index.ts
================================================
import { net } from 'electron';
import { createPlugin } from '@/utils';
import { registerCallback } from '@/providers/song-info';
import { t } from '@/i18n';
type LumiaData = {
origin: string;
eventType: string;
url?: string;
videoId?: string;
playlistId?: string;
cover?: string | null;
cover_url?: string | null;
title?: string;
artists?: string[];
status?: string;
progress?: number;
duration?: number;
album_url?: string | null;
album?: string | null;
views?: number;
isPaused?: boolean;
};
export default createPlugin({
name: () => t('plugins.lumiastream.name'),
description: () => t('plugins.lumiastream.description'),
restartNeeded: true,
config: {
enabled: false,
},
backend({ ipc }) {
const secToMilisec = (t?: number) =>
t ? Math.round(Number(t) * 1e3) : undefined;
const previousStatePaused = null;
const data: LumiaData = {
origin: '\u0079\u006f\u0075\u0074\u0075\u0062\u0065\u006d\u0075\u0073\u0069\u0063',
eventType: 'switchSong',
};
const post = (data: LumiaData) => {
const port = 39231;
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
} as const;
const url = `http://127.0.0.1:${port}/api/media`;
net
.fetch(url, {
method: 'POST',
body: JSON.stringify({ token: 'lsmedia_ytmsI7812', data }),
headers,
})
.catch((error: { code: number; errno: number }) => {
console.log(
`Error: '${
error.code || error.errno
}' - when trying to access lumiastream webserver at port ${port}`,
);
});
};
ipc.on('peard:player-api-loaded', () =>
ipc.send('peard:setup-time-changed-listener'),
);
registerCallback((songInfo) => {
if (!songInfo.title && !songInfo.artist) {
return;
}
if (previousStatePaused === null) {
data.eventType = 'switchSong';
} else if (previousStatePaused !== songInfo.isPaused) {
data.eventType = 'playPause';
}
data.duration = secToMilisec(songInfo.songDuration);
data.progress = secToMilisec(songInfo.elapsedSeconds);
data.url = songInfo.url;
data.videoId = songInfo.videoId;
data.playlistId = songInfo.playlistId;
data.cover = songInfo.imageSrc;
data.cover_url = songInfo.imageSrc;
data.album_url = songInfo.imageSrc;
data.title = songInfo.title;
data.artists = [songInfo.artist];
data.status = songInfo.isPaused ? 'stopped' : 'playing';
data.isPaused = songInfo.isPaused;
data.album = songInfo.album;
data.views = songInfo.views;
post(data);
});
},
});
================================================
FILE: src/plugins/music-together/connection.ts
================================================
import { type DataConnection, Peer, type PeerError } from 'peerjs';
import type { Permission, Profile, VideoData } from './types';
export type ConnectionEventMap = {
CLEAR_QUEUE: null;
ADD_SONGS: { videoList: VideoData[]; index?: number };
REMOVE_SONG: { index: number };
MOVE_SONG: { fromIndex: number; toIndex: number };
SET_INDEX: { index: number };
IDENTIFY: { profile: Profile } | undefined;
SYNC_PROFILE: { profiles: Record } | undefined;
SYNC_QUEUE: { videoList: VideoData[] } | undefined;
SYNC_PROGRESS:
| { progress?: number; state?: number; index?: number }
| undefined;
PERMISSION: Permission | undefined;
CONNECTION_CLOSED: null;
};
export type ConnectionEventUnion = {
[Event in keyof ConnectionEventMap]: {
type: Event;
payload: ConnectionEventMap[Event];
after?: ConnectionEventUnion[];
};
}[keyof ConnectionEventMap];
type PromiseUtil = {
promise: Promise;
resolve: (id: T) => void;
reject: (err: unknown) => void;
};
export type ConnectionListener = (
event: ConnectionEventUnion,
conn: DataConnection | null,
) => void;
export type ConnectionMode = 'host' | 'guest' | 'disconnected';
export class Connection {
private peer: Peer;
private _mode: ConnectionMode = 'disconnected';
private connections: Record = {};
private waitOpen: PromiseUtil = {} as PromiseUtil;
private listeners: ConnectionListener[] = [];
private connectionListeners: ((connection?: DataConnection) => void)[] = [];
constructor() {
this.peer = new Peer({
debug: 0,
config: {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: [
'turn:eu-0.turn.peerjs.com:3478',
'turn:us-0.turn.peerjs.com:3478',
],
username: 'peerjs',
credential: 'peerjsp',
},
{
urls: 'stun:freestun.net:3478',
},
{
urls: 'turn:freestun.net:3478',
username: 'free',
credential: 'free',
},
],
sdpSemantics: 'unified-plan',
},
});
this.waitOpen.promise = new Promise((resolve, reject) => {
this.waitOpen.resolve = resolve;
this.waitOpen.reject = reject;
});
this.peer.on('open', (id) => {
this._mode = 'host';
this.waitOpen.resolve(id);
});
this.peer.on('connection', async (conn) => {
this._mode = 'host';
await this.registerConnection(conn);
});
this.peer.on('close', () => {
for (const listener of this.listeners) {
listener({ type: 'CONNECTION_CLOSED', payload: null }, null);
}
this.listeners = [];
this.connectionListeners.forEach((listener) => listener());
this.connectionListeners = [];
this.connections = {};
this.peer.disconnect();
this.peer.destroy();
});
this.peer.on('error', (err) => {
if (err.type === 'network') {
setTimeout(() => {
try {
this.peer.reconnect();
} catch {}
}, 10000);
return;
}
this.waitOpen.reject(err);
this.connectionListeners.forEach((listener) => listener());
this.disconnect();
console.trace(err);
});
}
/* public */
async waitForReady() {
return this.waitOpen.promise;
}
async connect(id: string) {
this._mode = 'guest';
const conn = this.peer.connect(id, {
reliable: true,
});
await this.registerConnection(conn);
return conn;
}
disconnect() {
if (this._mode === 'disconnected') throw new Error('Already disconnected');
this._mode = 'disconnected';
this.getConnections().forEach((conn) =>
conn.close({
flush: true,
}),
);
this.connections = {};
this.connectionListeners = [];
for (const listener of this.listeners) {
listener({ type: 'CONNECTION_CLOSED', payload: null }, null);
}
this.listeners = [];
this.peer.disconnect();
this.peer.destroy();
}
/* utils */
public get id() {
return this.peer.id;
}
public get mode() {
return this._mode;
}
public getConnections() {
return Object.values(this.connections);
}
public async broadcast(
type: Event,
payload: ConnectionEventMap[Event],
after?: ConnectionEventUnion[],
) {
await Promise.all(
this.getConnections().map(
(conn) => conn.send({ type, payload, after }) ?? Promise.resolve(),
),
);
}
public on(listener: ConnectionListener) {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
}
public onConnections(listener: (connections?: DataConnection) => void) {
this.connectionListeners.push(listener);
}
/* privates */
private async registerConnection(conn: DataConnection) {
return new Promise((resolve, reject) => {
this.peer.once('error', (err) => {
reject(err);
this.connectionListeners.forEach((listener) => listener());
this.disconnect();
});
conn.on('open', () => {
this.connections[conn.connectionId] = conn;
resolve(conn);
this.connectionListeners.forEach((listener) => listener(conn));
conn.on('data', (data) => {
if (
!data ||
typeof data !== 'object' ||
!('type' in data) ||
!('payload' in data) ||
!data.type
) {
console.warn('Music Together: Invalid data', data);
return;
}
for (const listener of this.listeners) {
listener(data as ConnectionEventUnion, conn);
}
});
});
const onClose = (
err?: PeerError<
| 'not-open-yet'
| 'message-too-big'
| 'negotiation-failed'
| 'connection-closed'
>,
) => {
if (conn.open) {
conn.close();
}
delete this.connections[conn.connectionId];
if (err) {
if (err.type === 'connection-closed') {
this.connectionListeners.forEach((listener) => listener());
}
reject(err);
} else {
this.connectionListeners.forEach((listener) => listener(conn));
}
};
conn.on('error', onClose);
conn.on('close', onClose);
});
}
}
================================================
FILE: src/plugins/music-together/element.ts
================================================
import { ElementFromHtml } from '@/plugins/utils/renderer';
import itemHTML from './templates/item.html?raw';
import popupHTML from './templates/popup.html?raw';
type Placement =
| 'top'
| 'bottom'
| 'right'
| 'left'
| 'center'
| 'middle'
| 'center-middle'
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right';
type PopupItem =
| (ItemRendererProps & { type: 'item' })
| { type: 'divider' }
| { type: 'custom'; element: HTMLElement };
type PopupProps = {
data: PopupItem[];
anchorAt?: Placement;
popupAt?: Placement;
};
export const Popup = (props: PopupProps) => {
const popup = ElementFromHtml(popupHTML);
const container = popup.querySelector(
'.music-together-popup-container',
)!;
const items = props.data
.map((props) => {
if (props.type === 'item')
return {
type: 'item' as const,
...ItemRenderer(props),
};
if (props.type === 'divider')
return {
type: 'divider' as const,
element: ElementFromHtml(
'
',
),
};
if (props.type === 'custom')
return {
type: 'custom' as const,
element: props.element,
};
return null;
})
.filter(Boolean);
container.append(...items.map(({ element }) => element));
popup.style.setProperty('opacity', '0');
popup.style.setProperty('pointer-events', 'none');
document.body.append(popup);
return {
element: popup,
container,
items,
show(x: number, y: number, anchor?: HTMLElement) {
let left = x;
let top = y;
if (anchor) {
if (props.anchorAt?.includes('right')) left += anchor.clientWidth;
if (props.anchorAt?.includes('bottom')) top += anchor.clientHeight;
if (props.anchorAt?.includes('center')) left += anchor.clientWidth / 2;
if (props.anchorAt?.includes('middle')) top += anchor.clientHeight / 2;
}
if (props.popupAt?.includes('right')) left -= popup.clientWidth;
if (props.popupAt?.includes('bottom')) top -= popup.clientHeight;
if (props.popupAt?.includes('center')) left -= popup.clientWidth / 2;
if (props.popupAt?.includes('middle')) top -= popup.clientHeight / 2;
popup.style.setProperty('left', `${left}px`);
popup.style.setProperty('top', `${top}px`);
popup.style.setProperty('opacity', '1');
popup.style.setProperty('pointer-events', 'unset');
setTimeout(() => {
const onClose = (event: MouseEvent) => {
const isPopupClick = event
.composedPath()
.some((element) => element === popup);
if (!isPopupClick) {
this.dismiss();
document.removeEventListener('click', onClose);
}
};
document.addEventListener('click', onClose);
}, 16);
},
showAtAnchor(anchor: HTMLElement) {
const { x, y } = anchor.getBoundingClientRect();
this.show(x, y, anchor);
},
isShowing() {
return popup.style.getPropertyValue('opacity') === '1';
},
dismiss() {
popup.style.setProperty('opacity', '0');
popup.style.setProperty('pointer-events', 'none');
},
};
};
type ItemRendererProps = {
id?: string;
icon?: Element;
text: string;
onClick?: () => void;
};
export const ItemRenderer = (props: ItemRendererProps) => {
const element = ElementFromHtml(itemHTML);
const iconContainer = element.querySelector('div.icon')!;
const textContainer = element.querySelector('div.text')!;
if (props.icon) iconContainer.appendChild(props.icon);
textContainer.append(props.text);
if (props.onClick) {
element.addEventListener('click', () => {
props.onClick?.();
});
}
if (props.id) element.id = props.id;
return {
element,
setIcon(icon: Element) {
iconContainer.replaceChildren(icon);
},
setText(text: string) {
textContainer.replaceChildren(text);
},
id: props.id,
};
};
================================================
FILE: src/plugins/music-together/index.ts
================================================
import prompt from 'custom-electron-prompt';
import { t } from '@/i18n';
import { createPlugin } from '@/utils';
import promptOptions from '@/providers/prompt-options';
import { waitForElement } from '@/utils/wait-for-element';
import {
getDefaultProfile,
type Permission,
type Profile,
type VideoData,
} from './types';
import { Queue } from './queue';
import { Connection, type ConnectionEventUnion } from './connection';
import { createHostPopup } from './ui/host';
import { createGuestPopup } from './ui/guest';
import { createSettingPopup } from './ui/setting';
import settingHTML from './templates/setting.html?raw';
import style from './style.css?inline';
import type { DataConnection } from 'peerjs';
import type { MusicPlayer } from '@/types/music-player';
import type { RendererContext } from '@/types/contexts';
import type { VideoDataChanged } from '@/types/video-data-changed';
import type { AppElement } from '@/types/queue';
type RawAccountData = {
accountName: {
runs: { text: string }[];
};
accountPhoto: {
thumbnails: { url: string; width: number; height: number }[];
};
settingsEndpoint: unknown;
manageAccountTitle: unknown;
trackingParams: string;
channelHandle: {
runs: { text: string }[];
};
};
export default createPlugin<
unknown,
unknown,
{
connection?: Connection;
ipc?: RendererContext['ipc'];
api: AppElement | null;
queue?: Queue;
playerApi?: MusicPlayer;
showPrompt: (title: string, label: string) => Promise;
popups: {
host: ReturnType;
guest: ReturnType;
setting: ReturnType;
};
elements: {
setting: HTMLElement;
icon: SVGElement;
spinner: HTMLElement;
};
stateInterval?: number;
updateNext: boolean;
ignoreChange: boolean;
rollbackInjector?: () => void;
me?: Omit;
profiles: Record;
permission: Permission;
videoChangeListener: (
event: CustomEvent,
) => Promise;
videoStateChangeListener: () => Promise;
onHost: () => Promise;
onJoin: () => Promise;
onStop: () => void;
putProfile: (id: string, profile?: Profile) => void;
showSpinner: () => void;
hideSpinner: () => void;
initMyProfile: () => void;
}
>({
name: () => t('plugins.music-together.name'),
description: () => t('plugins.music-together.description'),
restartNeeded: false,
addedVersion: '3.2.X',
config: {
enabled: false,
},
stylesheets: [style],
backend({ ipc }) {
ipc.handle('music-together:prompt', async (title: string, label: string) =>
prompt({
title,
label,
type: 'input',
...promptOptions(),
}),
);
},
renderer: {
updateNext: false,
ignoreChange: false,
permission: 'playlist',
popups: {} as {
host: ReturnType;
guest: ReturnType;
setting: ReturnType;
},
elements: {} as {
setting: HTMLElement;
icon: SVGElement;
spinner: HTMLElement;
},
profiles: {},
showPrompt: () => Promise.resolve(''),
api: null,
/* events */
async videoChangeListener(event: CustomEvent) {
if (event.detail.name === 'dataloaded' || this.updateNext) {
if (this.connection?.mode === 'host') {
const videoList: VideoData[] =
this.queue?.flatItems.map(
(it, index) =>
({
videoId: it!.videoId,
ownerId:
this.queue?.videoList[index]?.ownerId ??
this.connection!.id,
}) satisfies VideoData,
) ?? [];
this.queue?.setVideoList(videoList, false);
this.queue?.syncQueueOwner();
await this.connection.broadcast('SYNC_QUEUE', {
videoList,
});
this.updateNext = event.detail.name === 'dataloaded';
}
}
},
async videoStateChangeListener() {
if (this.connection?.mode !== 'guest') return;
if (this.ignoreChange) return;
if (this.permission !== 'all') return;
const state = this.playerApi?.getPlayerState();
if (state !== 1 && state !== 2) return;
await this.connection.broadcast('SYNC_PROGRESS', {
// progress: this.playerApi?.getCurrentTime(),
state: this.playerApi?.getPlayerState(),
// index: this.queue?.selectedIndex ?? 0,
});
},
/* connection */
async onHost() {
this.connection = new Connection();
const wait = await this.connection.waitForReady().catch(() => null);
if (!wait) return false;
if (!this.me) this.me = getDefaultProfile(this.connection.id);
this.profiles = {};
this.putProfile(this.connection.id, {
id: this.connection.id,
...this.me,
});
this.queue?.setOwner({
id: this.connection.id,
...this.me,
});
const rawItems =
this.queue?.flatItems?.map(
(it) =>
({
videoId: it!.videoId,
ownerId: this.connection!.id,
}) satisfies VideoData,
) ?? [];
this.queue?.setVideoList(rawItems, false);
this.queue?.syncQueueOwner();
this.queue?.initQueue();
this.queue?.injection();
this.connection.onConnections((connection) => {
if (!connection) {
this.api?.toastService?.show(
t('plugins.music-together.toast.disconnected'),
);
this.onStop();
return;
}
if (!connection.open) {
this.api?.toastService?.show(
t('plugins.music-together.toast.user-disconnected', {
name: this.profiles[connection.peer]?.name,
}),
);
this.putProfile(connection.peer, undefined);
}
});
const listener = async (
event: ConnectionEventUnion,
conn?: DataConnection | null,
) => {
this.ignoreChange = true;
switch (event.type) {
case 'CLEAR_QUEUE': {
if (conn && this.permission === 'host-only') {
await this.connection?.broadcast('SYNC_QUEUE', {
videoList: this.queue?.videoList ?? [],
});
return;
}
this.queue?.clear();
await this.connection?.broadcast('CLEAR_QUEUE', null);
break;
}
case 'SET_INDEX': {
this.queue?.setIndex(event.payload.index);
await this.connection?.broadcast('SET_INDEX', {
index: event.payload.index,
});
break;
}
case 'ADD_SONGS': {
if (conn && this.permission === 'host-only') {
await this.connection?.broadcast('SYNC_QUEUE', {
videoList: this.queue?.videoList ?? [],
});
return;
}
const videoList: VideoData[] = event.payload.videoList.map(
(it) => ({
...it,
ownerId: it.ownerId ?? conn?.peer ?? this.connection!.id,
}),
);
await this.queue?.addVideos(videoList, event.payload.index);
await this.connection?.broadcast('ADD_SONGS', {
...event.payload,
videoList,
},
event.after,
);
const afterevent = event.after?.at(0);
if (afterevent?.type === 'SET_INDEX') {
this.queue?.setIndex(afterevent.payload.index);
}
break;
}
case 'REMOVE_SONG': {
if (conn && this.permission === 'host-only') {
await this.connection?.broadcast('SYNC_QUEUE', {
videoList: this.queue?.videoList ?? [],
});
return;
}
this.queue?.removeVideo(event.payload.index);
await this.connection?.broadcast('REMOVE_SONG', event.payload);
break;
}
case 'MOVE_SONG': {
if (conn && this.permission === 'host-only') {
await this.connection?.broadcast('SYNC_QUEUE', {
videoList: this.queue?.videoList ?? [],
});
break;
}
this.queue?.moveItem(
event.payload.fromIndex,
event.payload.toIndex,
);
await this.connection?.broadcast('MOVE_SONG', event.payload);
break;
}
case 'IDENTIFY': {
if (!event.payload || !conn) {
console.warn(
'Music Together [Host]: Received "IDENTIFY" event without payload or connection',
);
break;
}
this.api?.toastService?.show(
t('plugins.music-together.toast.user-connected', {
name: event.payload.profile.name,
}),
);
this.putProfile(conn.peer, event.payload.profile);
break;
}
case 'SYNC_PROFILE': {
await this.connection?.broadcast('SYNC_PROFILE', {
profiles: this.profiles,
});
break;
}
case 'PERMISSION': {
await this.connection?.broadcast('PERMISSION', this.permission);
this.popups.guest.setPermission(this.permission);
this.popups.host.setPermission(this.permission);
this.popups.setting.setPermission(this.permission);
break;
}
case 'SYNC_QUEUE': {
await this.connection?.broadcast('SYNC_QUEUE', {
videoList: this.queue?.videoList ?? [],
});
break;
}
case 'SYNC_PROGRESS': {
let permissionLevel = 0;
if (this.permission === 'all') permissionLevel = 2;
if (this.permission === 'playlist') permissionLevel = 1;
if (this.permission === 'host-only') permissionLevel = 0;
if (!conn) permissionLevel = 3;
if (permissionLevel >= 2) {
if (typeof event.payload?.progress === 'number') {
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
if (Math.abs(event.payload.progress - currentTime) > 3)
this.playerApi?.seekTo(event.payload.progress);
}
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
if (event.payload?.state === 1) this.playerApi?.playVideo();
}
}
if (permissionLevel >= 1) {
if (typeof event.payload?.index === 'number') {
const nowIndex = this.queue?.selectedIndex ?? 0;
if (nowIndex !== event.payload.index) {
this.queue?.setIndex(event.payload.index);
}
}
}
break;
}
case 'CONNECTION_CLOSED': {
this.queue?.off(listener);
break;
}
default: {
console.warn('Music Together [Host]: Unknown Event', event);
break;
}
}
if (event.after) {
const now = event.after.shift();
if (now) {
now.after = event.after;
await listener(now, conn);
}
}
};
this.connection.on(listener);
this.queue?.on(listener);
setTimeout(() => {
this.ignoreChange = false;
}, 16); // wait 1 frame
return true;
},
async onJoin() {
this.connection = new Connection();
const wait = await this.connection.waitForReady().catch(() => null);
if (!wait) return false;
this.profiles = {};
const id = await this.showPrompt(
t('plugins.music-together.name'),
t('plugins.music-together.dialog.enter-host'),
);
if (typeof id !== 'string') return false;
const connection = await this.connection.connect(id).catch(() => false);
if (!connection) return false;
this.connection.onConnections((connection) => {
if (!connection?.open) {
this.api?.toastService?.show(
t('plugins.music-together.toast.disconnected'),
);
this.onStop();
}
});
let resolveIgnore: number | null = null;
const queueListener = async (event: ConnectionEventUnion) => {
this.ignoreChange = true;
switch (event.type) {
case 'CLEAR_QUEUE': {
await this.connection?.broadcast('CLEAR_QUEUE', null);
break;
}
case 'SET_INDEX': {
await this.connection?.broadcast('SET_INDEX', {
index: event.payload.index,
});
break;
}
case 'ADD_SONGS': {
await this.connection?.broadcast('ADD_SONGS', {
...event.payload,
videoList: event.payload.videoList.map((it) => ({
...it,
ownerId: it.ownerId ?? this.connection!.id,
})),
},
event.after,
);
break;
}
case 'REMOVE_SONG': {
await this.connection?.broadcast('REMOVE_SONG', event.payload);
break;
}
case 'MOVE_SONG': {
await this.connection?.broadcast('MOVE_SONG', event.payload);
break;
}
case 'SYNC_PROGRESS': {
if (this.permission === 'host-only')
await this.connection?.broadcast('SYNC_QUEUE', undefined);
else
await this.connection?.broadcast('SYNC_PROGRESS', event.payload);
break;
}
}
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
resolveIgnore = window.setTimeout(() => {
this.ignoreChange = false;
}, 16); // wait 1 frame
};
const listener = async (event: ConnectionEventUnion) => {
this.ignoreChange = true;
switch (event.type) {
case 'CLEAR_QUEUE': {
this.queue?.clear();
break;
}
case 'SET_INDEX': {
this.queue?.setIndex(event.payload.index);
break;
}
case 'ADD_SONGS': {
const videoList: VideoData[] = event.payload.videoList.map(
(it) => ({
...it,
ownerId: it.ownerId ?? this.connection!.id,
}),
);
await this.queue?.addVideos(videoList, event.payload.index);
const afterevent = event.after?.at(0);
if (afterevent?.type === 'SET_INDEX') {
this.queue?.setIndex(afterevent.payload.index);
}
break;
}
case 'REMOVE_SONG': {
this.queue?.removeVideo(event.payload.index);
break;
}
case 'MOVE_SONG': {
this.queue?.moveItem(
event.payload.fromIndex,
event.payload.toIndex,
);
break;
}
case 'IDENTIFY': {
console.warn(
'Music Together [Guest]: Received "IDENTIFY" event from guest',
);
break;
}
case 'SYNC_QUEUE': {
if (Array.isArray(event.payload?.videoList)) {
await this.queue?.setVideoList(event.payload.videoList);
}
break;
}
case 'SYNC_PROFILE': {
if (!event.payload) {
console.warn(
'Music Together [Guest]: Received "SYNC_PROFILE" event without payload',
);
break;
}
Object.entries(event.payload.profiles).forEach(([id, profile]) => {
this.putProfile(id, profile);
});
break;
}
case 'SYNC_PROGRESS': {
if (typeof event.payload?.progress === 'number') {
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
if (Math.abs(event.payload.progress - currentTime) > 3)
this.playerApi?.seekTo(event.payload.progress);
}
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
if (event.payload?.state === 1) this.playerApi?.playVideo();
}
if (typeof event.payload?.index === 'number') {
const nowIndex = this.queue?.selectedIndex ?? 0;
if (nowIndex !== event.payload.index) {
this.queue?.setIndex(event.payload.index);
}
}
break;
}
case 'PERMISSION': {
if (!event.payload) {
console.warn(
'Music Together [Guest]: Received "PERMISSION" event without payload',
);
break;
}
this.permission = event.payload;
this.popups.guest.setPermission(this.permission);
this.popups.host.setPermission(this.permission);
this.popups.setting.setPermission(this.permission);
const permissionLabel = t(
`plugins.music-together.menu.permission.${this.permission}`,
);
this.api?.toastService?.show(
t('plugins.music-together.toast.permission-changed', {
permission: permissionLabel,
}),
);
break;
}
case 'CONNECTION_CLOSED': {
this.queue?.off(queueListener);
break;
}
default: {
console.warn('Music Together [Guest]: Unknown Event', event);
break;
}
}
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
resolveIgnore = window.setTimeout(() => {
this.ignoreChange = false;
}, 16); // wait 1 frame
};
this.connection.on(listener);
this.queue?.on(queueListener);
if (!this.me) this.me = getDefaultProfile(this.connection.id);
this.queue?.injection();
this.queue?.setOwner({
id: this.connection.id,
...this.me,
});
const progress = Array.from(
document.querySelectorAll<
HTMLElement & {
_update: (...args: unknown[]) => void;
}
>('tp-yt-paper-progress'),
);
const rollbackList = progress.map((progress) => {
const original = progress._update;
progress._update = (...args) => {
const now = args[0];
if (this.permission === 'all' && typeof now === 'number') {
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
if (Math.abs(now - currentTime) > 3)
this.connection?.broadcast('SYNC_PROGRESS', {
progress: now,
state: this.playerApi?.getPlayerState(),
});
}
original.call(progress, ...args);
};
return () => {
progress._update = original;
};
});
this.rollbackInjector = () => {
rollbackList.forEach((rollback) => rollback());
};
await this.connection.broadcast('IDENTIFY', {
profile: {
id: this.connection.id,
handleId: this.me.handleId,
name: this.me.name,
thumbnail: this.me.thumbnail,
},
});
await this.connection.broadcast('SYNC_PROFILE', undefined);
await this.connection.broadcast('PERMISSION', undefined);
this.queue?.clear();
this.queue?.syncQueueOwner();
this.queue?.initQueue();
await this.connection.broadcast('SYNC_QUEUE', undefined);
return true;
},
onStop() {
if (this.connection?.mode !== 'disconnected') {
this.connection?.disconnect();
}
this.queue?.rollbackInjection();
this.queue?.removeQueueOwner();
if (this.rollbackInjector) {
this.rollbackInjector();
this.rollbackInjector = undefined;
}
this.profiles = {};
this.popups.host.setUsers(Object.values(this.profiles));
this.popups.guest.setUsers(Object.values(this.profiles));
this.popups.host.dismiss();
this.popups.guest.dismiss();
this.popups.setting.dismiss();
},
/* methods */
putProfile(id: string, profile?: Profile) {
if (profile === undefined) {
delete this.profiles[id];
} else {
this.profiles[id] = profile;
}
this.popups.host.setUsers(Object.values(this.profiles));
this.popups.guest.setUsers(Object.values(this.profiles));
},
showSpinner() {
this.elements.icon.style.setProperty('display', 'none');
this.elements.spinner.removeAttribute('hidden');
this.elements.spinner.setAttribute('active', '');
},
hideSpinner() {
this.elements.icon.style.removeProperty('display');
this.elements.spinner.removeAttribute('active');
this.elements.spinner.setAttribute('hidden', '');
},
async initMyProfile() {
const accountButton = await waitForElement(
'#right-content > ytmusic-settings-button *:where(tp-yt-paper-icon-button,yt-icon-button,.ytmusic-settings-button)',
{
maxRetry: 10000,
},
);
accountButton?.click();
setTimeout(async () => {
const renderer = await waitForElement(
'ytd-active-account-header-renderer',
{
maxRetry: 10000,
},
);
if (!accountButton || !renderer) {
console.warn('Music Together: Cannot find account');
this.me = getDefaultProfile(this.connection?.id ?? '');
return;
}
const accountData = renderer.data as RawAccountData;
this.me = {
handleId:
accountData.channelHandle.runs[0].text ??
accountData.accountName.runs[0].text,
name: accountData.accountName.runs[0].text,
thumbnail: accountData.accountPhoto.thumbnails[0].url,
};
if (this.me.thumbnail) {
this.popups.host.setProfile(this.me.thumbnail);
this.popups.guest.setProfile(this.me.thumbnail);
this.popups.setting.setProfile(this.me.thumbnail);
}
accountButton?.click(); // close menu
}, 0);
},
/* hooks */
start({ ipc }) {
this.ipc = ipc;
this.showPrompt = (title: string, label: string) =>
ipc.invoke('music-together:prompt', title, label) as Promise;
this.api = document.querySelector('ytmusic-app');
/* setup */
document
.querySelector('#right-content > ytmusic-settings-button')
?.insertAdjacentHTML('beforebegin', settingHTML);
const setting = document.querySelector(
'#music-together-setting-button',
);
const icon = document.querySelector(
'#music-together-setting-button > svg',
);
const spinner = document.querySelector(
'#music-together-setting-button > tp-yt-paper-spinner-lite',
);
if (!setting || !icon || !spinner) {
console.warn('Music Together: Cannot inject html');
console.log(setting, icon, spinner);
return;
}
this.elements = {
setting,
icon,
spinner,
};
this.stateInterval = window.setInterval(() => {
if (this.connection?.mode !== 'host') return;
const index = this.queue?.selectedIndex ?? 0;
this.connection.broadcast('SYNC_PROGRESS', {
progress: this.playerApi?.getCurrentTime(),
state: this.playerApi?.getPlayerState(),
index,
});
}, 1000);
/* UI */
const hostPopup = createHostPopup({
onItemClick: (id) => {
if (id === 'music-together-close') {
this.onStop();
this.api?.toastService?.show(
t('plugins.music-together.toast.closed'),
);
hostPopup.dismiss();
}
if (id === 'music-together-copy-id') {
navigator.clipboard
.writeText(this.connection?.id ?? '')
.then(() => {
this.api?.toastService?.show(
t('plugins.music-together.toast.id-copied'),
);
hostPopup.dismiss();
})
.catch(() => {
this.api?.toastService?.show(
t('plugins.music-together.toast.id-copy-failed'),
);
hostPopup.dismiss();
});
}
if (id === 'music-together-permission') {
if (this.permission === 'all') this.permission = 'host-only';
else if (this.permission === 'playlist') this.permission = 'all';
else if (this.permission === 'host-only')
this.permission = 'playlist';
this.connection?.broadcast('PERMISSION', this.permission);
hostPopup.setPermission(this.permission);
guestPopup.setPermission(this.permission);
settingPopup.setPermission(this.permission);
const permissionLabel = t(
`plugins.music-together.menu.permission.${this.permission}`,
);
this.api?.toastService?.show(
t('plugins.music-together.toast.permission-changed', {
permission: permissionLabel,
}),
);
const item = hostPopup.items.find((it) => it?.element.id === id);
if (item?.type === 'item') {
item.setText(t('plugins.music-together.menu.set-permission'));
}
}
},
});
const guestPopup = createGuestPopup({
onItemClick: (id) => {
if (id === 'music-together-disconnect') {
this.onStop();
this.api?.toastService?.show(
t('plugins.music-together.toast.disconnected'),
);
guestPopup.dismiss();
}
},
});
const settingPopup = createSettingPopup({
onItemClick: async (id) => {
if (id === 'music-together-host') {
settingPopup.dismiss();
this.showSpinner();
const result = await this.onHost();
this.hideSpinner();
if (result) {
navigator.clipboard
.writeText(this.connection?.id ?? '')
.then(() => {
this.api?.toastService?.show(
t('plugins.music-together.toast.id-copied'),
);
hostPopup.showAtAnchor(setting);
})
.catch(() => {
this.api?.toastService?.show(
t('plugins.music-together.toast.id-copy-failed'),
);
hostPopup.showAtAnchor(setting);
});
} else {
this.api?.toastService?.show(
t('plugins.music-together.toast.host-failed'),
);
}
}
if (id === 'music-together-join') {
settingPopup.dismiss();
this.showSpinner();
const result = await this.onJoin();
this.hideSpinner();
if (result) {
this.api?.toastService?.show(
t('plugins.music-together.toast.joined'),
);
guestPopup.showAtAnchor(setting);
} else {
this.api?.toastService?.show(
t('plugins.music-together.toast.join-failed'),
);
}
}
},
});
this.popups = {
host: hostPopup,
guest: guestPopup,
setting: settingPopup,
};
setting.addEventListener('click', () => {
let popup = settingPopup;
if (this.connection?.mode === 'host') popup = hostPopup;
if (this.connection?.mode === 'guest') popup = guestPopup;
if (popup.isShowing()) popup.dismiss();
else popup.showAtAnchor(setting);
});
/* account data getter */
this.initMyProfile();
},
onPlayerApiReady(playerApi) {
this.queue = new Queue({
owner: {
id: this.connection?.id ?? '',
...this.me!,
},
getProfile: (id) => this.profiles[id],
});
this.playerApi = playerApi;
this.playerApi.addEventListener(
'onStateChange',
this.videoStateChangeListener,
);
document.addEventListener('videodatachange', this.videoChangeListener);
},
stop() {
const dividers = Array.from(
document.querySelectorAll('.music-together-divider'),
);
dividers.forEach((divider) => divider.remove());
this.elements.setting?.remove();
this.onStop();
if (typeof this.stateInterval === 'number')
clearInterval(this.stateInterval);
if (this.playerApi)
this.playerApi.removeEventListener(
'onStateChange',
this.videoStateChangeListener,
);
if (this.videoChangeListener)
document.removeEventListener(
'videodatachange',
this.videoChangeListener,
);
},
},
});
================================================
FILE: src/plugins/music-together/queue/client.ts
================================================
import { SHA1Hash } from './sha1hash';
export const extractToken = (cookie = document.cookie) =>
cookie.match(/SAPISID=([^;]+);/)?.[1] ??
cookie.match(/__Secure-3PAPISID=([^;]+);/)?.[1];
export const getHash = async (
papisid: string,
millis = Date.now(),
origin: string = 'https://music.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com',
) => (await SHA1Hash(`${millis} ${papisid} ${origin}`)).toLowerCase();
export const getAuthorizationHeader = async (
papisid: string,
millis = Date.now(),
origin: string = 'https://music.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com',
) => {
return `SAPISIDHASH ${millis}_${await getHash(papisid, millis, origin)}`;
};
export const getClient = () => {
return {
hl: navigator.language.split('-')[0] ?? 'en',
gl: navigator.language.split('-')[1] ?? 'US',
deviceMake: '',
deviceModel: '',
userAgent: navigator.userAgent,
clientName: 'WEB_REMIX',
clientVersion: '1.20231208.05.02',
osName: '',
osVersion: '',
platform: 'DESKTOP',
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
locationInfo: {
locationPermissionAuthorizationStatus:
'LOCATION_PERMISSION_AUTHORIZATION_STATUS_UNSUPPORTED',
},
musicAppInfo: {
pwaInstallabilityStatus: 'PWA_INSTALLABILITY_STATUS_UNKNOWN',
webDisplayMode: 'WEB_DISPLAY_MODE_BROWSER',
storeDigitalGoodsApiSupportStatus: {
playStoreDigitalGoodsApiSupportStatus:
'DIGITAL_GOODS_API_SUPPORT_STATUS_UNSUPPORTED',
},
},
utcOffsetMinutes: -1 * new Date().getTimezoneOffset(),
};
};
================================================
FILE: src/plugins/music-together/queue/index.ts
================================================
export * from './queue';
================================================
FILE: src/plugins/music-together/queue/queue.ts
================================================
import { getMusicQueueRenderer } from './song';
import { mapQueueItem } from './utils';
import { t } from '@/i18n';
import { getDefaultProfile, type Profile, type VideoData } from '../types';
import type { ConnectionEventUnion } from '@/plugins/music-together/connection';
import type { QueueItem } from '@/types/datahost-get-state';
import type { QueueElement, Store } from '@/types/queue';
const getHeaderPayload = (() => {
let payload: {
items?: QueueItem[] | undefined;
title: {
runs: {
text: string;
}[];
};
subtitle: {
runs: {
text: string;
}[];
};
buttons: {
chipCloudChipRenderer: {
style: {
styleType: string;
};
text: {
runs: {
text: string;
}[];
};
navigationEndpoint: {
saveQueueToPlaylistCommand: unknown;
};
icon: {
iconType: string;
};
accessibilityData: {
accessibilityData: {
label: string;
};
};
isSelected: boolean;
uniqueId: string;
};
}[];
} | null = null;
return () => {
if (!payload) {
payload = {
title: {
runs: [
{
text: t('plugins.music-together.internal.track-source'),
},
],
},
subtitle: {
runs: [
{
text: t('plugins.music-together.name'),
},
],
},
buttons: [
{
chipCloudChipRenderer: {
style: {
styleType: 'STYLE_TRANSPARENT',
},
text: {
runs: [
{
text: t('plugins.music-together.internal.save'),
},
],
},
navigationEndpoint: {
saveQueueToPlaylistCommand: {},
},
icon: {
iconType: 'ADD_TO_PLAYLIST',
},
accessibilityData: {
accessibilityData: {
label: t('plugins.music-together.internal.save'),
},
},
isSelected: false,
uniqueId: t('plugins.music-together.internal.save'),
},
},
],
};
}
return payload;
};
})();
export type QueueOptions = {
videoList?: VideoData[];
owner?: Profile;
queue?: QueueElement;
getProfile: (id: string) => Profile | undefined;
};
export type QueueEventListener = (event: ConnectionEventUnion) => void;
export class Queue {
private readonly queue: QueueElement;
private originalDispatch?: (obj: {
type: string;
payload?: { items?: QueueItem[] | undefined };
}) => void;
private internalDispatch = false;
private ignoreFlag = false;
private listeners: QueueEventListener[] = [];
private owner: Profile | null;
private readonly getProfile: (id: string) => Profile | undefined;
constructor(options: QueueOptions) {
this.getProfile = options.getProfile;
this.queue =
options.queue ?? document.querySelector('#queue')!;
this.owner = options.owner ?? null;
this._videoList = options.videoList ?? [];
}
private _videoList: VideoData[] = [];
/* utils */
get videoList() {
return this._videoList;
}
get selectedIndex() {
return (
mapQueueItem(
(it) => it?.selected,
this.queue.queue.store.store.getState().queue.items,
).findIndex(Boolean) ?? 0
);
}
get rawItems() {
return this.queue?.queue.store.store.getState().queue.items;
}
get flatItems() {
return mapQueueItem((it) => it, this.rawItems);
}
setOwner(owner: Profile) {
this.owner = owner;
}
/* public */
async setVideoList(videoList: VideoData[], sync = true) {
this._videoList = videoList;
if (sync) await this.syncVideo();
}
async addVideos(videos: VideoData[], index?: number) {
const response = await getMusicQueueRenderer(
videos.map((it) => it.videoId),
);
if (!response) return false;
const items = response.queueDatas.map((it) => it?.content).filter(Boolean);
if (!items) return false;
this.internalDispatch = true;
this._videoList = this._videoList.map(
(it) =>
({
videoId: it.videoId,
ownerId: it.ownerId ?? this.owner!.id,
}) satisfies VideoData,
);
const state = this.queue.queue.store.store.getState();
this.queue?.dispatch({
type: 'ADD_ITEMS',
payload: {
nextQueueItemId: state.queue.nextQueueItemId,
index:
index ??
(state.queue.items.length ? state.queue.items.length - 1 : null) ??
0,
items,
shuffleEnabled: false,
shouldAssignIds: true,
},
});
const insertedItem = this._videoList[index ?? this._videoList.length];
if (
!insertedItem ||
(insertedItem.videoId !== videos[0].videoId &&
insertedItem.ownerId !== videos[0].ownerId)
) {
this._videoList.splice(
index ?? this._videoList.length,
0,
...videos.map((it) => ({
...it,
ownerId: it.ownerId ?? this.owner?.id,
})),
);
}
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
return true;
}
removeVideo(index: number) {
this.internalDispatch = true;
this._videoList.splice(index, 1);
this.queue?.dispatch({
type: 'REMOVE_ITEM',
payload: index,
});
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
}
setIndex(index: number) {
this.internalDispatch = true;
this.queue?.dispatch({
type: 'SET_INDEX',
payload: index,
});
this.internalDispatch = false;
}
moveItem(fromIndex: number, toIndex: number) {
this.internalDispatch = true;
const data = this._videoList.splice(fromIndex, 1)[0];
this._videoList.splice(toIndex, 0, data);
this.queue?.dispatch({
type: 'MOVE_ITEM',
payload: {
fromIndex,
toIndex,
},
});
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
}
clear() {
this.internalDispatch = true;
this._videoList = [];
this.queue?.dispatch({
type: 'CLEAR',
});
this.internalDispatch = false;
}
on(listener: QueueEventListener) {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
}
off(listener: QueueEventListener) {
this.listeners = this.listeners.filter((it) => it !== listener);
}
rollbackInjection() {
if (!this.queue) {
console.error('Queue is not initialized!');
return;
}
if (this.originalDispatch)
this.queue.queue.store.store.dispatch = this
.originalDispatch as Store['dispatch'];
}
injection() {
if (!this.queue) {
console.error('Queue is not initialized!');
return;
}
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;
}
if (!this.internalDispatch) {
if (event.type === 'CLEAR') {
this.ignoreFlag = true;
this.broadcast({
type: 'CLEAR_QUEUE',
payload: null,
});
return;
}
if (event.type === 'ADD_ITEMS') {
if (this.ignoreFlag) {
this.ignoreFlag = false;
const videoList = mapQueueItem(
(it) =>
({
videoId: it!.videoId,
ownerId: this.owner!.id,
}) satisfies VideoData,
(
event.payload! as {
items: QueueItem[];
}
).items,
);
const index = this._videoList.length;
if (videoList.length > 0) {
this._videoList = [
...videoList.map((it) => ({
...it,
ownerId: it.ownerId ?? this.owner?.id,
})),
];
this.broadcast({
// play
type: 'ADD_SONGS',
payload: {
videoList,
},
after: [
{
type: 'SET_INDEX',
payload: {
index,
},
},
],
});
}
} else if (
(
event.payload as {
items: unknown[];
}
).items.length === 1
) {
const videoList = mapQueueItem(
(it) =>
({
videoId: it!.videoId,
ownerId: this.owner!.id,
}) satisfies VideoData,
(
event.payload! as {
items: QueueItem[];
}
).items,
);
this._videoList.splice(
event.payload && Object.hasOwn(event.payload, 'index')
? (
event.payload as {
index: number;
}
).index
: this._videoList.length,
0,
...videoList.map((it) => ({
...it,
ownerId: it.ownerId ?? this.owner?.id,
})),
);
this.broadcast({
// add playlist
type: 'ADD_SONGS',
payload: {
index:
event.payload && Object.hasOwn(event.payload, 'index')
? (
event.payload as {
index: number;
}
).index
: undefined,
videoList,
},
});
}
return;
}
if (event.type === 'MOVE_ITEM') {
this.broadcast({
type: 'MOVE_SONG',
payload: {
fromIndex: (
event.payload as {
fromIndex: number;
}
).fromIndex,
toIndex: (
event.payload as {
toIndex: number;
}
).toIndex,
},
});
return;
}
if (event.type === 'REMOVE_ITEM') {
this.broadcast({
type: 'REMOVE_SONG',
payload: {
index: event.payload as number,
},
});
return;
}
if (event.type === 'SET_INDEX') {
this.broadcast({
type: 'SYNC_PROGRESS',
payload: {
index: event.payload as number,
},
});
return;
}
if (event.type === 'SET_HEADER') event.payload = getHeaderPayload();
if (event.type === 'ADD_STEERING_CHIPS') {
event.type = 'CLEAR_STEERING_CHIPS';
event.payload = undefined;
}
if (event.type === 'SET_PLAYER_UI_STATE') {
if (
(event.payload as string) === 'INACTIVE' &&
this.videoList.length > 0
) {
return;
}
}
if (event.type === 'HAS_SHOWN_AUTOPLAY') return;
if (event.type === 'ADD_AUTOMIX_ITEMS') return;
}
const fakeContext = {
...this.queue,
queue: {
...this.queue.queue,
store: {
...this.queue.queue.store,
dispatch: this.originalDispatch,
},
},
};
this.originalDispatch?.call(
fakeContext,
event as {
type: string;
payload?: { items?: QueueItem[] | undefined } | undefined;
},
);
};
}
/* sync */
initQueue() {
if (!this.queue) return;
this.internalDispatch = true;
this.queue.dispatch({
type: 'HAS_SHOWN_AUTOPLAY',
payload: false,
});
this.queue.dispatch({
type: 'SET_HEADER',
payload: getHeaderPayload(),
});
this.queue.dispatch({
type: 'CLEAR_STEERING_CHIPS',
});
this.internalDispatch = false;
}
async syncVideo() {
const response = await getMusicQueueRenderer(
this._videoList.map((it) => it.videoId),
);
if (!response) return false;
const items = response.queueDatas.map((it) => it.content);
this.internalDispatch = true;
this.queue?.dispatch({
type: 'UPDATE_ITEMS',
payload: {
items: items,
nextQueueItemId:
this.queue.queue.store.store.getState().queue.nextQueueItemId,
shouldAssignIds: true,
currentIndex: -1,
},
});
this.internalDispatch = false;
setTimeout(() => {
this.initQueue();
this.syncQueueOwner();
}, 0);
return true;
}
syncQueueOwner() {
const allQueue = document.querySelectorAll('#queue');
allQueue.forEach((queue) => {
const list = Array.from(
queue?.querySelectorAll(
'#contents > ytmusic-player-queue-item,#contents > ytmusic-playlist-panel-video-wrapper-renderer > #primary-renderer > ytmusic-player-queue-item',
) ?? [],
);
list.forEach((item, index: number | undefined) => {
if (typeof index !== 'number') return;
const id = this._videoList[index]?.ownerId;
let data = this.getProfile(id);
const profile =
item.querySelector('.music-together-owner') ??
document.createElement('img');
profile.classList.add('music-together-owner');
profile.dataset.id = id;
profile.dataset.index = index.toString();
const name =
item.querySelector('.music-together-name') ??
document.createElement('div');
name.classList.add('music-together-name');
name.textContent =
data?.name ?? t('plugins.music-together.internal.unknown-user');
if (!data?.name && !data?.handleId) {
data = getDefaultProfile(data?.id ?? '');
}
if (data) {
profile.dataset.thumbnail = data.thumbnail ?? '';
profile.dataset.name = data.name ?? '';
profile.dataset.handleId = data.handleId ?? '';
profile.dataset.id = data.id ?? '';
profile.src = data.thumbnail ?? '';
profile.title = data.name ?? '';
profile.alt = data.handleId ?? '';
}
if (!profile.isConnected) item.append(profile);
if (!name.isConnected) item.append(name);
});
});
}
removeQueueOwner() {
const allQueue = document.querySelectorAll('#queue');
allQueue.forEach((queue) => {
const list = Array.from(
queue?.querySelectorAll('ytmusic-player-queue-item') ?? [],
);
list.forEach((item) => {
const profile = item.querySelector(
'.music-together-owner',
);
const name = item.querySelector('.music-together-name');
profile?.remove();
name?.remove();
});
});
}
/* private */
private broadcast(event: ConnectionEventUnion) {
this.listeners.forEach((listener) => listener(event));
}
}
================================================
FILE: src/plugins/music-together/queue/sha1hash.ts
================================================
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('');
};
================================================
FILE: src/plugins/music-together/queue/song.ts
================================================
import type { MusicPlayerAppElement } from '@/types/music-player-app-element';
import type { QueueElement } from '@/types/queue';
type QueueRendererResponse = {
queueDatas: {
content: unknown;
}[];
responseContext: unknown;
trackingParams: string;
};
export const getMusicQueueRenderer = async (
videoIds: string[],
): Promise => {
const queue = document.querySelector('#queue');
const app = document.querySelector('ytmusic-app');
if (!app) return null;
const store = queue?.queue.store.store;
if (!store) return null;
return await app.networkManager.fetch<
QueueRendererResponse,
{
queueContextParams: string;
videoIds: string[];
}
>('/music/get_queue', {
queueContextParams: store.getState().queue.queueContextParams,
videoIds,
});
};
================================================
FILE: src/plugins/music-together/queue/utils.ts
================================================
import type {
ItemPlaylistPanelVideoRenderer,
PlaylistPanelVideoWrapperRenderer,
QueueItem,
} from '@/types/datahost-get-state';
export const mapQueueItem =