Merge remote-tracking branch 'swiftfork/main'

# Conflicts:
#	src/renderer/views/components/add-to-playlist.ejs
This commit is contained in:
Swiftzerr 2022-01-29 21:57:17 -05:00
commit bde33c4324
175 changed files with 35181 additions and 23240 deletions

View file

@ -27,6 +27,21 @@ on:
- cron: '44 20 * * 1' - cron: '44 20 * * 1'
jobs: jobs:
flatpak:
name: "Flatpak Builder"
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-40
options: --privileged
steps:
- uses: actions/checkout@v2
- uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v4
with:
bundle: Cider.flatpak
manifest-path: ./flatpak/org.cidercollective.cider.yml
cache-key: flatpak-builder-${{ github.sha }}
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest

31
.github/workflows/release-all.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: Build/release
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 16
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.github_token }}
# release the app after building
release: true

7
.gitignore vendored
View file

@ -4,6 +4,7 @@ dist
yarn* yarn*
package-lock.json package-lock.json
.yarnclean .yarnclean
build
# Misc # Misc
.idea .idea
@ -308,3 +309,9 @@ GitHub.sublime-settings
#Service Worker mappings #Service Worker mappings
src/renderer/sw.js.map src/renderer/sw.js.map
src/renderer/workbox-962786f2.js.map src/renderer/workbox-962786f2.js.map
/src/renderer/musickit-dev.js
#Mac certs
*.p12
keys.sh

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 577 205" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.788347,0,0,0.788347,-131.654,-154.131)">
<g id="Release.afdesign" transform="matrix(0.260038,0,0,0.260038,297.279,325.53)">
<g transform="matrix(1,0,0,1,-501,-500)">
<path d="M501,21C765.367,21 980,235.633 980,500C980,764.367 765.367,979 501,979C236.633,979 22,764.367 22,500C22,235.633 236.633,21 501,21ZM501,169C683.684,169 832,317.316 832,500C832,682.684 683.684,831 501,831C318.316,831 170,682.684 170,500C170,317.316 318.316,169 501,169Z" style="fill:rgb(255,38,84);"/>
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:rgb(255,38,84);"/>
</g>
</g>
<g transform="matrix(10.0544,0,0,10.0544,-4614.58,-2607.36)">
<text x="505.982px" y="297.612px" style="font-family:'Inter-Bold', 'Inter';font-weight:700;font-size:16px;fill:rgb(235,235,235);">Cider</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

6
Assets/Release.svg Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1002 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M501,21C765.367,21 980,235.633 980,500C980,764.367 765.367,979 501,979C236.633,979 22,764.367 22,500C22,235.633 236.633,21 501,21ZM501,169C683.684,169 832,317.316 832,500C832,682.684 683.684,831 501,831C318.316,831 170,682.684 170,500C170,317.316 318.316,169 501,169Z" style="fill:rgb(255,38,84);"/>
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:rgb(255,38,84);"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 257 257" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g id="Cider-Vinyl" serif:id="Cider Vinyl" transform="matrix(2.36243,0,0,2.36243,-4.17297,-4.04967)">
<g id="Vinyl-Ring" serif:id="Vinyl Ring" transform="matrix(0.447755,0,0,0.447755,-245.339,-9.81703)">
<circle cx="673" cy="146.877" r="121.123" style="fill:url(#_Linear1);"/>
<clipPath id="_clip2">
<circle cx="673" cy="146.877" r="121.123"/>
</clipPath>
<g clip-path="url(#_clip2)">
<g transform="matrix(0.945365,0,0,0.945365,36.7693,8.02459)">
<circle cx="673" cy="48" r="101.585" style="fill:white;fill-opacity:0.02;"/>
</g>
</g>
</g>
<g id="Vinyl-Surface" serif:id="Vinyl Surface" transform="matrix(0.423292,0,0,0.423292,-250.04,-148.398)">
<circle cx="723" cy="482.753" r="118.247" style="fill:rgb(15,15,15);"/>
<clipPath id="_clip3">
<circle cx="723" cy="482.753" r="118.247"/>
</clipPath>
<g clip-path="url(#_clip3)">
<g transform="matrix(1,0,0,1,594.877,354.63)">
<path d="M128.123,14.126C191.04,14.126 242.121,65.206 242.121,128.123C242.121,191.04 191.04,242.121 128.123,242.121C65.206,242.121 14.126,191.04 14.126,128.123C14.126,65.206 65.206,14.126 128.123,14.126ZM128.123,21.141C187.168,21.141 235.105,69.078 235.105,128.123C235.105,187.168 187.168,235.105 128.123,235.105C69.078,235.105 21.141,187.168 21.141,128.123C21.141,69.078 69.078,21.141 128.123,21.141ZM128.123,60.877C165.238,60.877 195.37,91.009 195.37,128.123C195.37,165.238 165.238,195.37 128.123,195.37C91.009,195.37 60.877,165.238 60.877,128.123C60.877,91.009 91.009,60.877 128.123,60.877ZM128.123,65.015C162.954,65.015 191.231,93.293 191.231,128.123C191.231,162.954 162.954,191.231 128.123,191.231C93.293,191.231 65.015,162.954 65.015,128.123C65.015,93.293 93.293,65.015 128.123,65.015Z" style="fill:rgb(20,20,20);"/>
</g>
<g transform="matrix(1,0,0,1,594.877,354.63)">
<path d="M236.794,189.735L189.735,236.794L139.329,162.859L162.859,139.329L236.794,189.735ZM19.452,66.512L66.512,19.452L116.917,93.388L93.388,116.917L19.452,66.512Z" style="fill:rgb(37,37,37);"/>
</g>
</g>
</g>
<g id="Play-Button-Fill-Color" serif:id="Play Button Fill Color" transform="matrix(0.399516,0,0,0.399516,30.431,30.431)">
<circle cx="64" cy="64" r="43" style="fill:white;"/>
</g>
<g id="Play-Button" serif:id="Play Button" transform="matrix(0.0920925,0,0,0.0920925,9.86165,9.94759)">
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:url(#_Linear4);"/>
</g>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.22412e-13,242.247,-242.247,1.22412e-13,673,25.7534)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.3739e-14,551,-551,3.3739e-14,501,224)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 257 257" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g id="Cider-Vinyl" serif:id="Cider Vinyl" transform="matrix(2.36243,0,0,2.36243,-4.17297,-4.04967)">
<g id="Vinyl-Ring" serif:id="Vinyl Ring" transform="matrix(0.447755,0,0,0.447755,-245.339,-9.81703)">
<circle cx="673" cy="146.877" r="121.123" style="fill:url(#_Linear1);"/>
<clipPath id="_clip2">
<circle cx="673" cy="146.877" r="121.123"/>
</clipPath>
<g clip-path="url(#_clip2)">
<g transform="matrix(0.945365,0,0,0.945365,36.7693,8.02459)">
<circle cx="673" cy="48" r="101.585" style="fill:white;fill-opacity:0.02;"/>
</g>
</g>
</g>
<g id="Vinyl-Surface" serif:id="Vinyl Surface" transform="matrix(0.423292,0,0,0.423292,-250.04,-148.398)">
<circle cx="723" cy="482.753" r="118.247" style="fill:url(#_Linear3);"/>
<clipPath id="_clip4">
<circle cx="723" cy="482.753" r="118.247"/>
</clipPath>
<g clip-path="url(#_clip4)">
<g transform="matrix(1,0,0,1,594.877,354.63)">
<path d="M128.123,14.126C191.04,14.126 242.121,65.206 242.121,128.123C242.121,191.04 191.04,242.121 128.123,242.121C65.206,242.121 14.126,191.04 14.126,128.123C14.126,65.206 65.206,14.126 128.123,14.126ZM128.123,21.141C187.168,21.141 235.105,69.078 235.105,128.123C235.105,187.168 187.168,235.105 128.123,235.105C69.078,235.105 21.141,187.168 21.141,128.123C21.141,69.078 69.078,21.141 128.123,21.141ZM128.123,60.877C165.238,60.877 195.37,91.009 195.37,128.123C195.37,165.238 165.238,195.37 128.123,195.37C91.009,195.37 60.877,165.238 60.877,128.123C60.877,91.009 91.009,60.877 128.123,60.877ZM128.123,65.015C162.954,65.015 191.231,93.293 191.231,128.123C191.231,162.954 162.954,191.231 128.123,191.231C93.293,191.231 65.015,162.954 65.015,128.123C65.015,93.293 93.293,65.015 128.123,65.015Z" style="fill:rgb(20,20,20);"/>
</g>
<g transform="matrix(1,0,0,1,594.877,354.63)">
<path d="M236.794,189.735L189.735,236.794L139.329,162.859L162.859,139.329L236.794,189.735ZM19.452,66.512L66.512,19.452L116.917,93.388L93.388,116.917L19.452,66.512Z" style="fill:rgb(37,37,37);"/>
</g>
</g>
</g>
<g id="Play-Button-Fill-Color" serif:id="Play Button Fill Color" transform="matrix(0.399516,0,0,0.399516,30.431,30.431)">
<circle cx="64" cy="64" r="43" style="fill:white;"/>
</g>
<g id="Play-Button" serif:id="Play Button" transform="matrix(0.0920925,0,0,0.0920925,9.86165,9.94759)">
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:url(#_Linear5);"/>
</g>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.22412e-13,242.247,-242.247,1.22412e-13,673,25.7534)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.24529e-13,236.493,-236.493,2.24529e-13,723,364.507)"><stop offset="0" style="stop-color:rgb(15,15,15);stop-opacity:1"/><stop offset="1" style="stop-color:black;stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.3739e-14,551,-551,3.3739e-14,501,224)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -9,7 +9,7 @@
<a target="_blank" href="https://opencollective.com/ciderapp"><img src="https://img.shields.io/opencollective/all/ciderapp?color=%237FADF2&label=Backers%20and%20Sponsors&logo=opencollective" alt="Open Collective"/></a> <a target="_blank" href="https://opencollective.com/ciderapp"><img src="https://img.shields.io/opencollective/all/ciderapp?color=%237FADF2&label=Backers%20and%20Sponsors&logo=opencollective" alt="Open Collective"/></a>
<br> <br>
<a target="_blank" href="https://discord.gg/applemusic"><img src="https://img.shields.io/discord/843954443845238864?label=Discord&color=5865F2&logo=discord&logoColor=white&style=flat" alt="Discord"/></a> <a target="_blank" href="https://discord.gg/applemusic"><img src="https://img.shields.io/discord/843954443845238864?label=Discord&color=5865F2&logo=discord&logoColor=white&style=flat" alt="Discord"/></a>
<a target="_blank" href="https://twitter.com/CollectiveCider"><img src="https://img.shields.io/twitter/follow/CollectiveCider?label=Twitter&color=%231DA1F2&logo=twitter&style=flat" alt="Twitter"/></a> <a target="_blank" href="https://twitter.com/UseCider"><img src="https://img.shields.io/twitter/follow/UseCider?label=Twitter&color=%231DA1F2&logo=twitter&style=flat" alt="Twitter"/></a>
<a target="_blank" href="https://reddit.com/r/applemusicelectron"><img src="https://custom-icon-badges.herokuapp.com/reddit/subreddit-subscribers/applemusicelectron?label=Reddit&color=FF5700&logo=redditnew" alt="Reddit"/></a> <a target="_blank" href="https://reddit.com/r/applemusicelectron"><img src="https://custom-icon-badges.herokuapp.com/reddit/subreddit-subscribers/applemusicelectron?label=Reddit&color=FF5700&logo=redditnew" alt="Reddit"/></a>
<br><br> <br><br>
<img src="https://github.com/ciderapp/Cider/actions/workflows/build-analyze-win.yml/badge.svg" alt="Windows Build Status"/> <img src="https://github.com/ciderapp/Cider/actions/workflows/build-analyze-win.yml/badge.svg" alt="Windows Build Status"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Before After
Before After

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,84 @@
app-id: org.cidercollective.cider
branch: main
runtime: org.freedesktop.Platform
runtime-version: '21.08'
sdk: org.freedesktop.Sdk
# Use the Electron 2 BaseApp, which adds several common libraries we'll need.
base: org.electronjs.Electron2.BaseApp
base-version: '21.08'
# Add the Node 10 SDK extension.
sdk-extensions:
- org.freedesktop.Sdk.Extension.node14
# Electron doesn't use a traditional locale format so separate-locales is useless.
separate-locales: false
command: cider
finish-args:
# These two lines add Xorg access for graphics.
- '--share=ipc'
- '--socket=x11'
# Sound access.
- '--socket=pulseaudio'
# Network access.
- '--share=network'
# If you need to access the filesystem, also add:
# - '--filesystem=home'
modules:
# With electron-webpack and electron-builder we don't install Node to /app/node,
# because electron-builder will bundle everything for us in one piece.
# Instead we jump straight to the quick start module.
# However, since this quick start uses yarn, we do have to install that.
- name: yarn
buildsystem: simple
build-commands:
- 'cp -a * /app'
# Only used for building, so clean it up afterwards.
cleanup:
- '*'
sources:
- type: archive
url: https://github.com/yarnpkg/yarn/releases/download/v1.22.17/yarn-v1.22.17.tar.gz
sha256: 267982c61119a055ba2b23d9cf90b02d3d16c202c03cb0c3a53b9633eae37249
- name: cider
buildsystem: simple
build-options:
# Add the node bin directory & yarn directory.
append-path: '/usr/lib/sdk/node14/bin:/app/yarn/bin'
env:
# Set the Electron cache directory.
# (The directory format is: /run/build/MODULE_NAME/flatpak-node/electron-cache)
ELECTRON_CACHE: '/run/build/cider/flatpak-node/electron-cache'
# Sets the directory where Node is located so way npm won't download the headers.
npm_config_nodedir: '/usr/lib/sdk/node14'
build-commands:
# Have Yarn use the offline mirror.
- 'HOME=$PWD yarn config --offline set yarn-offline-mirror $FLATPAK_BUILDER_BUILDDIR/flatpak-node/yarn-mirror'
# Download the packages.
- 'yarn install --offline'
# If you were using npm with electron-webpack/electron-builder, then the above two commands
# would look more like the npm commands in the vanilla-quick-start manifest, just without
# the --prefix.
# Run electron-builder, passing the architecture arguments to it.
# Note that the -- is important; without that, the argument will be passed to
# yarn instead of electron-builder.
- '. flatpak-node/electron-builder-arch-args.sh; yarn run --offline electron-builder build --linux dir -- $ELECTRON_BUILDER_ARCH_ARGS'
# Copy the resulting, unpacked directory to /app.
# (A glob is used because the directory name may contain the current arch.)
- 'cp -r dist/linux*unpacked /app/cider'
# If you passed --electron-non-patented-ffmpeg, you could install it like this:
# - 'install -Dm 755 flatpak-node/libffmpeg.so -t /app/electron-webpack-quick-start'
# Install the wrapper script to start it.
- 'install -Dm 755 cider.sh /app/bin/cider'
sources:
- type: git
url: https://github.com/ciderapp/cider
branch: main
# Add the flatpak-node-generator generated sources.
- generated-sources.json
# Our runner script.
- type: script
dest-filename: cider.sh
commands:
- '/app/cider/cider'

262
index.js
View file

@ -1,262 +0,0 @@
require('v8-compile-cache');
const { app, components } = require('electron'), { resolve, join } = require("path"),
CiderBase = require('./src/main/cider-base');
const customProtocols = require('./package.json').fileAssociations[0].protocols
console.log(customProtocols)
const comps = components;
// Analytics for debugging.
const ElectronSentry = require("@sentry/electron");
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" });
const configDefaults = {
"general": {
"close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray
"startup_behavior": 0, // 0 = nothing, 1 = open on startup
"discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music
"discordClearActivityOnPause": 1, // 0 = disabled, 1 = enabled
"volume": 1
},
"home": {
"followedArtists": [],
"favoriteItems": []
},
"libraryPrefs": {
"songs": {
"sort": "name",
"sortOrder": "asc",
"size": "normal"
}
},
"audio": {
"quality": "990",
"seamless_audio": true,
"normalization": false,
"spatial": false,
"spatial_properties": {
"presets": [],
"gain": 0.8,
"listener_position": [0, 0, 0],
"audio_position": [0, 0, 0],
"room_dimensions": {
"width": 32,
"height": 12,
"depth": 32
},
"room_materials": {
"left": 'metal',
"right": 'metal',
"front": 'brick-bare',
"back": 'brick-bare',
"down": 'acoustic-ceiling-tiles',
"up": 'acoustic-ceiling-tiles',
}
}
},
"visual": {
"theme": "",
"scrollbars": 0, // 0 = show on hover, 2 = always hide, 3 = always show
"refresh_rate": 0,
"animated_artwork": "limited", // 0 = always, 1 = limited, 2 = never
"animated_artwork_qualityLevel": 1,
"bg_artwork_rotation": false,
"hw_acceleration": "default", // default, webgpu, disabled
"videoRes": 720
},
"lyrics": {
"enable_mxm": false,
"mxm_karaoke": false,
"mxm_language": "en",
"enable_yt": false,
},
"lastfm": {
"enabled": false,
"scrobble_after": 50,
"auth_token": "",
"enabledRemoveFeaturingArtists": true,
"NowPlaying": "true"
},
"advanced": {
"AudioContext": false,
"experiments": []
}
}
const merge = (target, source) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (const key of Object.keys(source)) {
if (source[key] instanceof Object) Object.assign(source[key], merge(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
const Store = require("electron-store");
app.cfg = new Store({
defaults: configDefaults
});
let currentCfg = app.cfg.get()
app.cfg.set(merge(configDefaults, currentCfg))
app.paths = {
ciderCache: resolve(app.getPath("userData"), "CiderCache"),
themes: resolve(app.getPath("userData"), "Themes"),
plugins: resolve(app.getPath("userData"), "Plugins"),
}
switch (app.cfg.get("visual.hw_acceleration")) {
default:
case "default":
app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
app.commandLine.appendSwitch('enable-accelerated-video')
app.commandLine.appendSwitch('disable-gpu-driver-bug-workarounds')
app.commandLine.appendSwitch('ignore-gpu-blacklist')
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers')
app.commandLine.appendSwitch('enable-accelerated-video-decode');
app.commandLine.appendSwitch('enable-gpu-rasterization');
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
app.commandLine.appendSwitch('enable-oop-rasterization');
break;
case "webgpu":
console.info("WebGPU is enabled.");
app.commandLine.appendSwitch('enable-unsafe-webgpu')
break;
case "disabled":
console.info("Hardware acceleration is disabled.");
app.commandLine.appendSwitch('disable-gpu')
break;
}
// Creating the Application Window and Calling all the Functions
function CreateWindow() {
if (app.isQuiting) {
app.quit();
return;
}
/** CIDER **/
const ciderwin = require("./src/main/cider-base")
app.win = ciderwin
app.win.Start()
/** CIDER **/
}
if (process.platform === "linux") {
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
}
app.commandLine.appendSwitch('no-sandbox');
// app.commandLine.appendSwitch('js-flags', '--max-old-space-size=1024')
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* App Event Handlers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
app.whenReady().then(async() => {
if (process.platform === "win32") {
app.commandLine.appendSwitch('high-dpi-support', 'true')
app.commandLine.appendSwitch('force-device-scale-factor', '1')
app.commandLine.appendSwitch('disable-pinch');
}
if (comps == null) {
app.on("widevine-ready", () => {
console.log('[Cider] Application is Ready. Creating Window.')
if (!app.isPackaged) {
console.info('[Cider] Running in development mode.')
require('vue-devtools').install()
}
CreateWindow()
})
return
}
await comps.whenReady();
console.log('components ready:', comps.status());
console.log('[Cider] Application is Ready. Creating Window.')
if (!app.isPackaged) {
console.info('[Cider] Running in development mode.')
require('vue-devtools').install()
}
CreateWindow()
})
app.on('before-quit', () => {
console.warn(`${app.getName()} exited.`);
});
// Widevine Stuff
app.on('widevine-ready', (version, lastVersion) => {
if (null !== lastVersion) {
console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
} else {
console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
}
})
app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
})
app.on('widevine-error', (error) => {
console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
app.exit()
})
if (process.defaultApp) {
if (process.argv.length >= 2) {
customProtocols.forEach((customProtocol) => {
app.setAsDefaultProtocolClient(customProtocol, process.execPath, [resolve(process.argv[1])])
})
}
} else {
/*
* cider - Custom Cider Protocol
* ame - Custom AME Protocol (Backwards Compat.)
* itms - iTunes HTTP Protocol
* itmss - iTunes HTTPS Protocol
* musics - macOS Client Protocol
* music - macOS Client Protocol
*/
customProtocols.forEach((customProtocol) => {
app.setAsDefaultProtocolClient(customProtocol)
})
}
app.on('open-url', (event, url) => {
event.preventDefault()
if (customProtocols.some(protocol => url.includes(protocol))) {
CiderBase.LinkHandler(url)
}
})
app.on('second-instance', (_e, argv) => {
console.warn(`[InstanceHandler][SecondInstanceHandler] Second Instance Started with args: [${argv.join(', ')}]`)
// Checks if first instance is authorized and if second instance has protocol args
argv.forEach((value) => {
if (customProtocols.some(protocol => value.includes(protocol))) {
CiderBase.LinkHandler(value);
}
})
if (argv.includes("--force-quit")) {
console.warn('[InstanceHandler][SecondInstanceHandler] Force Quit found. Quitting App.');
// app.isQuiting = true
app.quit()
} else if (CiderBase.win && true) { // If a Second Instance has Been Started
console.warn('[InstanceHandler][SecondInstanceHandler] Showing window.');
app.win.show()
app.win.focus()
}
})
if (!app.requestSingleInstanceLock() && true) {
console.warn("[InstanceHandler] Existing Instance is Blocking Second Instance.");
app.quit();
// app.isQuiting = true
}

View file

@ -2,10 +2,11 @@
"name": "cider", "name": "cider",
"applicationId": "Cider", "applicationId": "Cider",
"productName": "Cider", "productName": "Cider",
"version": "1.0.0", "version": "1.1.0",
"description": "A new look into listening and enjoying music in style and performance.", "description": "A new look into listening and enjoying music in style and performance.",
"license": "MIT", "license": "AGPL-3.0",
"author": "Cider Collective <cryptofyre@cryptofyre.org> (https://cider.sh)", "main": "./build/index.js",
"author": "Cider Collective <cryptofyre@cider.sh> (https://cider.sh)",
"repository": "https://github.com/ciderapp/Cider.git", "repository": "https://github.com/ciderapp/Cider.git",
"bugs": { "bugs": {
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug" "url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
@ -13,11 +14,16 @@
"homepage": "https://cider.sh/", "homepage": "https://cider.sh/",
"buildResources": "resources", "buildResources": "resources",
"scripts": { "scripts": {
"init": "yarn install --force", "build": "tsc",
"start": "electron . --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers", "watch": "tsc --watch",
"start": "run-script-os",
"start:win32": "npm run build && set ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
"start:linux": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
"start:darwin": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
"pack": "electron-builder --dir", "pack": "electron-builder --dir",
"dist": "electron-builder", "dist": "npm run build && electron-builder",
"msft": "electron-builder -c msft-package.json", "dist:all": "npm run build && electron-builder -mwl",
"msft": "npm run build && electron-builder -c msft-package.json",
"postinstall": "electron-builder install-app-deps" "postinstall": "electron-builder install-app-deps"
}, },
"dependencies": { "dependencies": {
@ -25,29 +31,39 @@
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"ejs": "^3.1.6", "ejs": "^3.1.6",
"electron-fetch": "^1.7.4", "electron-fetch": "^1.7.4",
"electron-log": "^4.4.3", "electron-log": "^4.4.4",
"electron-notarize": "^1.1.1",
"electron-packager": "^15.4.0",
"electron-store": "^8.0.1", "electron-store": "^8.0.1",
"electron-updater": "^4.6.1", "electron-updater": "^4.6.1",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"express": "^4.17.2", "express": "^4.17.2",
"get-port": "^5.1.1", "get-port": "^5.1.1",
"jsonc": "^2.0.0",
"lastfmapi": "^0.1.1", "lastfmapi": "^0.1.1",
"mdns-js": "github:bitfocus/node-mdns-js",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"music-metadata": "^7.11.4", "music-metadata": "^7.11.4",
"qrcode": "^1.5.0",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"run-script-os": "^1.1.6",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"v8-compile-cache": "^2.3.0", "v8-compile-cache": "^2.3.0",
"ws": "^8.3.0", "ws": "^8.4.2",
"xml2js": "^0.4.23", "xml2js": "^0.4.23",
"youtube-search-without-api-key": "^1.0.7" "youtube-search-without-api-key": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
"@types/discord-rpc": "^4.0.0",
"@types/express": "^4.17.13",
"@types/ws": "^8.2.2",
"electron": "https://github.com/castlabs/electron-releases.git", "electron": "https://github.com/castlabs/electron-releases.git",
"electron-builder": "^22.14.5", "electron-builder": "^22.14.5",
"electron-webpack": "^2.8.2", "electron-webpack": "^2.8.2",
"musickit-typescript": "^1.2.4", "musickit-typescript": "^1.2.4",
"typescript": "^4.5.4",
"vue-devtools": "^5.1.4", "vue-devtools": "^5.1.4",
"webpack": "~5.65.0" "webpack": "~5.65.0"
}, },
@ -74,12 +90,14 @@
} }
], ],
"build": { "build": {
"electronVersion": "16.0.6", "electronVersion": "16.0.7",
"electronDownload": { "electronDownload": {
"version": "16.0.6+wvcus", "version": "16.0.7+wvcus",
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v" "mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
}, },
"appId": "cider", "appId": "cider",
"afterPack": "./resources/afterPack.js",
"afterSign": "./resources/notarize.js",
"protocols": [ "protocols": [
{ {
"name": "Cider", "name": "Cider",
@ -95,9 +113,9 @@
], ],
"extends": null, "extends": null,
"files": [ "files": [
"**/*", "./build/**/*",
"./src/**/*", "./resources/icons/icon.*",
"./resources/icons/icon.*" "./src/**/*"
], ],
"linux": { "linux": {
"target": [ "target": [
@ -119,6 +137,13 @@
"backgroundColor": "transparent", "backgroundColor": "transparent",
"setBuildNumber": true "setBuildNumber": true
}, },
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"license": "LICENSE",
"deleteAppDataOnUninstall": true
},
"win": { "win": {
"target": [ "target": [
"nsis" "nsis"
@ -126,13 +151,20 @@
"icon": "resources/icons/icon.ico" "icon": "resources/icons/icon.ico"
}, },
"directories": { "directories": {
"buildResources": "." "buildResources": ".",
"output": "dist"
}, },
"mac": { "mac": {
"hardenedRuntime": true,
"gatekeeperAssess": false,
"icon": "./resources/icons/icon.icns", "icon": "./resources/icons/icon.icns",
"category": "public.app-category.music", "category": "public.app-category.music",
"entitlements": "resources/entitlements.mac.plist", "entitlements": "./resources/entitlements.mac.plist",
"darkModeSupport": true "entitlementsInherit": "./resources/entitlements.mac.plist",
"darkModeSupport": true,
"target": [
"dmg"
]
} }
} }
} }

18
resources/afterPack.js Normal file
View file

@ -0,0 +1,18 @@
exports.default = function(context) {
const { execSync } = require('child_process')
if (process.platform !== 'darwin')
return
console.log('Castlabs-evs update start')
execSync('python3 -m pip install --upgrade castlabs-evs')
console.log('Castlabs-evs update complete')
console.log('VMP signing start')
execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac',{stdio: 'inherit'})
console.log('VMP signing complete')
}

View file

@ -2,11 +2,13 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<!-- https://github.com/electron/electron-notarize#prerequisites -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key> <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/> <true/>
<!-- https://github.cm/electron-userland/electron-builder/issues/3940 -->
<key>com.apple.security.cs.disable-library-validation</key> <key>com.apple.security.cs.disable-library-validation</key>
<true/> <true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<false/>
</dict> </dict>
</plist> </plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 295 KiB

Before After
Before After

20
resources/notarize.js Normal file
View file

@ -0,0 +1,20 @@
require('dotenv').config();
const { notarize } = require('electron-notarize');
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
return;
}
const appName = context.packager.appInfo.productFilename;
return await notarize({
appBundleId: 'com.ciderapp.cider',
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLEID,
appleIdPassword: process.env.APPLEIDPASS,
});
};

8
src/i18n/README.md Normal file
View file

@ -0,0 +1,8 @@
# Cider i18n
Some notes about Cider's i18n support.
* Localization files are stored in jsonc format aka "JSON with Comments"
* The default language is English.
* The default language is used for messages that are not translated.
* Try when possible to keep the messages the similar in length to the English ones.
* Most of the strings in the content area are provided and translated by Apple themselves, and do not need to be translated.
* The language Apple Music uses are dependent on the storefront region.

299
src/i18n/de_DE.jsonc Normal file
View file

@ -0,0 +1,299 @@
{ // Base File
// i18n Info
"i18n.languageName": "Deutsch", // name of language in native language
"i18n.languageNameEnglish": "German", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@motz0815", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${d}.${m}.${y}",
// Dialogs
"dialog.cancel": "Abbrechen",
"dialog.ok": "OK",
// Notification
"notification.updatingLibrarySongs": "Aktualisiere Songs...",
"notification.updatingLibraryAlbums": "Aktualisiere Alben...",
"notification.updatingLibraryArtists": "Aktualisiere Künstler...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "Mehr erfahren",
"term.accountSettings": "Account-Einstellungen",
"term.logout": "Abmelden",
"term.login": "Anmelden",
"term.about": "Über",
"term.privateSession": "Private Sitzung",
"term.queue": "Warteschlange",
"term.search": "Suche",
"term.library": "Mediathek",
"term.listenNow": "Jetzt Hören",
"term.browse": "Durchsuchen",
"term.radio": "Radio",
"term.recentlyAdded": "Zuletzt hinzugefügt",
"term.songs": "Songs",
"term.albums": "Alben",
"term.artists": "Künstler",
"term.podcasts": "Podcasts",
"term.playlists": "Playlists",
"term.playlist": "Playlist",
"term.play": "Play",
"term.pause": "Pause",
"term.previous": "Zurück",
"term.next": "Weiter",
"term.shuffle": "Zufällig",
"term.repeat": "Wiederholen",
"term.volume": "Lautstärke",
"term.mute": "Stummschalten",
"term.unmute": "Stummschaltung aufheben",
"term.share": "Teilen",
"term.settings": "Einstellungen",
"term.seeAll": "Alle Sehen",
"term.sortBy": "Sortieren nach",
"term.sortBy.album": "Album",
"term.sortBy.artist": "Künstler",
"term.sortBy.name": "Name",
"term.sortBy.genre": "Genre",
"term.sortBy.releaseDate": "Veröffentlichungsdatum",
"term.sortBy.duration": "Länge",
"term.sortOrder": "A-Z",
"term.sortOrder.ascending": "Aufsteigend",
"term.sortOrder.descending": "Absteigend",
"term.viewAs": "Ansehen als",
"term.viewAs.coverArt": "Cover-Bild",
"term.viewAs.list": "Liste",
"term.size": "Größe",
"term.size.normal": "Normal",
"term.size.compact": "Kompakt",
"term.enable": "Aktivieren",
"term.disable": "Deaktivieren",
"term.enabled": "Aktiv",
"term.disabled": "Nicht aktiv",
"term.connect": "Verbinden",
"term.connecting": "Verbindet",
"term.disconnect": "Trennen",
"term.authed": "Autorisiert",
"term.confirm": "Bestätigen ?",
"term.more": "Mehr",
"term.less": "Weniger",
"term.showMore": "Zeige mehr",
"term.showLess": "Zeige weniger",
"term.topSongs" : "Top Songs",
"term.latestReleases": "Letzte Veröffentlichungen",
"term.time.added": "Hinzugefügt",
"term.time.released": "Veröffentlicht",
"term.time.updated": "Aktualisiert",
"term.time.hours": "Stunden",
"term.time.hour": "Stunde",
"term.time.minutes": "Minuten",
"term.time.minute": "Minute",
"term.time.seconds": "Sekunden",
"term.time.second": "Sekunde",
"term.fullscreenView": "Vollbildansicht",
"term.defaultView": "Normale Ansicht",
"term.spacializedAudioSetting": "Räumliches Audio",
"term.clearAll": "Alle löschen",
"term.recentStations": "Letzte Stationen",
"term.language": "Sprache",
"term.funLanguages": "Spaß",
"term.noLyrics": "Lädt... / Liedtext nicht gefunden./ Instrumental.",
"term.copyright": "Copyright",
"term.rightsReserved": "All Rights Reserved.",
"term.sponsor": "Unterstütze dieses Projekt",
"term.ciderTeam": "Cider Team",
"term.developer": "Entwickler",
"term.socialTeam": "Social Team",
"term.contributors": "Mithelfer",
"term.equalizer": "Equalizer",
"term.reset": "Zurücksetzen",
"term.tracks": "Lieder", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "Videos",
"term.menu": "Menü",
"term.showAlbum": "Zeige ganzes Album",
// Home
"home.title": "Home",
"home.recentlyPlayed": "Zuletzt gespielt",
"home.recentlyAdded": "Zuletzt hinzugefügt",
"home.artistsFeed": "Dein Künstler-Feed",
"home.artistsFeed.noArtist": "Folge zuerst einigen Künstlern, dann wirst du hier die neusten Lieder sehen.",
"home.madeForYou": "Für dich gemacht",
"home.friendsListeningTo": "Freunde hören",
"home.followedArtists": "Gefolgte Künstler",
// Errors
"error.appleMusicSubRequired": "Apple Music benötigt ein Abonnement.",
"error.connectionError": "Es ist ein Fehler aufgetreten, während sich mit Apple Music verbunden wurde.",
"error.noResults": "Keine Ergebnisse.",
"error.noResults.description": "Versuche einen anderen Suchbegriff.",
//Podcasts
"podcast.followOnCider": "Folge auf Cider",
"podcast.followedOnCider": "Gefolgt auf Cider",
"podcast.subscribeOnItunes": "Folge auf iTunes",
"podcast.subscribedOnItunes": "Gefolgt auf iTunes",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Episoden",
"podcast.playEpisode": "Spiele Episode",
"podcast.website": "Podcast Webseite",
// Actions
"action.addToLibrary": "Zur Mediathek hinzufügen",
"action.addToLibrary.success": "Zur Mediathek hinzugefügt",
"action.addToLibrary.error": "Fehler beim Hinzufügen zur Mediathek",
"action.removeFromLibrary": "Aus Mediathek entfernen",
"action.removeFromLibrary.success": "Aus Mediathek entfernt",
"action.addToQueue": "Zur Warteschlange hinzufügen",
"action.addToQueue.success": "Zur Warteschlange hinzugefügt",
"action.addToQueue.error": "Fehler beim Hinzufügen zur Warteschlange",
"action.removeFromQueue": "Aus Warteschlange entfernen",
"action.removeFromQueue.success": "Aus Warteschlange entfernt",
"action.removeFromQueue.error": "Fehler beim Entfernen aus der Warteschlange",
"action.addToPlaylist": "Zur Playlist hinzufügen",
"action.removeFromPlaylist": "Aus Playlist entfernen",
"action.addToFavorites": "Zu Favoriten hinzufügen",
"action.follow": "Folgen",
"action.follow.success": "Gefolgt",
"action.follow.error": "Fehler beim Folgen",
"action.unfollow": "Entfolgen",
"action.unfollow.success": "Entfolgt",
"action.unfollow.error": "Fehler beim Entfolgen",
"action.playNext": "Spiele als Nächstes",
"action.playLater": "Spiele später",
"action.startRadio": "Starte Radio",
"action.goToArtist": "Gehe zu Künstler",
"action.goToAlbum": "Gehe zu Album",
"action.moveToTop": "Gehe zum Anfang",
"action.share": "Teilen",
"action.rename": "Umbenennen",
"action.love": "Lieben",
"action.unlove": "Entlieben",
"action.dislike": "Dislike",
"action.undoDislike": "Dislike entfernen",
"action.showWebRemoteQR": "Zeige Web-Remote QR-Code",
"action.playTracksNext": "Spiele ${app.selectedMediaItems.length} Lieder als Nächstes",
"action.playTracksLater": "Spiele ${app.selectedMediaItems.length} Lieder später",
"action.removeTracks": "Entferne ${self.selectedItems.length} Lieder aus der Warteschlange",
"action.import": "Importieren",
"action.export": "Exportieren",
// Settings - General
"settings.header.general": "Allgemein",
"settings.header.general.description": "Passe die allgemeinen Einstellungen für Cider an.",
"settings.option.general.language": "Sprache",
// Language optgroups
"settings.option.general.language.main": "Sprachen",
"settings.option.general.language.fun": "Lustige Sprachen",
"settings.option.general.language.unsorted": "Unsortiert",
// Settings - Audio
"settings.header.audio": "Audio",
"settings.header.audio.description": "Passe die Audio-Einstellungen für Cider an.",
"settings.option.audio.quality": "Audioqualität", // Dropdown
"settings.header.audio.quality.high": "Hoch",
"settings.header.audio.quality.low": "Niedrig",
"settings.header.audio.quality.auto": "Auto",
"settings.option.audio.seamlessTransition": "Nahtloser Audioübergang", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Akiviere erweiterte Funktionalität", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "Das Aktivieren der erweiterten Funktionalität ermöglicht spezielle Features wie Audio-Normalisierung, Equalizer und Visualizer, jedoch könnte dies auf einigen Systemen zu Aussetzern in der Musik führen.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Audio-Normalisierung", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalisiert die Lautstärke aller Lieder, um die Hörerfahrung einheitlicher zu machen.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Räumliches Audio", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Macht die Musik räumlicher (3D-Effekt) (Beachte: Dies ist kein Dolby Atmos)",
// Settings - Visual
"settings.header.visual": "Visuell",
"settings.header.visual.description": "Passe die visuellen Einstellungen für Cider an.",
"settings.option.visual.windowBackgroundStyle": "Fensterhintergrund-Stil", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "Keiner",
"settings.header.visual.windowBackgroundStyle.artwork": "Coverbild",
"settings.header.visual.windowBackgroundStyle.image": "Bild",
"settings.option.visual.animatedArtwork": "Animiertes Coverbild", // Dropdown
"settings.header.visual.animatedArtwork.always": "Immer",
"settings.header.visual.animatedArtwork.limited": "Beschränkt auf Seiten und Spezialeinträge",
"settings.header.visual.animatedArtwork.disable": "Überall deaktivieren",
"settings.option.visual.animatedArtworkQuality": "Qualität des Animierten Coverbilds", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Niedrig",
"settings.header.visual.animatedArtworkQuality.medium": "Mittel",
"settings.header.visual.animatedArtworkQuality.high": "Hoch",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Sehr hoch",
"settings.header.visual.animatedArtworkQuality.extreme": "Extrem",
"settings.option.visual.animatedWindowBackground": "Animierter Fensterhintergrund", // Toggle
"settings.option.visual.hardwareAcceleration": "Hardware-Beschleunigung", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Erfordert Neustart der Anwendung",
"settings.header.visual.hardwareAcceleration.default": "Normal",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Persönliche Daten anzeigen", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "Liedtext",
"settings.header.lyrics.description": "Passe die Liedtext-Einstellungen für Cider an.",
"settings.option.lyrics.enableMusixmatch": "Aktiviere Musixmatch-Liedtexte", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Aktiviere Karaoke-Modus (nur mit Musixmatch)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Bevorzugte Sprache für Musixmatch-Übersetzung", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "Aktiviere YouTube-Liedtexte für Musikvideos", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Konnektivität",
"settings.header.connectivity.description": "Passe die Konnektivitäts-Einstellungen für Cider an.",
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
"settings.option.connectivity.playbackNotifications": "Wiedergabe-Benachrichtigungen", // Toggle
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "Zeige als 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "Zeige als 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "Leere Discord Rich Presence wenn pausiert", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble Delay (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Aktiviere LastFM Jetzt spielend",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Entferne Künstler-Featuring von Liedtitel (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filtere gelooptes Lied (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Experimentell",
"settings.header.experimental.description": "Passe die experimentellen Einstellungen für Cider an.",
"settings.option.experimental.compactUI": "Kompaktes UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Verhalten der Schließtaste",
"settings.option.experimental.closeButtonBehaviour.quit": "Cider Schließen",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "In Taskleiste minimieren",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "In den Tray minimieren",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "Räumliche Einstellungen",
"spatial.width" : "Breite",
"spatial.height" : "Höhe",
"spatial.depth" : "Tiefe",
"spatial.gain" : "Gain",
"spatial.roomMaterials" : "Raummaterialien",
"spatial.roomDimensions" : "Raumabmessungen",
"spatial.roomPositions" : "Raumpositionen",
"spatial.setDimensions" : "Setze Abmessungen",
"spatial.setPositions" : "Setze Positionen",
"spatial.up" : "Oben",
"spatial.front" : "Vorne",
"spatial.left" : "Links",
"spatial.right" : "Rechts",
"spatial.back" : "Hinten",
"spatial.down" : "Unten",
"spatial.listener" : "Hörer*in",
"spatial.audioSource" : "Audioquelle",
// Settings - Unfinished
"settings.header.unfinished": "Unfertig",
// Web Remote
"remote.web.title": "Cider Remote",
"remote.web.description": "Scanne den QR-Code um dein Handy mit dieser Cider-Instanz zu verbinden",
// About
"about.thanks": "Vielen lieben Dank an das Cider Collective Team und an alle Mithelfer."
}

299
src/i18n/el_GR.jsonc Normal file
View file

@ -0,0 +1,299 @@
{
// i18n Info
"i18n.languageName": "Ελληνικά",
"i18n.languageNameEnglish": "Greek",
"i18n.category": "main",
"i18n.authors": "@down-bad",
// App info
"app.name": "Cider",
"date.format": "${d} ${m}, ${y}",
// Dialogs
"dialog.cancel": "Ακύρωση",
"dialog.ok": "ΟΚ",
// Notification
"notification.updatingLibrarySongs": "Ενημέρωση βιβλιοθήκης τραγουδιών...",
"notification.updatingLibraryAlbums": "Ενημέρωση βιβλιοθήκης άλμπουμ...",
"notification.updatingLibraryArtists": "Ενημέρωση βιβλιοθήκης καλλιτεχνών...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "Μάθετε περισσότερα",
"term.accountSettings": "Ρυθμίσεις λογαριασμού",
"term.logout": "Αποσύνδεση",
"term.login": "Σύνδεση",
"term.about": "Σχετικά με",
"term.privateSession": "Ιδιωτική περίοδος λειτουργίας",
"term.queue": "Ουρά",
"term.search": "Εύρεση",
"term.library": "Βιβλιοθήκη",
"term.listenNow": "Ακρόαση",
"term.browse": "Περιήγηση",
"term.radio": "Ράδιο",
"term.recentlyAdded": "Πρόσφατες προσθήκες",
"term.songs": "Τραγούδια",
"term.albums": "Άλμπουμ",
"term.artists": "Καλλιτέχνες",
"term.podcasts": "Podcast",
"term.playlists": "Λίστες αναπαραγωγής",
"term.playlist": "Λίστα αναπαραγωγής",
"term.play": "Αναπαραγωγή",
"term.pause": "Παύση",
"term.previous": "Προηγούμενο",
"term.next": "Επόμενο",
"term.shuffle": "Τυχαία σειρά",
"term.repeat": "Επανάληψη",
"term.volume": "Ένταση",
"term.mute": "Σίγαση",
"term.unmute": "Κατάργηση σίγασης",
"term.share": "Κοινή Χρήση",
"term.settings": "Ρυθμίσεις",
"term.seeAll": "Προβολή όλων",
"term.sortBy": "Ταξινόμηση κατά",
"term.sortBy.album": "Άλμπουμ",
"term.sortBy.artist": "Καλλιτέχνη",
"term.sortBy.name": "Όνομα",
"term.sortBy.genre": "Είδος",
"term.sortBy.releaseDate": "Ημερομηνία κυκλοφορίας",
"term.sortBy.duration": "Διάρκεια",
"term.sortOrder": "Α-Ω",
"term.sortOrder.ascending": "Αύξουσα",
"term.sortOrder.descending": "Φθίνουσα",
"term.viewAs": "Προβολή ως",
"term.viewAs.coverArt": "Εξώφυλλο",
"term.viewAs.list": "Λίστα",
"term.size": "Μέγεθος",
"term.size.normal": "Κανονικό",
"term.size.compact": "Συμπαγή",
"term.enable": "Ενεργοποίηση",
"term.disable": "Απενεργοποίηση",
"term.enabled": "Ενεργοποιημένο",
"term.disabled": "Απενεργοποιημένο",
"term.connect": "Σύνδεση",
"term.connecting": "Γίνεται σύνδεση",
"term.disconnect": "Αποσύνδεση",
"term.authed": "Επικυρωμένο",
"term.confirm": "Σίγουρα;",
"term.more": "Περισσότερα",
"term.less": "Λιγότερα",
"term.showMore": "Εμφάνιση περισσότερων",
"term.showLess": "Εμφάνιση λιγότερων",
"term.topSongs" : "Κορυφαία τραγούδια",
"term.latestReleases": "Τελευταίες κυκλοφορίες",
"term.time.added": "Προστέθηκε",
"term.time.released": "Κυκλοφόρησε",
"term.time.updated": "Ενημερώθηκε",
"term.time.hours": "ώρες",
"term.time.hour": "ώρα",
"term.time.minutes": "λεπτά",
"term.time.minute": "λεπτό",
"term.time.seconds": "δευτερόλεπτα",
"term.time.second": "δευτερόλεπτο",
"term.fullscreenView": "Πλήρης οθόνη",
"term.defaultView": "Κανονική οθόνη",
"term.spacializedAudioSetting": "Χωρική ρύθμιση ήχου",
"term.clearAll": "Εκκαθάριση όλων",
"term.recentStations": "Πρόσφατοι σταθμοί",
"term.language": "Γλώσσα",
"term.funLanguages": "Για πλάκα",
"term.noLyrics": "Φόρτωση... / Δεν βρέθηκαν στίχοι. / Ορχηστικό.",
"term.copyright": "Copyright",
"term.rightsReserved": "Όλα τα δικαιώματα διατηρούνται.",
"term.sponsor": "Χορήγησε αυτό το έργο",
"term.ciderTeam": "Ομάδα Cider",
"term.developer": "Προγραμματιστής",
"term.socialTeam": "Κοινωνική Ομάδα",
"term.contributors": "Συνεισφέροντες",
"term.equalizer": "Ισοσταθμιστής",
"term.reset": "Επαναφορά",
"term.tracks": "τραγούδια", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "Βίντεο",
"term.menu": "Μενού",
"term.showAlbum": "Εμφάνιση ολόκληρου άλμπουμ",
// Home
"home.title": "Αρχική",
"home.recentlyPlayed": "Έπαιξαν πρόσφατα",
"home.recentlyAdded": "Πρόσφατες προσθήκες",
"home.artistsFeed": "Ροή των καλλιτεχνών σου",
"home.artistsFeed.noArtist": "Ακολούθησε μερικούς καλλιτέχνες πρώτα και οι τελευταίες κυκλοφορίες τους θα εμφανίζονται εδώ",
"home.madeForYou": "Δημιουργήθηκε για εσάς",
"home.friendsListeningTo": "Οι φίλοι σου ακούν",
"home.followedArtists": "Καλλιτέχνες που ακολουθείτε",
// Errors
"error.appleMusicSubRequired": "Το Apple Music απαιτεί μια συνδρομή.",
"error.connectionError": "Δεν είναι δυνατή η σύνδεση με το Apple Music.",
"error.noResults": "Κανένα αποτέλεσμα.",
"error.noResults.description": "Δοκιμάστε μια νέα αναζήτηση.",
//Podcasts
"podcast.followOnCider": "Ακολούθηση στο Cider",
"podcast.followedOnCider": "Ακολουθείτε στο Cider",
"podcast.subscribeOnItunes": "Συνδρομή στο iTunes",
"podcast.subscribedOnItunes": "Συνδρομητής στο iTunes",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Επεισόδια",
"podcast.playEpisode": "Αναπαραγωγή επεισοδίου",
"podcast.website": "Ιστότοπος Podcast",
// Actions
"action.addToLibrary": "Προσθήκη στη βιβλιοθήκη",
"action.addToLibrary.success": "Προστέθηκε στη βιβλιοθήκη",
"action.addToLibrary.error": "Σφάλμα Προσθήκης στη βιβλιοθήκης",
"action.removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
"action.removeFromLibrary.success": "Αφαιρέθηκε από τη βιβλιοθήκη",
"action.addToQueue": "Προσθήκη στην ουρά",
"action.addToQueue.success": "Προστέθηκε στην ουρά",
"action.addToQueue.error": "Προστέθηκε στην ουρά",
"action.removeFromQueue": "Αφαίρεση από την ουρά",
"action.removeFromQueue.success": "Αφαιρέθηκε από την ουρά",
"action.removeFromQueue.error": "Σφάλμα Αφαίρεσης από την ουρά",
"action.addToPlaylist": "Προσθήκη σε λίστα",
"action.removeFromPlaylist": "Αφαίρεση από λίστα",
"action.addToFavorites": "Προσθήκη στα αγαπημένα",
"action.follow": "Ακολούθηση",
"action.follow.success": "Ακολουθήθηκε",
"action.follow.error": "Σφάλμα ακολούθησης",
"action.unfollow": "Διακοπή ακολούθησης",
"action.unfollow.success": "Έγινε διακοπή ακολούθησης",
"action.unfollow.error": "Σφάλμα διακοπής ακολούθησης ",
"action.playNext": "Αναπαραγωγή ως επόμενου",
"action.playLater": "Αναπαραγωγή αργότερα",
"action.startRadio": "Έναρξη ραδιοφώνου",
"action.goToArtist": "Μετάβαση σε καλλιτέχνη",
"action.goToAlbum": "Μετάβαση σε άλμπουμ",
"action.moveToTop": "Μετακίνηση στη κορυφή",
"action.share": "Κοινή χρήση",
"action.rename": "Μετονομασία",
"action.love": "Μου αρέσει πολύ",
"action.unlove": "Αναίρεση \"Μου αρέσει\"",
"action.dislike": "Δεν μου αρέσει",
"action.undoDislike": "Αναίρεση \"Δεν μου αρέσει\"",
"action.showWebRemoteQR": "Εμφάνιση Web Remote QR",
"action.playTracksNext": "Αναπαραγωγή ${app.selectedMediaItems.length} τραγουδιών ως επόμενων",
"action.playTracksLater": "Αναπαραγωγή ${app.selectedMediaItems.length} τραγουδιών αργότερα",
"action.removeTracks": "Αφαίρεση ${self.selectedItems.length} τραγουδιών από την ουρά",
"action.import": "Εισαγωγή",
"action.export": "Εξαγωγή",
// Settings - General
"settings.header.general": "Γενικά",
"settings.header.general.description": "Προσαρμογή γενικών ρυθμίσεων για το Cider.",
"settings.option.general.language": "Γλώσσα",
// Language optgroups
"settings.option.general.language.main": "Γλώσσες",
"settings.option.general.language.fun": "Γλώσσες για πλάκα",
"settings.option.general.language.unsorted": "Αταξινόμητες",
// Settings - Audio
"settings.header.audio": "Ήχος",
"settings.header.audio.description": "Προσαρμογή ρυθμίσεων ήχου για το Cider.",
"settings.option.audio.quality": "Ποιότητα Ήχου", // Dropdown
"settings.header.audio.quality.high": "Υψηλή",
"settings.header.audio.quality.low": "Χαμηλή",
"settings.header.audio.quality.auto": "Αυτόματη",
"settings.option.audio.seamlessTransition": "Αδιάκοπη Μετάβαση Ήχου", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Ενεργοποίηση Προηγμένης Λειτουργικότητας", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "Ενεργοποιώντας τη λειτουργικότητα AudioContext θα επιτρέψει σε επεκταμένες δυνατότητες ήχου όπως Κανονικοποίηση Έντασης Ήχου, Ισοσταθμιστές και Οπτικοποιητές, ωστόσο σε κάποια συστήματα μπορεί να προκαλέσει τραύλισμα ήχου.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Κανονικοποίηση Έντασης Ήχου", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Κανονικοποιεί την ένταση για μεμονωμένα κομμάτια για μια πιο ομοιόμορφη εμπειρία ακρόασης.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Χωρικοποίηση Ήχου", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Πιο τρισδιάστατος και χωρικοποιημένος ήχος (σημείωση: Αυτό δεν είναι Dolby Atmos)",
// Settings - Visual
"settings.header.visual": "Οπτικά",
"settings.header.visual.description": "Προσαρμογή οπτικών ρυθμίσεων για το Cider.",
"settings.option.visual.windowBackgroundStyle": "Στυλ Φόντου Παραθύρου", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "Κανένα",
"settings.header.visual.windowBackgroundStyle.artwork": "Εξώφυλλο",
"settings.header.visual.windowBackgroundStyle.image": "Εικόνα",
"settings.option.visual.animatedArtwork": "Κινούμενο Εξώφυλλο", // Dropdown
"settings.header.visual.animatedArtwork.always": "Πάντα",
"settings.header.visual.animatedArtwork.limited": "Περιορισμός σε σελίδες και ειδικές καταχωρήσεις",
"settings.header.visual.animatedArtwork.disable": "Απενεργοποιημένο παντού",
"settings.option.visual.animatedArtworkQuality": "Ποιότητα Κινούμενου Εξωφύλλου", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Χαμηλή",
"settings.header.visual.animatedArtworkQuality.medium": "Μέτρια",
"settings.header.visual.animatedArtworkQuality.high": "Υψηλή",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Πολύ Υψηλή",
"settings.header.visual.animatedArtworkQuality.extreme": "Ακραία",
"settings.option.visual.animatedWindowBackground": "Κινούμενο Φόντο Παραθύρου", // Toggle
"settings.option.visual.hardwareAcceleration": "Επιτάχυνση Υλικού", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Απαιτεί επανεκκίνηση",
"settings.header.visual.hardwareAcceleration.default": "Προεπιλογή",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Εμφάνιση προσωπικών στοιχείων", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "Στίχοι",
"settings.header.lyrics.description": "Προσαρμογή ρυθμίσεων στίχων για το Cider.",
"settings.option.lyrics.enableMusixmatch": "Ενεργοποίηση Στίχων Musixmatch", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Ενεργοποίηση Λειτουργίας Καραόκε (Musixmatch μόνο)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Προτιμώμενη Γλώσσα Μετάφρασης Musixmatch", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "Ενεργοποίηση Στίχων Youtube για Μουσικά Βίντεο", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Σύνδεση",
"settings.header.connectivity.description": "Προσαρμογή ρυθμίσεων σύνδεσης για το Cider.",
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
"settings.option.connectivity.playbackNotifications": "Ειδοποιήσεις Αναπαραγωγής", // Toggle
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "Εμφάνιση ως 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "Εμφάνιση ως 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "Εκκαθάριση του Discord Rich Presence στην Παύση", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "Καθυστέρηση LastFM Scrobble (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Ενεργοποίηση LastFM \"Now Playing\"",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Αφαίρεση καλλιτεχνών feature από τον τίτλο του τραγουδιού (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Φιλτράρισμα επανειλημμένου τραγουδιού (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Πειραματικές",
"settings.header.experimental.description": "Προσαρμογή πειραματικών ρυθμίσεων για το Cider.",
"settings.option.experimental.compactUI": "Συμπαγής Διεπαφή", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Συμπεριφορά Κουμπιού Εξόδου",
"settings.option.experimental.closeButtonBehaviour.quit": "Έξοδος του Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Ελαχιστοποίηση στη γραμμή εργασιών",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Ελαχιστοποίηση στη γωνία γραμμής εργασιών",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "Χωρικές Ιδιότητες",
"spatial.width" : "Πλάτος",
"spatial.height" : "Ύψος",
"spatial.depth" : "Βάθος",
"spatial.gain" : "Απολαβή",
"spatial.roomMaterials" : "Υλικά Δωματίου",
"spatial.roomDimensions" : "Διαστάσεις Δωματίου",
"spatial.roomPositions" : "Θέσεις Δωματίου",
"spatial.setDimensions" : "Ορισμός Διαστάσεων",
"spatial.setPositions" : "Ορισμός Θέσεων",
"spatial.up" : "Πάνω",
"spatial.front" : "Πρόσοψη",
"spatial.left" : "Αριστερά",
"spatial.right" : "Δεξιά",
"spatial.back" : "Πίσω Όψη",
"spatial.down" : "Κάτω",
"spatial.listener" : "Ακροατής",
"spatial.audioSource" : "Πηγή Ήχου",
// Settings - Unfinished
"settings.header.unfinished": "Ημιτελής",
// Web Remote
"remote.web.title": "Cider Remote",
"remote.web.description": "Σαρώστε τον κωδικό QR για σύζευξη του Cider με το κινητό σας",
//About
"about.thanks": "Μεγάλα ευχαριστώ στην Ομάδα Cider Collective και σε όλους τους συνεισφέροντές μας."
}

10
src/i18n/en_GB.jsonc Normal file
View file

@ -0,0 +1,10 @@
{
// i18n Info
"i18n.languageName": "English (UK) (Roadman)", // name of language in native language
"i18n.languageNameEnglish": "English (UK)", // name of language in English
"i18n.category": "fun", // main = real language, fun = fun community languages
"i18n.authors": "Core", // Authors, if you contribute to this file feel free to add your name seperated with a space
"date.format": "${d} ${m}, ${y}",
"home.friendsListeningTo": "Mandems Listening To"
}

296
src/i18n/en_HODOR.jsonc Normal file
View file

@ -0,0 +1,296 @@
{ // HODOR
// i18n Info
"i18n.languageName": "HODOR", // name of language in native language
"i18n.languageNameEnglish": "HODOR", // name of language in English
"i18n.category": "fun", // main = real language, fun = fun community languages
"i18n.authors": "HODOR", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "HODOR",
"date.format": "${m} ${d}, ${y}",
// Dialogs
"dialog.cancel": "HODOR",
"dialog.ok": "HODOR",
// Notification
"notification.updatingLibrarySongs": "HODOR HODOR HODOR...",
"notification.updatingLibraryAlbums": "HODOR HODOR HODOR...",
"notification.updatingLibraryArtists": "HODOR HODOR HODOR...",
// Terms
"term.appleInc": "HODOR Inc.",
"term.appleMusic": "HODOR HODOR",
"term.applePodcasts": "HODOR HODOR",
"term.itunes": "HODOR",
"term.github": "HODOR",
"term.discord": "HODOR",
"term.learnMore": "HODOR HODOR",
"term.accountSettings": "HODOR HODOR",
"term.logout": "HODOR",
"term.login": "HODOR",
"term.about": "HODOR",
"term.privateSession": "HODOR HODOR",
"term.queue": "HODOR",
"term.search": "HODOR",
"term.library": "HODOR",
"term.listenNow": "HODOR HODOR",
"term.browse": "HODOR",
"term.radio": "HODOR",
"term.recentlyAdded": "HODOR HODOR",
"term.songs": "HODOR",
"term.albums": "HODOR",
"term.artists": "HODOR",
"term.podcasts": "HODOR",
"term.playlists": "HODOR",
"term.playlist": "HODOR",
"term.play": "HODOR",
"term.pause": "HODOR",
"term.previous": "HODOR",
"term.next": "HODOR",
"term.shuffle": "HODOR",
"term.repeat": "HODOR",
"term.volume": "HODOR",
"term.mute": "HODOR",
"term.unmute": "HODOR",
"term.share": "HODOR",
"term.settings": "HODOR",
"term.seeAll": "HODOR HODOR",
"term.sortBy": "HODOR HODOR",
"term.sortBy.album": "HODOR",
"term.sortBy.artist": "HODOR",
"term.sortBy.name": "HODOR",
"term.sortBy.genre": "HODOR",
"term.sortBy.releaseDate": "HODOR HODOR",
"term.sortBy.duration": "HODOR",
"term.sortOrder": "HODOR-HODOR",
"term.sortOrder.ascending": "HODOR",
"term.sortOrder.descending": "HODOR",
"term.viewAs": "HODOR HODOR",
"term.viewAs.coverArt": "HODOR HODOR",
"term.viewAs.list": "HODOR",
"term.size": "HODOR",
"term.size.normal": "HODOR",
"term.size.compact": "HODOR",
"term.enable": "HODOR",
"term.disable": "HODOR",
"term.enabled": "HODOR",
"term.disabled": "HODOR",
"term.connect": "HODOR",
"term.connecting": "HODOR",
"term.disconnect": "HODOR",
"term.authed": "HODOR",
"term.confirm": "HODOR ?",
"term.more": "HODOR",
"term.less": "HODOR",
"term.showMore": "HODOR HODOR",
"term.showLess": "HODOR HODOR",
"term.topSongs" : "HODOR HODOR",
"term.latestReleases": "HODOR HODOR",
"term.time.added": "HODOR",
"term.time.released": "HODOR",
"term.time.updated": "HODOR",
"term.fullscreenView": "HODOR HODOR",
"term.defaultView": "HODOR HODOR",
"term.spacializedAudioSetting": "HODOR HODOR HODOR",
"term.clearAll": "HODOR HODOR",
"term.recentStations": "HODOR HODOR",
"term.language": "HODOR",
"term.noLyrics": "HODOR... / HODOR HODOR HODOR./ HODOR.",
"term.copyright": "HODOR",
"term.rightsReserved": "HODOR HODOR HODOR.",
"term.sponsor": "HODOR HODOR HODOR",
"term.ciderTeam": "HODOR HODOR",
"term.developer": "HODOR",
"term.socialTeam": "HODOR HODOR",
"term.contributors": "HODOR",
"term.equalizer": "HODOR",
"term.reset": "HODOR",
"term.tracks": "HODOR", // Assume x amount of tracks. e.g. 50 tracks
"term.time.hours": "HODOR",
"term.time.hour": "HODOR",
"term.time.minutes": "HODOR",
"term.time.minute": "HODOR",
"term.time.seconds": "HODOR",
"term.time.second": "HODOR",
"term.funLanguages": "HODOR",
"term.videos": "HODOR",
"term.menu": "HODOR",
"term.showAlbum": "HODOR HODOR HODOR",
// Home
"home.title": "HODOR",
"home.recentlyPlayed": "HODOR HODOR",
"home.recentlyAdded": "HODOR HODOR",
"home.artistsFeed": "HODOR HODOR HODOR",
"home.artistsFeed.noArtist": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR",
"home.madeForYou": "HODOR HODOR HODOR",
"home.friendsListeningTo": "HODOR HODOR HODOR",
"home.followedArtists": "HODOR HODOR",
// Errors
"error.appleMusicSubRequired": "HODOR HODOR HODOR HODOR HODOR.",
"error.connectionError": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR.",
"error.noResults": "HODOR HODOR.",
"error.noResults.description": "HODOR HODOR HODOR HODOR.",
//Podcasts
"podcast.followOnCider": "HODOR HODOR HODOR",
"podcast.followedOnCider": "HODOR HODOR HODOR",
"podcast.subscribeOnItunes": "HODOR HODOR HODOR",
"podcast.subscribedOnItunes": "HODOR HODOR HODOR",
"podcast.itunesStore": "HODOR HODOR",
"podcast.episodes": "HODOR",
"podcast.playEpisode": "HODOR HODOR",
"podcast.website": "HODOR HODOR",
// Actions
"action.addToLibrary": "HODOR HODOR HODOR",
"action.addToLibrary.success": "HODOR HODOR HODOR",
"action.addToLibrary.error": "HODOR HODOR HODOR LiHODORbrary",
"action.removeFromLibrary": "HODOR HODOR HODOR",
"action.removeFromLibrary.success": "HODOR HODOR HODOR",
"action.addToQueue": "HODOR HODOR HODOR",
"action.addToQueue.success": "HODOR HODOR HODOR",
"action.addToQueue.error": "HODOR HODOR HODOR HODOR",
"action.removeFromQueue": "HODOR HODOR HODOR",
"action.removeFromQueue.success": "HODOR HODOR HODOR",
"action.removeFromQueue.error": "HODOR HODOR HODOR HODOR",
"action.addToPlaylist": "HODOR HODOR HODOR",
"action.removeFromPlaylist": "HODOR HODOR HODOR",
"action.addToFavorites": "HODOR HODOR HODOR",
"action.follow": "HODOR",
"action.follow.success": "HODOR",
"action.follow.error": "HODOR HODOR",
"action.unfollow": "HODOR",
"action.unfollow.success": "HODOR",
"action.unfollow.error": "HODOR HODOR",
"action.playNext": "HODOR HODOR",
"action.playLater": "HODOR HODOR",
"action.startRadio": "HODOR HODOR",
"action.goToArtist": "HODOR HODOR HODOR",
"action.goToAlbum": "HODOR HODOR HODOR",
"action.moveToTop": "HODOR HODOR HODOR",
"action.share": "HODOR",
"action.rename": "HODOR",
"action.love": "HODOR",
"action.unlove": "HODOR",
"action.dislike": "HODOR",
"action.undoDislike": "HODOR HODOR",
"action.showWebRemoteQR": "HODOR HODOR HODOR HODOR",
"action.playTracksNext": "HODOR ${app.selectedMediaItems.length} HODOR HODOR",
"action.playTracksLater": "HODOR ${app.selectedMediaItems.length} HODOR HODOR",
"action.removeTracks": "HODOR ${self.selectedItems.length} HODOR HODOR HODOR",
"action.import": "HODOR",
"action.export": "HODOR",
// Settings - General (Reserved)
"settings.header.general": "HODOR HODOR",
"settings.header.general.description": "HODOR HODOR HODOR HODOR.",
"settings.option.general.language": "HODOR",
// Language optgroups
"settings.option.general.language.main": "HODOR",
"settings.option.general.language.fun": "HODOR HODOR",
"settings.option.general.language.unsorted": "HODOR",
// Settings - Audio
"settings.header.audio": "HODOR",
"settings.header.audio.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
"settings.option.audio.quality": "HODOR HODOR", // Dropdown
"settings.header.audio.quality.high": "HODOR.",
"settings.header.audio.quality.low": "HODOR!",
"settings.header.audio.quality.auto": "HODOR",
"settings.option.audio.seamlessTransition": "HODOR HODOR HODOR", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "HODOR HODOR HODOR", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR , HODOR HODOR HODOR, HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "HODOR HODOR", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "HODOR HODOR", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "HODOR HODOR HODOR HODOR HODOR HODOR 3-HODOR (HODOR: HODOR HODOR HODOR HODOR HODOR)",
// Settings - Visual
"settings.header.visual": "HODOR",
"settings.header.visual.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
"settings.option.visual.windowBackgroundStyle": "HODOR HODOR HODOR", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "HODOR",
"settings.header.visual.windowBackgroundStyle.artwork": "HODOR",
"settings.option.visual.animatedArtwork": "HODOR HODOR", // Dropdown
"settings.header.visual.animatedArtwork.always": "HODOR",
"settings.header.visual.animatedArtwork.limited": "HODOR HODOR HODOR HODOR HODOR HODOR",
"settings.header.visual.animatedArtwork.disable": "HODOR HODOR",
"settings.option.visual.animatedArtworkQuality": "HODOR HODOR HODOR", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "HODOR..",
"settings.header.visual.animatedArtworkQuality.medium": "HODOR.",
"settings.header.visual.animatedArtworkQuality.high": "HODOR!",
"settings.header.visual.animatedArtworkQuality.veryHigh": "HODOR HODOR!",
"settings.header.visual.animatedArtworkQuality.extreme": "HODOOOR!!",
"settings.option.visual.animatedWindowBackground": "HODOR HODOR HODOR", // Toggle
"settings.option.visual.hardwareAcceleration": "HODOR HODOR", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "HODOR HODOR",
"settings.header.visual.hardwareAcceleration.default": "HODOR.",
"settings.header.visual.hardwareAcceleration.webGPU": "HODOR!!",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "HODOR HODOR HODOR?", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "HODOR",
"settings.header.lyrics.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
"settings.option.lyrics.enableMusixmatch": "HODOR HODOR HODOR", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "HODOR HODOR HODOR (HODOR HODOR)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "HODOR HODOR HODOR HODOR", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "HODOR HODOR HODOR HODOR HODOR HODOR", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "HODOR",
"settings.header.connectivity.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
"settings.option.connectivity.discordRPC": "HODOR HODOR HODOR", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "HODOR HODOR 'HODOR'",
"settings.header.connectivity.discordRPC.appleMusic": "HODOR HODOR 'HODOR HODOR'",
"settings.option.connectivity.discordRPC.clearOnPause": "HODOR HODOR HODOR HODOR HODOR HODOR", // Toggle
"settings.option.connectivity.lastfmScrobble": "HODOR HODOR", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "HODOR HODOR HODOR (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "HODOR HODOR HODOR HODOR",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "HODOR HODOR HODOR HODOR HODOR HODOR (HODOR)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "HODOR HODOR HODOR (HODOR)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "HODOR",
"settings.header.experimental.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
"settings.option.experimental.compactUI": "HODOR UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "HODOR HODOR HODOR",
"settings.option.experimental.closeButtonBehaviour.quit": "HODOR HODOR",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "HODOR HODOR HODOR",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "HODOR HODOR HODOR HODOR",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "HODOR HODOR",
"spatial.width" : "HODOR",
"spatial.height" : "HODOR",
"spatial.depth" : "HODOR",
"spatial.roomMaterials" : "HODOR HODOR",
"spatial.roomDimensions" : "HODOR HODOR",
"spatial.roomPositions" : "HODOR HODOR",
"spatial.setDimensions" : "HODOR HODOR",
"spatial.setPositions" : "HODOR HODOR",
"spatial.up" : "HODOR",
"spatial.front" : "HODOR",
"spatial.left" : "HODOR",
"spatial.right" : "HODOR",
"spatial.back" : "HODOR",
"spatial.down" : "HODOR",
"spatial.listener" : "HODOR",
"spatial.audioSource" : "HODOR HODOR",
// Settings - Unfinished
"settings.header.unfinished": "HODOR",
// Web Remote
"remote.web.title": "HODOR HODOR",
"remote.web.description": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR",
//About
"about.thanks": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR."
}

297
src/i18n/en_SGA.jsonc Normal file
View file

@ -0,0 +1,297 @@
{
// i18n Info
"i18n.languageName": "┤ᖋ|:ᖋᔮᒣ╎ᔮ ᖋ|:i!⍑ᖋᕊᒷᒣ", // name of language in native language
"i18n.languageNameEnglish": "Galactic Alphabet", // name of language in English
"i18n.category": "fun", // main = real language, fun = fun community languages
"i18n.authors": "@kyw504100", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "ᔮ╎↸ᒷ∷",
"date.format": "${m} ${d}, ${y}",
// Dialogs
"dialog.cancel": "ᔮᖋリᔮᒷ|:",
"dialog.ok": "ᒍ·ǀ·",
// Notification
"notification.updatingLibrarySongs": "⚍i!↸ᖋᒣ╎リ┤ |:╎ᕊ∷ᖋ∷॥ ϟᒍリ┤ϟ...",
"notification.updatingLibraryAlbums": "⚍i!↸ᖋᒣ╎リ┤ |:╎ᕊ∷ᖋ∷॥ ᖋ|:ᕊ⚍ᒲϟ...",
"notification.updatingLibraryArtists": "⚍i!↸ᖋᒣ╎リ┤ |:╎ᕊ∷ᖋ∷॥ ᖋ∷ᒣ╎ϟᒣϟ...",
// Terms
"term.appleInc": "ᖋi!i!|:ᒷ ╎リᔮ.",
"term.appleMusic": "ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ",
"term.applePodcasts": "ᖋi!i!|:ᒷ i!ᒍ↸ᔮᖋϟᒣϟ",
"term.itunes": "╎ᒣ⚍リᒷϟ",
"term.github": "┤╎ᒣ⍑⚍ᕊ",
"term.discord": "↸╎ϟᔮᒍ∷↸",
"term.learnMore": "|:ᒷᖋ∷リ ᒲᒍ∷ᒷ",
"term.accountSettings": "ᖋᔮᔮᒍ⚍リᒣ ϟᒷᒣᒣ╎リ┤ϟ",
"term.logout": "|:ᒍ┤ᒍ⚍ᒣ",
"term.login": "|:ᒍ┤╎リ",
"term.about": "ᖋᕊᒍ⚍ᒣ",
"term.privateSession": "I!∷╎⍊ᖋᒣᒷ ϟᒷϟϟ╎ᒍリ",
"term.queue": "ᑑ⚍ᒷ⚍ᒷ",
"term.search": "ϟᒷᖋ∷ᔮ⍑",
"term.library": "|:╎ᕊ∷ᖋ∷॥",
"term.listenNow": "|:╎ϟᒣᒷリ",
"term.browse": "リᒍ∴",
"term.radio": "∷ᖋ↸╎ᒍ",
"term.recentlyAdded": "∷ᒷᔮᒷリᒣ|:॥ ᖋ↸↸ᒷ↸",
"term.songs": "ϟᒍリ┤ϟ",
"term.albums": "ᖋ|:ᕊ⚍ᒲϟ",
"term.artists": "ᖋ∷ᒣ╎ϟᒣϟ",
"term.podcasts": "I!ᒍ↸ᔮᖋϟᒣϟ",
"term.playlists": "i!|:ᖋ॥|:╎ϟᒣϟ",
"term.playlist": "i!|:ᖋ॥|:╎ϟᒣ",
"term.play": "i!|:ᖋ॥",
"term.pause": "I!ᖋ⚍ϟᒷ",
"term.previous": "I!∷ᒷ⍊╎ᒍ⚍ϟ",
"term.next": "リᒷ/ᒣ",
"term.shuffle": "ϟ⍑⚍⎓⎓|:ᒷ",
"term.repeat": "∷ᒷi!ᒷᖋᒣ",
"term.volume": "⍊ᒍ|:⚍ᒲᒷ",
"term.mute": "ᒲ⚍ᒣᒷ",
"term.unmute": "⚍リᒲ⚍ᒣᒷ",
"term.share": "ϟ⍑ᖋ∷ᒷ",
"term.settings": "ϟᒷᒣᒣ╎リ┤ϟ",
"term.seeAll": "ϟᒷᒷ ᖋ|:|:",
"term.sortBy": "ϟᒍ∷ᒣ ᕊ॥",
"term.sortBy.album": "ᖋ|:ᕊ⚍ᒲ",
"term.sortBy.artist": "ᖋ∷ᒣ╎ϟᒣ",
"term.sortBy.name": "リᖋᒲᒷ",
"term.sortBy.genre": "┤ᒷリ∷ᒷ",
"term.sortBy.releaseDate": "∷ᒷ|:ᒷᖋϟᒷ ↸ᖋᒣᒷ",
"term.sortBy.duration": "↸⚍∷ᖋᒣ╎ᒍリ",
"term.sortOrder": "ᖋ-∩",
"term.sortOrder.ascending": "ᖋϟᔮᒷリ↸╎リ┤",
"term.sortOrder.descending": "↸ᒷϟᔮᒷリ↸╎リ┤",
"term.viewAs": "⍊╎ᒷ∴ ᖋϟ",
"term.viewAs.coverArt": "ᔮᒍ⍊ᒷ∷ ᖋ∷ᒣ",
"term.viewAs.list": "|:╎ϟᒣ",
"term.size": "ϟ╎∩ᒷ",
"term.size.normal": "リᒍ∷ᒲᖋ|:",
"term.size.compact": "ᔮᒲi!ᖋᔮᒣ",
"term.enable": "ᒷリᖋᕊ|:ᒷ",
"term.disable": "↸╎ϟᖋᕊ|:ᒷ",
"term.enabled": "ᒷリᖋᕊ|:ᒷ↸",
"term.disabled": "↸╎ϟᖋᕊ|:ᒷ↸",
"term.connect": "ᔮᒍリリᒷᔮᒣ",
"term.connecting": "ᔮᒍリリᒷᔮᒣ╎リ┤",
"term.disconnect": "↸╎ϟᔮᒍリリᒷᔮᒣ",
"term.authed": "ᖋ⚍ᒣ⍑ᒷ↸",
"term.confirm": "ᔮᒍリ⎓╎∷ᒲ ?",
"term.more": "ᒲᒍ∷ᒷ",
"term.less": "|:ᒷϟϟ",
"term.showMore": "ϟ⍑ᒍ∴ ᒲᒍ∷ᒷ",
"term.showLess": "ϟ⍑ᒍ∴ |:ᒷϟϟ",
"term.topSongs" : "ᒣi! ϟᒍリ┤ϟ",
"term.latestReleases": "|:ᖋᒣᒷϟᒣ ∷ᒷ|:ᒷᖋϟᒷϟ",
"term.time.added": "ᖋ↸↸ᒷ↸",
"term.time.released": "∷ᒷ|:ᒷᖋϟᒷ↸",
"term.time.updated": "⚍i!↸ᖋᒣᒷ↸",
"term.time.hours": "⍑ᒍ⚍∷ϟ",
"term.time.hour": "⍑ᒍ⚍∷",
"term.time.minutes": "ᒲ╎リ⚍ᒣᒷϟ",
"term.time.minute": "ᒲ╎リ⚍ᒣᒷ",
"term.time.seconds": "ϟᒷᔮᒍリ↸ϟ",
"term.time.second": "ϟᒷᔮᒍリ↸",
"term.fullscreenView": "⎓⚍|:|:ϟᔮ∷ᒷᒷリ ⍊╎ᒷ∴",
"term.defaultView": "↸ᒷ⎓ᖋ⚍|:ᒣ ⍊╎ᒷ∴",
"term.spacializedAudioSetting": "ϟi!ᖋᔮ╎ᖋ|:╎∩ᒷ↸ ᖋ⚍↸╎ᒍ ϟᒷᒣᒣ╎リ┤",
"term.clearAll": "ᔮ|:ᒷᖋ∷ ᖋ|:|:",
"term.recentStations": "∷ᒷᔮᒷリᒣ ϟᒣᖋᒣ╎ᒍリϟ",
"term.language": "|:ᖋリ┤⚍ᖋ┤ᒷ",
"term.funLanguages": "⎓⚍リ",
"term.noLyrics": "|:ᒍᖋ↸╎リ┤... / |:॥∷╎ᔮϟ リᒍᒣ ⎓ᒍ⚍リ↸./ ╎リϟᒣ∷⚍ᒲᒷリᒣᖋ|:.",
"term.copyright": "ᔮi!॥∷╎┤⍑ᒣ",
"term.rightsReserved": "ᖋ|:|: ∷╎┤⍑ᒣϟ ∷ᒷϟᒷ∷⍊ᒷ↸.",
"term.sponsor": "ϟi!ᒍリϟᒍ∷ ᒣ⍑╎ϟ i!∷ᒍ⋮ᒷᔮᒣ",
"term.ciderTeam": "ᔮ╎↸ᒷ∷ ᒣᒷᖋᒲ",
"term.developer": "↸ᒷ⍊ᒷ|:i!ᒷ∷",
"term.socialTeam": "ϟᒍᔮ╎ᖋ|: ᒣᒷᖋᒲ",
"term.contributors": "ᔮᒍリᒣ∷╎ᕊ⚍ᒣᒍ∷ϟ",
"term.equalizer": "ᒷᑑ⚍ᖋ|:╎∩ᒷ∷",
"term.reset": "∷ᒷϟᒷᒣ",
"term.tracks": "ᒣ∷ᖋᔮ·ǀ·ϟ", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "⍊╎↸ᒷᒍ",
"term.menu": "ᒲᒷ⚍リ",
// Home
"home.title": "⍑ᒍᒲᒷ",
"home.recentlyPlayed": "∷ᒷᔮᒷリᒣ|:॥ i!|:ᖋ॥ᒷ↸",
"home.recentlyAdded": "∷ᒷᔮᒷリᒣ|:॥ ᖋ↸↸ᒷ↸",
"home.artistsFeed": "॥ᒍ⚍∷ ᖋ∷ᒣ╎ϟᒣϟ ⎓ᒷᒷ↸",
"home.artistsFeed.noArtist": "⎓ᒍ|:|:ᒍ∴ ϟᒍᒲᒷ ᖋ∷ᒣ╎ϟᒣϟ ⎓╎∷ϟᒣ ᖋリ↸ ᒣ⍑ᒷ╎∷ |:ᖋᒣᒷϟᒣ ∷ᒷ|:ᒷᖋϟᒷϟ ∴╎|:|: ᕊᒷ ⍑ᒷ∷ᒷ",
"home.madeForYou": "ᒲᖋ↸ᒷ ⎓ᒍ∷ ॥ᒍ⚍",
"home.friendsListeningTo": "⎓∷╎ᒷリ↸ϟ |:╎ϟᒣᒷリ╎リ┤ ᒣᒍ",
"home.followedArtists": "⎓ᒍ|:|:ᒍ∴ᒷ↸ ᖋ∷ᒣ╎ϟᒣϟ",
// Errors
"error.appleMusicSubRequired": "ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ ∷ᒷᑑ⚍╎∷ᒷϟ ᖋ ϟ⚍ᕊϟᔮ∷╎i!ᒣ╎ᒍリ.",
"error.connectionError": "ᒣ⍑ᒷ∷ᒷ ∴ᖋϟ ᖋ i!∷ᒍᕊ|:ᒷᒲ ᔮᒍリリᒷᔮᒣ╎リ┤ ᒣᒍ ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ.",
"error.noResults": "リᒍ ∷ᒷϟ⚍|:ᒣϟ.",
"error.noResults.description": "ᒣ∷॥ ᖋ リᒷ∴ ϟᒷᖋ∷ᔮ⍑.",
//Podcasts
"podcast.followOnCider": "⎓ᒍ|:|:ᒍ∴ ᒍリ ᔮ╎↸ᒷ∷",
"podcast.followedOnCider": "⎓ᒍ|:|:ᒍ∴╎リ┤ ᒍリ ᔮ╎↸ᒷ∷",
"podcast.subscribeOnItunes": "ϟ⚍ᕊϟᔮ∷╎ᕊᒷ ᒍリ ╎ᒣ⚍リᒷϟ",
"podcast.subscribedOnItunes": "ϟ⚍ᕊϟᔮ∷╎ᕊᒷ↸ ᒍリ ╎ᒣ⚍リᒷϟ",
"podcast.itunesStore": "╎ᒣ⚍リᒷϟ ϟᒣᒍ∷ᒷ",
"podcast.episodes": "ᒷi!╎ϟᒍ↸ᒷϟ",
"podcast.playEpisode": "i!|:ᖋ॥ ᒷi!╎ϟᒍ↸ᒷ",
"podcast.website": "I!ᒍ↸ᔮᖋϟᒣ ∴ᒷᕊϟ╎ᒣᒷ",
// Actions
"action.addToLibrary": "ᖋ↸↸ ᒣᒍ |:╎ᕊ∷ᖋ∷॥",
"action.addToLibrary.success": "ᖋ↸↸ᒷ↸ ᒣᒍ |:╎ᕊ∷ᖋ∷॥",
"action.addToLibrary.error": "ᒷ∷∷ᒍ∷ ᖋ↸↸╎リ┤ ᒣᒍ |:╎ᕊ∷ᖋ∷॥",
"action.removeFromLibrary": "∷ᒷᒲᒍ⍊ᒷ ⎓∷ᒍᒲ |:╎ᕊ∷ᖋ∷॥",
"action.removeFromLibrary.success": "∷ᒷᒲᒍ⍊ᒷ↸ ⎓∷ᒍᒲ |:╎ᕊ∷ᖋ∷॥",
"action.addToQueue": "ᖋ↸↸ ᒣᒍ ᑑ⚍ᒷ⚍ᒷ",
"action.addToQueue.success": "ᖋ↸↸ᒷ↸ ᒣᒍ ᑑ⚍ᒷ⚍ᒷ",
"action.addToQueue.error": "ᒷ∷∷ᒍ∷ ᖋ↸↸╎リ┤ ᒣᒍ ᑑ⚍ᒷ⚍ᒷ",
"action.removeFromQueue": "∷ᒷᒲᒍ⍊ᒷ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
"action.removeFromQueue.success": "∷ᒷᒲᒍ⍊ᒷ↸ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
"action.removeFromQueue.error": "ᒷ∷∷ᒍ∷ ∷ᒷᒲᒍ⍊╎リ┤ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
"action.addToPlaylist": "ᖋ↸↸ ᒣᒍ i!|:ᖋ॥|:╎ϟᒣ",
"action.removeFromPlaylist": "∷ᒷᒲᒍ⍊ᒷ ⎓∷ᒍᒲ i!|:ᖋ॥|:╎ϟᒣ",
"action.addToFavorites": "ᖋ↸↸ ᒣᒍ ⎓ᖋ⍊ᒍ∷╎ᒣᒷϟ",
"action.follow": "⎓ᒍ|:|:ᒍ∴",
"action.follow.success": "⎓ᒍ|:|:ᒍ∴ᒷ↸",
"action.follow.error": "ᒷ∷∷ᒍ∷ ⎓ᒍ|:|:ᒍ∴╎リ┤",
"action.unfollow": "⚍リ⎓ᒍ|:|:ᒍ∴",
"action.unfollow.success": "⚍リ⎓ᒍ|:|:ᒍ∴ᒷ↸",
"action.unfollow.error": "ᒷ∷∷ᒍ∷ ⚍リ⎓ᒍ|:|:ᒍ∴╎リ┤",
"action.playNext": "i!|:ᖋ॥ リᒷ/ᒣ",
"action.playLater": "i!|:ᖋ॥ |:ᖋᒣᒷ∷",
"action.startRadio": "ϟᒣᖋ∷ᒣ ∷ᖋ↸╎ᒍ",
"action.goToArtist": "┤ᒍ ᒣᒍ ᖋ∷ᒣ╎ϟᒣ",
"action.goToAlbum": "┤ᒍ ᒣᒍ ᖋ|:ᕊ⚍ᒲ",
"action.moveToTop": "ᒲᒍ⍊ᒷ ᒣᒍ ᒣi!",
"action.share": "ϟ⍑ᖋ∷ᒷ",
"action.rename": "∷ᒷリᖋᒲᒷ",
"action.love": "|:ᒍ⍊ᒷ",
"action.unlove": "⚍リ|:ᒍ⍊ᒷ",
"action.dislike": "↸╎ϟ|:╎·ǀ·ᒷ",
"action.undoDislike": "⚍リ↸ᒍ ↸╎ϟ|:╎·ǀ·ᒷ",
"action.showWebRemoteQR": "ϟ⍑ᒍ∴ ∴ᒷᕊ ∷ᒷᒲᒍᒣᒷ ᑑ∷",
"action.playTracksNext": "i!|:ᖋ॥ ${app.selectedMediaItems.length} ᒣ∷ᖋᔮ·ǀ·ϟ リᒷ/ᒣ",
"action.playTracksLater": "i!|:ᖋ॥ ${app.selectedMediaItems.length} ᒣ∷ᖋᔮ·ǀ·ϟ |:ᖋᒣᒷ∷",
"action.removeTracks": "∷ᒷᒲᒍ⍊ᒷ ${self.selectedItems.length} ᒣ∷ᖋᔮ·ǀ·ϟ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
"action.import": "╎ᒲi!ᒍ∷ᒣ",
"action.export": "ᒷ/i!ᒍ∷ᒣ",
// Settings - General
"settings.header.general": "┤ᒷリᒷ∷ᖋ|:",
"settings.header.general.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ┤ᒷリᒷ∷ᖋ|: ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
"settings.option.general.language": "|:ᖋリ┤⚍ᖋ┤ᒷ",
// Language optgroups
"settings.option.general.language.main": "|:ᖋリ┤⚍ᖋ┤ᒷϟ",
"settings.option.general.language.fun": "⎓⚍リ |:ᖋリ┤⚍ᖋ┤ᒷϟ",
"settings.option.general.language.unsorted": "⚍リϟᒍ∷ᒣᒷ↸",
// Settings - Audio
"settings.header.audio": "ᖋ⚍↸╎ᒍ",
"settings.header.audio.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ᖋ⚍↸╎ᒍ ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
"settings.option.audio.quality": "ᖋ⚍↸╎ᒍ ᑑ⚍ᖋ|:╎ᒣ॥", // Dropdown
"settings.header.audio.quality.high": "⍑╎┤⍑",
"settings.header.audio.quality.low": "|:ᒍ∴",
"settings.header.audio.quality.auto": "ᖋ⚍ᒣᒍ",
"settings.option.audio.seamlessTransition": "ϟᒷᖋᒲ|:ᒷϟϟ ᖋ⚍↸╎ᒍ ᒣ∷ᖋリϟ╎ᒣ╎ᒍリ", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "ᒷリᖋᕊ|:ᒷ ᖋ↸⍊ᖋリᔮᒷ↸ ⎓⚍リᔮᒣ╎ᒍリᖋ|:╎ᒣ॥", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "ᒷリᖋᕊ|:╎リ┤ ᖋ⚍↸╎ᒍᔮᒍリᒣᒷ/ᒣ ⎓⚍リᔮᒣ╎ᒍリᖋ|:╎ᒣ॥ ∴╎|:|: ᖋ|:|:ᒍ∴ ⎓ᒍ∷ ᒷ/ᒣᒷリ↸ᒷ↸ ᖋ⚍↸╎ᒍ ⎓ᒷᖋᒣ⚍∷ᒷϟ |:╎·ǀ·ᒷ ᖋ⚍↸╎ᒍ リᒍ∷ᒲᖋ|:╎∩ᖋᒣ╎ᒍリ , ᒷᑑ⚍ᖋ|:╎∩ᒷ∷ϟ ᖋリ↸ ⍊╎ϟ⚍ᖋ|:╎∩ᒷ∷ϟ, ⍑ᒍ∴ᒷ⍊ᒷ∷ ᒍリ ϟᒍᒲᒷ ϟ॥ϟᒣᒷᒲϟ ᒣ⍑╎ϟ ᒲᖋ॥ ᔮᖋ⚍ϟᒷ ϟᒣ⚍ᒣᒣᒷ∷╎リ┤ ╎リ ᖋ⚍↸╎ᒍ ᒣ∷ᖋᔮ·ǀ·ϟ.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "ᖋ⚍↸╎ᒍ リᒍ∷ᒲᖋ|:╎∩ᖋᒣ╎ᒍリ", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "リᒍ∷ᒲᖋ|:╎∩ᒷϟ i!ᒷᖋ·ǀ· ⍊ᒍ|:⚍ᒲᒷ ⎓ᒍ∷ ╎リ↸╎⍊╎↸⚍ᖋ|: ᒣ∷ᖋᔮ·ǀ·ϟ ᒣᒍ ᔮ∷ᒷᖋᒣᒷ ᖋ ᒲᒍ∷ᒷ ⚍リ╎⎓ᒍ∷ᒲ |:╎ϟᒣᒷリ╎リ┤ ᒷ/i!ᒷ∷╎ᒷリᔮᒷ.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "ᖋ⚍↸╎ᒍ ϟi!ᖋᒣ╎ᖋ|:╎∩ᖋᒣ╎ᒍリ", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "ϟi!ᖋᒣ╎ᖋ|:╎∩ᒷ ᖋ⚍↸╎ᒍ ᖋリ↸ ᒲᖋ·ǀ·ᒷ ᖋ⚍↸╎ᒍ ᒲᒍ∷ᒷ 3-↸╎ᒲᒷリϟ╎ᒍリᖋ|: (リᒍᒣᒷ: ᒣ⍑╎ϟ ╎ϟ リᒍᒣ ↸ᒍ|:ᕊ॥ ᖋᒣᒲᒍϟ)",
// Settings - Visual
"settings.header.visual": "⍊╎ϟ⚍ᖋ|:",
"settings.header.visual.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ⍊╎ϟ⚍ᖋ| ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
"settings.option.visual.windowBackgroundStyle": "∴╎リ↸ᒍ∴ ᕊᖋᔮ·ǀ·┤∷ᒍ⚍リ↸ ϟᒣ॥|:ᒷ", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "リᒍリᒷ",
"settings.header.visual.windowBackgroundStyle.artwork": "ᖋ∷ᒣ∴ᒍ∷·ǀ·",
"settings.header.visual.windowBackgroundStyle.image": "╎ᒲᖋ┤ᒷ",
"settings.option.visual.animatedArtwork": "ᖋリ╎ᒲᖋᒣᒷ↸ ᖋ∷ᒣ∴ᒍ∷·ǀ·", // Dropdown
"settings.header.visual.animatedArtwork.always": "ᖋ|:∴ᖋ॥ϟ",
"settings.header.visual.animatedArtwork.limited": "|:╎ᒲ╎ᒣᒷ↸ ᒣᒍ i!ᖋ┤ᒷϟ ᖋリ↸ ϟi!ᒷᔮ╎ᖋ|: ᒷリᒣ∷╎ᒷϟ",
"settings.header.visual.animatedArtwork.disable": "↸╎ϟᖋᕊ|:ᒷ ᒷ⍊ᒷ∷॥∴⍑ᒷ∷ᒷ",
"settings.option.visual.animatedArtworkQuality": "ᖋリ╎ᒲᖋᒣᒷ↸ ᖋ∷ᒣ∴ᒍ∷·ǀ· ᑑ⚍ᖋ|:╎ᒣ॥", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "|:ᒍ∴",
"settings.header.visual.animatedArtworkQuality.medium": "ᒲᒷ↸╎⚍ᒲ",
"settings.header.visual.animatedArtworkQuality.high": "⍑╎┤⍑",
"settings.header.visual.animatedArtworkQuality.veryHigh": "⍊ᒷ∷॥ ⍑╎┤⍑",
"settings.header.visual.animatedArtworkQuality.extreme": "ᒷ/ᒣ∷ᒷᒲᒷ",
"settings.option.visual.animatedWindowBackground": "ᖋリ╎ᒲᖋᒣᒷ↸ ∴╎リ↸ᒍ∴ ᕊᖋᔮ·ǀ·┤∷ᒍ⚍リ↸", // Toggle
"settings.option.visual.hardwareAcceleration": "⍑ᖋ∷↸∴ᖋ∷ᒷ ᖋᔮᔮᒷ|:ᒷ∷ᖋᒣ╎ᒍリ", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "∷ᒷᑑ⚍╎∷ᒷϟ ∷ᒷ|:ᖋ⚍リᔮ⍑",
"settings.header.visual.hardwareAcceleration.default": "↸ᒷ⎓ᖋ⚍|:ᒣ",
"settings.header.visual.hardwareAcceleration.webGPU": "∴ᒷᕊ┤i!⚍",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "ϟ⍑ᒍ∴ i!ᒷ∷ϟᒍリᖋ|: ╎リ⎓ᒍ", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "|:॥∷╎ᔮϟ",
"settings.header.lyrics.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ |:॥∷╎ᔮϟ ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
"settings.option.lyrics.enableMusixmatch": "ᒷリᖋᕊ|:ᒷ ᒲ⚍ϟ╎̇/ᒲᖋᒣᔮ⍑ |:॥∷╎ᔮϟ", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "ᒷリᖋᕊ|:ᒷ ·ǀ·ᖋ∷ᖋᒍ·ǀ·ᒷ ᒲᒍ↸ᒷ (ᒲ⚍ϟ╎̇/ᒲᖋᒣᔮ⍑ ᒍリ|:॥)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "ᒲ⚍ϟ╎̇/ᒲᖋᒣᔮ⍑ ᒣ∷ᖋリϟ|:ᖋᒣ╎ᒍリ i!∷ᒷ⎓ᒷ∷∷ᒷ↸ |:ᖋリ┤⚍ᖋ┤ᒷ", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "ᒷリᖋᕊ|:ᒷ ॥ᒍ⚍ᒣ⚍ᕊᒷ |:॥∷╎ᔮϟ ⎓ᒍ∷ ᒲ⚍ϟ╎ᔮ ⍊╎↸ᒷᒍϟ", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "ᔮᒍリリᒷᔮᒣ╎⍊╎ᒣ॥",
"settings.header.connectivity.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ᔮᒍリリᒷᔮᒣ╎⍊╎ᒣ॥ ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
"settings.option.connectivity.discordRPC": "↸╎ϟᔮᒍ∷↸ ∷╎ᔮ⍑ i!∷ᒷϟᒷリᔮᒷ", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "↸╎ϟi!|:ᖋ॥ ᖋϟ 'ᔮ╎↸ᒷ∷'",
"settings.header.connectivity.discordRPC.appleMusic": "↸╎ϟi!|:ᖋ॥ ᖋϟ 'ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ'",
"settings.option.connectivity.discordRPC.clearOnPause": "ᔮ|:ᒷᖋ∷ ↸╎ϟᔮᒍ∷↸ ∷╎ᔮ⍑ i!∷ᒷϟᒷリᔮᒷ ᒍリ i!ᖋ⚍ϟᒷ", // Toggle
"settings.option.connectivity.lastfmScrobble": "|:ᖋϟᒣ⎓ᒲ ϟᔮ∷ᒍᕊᕊ|:╎リ┤", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "|:ᖋϟᒣ⎓ᒲ ϟᔮ∷ᒍᕊᕊ|:ᒷ ↸ᒷ|:ᖋ॥ (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "ᒷリᖋᕊ|:ᒷ |:ᖋϟᒣ⎓ᒲ リᒍ∴ i!|:ᖋ॥╎リ┤",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "∷ᒷᒲᒍ⍊ᒷ ⎓ᒷᖋᒣ⚍∷╎リ┤ ᖋ∷ᒣ╎ϟᒣϟ ⎓∷ᒍᒲ ϟᒍリ┤ ᒣ╎ᒣ|:ᒷ (|:ᖋϟᒣ⎓ᒲ)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "⎓╎|:ᒣᒷ∷ |:i!ᒷ↸ ᒣ∷ᖋᔮ·ǀ· (|:ᖋϟᒣ⎓ᒲ)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "ᒷ/i!ᒷ∷╎ᒲᒷリᒣᖋ|:",
"settings.header.experimental.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ᒷ/i!ᒷ∷╎ᒲᒷリᒣᖋ|: ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
"settings.option.experimental.compactUI": "ᔮᒲi!ᖋᔮᒣ ⚍╎", // Toggle
"settings.option.experimental.closeButtonBehaviour": "ᔮ|:ᒍϟᒷ ᕊ⚍ᒣᒣᒍリ ᕊᒷ⍑ᖋ⍊╎ᒍ⚍∷",
"settings.option.experimental.closeButtonBehaviour.quit": "ᑑ⚍╎ᒣ ᔮ╎↸ᒷ∷",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "ᒲ╎リ╎ᒲ╎∩ᒷ ᒣᒍ ᒣᖋϟ·ǀ·ᕊᖋ∷",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "ᒲ╎リ╎ᒲ╎∩ᒷ ᒣᒍ ϟ॥ϟᒣᒷᒲ ᒣ∷ᖋ॥",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "ϟi!ᖋᒣ╎ᖋ|: i!∷i!ᒷ∷ᒣ╎ᒷϟ",
"spatial.width" : "∴╎↸ᒣ⍑",
"spatial.height" : "⍑ᒷ╎┤⍑ᒣ",
"spatial.depth" : "↸ᒷi!ᒣ⍑",
"spatial.gain" : "┤ᖋ╎リ",
"spatial.roomMaterials" : "∷ᒍᒍᒲ ᒲᖋᒣᒷ∷╎ᖋ|:ϟ",
"spatial.roomDimensions" : "∷ᒍᒍᒲ ↸╎ᒲᒷリϟ╎ᒍリϟ",
"spatial.roomPositions" : "∷ᒍᒍᒲ i!ᒍϟ╎ᒣ╎ᒍリϟ",
"spatial.setDimensions" : "ϟᒷᒣ ↸╎ᒲᒷリϟ╎ᒍリϟ",
"spatial.setPositions" : "ϟᒷᒣ i!ᒍϟ╎ᒣ╎ᒍリϟ",
"spatial.up" : "⚍i!",
"spatial.front" : "⎓∷ᒍリᒣ",
"spatial.left" : "|:ᒷ⎓ᒣ",
"spatial.right" : "∷╎┤⍑ᒣ",
"spatial.back" : "ᕊᖋᔮ·ǀ·",
"spatial.down" : "↸ᒍ∴リ",
"spatial.listener" : "|:╎ϟᒣᒷリᒷ∷",
"spatial.audioSource" : "ᖋ⚍↸╎ᒍ ϟᒍ⚍∷ᔮᒷ",
// Settings - Unfinished
"settings.header.unfinished": "⚍リ⎓╎リ╎ϟ⍑ᒷ↸",
// Web Remote
"remote.web.title": "ᔮ╎↸ᒷ∷ ∷ᒷᒲᒍᒣᒷ",
"remote.web.description": "ϟᔮᖋリ ᒣ⍑ᒷ ᑑ∷ ᔮᒍ↸ᒷ ᒣᒍ i!ᖋ╎∷ ॥ᒍ⚍∷ i!⍑ᒍリᒷ ⚍i! ∴╎ᒣ⍑ ᒣ⍑╎ϟ ᔮ╎↸ᒷ∷ ╎リϟᒣᖋリᔮᒷ",
// About
"about.thanks": "ᒲᖋ⋮ᒍ∷ ᒣ⍑ᖋリ·ǀ·ϟ ᒣᒍ ᒣ⍑ᒷ ᔮ╎↸ᒷ∷ ᔮᒍ|:|:ᒷᔮᒣ╎⍊ᒷ ᒣᒷᖋᒲ ᖋリ↸ ᖋ|:|: ᒍ⎓ ᒍ⚍∷ ᔮᒍリᒣ∷╎ᕊ⚍ᒣᒍ∷ϟ."
}

299
src/i18n/en_US.jsonc Normal file
View file

@ -0,0 +1,299 @@
{ // Base File
// i18n Info
"i18n.languageName": "English (US)", // name of language in native language
"i18n.languageNameEnglish": "English (US)", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${m} ${d}, ${y}",
// Dialogs
"dialog.cancel": "Cancel",
"dialog.ok": "OK",
// Notification
"notification.updatingLibrarySongs": "Updating library songs...",
"notification.updatingLibraryAlbums": "Updating library albums...",
"notification.updatingLibraryArtists": "Updating library artists...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "Learn more",
"term.accountSettings": "Account Settings",
"term.logout": "Logout",
"term.login": "Login",
"term.about": "About",
"term.privateSession": "Private Session",
"term.queue": "Queue",
"term.search": "Search",
"term.library": "Library",
"term.listenNow": "Listen Now",
"term.browse": "Browse",
"term.radio": "Radio",
"term.recentlyAdded": "Recently Added",
"term.songs": "Songs",
"term.albums": "Albums",
"term.artists": "Artists",
"term.podcasts": "Podcasts",
"term.playlists": "Playlists",
"term.playlist": "Playlist",
"term.play": "Play",
"term.pause": "Pause",
"term.previous": "Previous",
"term.next": "Next",
"term.shuffle": "Shuffle",
"term.repeat": "Repeat",
"term.volume": "Volume",
"term.mute": "Mute",
"term.unmute": "Unmute",
"term.share": "Share",
"term.settings": "Settings",
"term.seeAll": "See All",
"term.sortBy": "Sort By",
"term.sortBy.album": "Album",
"term.sortBy.artist": "Artist",
"term.sortBy.name": "Name",
"term.sortBy.genre": "Genre",
"term.sortBy.releaseDate": "Release Date",
"term.sortBy.duration": "Duration",
"term.sortOrder": "A-Z",
"term.sortOrder.ascending": "Ascending",
"term.sortOrder.descending": "Descending",
"term.viewAs": "View As",
"term.viewAs.coverArt": "Cover Art",
"term.viewAs.list": "List",
"term.size": "Size",
"term.size.normal": "Normal",
"term.size.compact": "Compact",
"term.enable": "Enable",
"term.disable": "Disable",
"term.enabled": "Enabled",
"term.disabled": "Disabled",
"term.connect": "Connect",
"term.connecting": "Connecting",
"term.disconnect": "Disconnect",
"term.authed": "Authed",
"term.confirm": "Confirm ?",
"term.more": "More",
"term.less": "Less",
"term.showMore": "Show more",
"term.showLess": "Show less",
"term.topSongs" : "Top Songs",
"term.latestReleases": "Latest Releases",
"term.time.added": "Added",
"term.time.released": "Released",
"term.time.updated": "Updated",
"term.time.hours": "hours",
"term.time.hour": "hour",
"term.time.minutes": "minutes",
"term.time.minute": "minute",
"term.time.seconds": "seconds",
"term.time.second": "second",
"term.fullscreenView": "Fullscreen View",
"term.defaultView": "Default View",
"term.spacializedAudioSetting": "Spacialized Audio Setting",
"term.clearAll": "Clear All",
"term.recentStations": "Recent Stations",
"term.language": "Language",
"term.funLanguages": "Fun",
"term.noLyrics": "Loading... / Lyrics not found./ Instrumental.",
"term.copyright": "Copyright",
"term.rightsReserved": "All Rights Reserved.",
"term.sponsor": "Sponsor this project",
"term.ciderTeam": "Cider Team",
"term.developer": "Developer",
"term.socialTeam": "Social Team",
"term.contributors": "Contributors",
"term.equalizer": "Equalizer",
"term.reset": "Reset",
"term.tracks": "tracks", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "Videos",
"term.menu": "Menu",
"term.showAlbum": "Show Complete Album",
// Home
"home.title": "Home",
"home.recentlyPlayed": "Recently Played",
"home.recentlyAdded": "Recently Added",
"home.artistsFeed": "Your Artists Feed",
"home.artistsFeed.noArtist": "Follow some artists first and their latest releases will be here",
"home.madeForYou": "Made For You",
"home.friendsListeningTo": "Friends Listening To",
"home.followedArtists": "Followed Artists",
// Errors
"error.appleMusicSubRequired": "Apple Music requires a subscription.",
"error.connectionError": "There was a problem connecting to Apple Music.",
"error.noResults": "No Results.",
"error.noResults.description": "Try a new search.",
//Podcasts
"podcast.followOnCider": "Follow On Cider",
"podcast.followedOnCider": "Following On Cider",
"podcast.subscribeOnItunes": "Subscribe On iTunes",
"podcast.subscribedOnItunes": "Subscribed On iTunes",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Episodes",
"podcast.playEpisode": "Play Episode",
"podcast.website": "Podcast Website",
// Actions
"action.addToLibrary": "Add to Library",
"action.addToLibrary.success": "Added to Library",
"action.addToLibrary.error": "Error Adding to Library",
"action.removeFromLibrary": "Remove from Library",
"action.removeFromLibrary.success": "Removed from Library",
"action.addToQueue": "Add to Queue",
"action.addToQueue.success": "Added to Queue",
"action.addToQueue.error": "Error Adding to Queue",
"action.removeFromQueue": "Remove from Queue",
"action.removeFromQueue.success": "Removed from Queue",
"action.removeFromQueue.error": "Error Removing from Queue",
"action.addToPlaylist": "Add to Playlist",
"action.removeFromPlaylist": "Remove from Playlist",
"action.addToFavorites": "Add to Favorites",
"action.follow": "Follow",
"action.follow.success": "Followed",
"action.follow.error": "Error Following",
"action.unfollow": "Unfollow",
"action.unfollow.success": "Unfollowed",
"action.unfollow.error": "Error Unfollowing",
"action.playNext": "Play Next",
"action.playLater": "Play Later",
"action.startRadio": "Start Radio",
"action.goToArtist": "Go to Artist",
"action.goToAlbum": "Go to Album",
"action.moveToTop": "Move to top",
"action.share": "Share",
"action.rename": "Rename",
"action.love": "Love",
"action.unlove": "Unlove",
"action.dislike": "Dislike",
"action.undoDislike": "Undo dislike",
"action.showWebRemoteQR": "Show Web Remote QR",
"action.playTracksNext": "Play ${app.selectedMediaItems.length} tracks next",
"action.playTracksLater": "Play ${app.selectedMediaItems.length} tracks later",
"action.removeTracks": "Remove ${self.selectedItems.length} tracks from queue",
"action.import": "Import",
"action.export": "Export",
// Settings - General
"settings.header.general": "General",
"settings.header.general.description": "Adjust the general settings for Cider.",
"settings.option.general.language": "Language",
// Language optgroups
"settings.option.general.language.main": "Languages",
"settings.option.general.language.fun": "Fun Languages",
"settings.option.general.language.unsorted": "Unsorted",
// Settings - Audio
"settings.header.audio": "Audio",
"settings.header.audio.description": "Adjust the audio settings for Cider.",
"settings.option.audio.quality": "Audio Quality", // Dropdown
"settings.header.audio.quality.high": "High",
"settings.header.audio.quality.low": "Low",
"settings.header.audio.quality.auto": "Auto",
"settings.option.audio.seamlessTransition": "Seamless Audio Transition", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Enable Advanced Functionality", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "Enabling AudioContext functionality will allow for extended audio features like Audio Normalization , Equalizers and Visualizers, however on some systems this may cause stuttering in audio tracks.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Audio Normalization", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalizes peak volume for individual tracks to create a more uniform listening experience.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Audio Spatialization", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Spatialize audio and make audio more 3-dimensional (note: This is not Dolby Atmos)",
// Settings - Visual
"settings.header.visual": "Visual",
"settings.header.visual.description": "Adjust the visual settings for Cider.",
"settings.option.visual.windowBackgroundStyle": "Window Background Style", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "None",
"settings.header.visual.windowBackgroundStyle.artwork": "Artwork",
"settings.header.visual.windowBackgroundStyle.image": "Image",
"settings.option.visual.animatedArtwork": "Animated Artwork", // Dropdown
"settings.header.visual.animatedArtwork.always": "Always",
"settings.header.visual.animatedArtwork.limited": "Limited to pages and special entries",
"settings.header.visual.animatedArtwork.disable": "Disable everywhere",
"settings.option.visual.animatedArtworkQuality": "Animated Artwork Quality", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Low",
"settings.header.visual.animatedArtworkQuality.medium": "Medium",
"settings.header.visual.animatedArtworkQuality.high": "High",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Very High",
"settings.header.visual.animatedArtworkQuality.extreme": "Extreme",
"settings.option.visual.animatedWindowBackground": "Animated Window Background", // Toggle
"settings.option.visual.hardwareAcceleration": "Hardware Acceleration", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Requires relaunch",
"settings.header.visual.hardwareAcceleration.default": "Default",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Show Personal Info", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "Lyrics",
"settings.header.lyrics.description": "Adjust the lyrics settings for Cider.",
"settings.option.lyrics.enableMusixmatch": "Enable Musixmatch Lyrics", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Enable Karaoke Mode (Musixmatch only)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch Translation Preferred Language", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "Enable Youtube Lyrics for Music Videos", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Connectivity",
"settings.header.connectivity.description": "Adjust the connectivity settings for Cider.",
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
"settings.option.connectivity.playbackNotifications": "Playback Notifications", // Toggle
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "Display as 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "Display as 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "Clear Discord Rich Presence on Pause", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble Delay (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable LastFM Now Playing",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Experimental",
"settings.header.experimental.description": "Adjust the experimental settings for Cider.",
"settings.option.experimental.compactUI": "Compact UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Close Button Behavior",
"settings.option.experimental.closeButtonBehaviour.quit": "Quit Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Minimize to Taskbar",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Minimize to Tray",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "Spatial Properties",
"spatial.width" : "Width",
"spatial.height" : "Height",
"spatial.depth" : "Depth",
"spatial.gain" : "Gain",
"spatial.roomMaterials" : "Room Materials",
"spatial.roomDimensions" : "Room Dimensions",
"spatial.roomPositions" : "Room Positions",
"spatial.setDimensions" : "Set Dimensions",
"spatial.setPositions" : "Set Positions",
"spatial.up" : "Up",
"spatial.front" : "Front",
"spatial.left" : "Left",
"spatial.right" : "Right",
"spatial.back" : "Back",
"spatial.down" : "Down",
"spatial.listener" : "Listener",
"spatial.audioSource" : "Audio Source",
// Settings - Unfinished
"settings.header.unfinished": "Unfinished",
// Web Remote
"remote.web.title": "Cider Remote",
"remote.web.description": "Scan the QR code to pair your phone up with this Cider instance",
// About
"about.thanks": "Major thanks to the Cider Collective Team and all of our contributors."
}

299
src/i18n/fr_CA.jsonc Normal file
View file

@ -0,0 +1,299 @@
{ // Base File
// i18n Info
"i18n.languageName": "Français (CA)", // name of language in native language
"i18n.languageNameEnglish": "French (CA)", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "brock#7527", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${m} ${d}, ${y}",
// Dialogs
"dialog.cancel": "Annuler",
"dialog.ok": "D'accord",
// Notification
"notification.updatingLibrarySongs": "Rafraîchir des chansons...",
"notification.updatingLibraryAlbums": "Rafraîchir des albums...",
"notification.updatingLibraryArtists": "Rafraîchir des artistes...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "Savoir plus",
"term.accountSettings": "Réglages du compte",
"term.logout": "Se déconnecter",
"term.login": "Se connecter",
"term.about": "À propos",
"term.privateSession": "Session en privée",
"term.queue": "Suivants",
"term.search": "Rechercher",
"term.library": "Bibliothèque",
"term.listenNow": "À écouter",
"term.browse": "Parcourir",
"term.radio": "Radio",
"term.recentlyAdded": "Ajouté récemment",
"term.songs": "Chansons",
"term.albums": "Albums",
"term.artists": "Artistes",
"term.podcasts": "Podcasts",
"term.playlists": "Listes de lecture",
"term.playlist": "Liste de lecture",
"term.play": "Écouter",
"term.pause": "Pause",
"term.previous": "Retour",
"term.next": "Suivant",
"term.shuffle": "Lecture aléatoire",
"term.repeat": "Répéter",
"term.volume": "Volume",
"term.mute": "Assourde",
"term.unmute": "Réactiver",
"term.share": "Partager",
"term.settings": "Réglages",
"term.seeAll": "Voix tous",
"term.sortBy": "Sorter par",
"term.sortBy.album": "Album",
"term.sortBy.artist": "Artiste",
"term.sortBy.name": "Nom",
"term.sortBy.genre": "Genre",
"term.sortBy.releaseDate": "Date de sortie",
"term.sortBy.duration": "Duration",
"term.sortOrder": "A-Z",
"term.sortOrder.ascending": "Ascender",
"term.sortOrder.descending": "Descender",
"term.viewAs": "Voix comme",
"term.viewAs.coverArt": "Art de couverture",
"term.viewAs.list": "Liste",
"term.size": "Grandeur",
"term.size.normal": "Normal",
"term.size.compact": "Compacte",
"term.enable": "Activer",
"term.disable": "Désactiver",
"term.enabled": "Activée",
"term.disabled": "Désactivée",
"term.connect": "Connecter",
"term.connecting": "De liaison",
"term.disconnect": "Déconnecter",
"term.authed": "Authed",
"term.confirm": "Confirmer ?",
"term.more": "De plus",
"term.less": "De moins",
"term.showMore": "Voir plus",
"term.showLess": "Voir moins",
"term.topSongs" : "Meilleures chansons",
"term.latestReleases": "Nouvelles sorties",
"term.time.added": "Ajouté",
"term.time.released": "Publié",
"term.time.updated": "Mis à jour",
"term.time.hours": "heures",
"term.time.hour": "heur",
"term.time.minutes": "minutes",
"term.time.minute": "minute",
"term.time.seconds": "secondes",
"term.time.second": "seconde",
"term.fullscreenView": "Vue plein écran",
"term.defaultView": "Vue par défaut",
"term.spacializedAudioSetting": "Réglage audio spacialisé",
"term.clearAll": "Tout effacer",
"term.recentStations": "Stations récentes",
"term.language": "Langue",
"term.funLanguages": "Amusement",
"term.noLyrics": "Loading... / Paroles pas trouvé./ Instrumental.",
"term.copyright": "Droits d'auteur",
"term.rightsReserved": "Tous les droits sont réservés.",
"term.sponsor": "Parrainez ce projet",
"term.ciderTeam": "Team Cider",
"term.developer": "Développeur",
"term.socialTeam": "Équipe sociale",
"term.contributors": "Contributeurs",
"term.equalizer": "Égaliseur",
"term.reset": "Réinitialiser",
"term.tracks": "chansons", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "Vidéos",
"term.menu": "Menu",
"term.showAlbum": "Afficher l'album complet",
// Home
"home.title": "Maison",
"home.recentlyPlayed": "Joué récemment",
"home.recentlyAdded": "Ajouté récemment",
"home.artistsFeed": "Votre flux d'artistes",
"home.artistsFeed.noArtist": "Suivez certains artistes en premier et leurs dernières sorties seront ici",
"home.madeForYou": "Creér pour toi",
"home.friendsListeningTo": "Amis écoutant",
"home.followedArtists": "Artistes suivis",
// Errors
"error.appleMusicSubRequired": "Apple Music nécessite un abonnement.",
"error.connectionError": "Un problème est survenu lors de la connexion à Apple Music.",
"error.noResults": "Pas de resultats.",
"error.noResults.description": "Essayez une nouvelle recherche.",
//Podcasts
"podcast.followOnCider": "Suivez sur Cider",
"podcast.followedOnCider": "Suivi sur Cider",
"podcast.subscribeOnItunes": "S'abonner sur iTunes",
"podcast.subscribedOnItunes": "Abonné sur iTunes",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Épisodes",
"podcast.playEpisode": "Lire l'épisode",
"podcast.website": "Site web du Podcast",
// Actions
"action.addToLibrary": "Ajouter à biblio",
"action.addToLibrary.success": "Ajouté à biblio",
"action.addToLibrary.error": "Erreur lors de l'ajout à biblio",
"action.removeFromLibrary": "Enlever du biblio",
"action.removeFromLibrary.success": "Enlevé du biblio",
"action.addToQueue": "Ajouter à suivants",
"action.addToQueue.success": "Ajouté à suivants",
"action.addToQueue.error": "Erreur lors de l'ajout à suivants",
"action.removeFromQueue": "Enlever du suivants",
"action.removeFromQueue.success": "Enlevé du suivants",
"action.removeFromQueue.error": "Erreur lors d'enleve du suivants",
"action.addToPlaylist": "Ajoute à liste de lecture",
"action.removeFromPlaylist": "Enleve de liste de lecture",
"action.addToFavorites": "Ajoute à Favorites",
"action.follow": "Suivre",
"action.follow.success": "Suivi",
"action.follow.error": "Erreur a suivre",
"action.unfollow": "Arreter a suivi",
"action.unfollow.success": "Arreter a suivre",
"action.unfollow.error": "Erreur de arreter à suivre",
"action.playNext": "Jouer en prochaine",
"action.playLater": "Jouer plus tard",
"action.startRadio": "Commencer la radio",
"action.goToArtist": "Aller à l'artiste",
"action.goToAlbum": "Aller à l'album",
"action.moveToTop": "Déplacer vers le haut",
"action.share": "Partager",
"action.rename": "Renommer",
"action.love": "J'adore",
"action.unlove": "Je n'adore plus",
"action.dislike": "Moins de suggestions similaires",
"action.undoDislike": "Annuler le choix Moins de suggestions de ce type",
"action.showWebRemoteQR": "Demontrer la Web Remote QR",
"action.playTracksNext": "Joue ${app.selectedMediaItems.length} chansons en prochain",
"action.playTracksLater": "Joue ${app.selectedMediaItems.length} chansons plus tard",
"action.removeTracks": "Remove ${self.selectedItems.length} tracks from queue",
"action.import": "Importer",
"action.export": "Exporter",
// Settings - General
"settings.header.general": "Réglages généraux",
"settings.header.general.description": "Réglez les réglages généraux de Cider.",
"settings.option.general.language": "Langue",
// Language optgroups
"settings.option.general.language.main": "Langue",
"settings.option.general.language.fun": "Langues amusantes",
"settings.option.general.language.unsorted": "Non trié",
// Settings - Audio
"settings.header.audio": "Audio",
"settings.header.audio.description": "Réglez les réglages audio de Cider.",
"settings.option.audio.quality": "Qualité audio", // Dropdown
"settings.header.audio.quality.high": "Haut",
"settings.header.audio.quality.low": "Bas",
"settings.header.audio.quality.auto": "Auto",
"settings.option.audio.seamlessTransition": "Transition audio fluide", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Activer la fonctionnalité avancée", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "L'activation de la fonctionnalité AudioContext permettra des fonctionnalités audio étendues telles que la normalisation audio, les égaliseurs et les visualiseurs, mais sur certains systèmes, cela peut provoquer des saccades dans les chansons audio.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalisation audio", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalise le volume maximal des pistes individuelles pour créer une expérience d'écoute plus uniforme.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Audio Spatialization", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Spatialisez l'audio et rendez l'audio plus tridimensionnel (remarque: il ne s'agit pas de Dolby Atmos)",
// Settings - Visual
"settings.header.visual": "Visuel",
"settings.header.visual.description": "Ajustez les réglages visuels de Cider.",
"settings.option.visual.windowBackgroundStyle": "Style d'arrière-plan de la window", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "Rien",
"settings.header.visual.windowBackgroundStyle.artwork": "Ouvrages d'art",
"settings.header.visual.windowBackgroundStyle.image": "Image",
"settings.option.visual.animatedArtwork": "Oeuvre animée", // Dropdown
"settings.header.visual.animatedArtwork.always": "Toujours",
"settings.header.visual.animatedArtwork.limited": "Limité aux pages et aux entrées spéciales",
"settings.header.visual.animatedArtwork.disable": "Désactiver partout",
"settings.option.visual.animatedArtworkQuality": "Qualité des illustrations animées", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Bas",
"settings.header.visual.animatedArtworkQuality.medium": "Moyen",
"settings.header.visual.animatedArtworkQuality.high": "Haut",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Très haut",
"settings.header.visual.animatedArtworkQuality.extreme": "Extrême",
"settings.option.visual.animatedWindowBackground": "Fond de window animé", // Toggle
"settings.option.visual.hardwareAcceleration": "Accélération matérielle", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Nécessite une relance",
"settings.header.visual.hardwareAcceleration.default": "Défaut",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Afficher les informations personnelles", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "Paroles",
"settings.header.lyrics.description": "Ajustez les réglages des paroles de Cider.",
"settings.option.lyrics.enableMusixmatch": "Activer les paroles de Musixmatch", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Activer le mode karaoké (Musixmatch uniquement)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Traduction Musixmatch langue préférée", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "Activer les paroles Youtube pour les clips vidéo", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Connectivité",
"settings.header.connectivity.description": "Ajustez les réglages de connectivité pour Cider.",
"settings.option.connectivity.discordRPC": "Discord Riche Présence", // Dropdown
"settings.option.connectivity.playbackNotifications": "Notifications de lecture", // Toggle
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "Afficher comme 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "Afficher comme 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "Effacer la Discord Riche Présence en pause", // Toggle
"settings.option.connectivity.lastfmScrobble": "Scrobbling LastFM", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "Délai de scrobble LastFM (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Activer LastFM en jouer maitenant",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Supprimer les artistes en vedette du titre de la chanson (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filtrer la chanson en boucle (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Expérimental",
"settings.header.experimental.description": "Ajustez les réglages expérimentaux pour Cider.",
"settings.option.experimental.compactUI": "UI Compact", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Comportement du bouton de fermeture",
"settings.option.experimental.closeButtonBehaviour.quit": "Quitter Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Réduire à la barre des tâches",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Réduire dans le bac",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "Propriétés spatiales",
"spatial.width" : "Largeur",
"spatial.height" : "Hauteur",
"spatial.depth" : "Profondeur",
"spatial.gain" : "Gagner",
"spatial.roomMaterials" : "Matériaux de la chambre",
"spatial.roomDimensions" : "Dimensions de la chambre",
"spatial.roomPositions" : "Positions de la chambre",
"spatial.setDimensions" : "Définir les cotes",
"spatial.setPositions" : "Définir les positions",
"spatial.up" : "Haut",
"spatial.front" : "Avant",
"spatial.left" : "Gauche",
"spatial.right" : "Droite",
"spatial.back" : "Arrière",
"spatial.down" : "Bas",
"spatial.listener" : "Auditeur",
"spatial.audioSource" : "Source audio",
// Settings - Unfinished
"settings.header.unfinished": "Inachevée",
// Web Remote
"remote.web.title": "Cider Remote",
"remote.web.description": "Scannez le code QR pour coupler votre téléphone avec cette instance Cider",
// About
"about.thanks": "Un grand merci à l'équipe de Cider Collective et à tous nos contributeurs."
}

300
src/i18n/fr_FR.jsonc Normal file
View file

@ -0,0 +1,300 @@
{ // Base File
// i18n Info
"i18n.languageName": "Français", // name of language in native language
"i18n.languageNameEnglish": "French", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@ErwanGit", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${d} ${m} ${y}",
// Dialogs
"dialog.cancel": "Annuler",
"dialog.ok": "OK",
// Notification
"notification.updatingLibrarySongs": "Mise à jour des chansons de la bibliothèque...",
"notification.updatingLibraryAlbums": "Mise à jour des albums de la bibliothèque...",
"notification.updatingLibraryArtists": "Mise à jour des artistes de la bibliothèque...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "En savoir plus",
"term.accountSettings": "Paramètres du compte",
"term.logout": "Déconnexion",
"term.login": "Connexion",
"term.about": "À propos",
"term.privateSession": "Session privée",
"term.queue": "File d'attente",
"term.search": "Recherche",
"term.library": "Bibliothèque",
"term.listenNow": "Écoutez maintenant",
"term.browse": "Explorer",
"term.radio": "Radio",
"term.recentlyAdded": "Ajouté récemment",
"term.songs": "Musiques",
"term.albums": "Albums",
"term.artists": "Artistes",
"term.podcasts": "Podcasts",
"term.playlists": "Playlists",
"term.playlist": "Playlist",
"term.play": "Lecture",
"term.pause": "Pause",
"term.previous": "Précédent",
"term.next": "Suivant",
"term.shuffle": "Aléatoire",
"term.repeat": "Répéter",
"term.volume": "Volume",
"term.mute": "Rendre muet",
"term.unmute": "Ne plus rendre muet",
"term.share": "Partager",
"term.settings": "Paramètres",
"term.seeAll": "Voir tout",
"term.sortBy": "Trier par",
"term.sortBy.album": "Album",
"term.sortBy.artist": "Artiste",
"term.sortBy.name": "Nom",
"term.sortBy.genre": "Genre",
"term.sortBy.releaseDate": "Date de sortie",
"term.sortBy.duration": "Durée",
"term.sortOrder": "A-Z",
"term.sortOrder.ascending": "Ascendant",
"term.sortOrder.descending": "Descendant",
"term.viewAs": "Voir comme",
"term.viewAs.coverArt": "Pochette d'album",
"term.viewAs.list": "Liste",
"term.size": "Taille",
"term.size.normal": "Normal",
"term.size.compact": "Compacte",
"term.enable": "Activer",
"term.disable": "Désactiver",
"term.enabled": "Activé",
"term.disabled": "Désactivé",
"term.connect": "Connecter",
"term.connecting": "Connexion",
"term.disconnect": "Déconnexion",
"term.authed": "Authentifié",
"term.confirm": "Confirmer ?",
"term.more": "Plus",
"term.less": "Moins",
"term.showMore": "Afficher plus",
"term.showLess": "Afficher moins",
"term.topSongs" : "Meilleurs titres",
"term.latestReleases": "Dernières sorties",
"term.time.added": "Ajouté",
"term.time.released": "Publié",
"term.time.updated": "Mis à jour",
"term.time.hours": "heures",
"term.time.hour": "heure",
"term.time.minutes": "minutes",
"term.time.minute": "minute",
"term.time.seconds": "secondes",
"term.time.second": "seconde",
"term.fullscreenView": "Vue plein écran",
"term.defaultView": "Vue par défaut",
"term.spacializedAudioSetting": "Paramètres audio spatialisés",
"term.clearAll": "Tout effacer",
"term.recentStations": "Stations récentes",
"term.language": "Langue",
"term.funLanguages": "Amusant",
"term.noLyrics": "Chargement... / Paroles non trouvé./ Instrumental.",
"term.copyright": "Copyright",
"term.rightsReserved": "Tous droits réservés.",
"term.sponsor": "Soutenez ce projet",
"term.ciderTeam": "Équipe Cider",
"term.developer": "Développeur",
"term.socialTeam": "Équipe de communication",
"term.contributors": "Contributeurs",
"term.equalizer": "Égaliseur",
"term.reset": "Réinitialiser",
"term.tracks": "musiques", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "Vidéos",
"term.menu": "Menu",
"term.showAlbum": "Afficher l'album complet",
// Home
"home.title": "Accueil",
"home.recentlyPlayed": "Joué récemment",
"home.recentlyAdded": "Récemment ajouté",
"home.artistsFeed": "Votre file d'artistes",
"home.artistsFeed.noArtist": "Suivez d'abord certains artistes et leurs dernières sorties seront ici",
"home.madeForYou": "Fait pour vous",
"home.friendsListeningTo": "Vos amis écoutent",
"home.followedArtists": "Artistes suivis",
// Errors
"error.appleMusicSubRequired": "Apple Music nécessite un abonnement.",
"error.connectionError": "Il y a eu un problème de connexion à Apple Music.",
"error.noResults": "Aucun résultat.",
"error.noResults.description": "Essayez une nouvelle recherche.",
//Podcasts
"podcast.followOnCider": "Suivre sur Cider",
"podcast.followedOnCider": "Suivi sur Cider",
"podcast.subscribeOnItunes": "Suivre sur iTunes",
"podcast.subscribedOnItunes": "Suivi sur iTunes",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Épisodes",
"podcast.playEpisode": "Lire l'épisode",
"podcast.website": "Site du podcast",
// Actions
"action.addToLibrary": "Ajouter à la bibliothèque",
"action.addToLibrary.success": "Ajouté à la bibliothèque",
"action.addToLibrary.error": "Erreur lors de l'ajout à la bibliothèque",
"action.removeFromLibrary": "Retirer de la bibliothèque",
"action.removeFromLibrary.success": "Retiré de la bibliothèque",
"action.addToQueue": "Ajouter à la file d'attente",
"action.addToQueue.success": "Ajouté à la file d'attente",
"action.addToQueue.error": "Erreur lors de l'ajout à la file d'attente",
"action.removeFromQueue": "Retirer de la file d'attente",
"action.removeFromQueue.success": "Retiré de la file d'attente",
"action.removeFromQueue.error": "Erreur lors du retrait de la file d'attente",
"action.addToPlaylist": "Ajouter à une playlist",
"action.removeFromPlaylist": "Retirer de la playlist",
"action.addToFavorites": "Ajouter aux favoris",
"action.follow": "Suivre",
"action.follow.success": "Suivi",
"action.follow.error": "Erreur lors de l'abonnement",
"action.unfollow": "Se désabonner",
"action.unfollow.success": "Désabonné",
"action.unfollow.error": "Erreur lors du désabonnement",
"action.playNext": "Jouer après",
"action.playLater": "Jouer plus tard",
"action.startRadio": "Lancer la radio",
"action.goToArtist": "Aller à l'artiste",
"action.goToAlbum": "Aller à l'album",
"action.moveToTop": "Haut de page",
"action.share": "Partager",
"action.rename": "Renommer",
"action.love": "Aimer",
"action.unlove": "Ne plus aimer",
"action.dislike": "Je n'aime pas",
"action.undoDislike": "Je n'aime plus",
"action.showWebRemoteQR": "Afficher le QR Code de la télécommande à distance",
"action.playTracksNext": "Jouer ${app.selectedMediaItems.length} musiques après",
"action.playTracksLater": "Jouer ${app.selectedMediaItems.length} musiques plus tard",
"action.removeTracks": "Retirer ${self.selectedItems.length} musiques de la file d'attente",
"action.import": "Importer",
"action.export": "Exporter",
// Settings - General
"settings.header.general": "Général",
"settings.header.general.description": "Ajuster les paramètres généraux de Cider.",
"settings.option.general.language": "Langue",
// Language optgroups
"settings.option.general.language.main": "Langues",
"settings.option.general.language.fun": "Langues amusantes",
"settings.option.general.language.unsorted": "Non trié",
// Settings - Audio
"settings.header.audio": "Audio",
"settings.header.audio.description": "Ajuster les paramètres audio de Cider.",
"settings.option.audio.quality": "Qualité Audio", // Dropdown
"settings.header.audio.quality.high": "Élevée",
"settings.header.audio.quality.low": "Faible",
"settings.header.audio.quality.auto": "Automatique",
"settings.option.audio.seamlessTransition": "Fondu audio transparent", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Activer les fonctions avancées", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "L'activation de la fonctionnalité AudioContext permet d'utiliser des fonctions audio étendues telles que la normalisation du son, les égaliseurs et les visualiseurs. Toutefois, sur certains systèmes, cela peut provoquer des bégaiements dans les pistes audio.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalisation du son", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalise le volume maximal des pistes individuelles pour créer une expérience d'écoute plus uniforme.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Spatialisation audio", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Spatialiser l'audio et rendre l'audio plus tridimensionnel (note: Ce n'est pas Dolby Atmos)",
// Settings - Visual
"settings.header.visual": "Visuel",
"settings.header.visual.description": "Ajuster les paramètres visuels de Cider.",
"settings.option.visual.windowBackgroundStyle": "Style d'arrière-plan de la fenêtre", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "Aucun",
"settings.header.visual.windowBackgroundStyle.artwork": "Pochette d'album",
"settings.header.visual.windowBackgroundStyle.image": "Image",
"settings.option.visual.animatedArtwork": "Pochette d'album animée", // Dropdown
"settings.header.visual.animatedArtwork.always": "Toujours",
"settings.header.visual.animatedArtwork.limited": "Limité aux pages et aux entrées spéciales",
"settings.header.visual.animatedArtwork.disable": "Désactiver partout",
"settings.option.visual.animatedArtworkQuality": "Qualité de la pochette d'album animée", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Faible",
"settings.header.visual.animatedArtworkQuality.medium": "Moyen",
"settings.header.visual.animatedArtworkQuality.high": "Élevée",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Très élevée",
"settings.header.visual.animatedArtworkQuality.extreme": "Extrême",
"settings.option.visual.animatedWindowBackground": "Arrière-plan de fenêtre animé", // Toggle
"settings.option.visual.hardwareAcceleration": "Accélération matérielle", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Nécessite un relancement",
"settings.header.visual.hardwareAcceleration.default": "Défaut",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Afficher vos informations personnelles", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "Paroles",
"settings.header.lyrics.description": "Ajuster les paramètres des paroles pour Cider.",
"settings.option.lyrics.enableMusixmatch": "Activer les paroles Musixmatch", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Activer le mode karaoké (Musixmatch seulement)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Langue préférée pour les traductions Musixmatch", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "Activer les paroles YouTube pour les vidéos de musique", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Connectivité",
"settings.header.connectivity.description": "Ajuster les paramètres de connectivité de Cider.",
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
"settings.option.connectivity.playbackNotifications": "Notifications de lecture", // Toggle
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "Afficher comme 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "Afficher comme 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "Désactiver le Discord Rich Presence quand la musique est en pause", // Toggle
"settings.option.connectivity.lastfmScrobble": "Scrobble LastFM", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "Délai de Scrobble LastFM (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Activer la lecture en cours sur LastFM",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Supprimer les artistes en vedette du titre de la chanson (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filtrer les titres en boucle (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Expérimental",
"settings.header.experimental.description": "Ajuster les paramètres expérimentaux de Cider.",
"settings.option.experimental.compactUI": "Interface utilisateur compacte", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Comportement du bouton de fermeture",
"settings.option.experimental.closeButtonBehaviour.quit": "Quitter Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Réduire dans la barre des tâches",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Réduire dans la barre d'outils",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties": "Propriétés spatiales",
"spatial.width": "Largeur",
"spatial.height": "Hauteur",
"spatial.depth": "Profondeur",
"spatial.gain": "Gain",
"spatial.roomMaterials": "Matériaux de la pièce",
"spatial.roomDimensions": "Dimensions de la pièce",
"spatial.roomPositions": "Positions de la pièce",
"spatial.setDimensions": "Définir les dimensions",
"spatial.setPositions": "Définir les positions",
"spatial.up": "Haut",
"spatial.front": "Avant",
"spatial.left": "Gauche",
"spatial.right": "Droite",
"spatial.back": "Retour",
"spatial.down": "Bas",
"spatial.listener": "Auditeur",
"spatial.audioSource": "Source Audio",
// Settings - Unfinished
"settings.header.unfinished": "Inachevée",
// Web Remote
"remote.web.title": "Cider Remote",
"remote.web.description": "Scanner le QR code pour associer votre téléphone avec cette instance Cider",
// About
"about.thanks": "Un grand merci à l'équipe de la Cider Collective et à tous nos contributeurs."
}

297
src/i18n/hu_HU.jsonc Normal file
View file

@ -0,0 +1,297 @@
{ // Base File
// i18n Info
"i18n.languageName": "Magyar", // name of language in native language
"i18n.languageNameEnglish": "Hungarian", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@Greenoliv @Rias", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${m} ${d}, ${y}",
// Dialogs
"dialog.cancel": "Mégsem",
"dialog.ok": "OK",
// Notification
"notification.updatingLibrarySongs": "Zenekönyvtár frissítése...",
"notification.updatingLibraryAlbums": "Albumok frissítése...",
"notification.updatingLibraryArtists": "Előadók frissítése...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcastok",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "Tudj meg többet",
"term.accountSettings": "Fiókbeállítások",
"term.logout": "Kijelentkezés",
"term.login": "Bejelentkezés",
"term.about": "About",
"term.privateSession": "Privát hallgatás",
"term.queue": "Várólista",
"term.search": "Keresés",
"term.library": "Könyvtár",
"term.listenNow": "Hallgatás most",
"term.browse": "Böngészés",
"term.radio": "Rádió",
"term.recentlyAdded": "Nemrég hozzáadott",
"term.songs": "Dalok",
"term.albums": "Albumok",
"term.artists": "Előadók",
"term.podcasts": "Podcastok",
"term.playlists": "Lejátszási listák",
"term.playlist": "Lejátszási lista",
"term.play": "Lejátszás",
"term.pause": "Megállítás",
"term.previous": "Előző",
"term.next": "Következő",
"term.shuffle": "Keverés",
"term.repeat": "Ismétlés",
"term.volume": "Hangerő",
"term.mute": "Némítás",
"term.unmute": "Némítás feloldása",
"term.share": "Megosztás",
"term.settings": "Beállítások",
"term.seeAll": "Összes",
"term.sortBy": "Rendezés",
"term.sortBy.album": "Album",
"term.sortBy.artist": "Előadó",
"term.sortBy.name": "Név",
"term.sortBy.genre": "Műfaj",
"term.sortBy.releaseDate": "Kiadás dátuma",
"term.sortBy.duration": "Időtartam",
"term.sortOrder": "A-Z",
"term.sortOrder.ascending": "Növekvő",
"term.sortOrder.descending": "Csökkenő",
"term.viewAs": "Megjelenítés",
"term.viewAs.coverArt": "Borító",
"term.viewAs.list": "Lista",
"term.size": "Méret",
"term.size.normal": "Normál",
"term.size.compact": "Kompakt",
"term.enable": "Be",
"term.disable": "Ki",
"term.enabled": "Be",
"term.disabled": "Ki",
"term.connect": "Csatlakoztatás",
"term.connecting": "Csatlakozás",
"term.disconnect": "Lecsatlakozatás",
"term.authed": "Authed",
"term.confirm": "Jóváhagyás ?",
"term.more": "Több",
"term.less": "Kevesebb",
"term.showMore": "Mutass többet",
"term.showLess": "Mutass kevesebbet",
"term.topSongs" : "A legjobb dalok",
"term.latestReleases": "Új megjelenések",
"term.time.added": "Hozzáadva",
"term.time.released": "Kiadva",
"term.time.updated": "Frissítve",
"term.time.hours": "óra",
"term.time.hour": "óra",
"term.time.minutes": "perc",
"term.time.minute": "perc",
"term.time.seconds": "másodperc",
"term.time.second": "másodperc",
"term.fullscreenView": "Teljes képernyős mód",
"term.defaultView": "Alapértelmezett nézet",
"term.spacializedAudioSetting": "Térbeli hang",
"term.clearAll": "Összes törlése",
"term.recentStations": "Nemrég játszott",
"term.language": "Nyelv",
"term.funLanguages": "Játékos",
"term.noLyrics": "Betöltés... / Dalszöveg nem található. / Instrumentális.",
"term.copyright": "Copyright",
"term.rightsReserved": "Minden jog fenntartva.",
"term.sponsor": "Támogasd ezt a projektet",
"term.ciderTeam": "A Cider Csapata",
"term.developer": "Fejlesztő",
"term.socialTeam": "Social Team",
"term.contributors": "Contributors",
"term.equalizer": "Equalizer",
"term.reset": "Reset",
"term.tracks": "zeneszám", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "Videók",
"term.menu": "Menü",
// Home
"home.title": "Kezdőlap",
"home.recentlyPlayed": "Nemrég játszott",
"home.recentlyAdded": "Nemrég hozzáadott",
"home.artistsFeed": "Az előadóid feedje",
"home.artistsFeed.noArtist": "Kövess néhány művészt, hogy a legújabb zenéik itt legyenek",
"home.madeForYou": "Személyre szabva",
"home.friendsListeningTo": "A barátok épp ezt hallgatják",
"home.followedArtists": "Követett előadók",
// Errors
"error.appleMusicSubRequired": "Apple Music előfizetés szükséges.",
"error.connectionError": "Hiba történt az Apple Musichoz való csatlakozás közben.",
"error.noResults": "Nincs találat",
"error.noResults.description": "Próbálkozzon új kereséssel",
//Podcasts
"podcast.followOnCider": "Követés a Cideren",
"podcast.followedOnCider": "Követve a Cideren",
"podcast.subscribeOnItunes": "Feliratkozás az iTunesban",
"podcast.subscribedOnItunes": "Feliratkozva az iTunesban",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Epizódok",
"podcast.playEpisode": "Epizód lejátszása",
"podcast.website": "Podcast Weboldala",
// Actions
"action.addToLibrary": "Hozzáadás a Könyvtárhoz",
"action.addToLibrary.success": "Hozzáadva a Könyvtárhoz",
"action.addToLibrary.error": "Hiba történt! Sikertelen hozzáadás.",
"action.removeFromLibrary": "Törlés a Könytárból",
"action.removeFromLibrary.success": "Törölve a Könyvtárból",
"action.addToQueue": "Hozzáadás a várólistához",
"action.addToQueue.success": "Hozzáadva a várólistához",
"action.addToQueue.error": "Sikertelen hozzáadás a várólistához",
"action.removeFromQueue": "Törlés a várólistáról",
"action.removeFromQueue.success": "Törölve a várólistáról",
"action.removeFromQueue.error": "Sikertelen törlés a várólistáról",
"action.addToPlaylist": "Lejátszási listához adás",
"action.removeFromPlaylist": "Törlés a lejátszási listáról",
"action.addToFavorites": "Hozzáadás a kedvencekhez",
"action.follow": "Követés",
"action.follow.success": "Követve",
"action.follow.error": "Sikertelen követés",
"action.unfollow": "Követés visszavonása",
"action.unfollow.success": "Követés visszavonva",
"action.unfollow.error": "Sikertelen visszavonás",
"action.playNext": "Lejátszás következőként",
"action.playLater": "Lejátszás utolsóként",
"action.startRadio": "Állomás létrehozása",
"action.goToArtist": "Előadó megjelenítése",
"action.goToAlbum": "Album megjelenítése",
"action.moveToTop": "Mozgatás legfelülre",
"action.share": "Megosztás",
"action.rename": "Átnevezés",
"action.love": "Szeretem",
"action.unlove": "Mégsem szeretem",
"action.dislike": "Kevesebb ilyen javasolása",
"action.undoDislike": "A Kevesebb ilyen javasolása visszavonása",
"action.showWebRemoteQR": "Távirányító QR kód megjelenítése",
"action.playTracksNext": "Play ${app.selectedMediaItems.length} tracks next",
"action.playTracksLater": "Play ${app.selectedMediaItems.length} tracks later",
"action.removeTracks": "Remove ${self.selectedItems.length} tracks from queue",
"action.import": "Importálás",
"action.export": "Exportálás",
// Settings - General
"settings.header.general": "General",
"settings.header.general.description": "Adjust the general settings for Cider.",
"settings.option.general.language": "Language",
// Language optgroups
"settings.option.general.language.main": "Languages",
"settings.option.general.language.fun": "Fun Languages",
"settings.option.general.language.unsorted": "Unsorted",
// Settings - Audio
"settings.header.audio": "Hang",
"settings.header.audio.description": "A Cider hangbeállításainak módosítása.",
"settings.option.audio.quality": "Hangminőség", // Dropdown
"settings.header.audio.quality.high": "Magas",
"settings.header.audio.quality.low": "Alacsony",
"settings.header.audio.quality.auto": "Auto",
"settings.option.audio.seamlessTransition": "Szünetmentes lejátszás", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Haladó funkcionalitás engedélyezése", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "Az AudioContext funkció engedélyezése lehetővé teszi a fejlettebb hangfunkciókat, például a Normalizásást, az Equalizereket és a Visualizer funkciókat, azonban egyes számítógépeken ez akadozást okozhat a hangsávokban.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalizálás", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalizálja az egyes zeneszámok hangosabb részeit, hogy egységesebb hallgatási élményt hozzon létre.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Térbeli hang", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Térbeli hang és a hang háromdimenziósabbá tétele (Ez nem összekeverendő a Dolby Atmos-szal!)",
// Settings - Visual
"settings.header.visual": "Vizuális",
"settings.header.visual.description": "A Cider vizuális beállításainak módosítása.",
"settings.option.visual.windowBackgroundStyle": "Ablak háttér stílusa", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "None",
"settings.header.visual.windowBackgroundStyle.artwork": "Borító",
"settings.header.visual.windowBackgroundStyle.image": "Kép",
"settings.option.visual.animatedArtwork": "Animált borító", // Dropdown
"settings.header.visual.animatedArtwork.always": "Mindig",
"settings.header.visual.animatedArtwork.limited": "Oldalakra és speciális bejegyzésekre korlátozva.",
"settings.header.visual.animatedArtwork.disable": "Kikapcsolás mindenhol",
"settings.option.visual.animatedArtworkQuality": "Animált borító minősége", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Alacsony",
"settings.header.visual.animatedArtworkQuality.medium": "Közepes",
"settings.header.visual.animatedArtworkQuality.high": "Magas",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Nagyon magas",
"settings.header.visual.animatedArtworkQuality.extreme": "Extrém",
"settings.option.visual.animatedWindowBackground": "Animált ablakháttér", // Toggle
"settings.option.visual.hardwareAcceleration": "Hardveres gyorsítás", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Újraindítás szükséges",
"settings.header.visual.hardwareAcceleration.default": "Alap",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Személyes adatok mutatása", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "Dalszöveg",
"settings.header.lyrics.description": "A Cider dalszöveg beállításainak módosítása.",
"settings.option.lyrics.enableMusixmatch": "MusixMatch dalszövegek engedélyezése", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Karaoke mód bekapcsolása (Csak MusixMatch)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "MusixMatch fordítás nyelve", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "YouTube dalszövegek engedélyezése a zenei videóknál", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Csatlakozások",
"settings.header.connectivity.description": "A Cider csatlakozás beállításainak módosítása.",
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "Megjelenítés 'Cider'-ként",
"settings.header.connectivity.discordRPC.appleMusic": "Megjelenítés 'Apple Music'-ként",
"settings.option.connectivity.discordRPC.clearOnPause": "Discord Rich Presence törlése megállításnál", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble Késleltetés (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "LastFM Now Playing engedélyezése",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Loopolt zeneszám szűrése (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Kísérleti",
"settings.header.experimental.description": "A Cider kísérleti beállításainak módosítása.",
"settings.option.experimental.compactUI": "Kompakt UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Bezárás gomb működése",
"settings.option.experimental.closeButtonBehaviour.quit": "Cider bezárása",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Kis méret",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Kicsinyítés tálcára",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "Térbeli hang",
"spatial.width" : "Szélesség",
"spatial.height" : "Magasság",
"spatial.depth" : "Hosszúság",
"spatial.gain" : "Gain",
"spatial.roomMaterials" : "Szoba felépítése",
"spatial.roomDimensions" : "Room Dimensions",
"spatial.roomPositions" : "Room Positions",
"spatial.setDimensions" : "Set Dimensions",
"spatial.setPositions" : "Set Positions",
"spatial.up" : "Fent",
"spatial.front" : "Elől",
"spatial.left" : "Balra",
"spatial.right" : "Jobbra",
"spatial.back" : "Hátul",
"spatial.down" : "Lent",
"spatial.listener" : "Listener",
"spatial.audioSource" : "Audio Source",
// Settings - Unfinished
"settings.header.unfinished": "Félkész",
// Web Remote
"remote.web.title": "Cider Remote",
"remote.web.description": "Olvasd be ezt a QR-kódot a telefonoddal, hogy tudd vezérelni a lejátszót.",
// About
"about.thanks": "Köszönet a Cider Collective csapatának és minden hozzájárulónak."
}

287
src/i18n/ja_JP.jsonc Normal file
View file

@ -0,0 +1,287 @@
{
// App info
"app.name": "Cider",
"date.format": "${y}年${m}月${d}日",
// i18n Info
"i18n.languageName": "日本語", // name of language in native language
"i18n.languageNameEnglish": "Japanese", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
// Dialogs
"dialog.cancel": "キャンセル",
"dialog.ok": "OK",
// Notification
"notification.updatingLibrarySongs": "ライブラリの更新中...",
"notification.updatingLibraryAlbums": "ライブラリの更新中...",
"notification.updatingLibraryArtists": "ライブラリの更新中...",
// Terms
"term.appleMusic": "Apple Music", // Follows brand term
"term.applePodcasts": "Apple Podcasts", // Follows brand term
"term.itunes": "iTunes", // Follows brand term
"term.github": "GitHub", // Follows brand term
"term.discord": "Discord", // Follows brand term
"term.learnMore": "詳しい情報",
"term.accountSettings": "アカウント設定",
"term.logout": "サインアウト",
"term.login": "サインイン",
"term.about": "Ciderについて",
"term.privateSession": "プライベートセッション",
"term.queue": "次はこちら",
"term.search": "検索",
"term.library": "ライブラリ",
"term.listenNow": "今すぐ聴く",
"term.browse": "見つける",
"term.radio": "ラジオ",
"term.recentlyAdded": "最近追加した項目",
"term.songs": "曲",
"term.albums": "アルバム",
"term.artists": "アーティスト",
"term.podcasts": "Podcast",
"term.playlists": "プレイリスト",
"term.playlist": "プレイリスト",
"term.play": "再生",
"term.pause": "停止",
"term.previous": "戻る",
"term.next": "次へ",
"term.shuffle": "シャッフル",
"term.repeat": "リピート",
"term.volume": "音量",
"term.mute": "ミュート",
"term.unmute": "ミュート解除",
"term.share": "共有",
"term.settings": "設定",
"term.seeAll": "すべて見る",
"term.sortBy": "並べ替え",
"term.sortBy.album": "アルバム",
"term.sortBy.artist": "アーティスト",
"term.sortBy.name": "曲名",
"term.sortBy.genre": "ジャンル",
"term.sortBy.releaseDate": "配信開始日",
"term.sortBy.duration": "時間",
"term.sortOrder": "並べ替え",
"term.sortOrder.ascending": "昇順",
"term.sortOrder.descending": "降順",
"term.viewAs": "表示",
"term.viewAs.coverArt": "カバーアート",
"term.viewAs.list": "リスト",
"term.size": "サイズ",
"term.size.normal": "普通",
"term.size.compact": "コンパクト",
"term.enable": "ON",
"term.disable": "OFF",
"term.enabled": "ON",
"term.disabled": "OFF",
"term.connect": "接続",
"term.connecting": "接続中",
"term.disconnect": "切断",
"term.authed": "認証済み",
"term.confirm": "よろしいでしょうか?",
"term.more": "もっと",
"term.less": "減らす",
"term.showMore": "もっと見る",
"term.showLess": "表示数を少なくする",
"term.topSongs" : "トップソング",
"term.latestReleases": "ニューリリース",
"term.time.added": "追加日",
"term.time.released": "配信開始日",
"term.time.updated": "最終更新日",
"term.time.hours": "時間",
"term.time.hour": "時間",
"term.time.minutes": "分",
"term.time.minute": "分",
"term.time.seconds": "秒",
"term.time.second": "秒",
"term.fullscreenView": "全画面表示",
"term.defaultView": "ウィンドウ表示",
"term.spacializedAudioSetting": "オーディオ空間化設定",
"term.clearAll": "消去",
"term.recentStations": "最近の再生",
"term.language": "言語",
"term.noLyrics": "ローディング。。 / 歌詞が見つからない / 器楽曲.",
"term.copyright": "著作権",
"term.rightsReserved": "All Rights Reserved.", // Translation does not exist in Japanese
"term.sponsor": "スポンサーになりましょう",
"term.ciderTeam": "Cider チーム",
"term.developer": "開発者",
"term.socialTeam": "ソーシャル チーム",
"term.contributors": "貢献者",
"term.equalizer": "イコライザー",
"term.reset": "リセット",
"term.tracks": "曲", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "ビデオ",
"term.menu": "メニュー",
// Home
"home.title": "ホーム",
"home.recentlyPlayed": "最近の再生",
"home.recentlyAdded": "最近追加した項目",
"home.artistsFeed": "アーティストのフィード",
"home.artistsFeed.noArtist": "自分の好きなアーティストをフォローしましょう・",
"home.madeForYou": "あなたにおすすめ",
"home.friendsListeningTo": "友達が聴いている",
"home.followedArtists": "フォローしているアーティスト",
// Errors
"error.appleMusicSubRequired": "Apple Musicのサブスクリプションが必要です。",
"error.connectionError": "Apple Musicに接続できません。",
"error.noResults": "見つかりませんでした",
"error.noResults.description": "もう一度お試しください。",
//Podcasts
"podcast.followOnCider": "Ciderでフォロー",
"podcast.followedOnCider": "フォロー中",
"podcast.subscribeOnItunes": "iTunesで購読",
"podcast.subscribedOnItunes": "購読中",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "番組",
"podcast.playEpisode": "再生",
"podcast.website": "Podcast ウェブ",
// Actions
"action.addToLibrary": "ライブラリに追加",
"action.addToLibrary.success": "ライブラリに追加されました",
"action.addToLibrary.error": "ライブラリへの追加に失敗しました",
"action.removeFromLibrary": "ライブラリから削除",
"action.removeFromLibrary.success": "ライブラリから削除されました",
"action.addToQueue": "「次はこちら」に項目を追加",
"action.addToQueue.success": "「次はこちら」に項目を追加されました",
"action.addToQueue.error": "操作を完了できませんでした",
"action.addToPlaylist": "プレイリストに追加",
"action.removeFromPlaylist": "プレイリストから削除",
"action.addToFavorites": "ラブに追加",
"action.removeFromQueue": "「次はこちら」から項目を削除",
"action.removeFromQueue.success": "「次はこちら」から項目を削除されました",
"action.removeFromQueue.error": "操作を完了できませんでした",
"action.follow": "フォロー",
"action.follow.success": "フォロー中",
"action.follow.error": "操作を完了できませんでした",
"action.unfollow": "フォロー解除",
"action.unfollow.success": "フォローをやめました",
"action.unfollow.error": "操作を完了できませんでした",
"action.playNext": "次に再生",
"action.playLater": "最後に再生",
"action.startRadio": "ステーションを作成",
"action.goToArtist": "アーティストへ移動",
"action.goToAlbum": "アルバムへ移動",
"action.moveToTop": "上に戻る",
"action.share": "曲を共有",
"action.rename": "名前の変更",
"action.love": "ラブ",
"action.unlove": "ラブを解除",
"action.dislike": "これに似たものをすすめない",
"action.undoDislike": "「これと似た曲のおすすめを減らす」を取り消す",
"action.showWebRemoteQR": "WEBリモコンQRコードを表示",
"action.playTracksNext": "${app.selectedMediaItems.length}曲が次に再生",
"action.playTracksLater": "${app.selectedMediaItems.length}曲が最後に再生",
"action.removeTracks": "${self.selectedItems.length}曲が「次はこちら」から削除",
"action.import": "インポート",
"action.export": "エクスポート",
// Settings - Audio
"settings.header.audio": "オーディオ",
"settings.header.audio.description": "Ciderのオーディオ設定",
"settings.option.audio.quality": "音質", // Dropdown
"settings.header.audio.quality.high": "高品質",
"settings.header.audio.quality.low": "高効率",
"settings.header.audio.quality.auto": "自動",
"settings.option.audio.seamlessTransition": "曲間なしで再生", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "先進的な機能", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "AudioContext 機能を有効にすると、オーディオノーマライズ、空間オーディオ、イコライザーなどの機能を使用できますが、音が途切れるかもしれません。", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "オーディオノーマライズ", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "さまざまな曲の音量を均一にし、より整った音を楽しめるようにする機能です。",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "オーディオ空間化", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "オーディオを空間に分散させる機能です。 (ドルビーアトモスではありません)",
// Settings - Visual
"settings.header.visual": "ビジュアル",
"settings.header.visual.description": "Ciderのビジュアル設定",
"settings.option.visual.windowBackgroundStyle": "アプリウインドウの背景スタイル", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "なし",
"settings.header.visual.windowBackgroundStyle.artwork": "アートワーク",
"settings.option.visual.animatedArtwork": "アニメーションアートワーク", // Dropdown
"settings.header.visual.animatedArtwork.always": "常に表示",
"settings.header.visual.animatedArtwork.limited": "アーティストページのみ表示",
"settings.header.visual.animatedArtwork.disable": "オフ",
"settings.option.visual.animatedArtworkQuality": "アニメーションアートワークの品質", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "低",
"settings.header.visual.animatedArtworkQuality.medium": "中",
"settings.header.visual.animatedArtworkQuality.high": "高",
"settings.header.visual.animatedArtworkQuality.veryHigh": "超高",
"settings.header.visual.animatedArtworkQuality.extreme": "最高",
"settings.option.visual.animatedWindowBackground": "アプリウィンドウの背景をアニメーション化", // Toggle
"settings.option.visual.hardwareAcceleration": "ハードウェア アクセラレーション", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "アプリを再起動する必要があります",
"settings.header.visual.hardwareAcceleration.default": "既定",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "プロフィールを表示", // Toggle
// Settings - General (Reserved)
"settings.header.general": "一般",
"settings.header.general.description": "Ciderの一般設定",
// Settings - Lyrics
"settings.header.lyrics": "歌詞",
"settings.header.lyrics.description": "歌詞の表示設定",
"settings.option.lyrics.enableMusixmatch": "Musixmatchで歌詞を表示する", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "カラオケモードを有効にする (Musixmatchのみ)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "歌詞の優先言語", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "YouTubeの歌詞をミュージックビデオに使用する", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "アプリと連携",
"settings.header.connectivity.description": "Ciderの連携設定",
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "'Cider' を表示する",
"settings.header.connectivity.discordRPC.appleMusic": "'Apple Music' を表示する",
"settings.option.connectivity.discordRPC.clearOnPause": "一時停止時にDiscord Rich Presenceをクリアする", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble Delay (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable LastFM Now Playing",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "試験的な機能",
"settings.header.experimental.description": "開発中の実験的な機能は不完全で不安定である可能性があります",
"settings.option.experimental.compactUI": "コンパクトインターフェース", // Toggle
"settings.option.experimental.closeButtonBehaviour": "「閉じる」ボタンの動作", // Dropdown
"settings.option.experimental.closeButtonBehaviour.quit": "アプリを終了する",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "タスクバーに最小化する",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "トレイに最小化する",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "空間化のプロパティ",
"spatial.width" : "幅",
"spatial.height" : "高さ",
"spatial.depth" : "奥行",
"spatial.gain" : "ゲイン",
"spatial.roomMaterials" : "部屋のマテリアル",
"spatial.roomDimensions" : "部屋の大きさ",
"spatial.roomPositions" : "部屋の位置",
"spatial.setDimensions" : "大きさを設定",
"spatial.setPositions" : "位置を設定",
"spatial.up" : "上",
"spatial.front" : "前",
"spatial.left" : "左",
"spatial.right" : "右",
"spatial.back" : "後",
"spatial.down" : "下",
"spatial.listener" : "リスナー",
"spatial.audioSource" : "音源",
// Settings - Unfinished
"settings.header.unfinished": "未完成",
// Web Remote
"remote.web.title": "Cider リモート",
"remote.web.description": "QRコードを使用して、Ciderとスマートフォンをペアリングする",
//About
"about.thanks": "Cider Collective とご協力いただいた貢献者様に感謝申し上げます。"
}

238
src/i18n/pt_BR.jsonc Normal file
View file

@ -0,0 +1,238 @@
{ // Base File
// App info
"app.name": "Cider",
"date.format": "${d} ${m}, ${y}",
// Dialogs
"dialog.cancel": "Cancelar",
"dialog.ok": "OK",
// Notification
"notification.updatingLibrarySongs": "Atualizando músicas na biblioteca...",
"notification.updatingLibraryAlbums": "Atualizando albuns na biblioteca...",
"notification.updatingLibraryArtists": "Atualizando artistas na biblioteca...",
"notification.connectionError": "Houve um problema a se conectar no Apple Music",
// Terms
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "Saiba Mais",
"term.accountSettings": "Definições da Conta",
"term.logout": "Sair",
"term.login": "Entrar",
"term.about": "Sobre",
"term.privateSession": "Sessão Privada",
"term.queue": "Fila",
"term.search": "Procurar",
"term.library": "Biblioteca",
"term.listenNow": "Ouça Agora",
"term.browse": "Explorar",
"term.radio": "Radio",
"term.recentlyAdded": "Adicionado Recentemente",
"term.songs": "Músicas",
"term.albums": "Albuns",
"term.artists": "Artistas",
"term.podcasts": "Podcasts",
"term.playlists": "Listas de Reprodução",
"term.playlist": "Lista de Reprodução",
"term.play": "Tocar",
"term.pause": "Pausar",
"term.previous": "Anterior",
"term.next": "Próximo",
"term.shuffle": "Aleatório",
"term.repeat": "Repetir",
"term.volume": "Volume",
"term.mute": "Mudo",
"term.unmute": "Tirar o Mudo",
"term.share": "Partilhar",
"term.settings": "Definições",
"term.seeAll": "Ver Tudo",
"term.sortBy": "Organizar Por",
"term.sortBy.album": "Album",
"term.sortBy.artist": "Artista",
"term.sortBy.name": "Nome",
"term.sortBy.genre": "Genero",
"term.sortBy.releaseDate": "Data de Lançamento",
"term.sortBy.duration": "Duração",
"term.sortOrder": "A-Z",
"term.sortOrder.ascending": "Ascendente",
"term.sortOrder.descending": "Descendente",
"term.viewAs": "Ver Como",
"term.viewAs.coverArt": "Capa",
"term.viewAs.list": "Lista",
"term.size": "Tamanho",
"term.size.normal": "Normal",
"term.size.compact": "Compacto",
"term.enable": "Ativar",
"term.disable": "Desativar",
"term.enabled": "Ativado",
"term.disabled": "Desativado",
"term.connect": "Conectar",
"term.disconnect": "Desconectar",
"term.connecting": "Conectando",
"term.confirm": "Confirmar ?",
"term.more": "Mais",
"term.less": "Menos",
"term.showMore": "Mostrar Mais",
"term.showLess": "Mostrar Menos",
"term.topSongs" : "Top de Músicas",
"term.latestReleases": "Ultimos Lançamentos",
"term.time.added": "Adicionado",
"term.time.released": "Lançado",
"term.time.updated": "Atualizado",
"term.fullscreenView": "Visualização em Tela Cheia",
"term.defaultView": "Visualização Normal",
"term.spacializedAudioSetting": "Definições de Audio Espacial",
"term.clearAll": "Limpar Tudo",
"term.language": "Idioma",
"term.recentStations": "Estações Recentes",
// Home
"home.title": "Inicio",
"home.recentlyPlayed": "Tocado Recentemente",
"home.recentlyAdded": "Adicionado Recentemente",
"home.artistsFeed": "Novidades dos seus Artistas",
"home.madeForYou": "Feito para Você",
"home.friendsListeningTo": "Amigos Ouvindo",
"home.followedArtists": "Artistas Seguidos",
// Errors
"error.appleMusicSubRequired": "Necessário uma assinatura Apple Music.",
// Actions
"action.addToLibrary": "Adicionar à Biblioteca",
"action.addToLibrary.success": "Adicionado à Biblioteca",
"action.addToLibrary.error": "Erro ao Adicionar na Biblioteca",
"action.removeFromLibrary": "Remover da Biblioteca",
"action.removeFromLibrary.success": "Removido da Biblioteca",
"action.addToQueue": "Adicionar à Fila",
"action.addToQueue.success": "Adicionado à Fila",
"action.addToQueue.error": "Erro ao Adicionar à Fila",
"action.removeFromQueue": "Remover da Fila",
"action.removeFromQueue.success": "Removido da Fila",
"action.removeFromQueue.error": "Erro ao Remover da Fila",
"action.addToPlaylist": "Adicionar à Lista de Reprodução",
"action.removeFromPlaylist": "Remover da Lista de Reprodução",
"action.addToFavorites": "Adicionar aos Favoritos",
"action.follow": "Seguir",
"action.follow.success": "A Seguir",
"action.follow.error": "Erro ao Seguir",
"action.unfollow": "Deixar de Seguir",
"action.unfollow.success": "Deixou de Seguir",
"action.unfollow.error": "Erro ao Deixar de Seguir",
"action.playNext": "Tocar Proximo",
"action.playLater": "Tocar por Ultimo",
"action.startRadio": "Começar Radio",
"action.goToArtist": "Ir para o Artista",
"action.goToAlbum": "Ir para o Album",
"action.moveToTop": "Mover para cima",
"action.share": "Partilhar",
"action.rename": "Mudar o Nome",
"action.love": "Gostar",
"action.unlove": "Deixar de Gostar",
"action.dislike": "Não Gostar",
"action.undoDislike": "Deixar de Não Gostar",
"action.showWebRemoteQR": "Mostrar o QR para Página Remota",
// Settings - Audio
"settings.header.audio": "Audio",
"settings.header.audio.description": "Ajustar as definições de audio no Cider.",
"settings.option.audio.quality": "Qualidade do Audio", // Dropdown
"settings.header.audio.quality.high": "Alta",
"settings.header.audio.quality.low": "Baixa",
"settings.header.audio.quality.auto": "Automatico",
"settings.option.audio.seamlessTransition": "Transição de Áudio Perfeita", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Ativar Funcionabilidades Avançadas", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "Habilitar a funcionalidade AudioContext permitirá recursos de áudio estendidos, como Normalização de Áudio , Equalizadores e Visualizadores. No entanto, em alguns sistemas, isso pode causar travamentos nas faixas de áudio.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalização de Audio", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normaliza o volume alto para faixas individuais para criar uma experiência de audição mais uniforme.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Audio Espacial", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Espacialize o áudio e torne o áudio mais tridimensional (nota: isto não é Dolby Atmos)",
// Settings - Visual
"settings.header.visual": "Visual",
"settings.header.visual.description": "Ajustar as Definições de Visual do Cider.",
"settings.option.visual.windowBackgroundStyle": "Estilo do Fundo da Janela", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "Nenhum",
"settings.header.visual.windowBackgroundStyle.artwork": "Capa",
"settings.option.visual.animatedArtwork": "Capa Animada", // Dropdown
"settings.header.visual.animatedArtwork.always": "Sempre",
"settings.header.visual.animatedArtwork.limited": "Limitado a páginas e entradas especiais",
"settings.header.visual.animatedArtwork.disable": "Desativar em Tudo",
"settings.option.visual.animatedArtworkQuality": "Qualidade da Capa Animada", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Baixa",
"settings.header.visual.animatedArtworkQuality.medium": "Media",
"settings.header.visual.animatedArtworkQuality.high": "Alta",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Muito Alta",
"settings.header.visual.animatedArtworkQuality.extreme": "Extrema",
"settings.option.visual.animatedWindowBackground": "Fundo de Janela Animado", // Toggle
"settings.option.visual.hardwareAcceleration": "Acelaração no Hardware", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Necessário reiniciar a aplicação",
"settings.header.visual.hardwareAcceleration.default": "Normal",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Mostrar Informaçoes Pessoais", // Toggle
// Settings - General (Reserved)
"settings.header.general": "Principal",
"settings.header.general.description": "Ajustar as definiçoes principais no Cider.",
// Settings - Lyrics
"settings.header.lyrics": "Letras",
"settings.header.lyrics.description": "Ajustar as definições das letras no Cider.",
"settings.option.lyrics.enableMusixmatch": "Ativar Letras do Musixmatch", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Ativar Modo Karaoke (só para Musixmatch)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Idioma Preferido para Tradução Musixmatch", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "Ativar letras do Youtube para videos musicais", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Conectividade",
"settings.header.connectivity.description": "Ajustar as definições de conectividade no Cider.",
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "Mostrar como 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "Mostrar como 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "Apagar Discord Rich Presence quando estiver pausado", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "Atraso dos Scrobbles do LastFM (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Ativar LastFM Now Playing",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remover artistas de colaboração do nome da música (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Experimental",
"settings.header.experimental.description": "Ajustar as definições experimental no Cider.",
"settings.option.experimental.compactUI": "UI Compacto", // Toggle
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "Propriedades do Espacial",
"spatial.width" : "Largura",
"spatial.height" : "Altura",
"spatial.depth" : "Profundidade",
"spatial.roomMaterials" : "Materiais da Sala",
"spatial.roomDimensions" : "Dimensões da Sala",
"spatial.roomPositions" : "Posições da Sala",
"spatial.setDimensions" : "Definir Dimensões",
"spatial.setPositions" : "Definir Posições",
"spatial.up" : "Acima",
"spatial.front" : "Frente",
"spatial.left" : "Esquerda",
"spatial.right" : "Direita",
"spatial.back" : "Atrás",
"spatial.down" : "Abaixo",
"spatial.listener" : "Ouvinte",
"spatial.audioSource" : "Fonte de Audio",
// Settings - Unfinished
"settings.header.unfinished": "Inacabado",
// Web Remote
"remote.web.title": "Cider Remoto",
"remote.web.description": "Digitalize o código QR para emparelhar seu telefone com esta instância Cider"
}

299
src/i18n/tr_TR.jsonc Normal file
View file

@ -0,0 +1,299 @@
{
// i18n Info
"i18n.languageName": "Türkçe", // name of language in native language
"i18n.languageNameEnglish": "Turkish", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@gms10ur", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${d} ${m}, ${y}",
// Dialogs
"dialog.cancel": "İptal",
"dialog.ok": "Tamam",
// Notification
"notification.updatingLibrarySongs": "Arşiv'deki şarkılar alınıyor...",
"notification.updatingLibraryAlbums": "Arşiv'deki albümler alınıyor...",
"notification.updatingLibraryArtists": "Arşiv'deki sanatçılar alınıyor...",
// Terms
"term.appleInc": "Apple",
"term.appleMusic": "Apple Müzik",
"term.applePodcasts": "Apple Podcastler",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "Daha fazla bilgi edin",
"term.accountSettings": "Hesap Ayarları",
"term.logout": ıkış Yap",
"term.login": "Giriş yap",
"term.about": "Hakkında",
"term.privateSession": "Özel Oturum",
"term.queue": "Çalma Sırası",
"term.search": "Arama",
"term.library": "Arşiv",
"term.listenNow": "Şimdi Dinle",
"term.browse": "Göz At",
"term.radio": "Radyo",
"term.recentlyAdded": "Son Eklenenler",
"term.songs": "Şarkılar",
"term.albums": "Albümler",
"term.artists": "Sanatçılar",
"term.podcasts": "Podcastler",
"term.playlists": "Listeler",
"term.playlist": "Liste",
"term.play": "Oynat",
"term.pause": "Duraklat",
"term.previous": "Önceki",
"term.next": "Sonraki",
"term.shuffle": "Karıştır",
"term.repeat": "Tekrarla",
"term.volume": "Ses",
"term.mute": "Sustur",
"term.unmute": "Sesi Aç",
"term.share": "Paylaş",
"term.settings": "Ayarlar",
"term.seeAll": "Tümünü Gör",
"term.sortBy": "Sırala",
"term.sortBy.album": "Albüm",
"term.sortBy.artist": "Sanatçı",
"term.sortBy.name": "Şarkı İsmi",
"term.sortBy.genre": "Tür",
"term.sortBy.releaseDate": "Yayınlanma Tarihi",
"term.sortBy.duration": "Süre",
"term.sortOrder": "A-Z",
"term.sortOrder.ascending": "Çoğalan",
"term.sortOrder.descending": "Azalan",
"term.viewAs": "Şöyle Göster",
"term.viewAs.coverArt": "Albüm Kapağı",
"term.viewAs.list": "Liste",
"term.size": "Boyut",
"term.size.normal": "Normal",
"term.size.compact": "Daha Sıkı",
"term.enable": "Aç",
"term.disable": "Kapa",
"term.enabled": "Açık",
"term.disabled": "Kapalı",
"term.connect": "Bağlan",
"term.connecting": "Bağlanıyor",
"term.disconnect": "Bağlantıyı Kes",
"term.authed": "Bağlantı sağlandı",
"term.confirm": "Onayla?",
"term.more": "Daha Fazla",
"term.less": "Daha Az",
"term.showMore": "Daha Fazla Göster",
"term.showLess": "Daha Az Göster",
"term.topSongs" : "Popüler Parçalar",
"term.latestReleases": "Yeni Çıkan",
"term.time.added": "Şu tarihte eklendi: ",
"term.time.released": "Şu tarihte yayınalndı: ",
"term.time.updated": "Şu tarihte güncellendi: ",
"term.fullscreenView": "Tam Ekrana Geç",
"term.defaultView": "Normal Görünüme Dön",
"term.spacializedAudioSetting": "Uzamsal Ses Ayarları",
"term.clearAll": "Tümünü Temizle",
"term.recentStations": "Son İstasyonlar",
"term.language": "Dil",
"term.funLanguages": "Mizahi Diller",
"term.noLyrics": "Yükleniyor... / Şarkı Sözü Bulunamadı./ Enstrumantal.",
"term.copyright": "Copyright",
"term.rightsReserved": "Tüm Haklası Saklıdır.",
"term.sponsor": "Bu Projeye Destek Olun",
"term.ciderTeam": "Cider Ekibi",
"term.developer": "Geliştirici",
"term.socialTeam": "Sosyal Ekip",
"term.contributors": "Katkıda Bulunanlar",
"term.equalizer": "Ekolayzer",
"term.reset": "Sıfırla",
"term.tracks": "adet şarkı", // Assume x amount of tracks. e.g. 50 tracks
"term.time.hours": "saat",
"term.time.hour": "saat",
"term.time.minutes": "dakika",
"term.time.minute": "dakika",
"term.time.seconds": "saniye",
"term.time.second": "saniye",
"term.videos": "Video Klipler",
"term.menu": "Menü",
// Home
"home.title": "Ana Sayfa",
"home.recentlyPlayed": "Son Oynatılanlar",
"home.recentlyAdded": "Son Eklenenler",
"home.artistsFeed": "Son Çıkanlar",
"home.artistsFeed.noArtist": "Birkaç sanatçı takip ettiğinizde, sanatçılarınızın son çıkan yayınları burada gözükür.",
"home.madeForYou": "Sadece Size Özel",
"home.friendsListeningTo": "Arkadaşlarınızın Dinledikleri",
"home.followedArtists": "Takip Edilen Sanatçılar",
// Errors
"error.appleMusicSubRequired": "Apple Müzik, aktif bir abonelik gerektirir.",
"error.connectionError": "Apple Müzik ile bağlantı kurulamadı.",
"error.noResults": "Hiç sonuç yok",
"error.noResults.description": "Tekrar deneyin.",
//Podcasts
"podcast.followOnCider": "Cider'de Takip Et",
"podcast.followedOnCider": "Cider'de Takip Ediliyor",
"podcast.subscribeOnItunes": "itunes'de Abone Ol",
"podcast.subscribedOnItunes": "iTunes'de Abone Olundu",
"podcast.itunesStore": "iTunes Mağazası",
"podcast.episodes": "Bölümler",
"podcast.playEpisode": "Bölümü Oynat",
"podcast.website": "Web Sayfası",
// Actions
"action.addToLibrary": "Arşiv'e Ekle",
"action.addToLibrary.success": "Arşiv'e Eklendi",
"action.addToLibrary.error": "Arşiv'e Eklenemedi",
"action.removeFromLibrary": "Arşiv'den Sil",
"action.removeFromLibrary.success": "Arşiv'den Silindi",
"action.addToPlaylist": "Liste'ye Ekle",
"action.removeFromPlaylist": "Liste'den Sil",
"action.addToFavorites": "Favorilere Ekle",
"action.moveToTop": "En Başa Taşı",
"action.rename": "Yeniden Adlandır",
"action.addToQueue": "Sıraya Ekle",
"action.addToQueue.success": "Sıraya Eklendi",
"action.addToQueue.error": "Sıraya Eklenemedi",
"action.removeFromQueue": "Sıradan Kaldır",
"action.removeFromQueue.success": "Sıradan Kaldırıldı",
"action.removeFromQueue.error": "Sıradan Kaldırılamadı",
"action.follow": "Takip Et",
"action.follow.success": "Takip Ediliyor",
"action.follow.error": "Takip Edilemedi",
"action.unfollow": "Takibi Bırak",
"action.unfollow.success": "Takipten Çıkıldı",
"action.unfollow.error": "Takipten Çıkılamadı",
"action.playNext": "Sıradaki Yap",
"action.playLater": "En Son Çal",
"action.startRadio": "İstasyon Yarat",
"action.goToArtist": "Sanatçıya Git",
"action.goToAlbum": "Albüme Git",
"action.share": "Paylaş",
"action.love": "Beğen",
"action.unlove": "Beğeniyi Kaldır",
"action.dislike": "Bunun Gibileri Daha Az Öner",
"action.undoDislike": "Bunun Gibileri Daha Az Önermeyi Geri Al",
"action.showWebRemoteQR": "Uzaktan Kumanda Bağla",
"action.playTracksNext": "${app.selectedMediaItems.length} adet şarkıyı sıraya ekle",
"action.playTracksLater": "${app.selectedMediaItems.length} adet şarkıyı sıranın en sonuna ekle",
"action.removeTracks": "Sıradan ${self.selectedItems.length} adet şarkıyı kaldır",
"action.import": "Import",
"action.export": "Export",
"term.showAlbum": "Tüm Albümü Göster",
// Settings - General (Reserved)
"settings.header.general": "Genel Ayarlar",
"settings.header.general.description": "Genel ayarları buradan düzenleyin.",
"settings.option.general.language": "Dil Seçeneği",
// Language optgroups
"settings.option.general.language.main": "Gerçek Diller",
"settings.option.general.language.fun": "Mizahi Diller",
"settings.option.general.language.unsorted": "Henüz Tamamlanmamış Diller",
// Settings - Audio
"settings.header.audio": "Çalma / Oynatma",
"settings.header.audio.description": "Cider'in sizin için en iyi dinleme deneyimini sağlayabilmesi için ses ayarlarınızı yapın.",
"settings.option.audio.quality": "Ses Kalitesi", // Dropdown
"settings.header.audio.quality.high": "Yüksek Kalite",
"settings.header.audio.quality.low": "Yüksek Verimlilik",
"settings.header.audio.quality.auto": "Otomatik",
"settings.option.audio.seamlessTransition": "Kesintisiz Ses Geçişi", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "Gelişmiş Ses Deneyimi", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "Gelişmiş ses deneyiminin etkinleştirilmesi, Ses Normalleştirme, Ekolayzer ve Görselleştirici gibi genişletilmiş ses özelliklerine izin verir, ancak bu durum bazı sistemlerde seste bozulmalara neden olabilir.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Ses Normalleştirme", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Ses normalleştirme alçak ve yüksek sesli şarkıları dengeler ve daha düzgün bir dinleme deneyimi sağlar.",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Uzamsal Ses", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Sesi uzamsallaştırın ve sesi daha 3 boyutlu hale getirin (not: Bu Dolby Atmos değildir)",
// Settings - Visual
"settings.header.visual": "Görünüm",
"settings.header.visual.description": "Cider'in nasıl gözükmesini istediğinizi ayarlayın",
"settings.option.visual.windowBackgroundStyle": "Uygulama Arka Plan Stili", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "Karanlık",
"settings.header.visual.windowBackgroundStyle.artwork": "Albüm Kapağı",
"settings.option.visual.animatedArtwork": "Hareketli Albüm Kapakları", // Dropdown
"settings.header.visual.animatedArtwork.always": "Her Zaman Açık",
"settings.header.visual.animatedArtwork.limited": "Bazı Sayfalara ve Özel Bölgelerle Sınırlı",
"settings.header.visual.animatedArtwork.disable": "Her Zaman Kapalı",
"settings.option.visual.animatedArtworkQuality": "Hareketli Albüm Kapağı Kalitesi", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "Düşük",
"settings.header.visual.animatedArtworkQuality.medium": "Orta",
"settings.header.visual.animatedArtworkQuality.high": "Yüksek",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Daha Yüksek",
"settings.header.visual.animatedArtworkQuality.extreme": "Ekstrem",
"settings.option.visual.animatedWindowBackground": "Hareketli Uygulama Arka Planı", // Toggle
"settings.option.visual.hardwareAcceleration": "Donanım Hızlandırması", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "Etki etmesi için uygulamayı yeniden başlatmak gerekir.",
"settings.header.visual.hardwareAcceleration.default": "Varsayılan",
"settings.header.visual.hardwareAcceleration.webGPU": "Gelişmiş",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "Kullanıcı Adımı Göster", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "Şarkı Sözleri",
"settings.header.lyrics.description": "Cider'in şarkı sözlerini nasıl görüntülemesini istediğini buradan ayarlayın.",
"settings.option.lyrics.enableMusixmatch": "Musixmatch Kullan", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "Karaoke Modunu Etkinleştir (Sadece Musixmatch)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch için Otomatik Çeviri Dili", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "Müzik Videoları için Şarkı Sözünü YouTube'dan Al", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "Diğer Servisler",
"settings.header.connectivity.description": "Cider'i diğer servislere bağlayarak deneyiminizi zenginleştirin.",
"settings.option.connectivity.discordRPC": "Ne Dinlediğimi Discord'da Göster", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "'Cider' Olarak",
"settings.header.connectivity.discordRPC.appleMusic": "'Apple Music' Olarak",
"settings.option.connectivity.discordRPC.clearOnPause": "Duraklatıldığında Discord'da Gösterme", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Bağlantısı", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobblalma Yüzdesi (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Şimdi çalan şarkıyı LastFM'de göster",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Albüm sanatçısını Scrobbledan kaldır(LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Tekrar edilen şarkıyı filtrele (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "Deneysel",
"settings.header.experimental.description": "Cider'deki deneysel özelliklere erişim sağlayın. (Not: Bazı özellikler düzgün çalışmayabilir.)",
"settings.option.experimental.compactUI": "Kompakt Arayüz", // Toggle
"settings.option.experimental.closeButtonBehaviour": "Kapat düğmesi davranışı",
"settings.option.experimental.closeButtonBehaviour.quit": "Cider'den çık",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Simge durumuna küçült",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Görev çubuğuna küçült",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "Uzamsal Özellikler",
"spatial.width" : "Genişlik",
"spatial.height" : "Yükseklik",
"spatial.depth" : "Derinlik",
"spatial.roomMaterials" : "Oda Materyalleri",
"spatial.roomDimensions" : "Oda Ölçüleri",
"spatial.roomPositions" : "Oda Pozisyonu",
"spatial.setDimensions" : "Ölçüleri Ayarla",
"spatial.setPositions" : "Pozisyonu Ayarla",
"spatial.up" : "Üst",
"spatial.front" : "Ön",
"spatial.left" : "Sol",
"spatial.right" : "Sağ",
"spatial.back" : "Arka",
"spatial.down" : "Aşağı",
"spatial.listener" : "Dinleyici",
"spatial.audioSource" : "Ses Kaynağı",
// Settings - Unfinished
"settings.header.unfinished": "Geliştirme Aşamasında",
// Web Remote
"remote.web.title": "Cider'e Bağlan",
"remote.web.description": "Telefonunuzu Bu Cider Oturumuyla Eşleştirmek için QR Kodunu Tarayın",
//About
"about.thanks": "Cider Collective Ekibine ve tüm katkıda bulunanlara çok teşekkür ederiz."
}

288
src/i18n/zh_CN.jsonc Normal file
View file

@ -0,0 +1,288 @@
{
// App info
"app.name": "Cider",
"date.format": "${y}年${m}月${d}日",
// i18n Info
"i18n.languageName": "简体中文(中国)", // name of language in native language
"i18n.languageNameEnglish": "Simp. Chinese (China)", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
// Dialogs
"dialog.cancel": "取消",
"dialog.ok": "确定",
// Notification
"notification.updatingLibrarySongs": "正在更新资料库的歌曲信息...",
"notification.updatingLibraryAlbums": "正在更新资料库的专辑信息...",
"notification.updatingLibraryArtists": "正在更新资料库的艺人信息...",
// Terms
"term.appleMusic": "Apple Music", // Follows brand term
"term.applePodcasts": "Apple Podcasts", // Follows brand term
"term.itunes": "iTunes", // Follows brand term
"term.github": "GitHub", // Follows brand term
"term.discord": "Discord", // Follows brand term
"term.learnMore": "更多信息",
"term.accountSettings": "账户设置",
"term.logout": "登出",
"term.login": "登录",
"term.about": "关于",
"term.privateSession": "私人聆听",
"term.queue": "队列",
"term.search": "搜索",
"term.library": "资料库",
"term.listenNow": "现在就听",
"term.browse": "浏览",
"term.radio": "广播",
"term.recentlyAdded": "最近添加",
"term.songs": "歌曲",
"term.albums": "专辑",
"term.artists": "艺人",
"term.podcasts": "播客",
"term.playlists": "播放列表",
"term.playlist": "播放列表",
"term.play": "播放",
"term.pause": "暂停",
"term.previous": "上一首",
"term.next": "下一首",
"term.shuffle": "随机播放",
"term.repeat": "重复播放",
"term.volume": "音量",
"term.mute": "静音",
"term.unmute": "解除静音",
"term.share": "分享",
"term.settings": "设置",
"term.seeAll": "查看全部",
"term.sortBy": "排序",
"term.sortBy.album": "专辑",
"term.sortBy.artist": "艺人",
"term.sortBy.name": "歌名",
"term.sortBy.genre": "类型",
"term.sortBy.releaseDate": "发行日期",
"term.sortBy.duration": "时长",
"term.sortOrder": "字母排序",
"term.sortOrder.ascending": "升序",
"term.sortOrder.descending": "倒序",
"term.viewAs": "显示模式",
"term.viewAs.coverArt": "专辑封面",
"term.viewAs.list": "列表",
"term.size": "大小",
"term.size.normal": "正常",
"term.size.compact": "紧凑",
"term.enable": "启用",
"term.disable": "禁用",
"term.enabled": "已启用",
"term.disabled": "已禁用",
"term.connect": "连接",
"term.connecting": "连接中",
"term.disconnect": "断开",
"term.authed": "已认证",
"term.confirm": "确认?",
"term.more": "更多",
"term.less": "较少",
"term.showMore": "显示更多",
"term.showLess": "显示更少",
"term.topSongs" : "热门歌曲",
"term.latestReleases": "最新发行",
"term.time.added": "添加于",
"term.time.released": "发行于",
"term.time.updated": "更新于",
"term.time.hours": "小时",
"term.time.hour": "小时",
"term.time.minutes": "分钟",
"term.time.minute": "分钟",
"term.time.seconds": "秒",
"term.time.second": "秒",
"term.fullscreenView": "全屏",
"term.defaultView": "默认",
"term.spacializedAudioSetting": "音频空间化设置",
"term.clearAll": "清空",
"term.recentStations": "最近播放的频道",
"term.language": "语言",
"term.noLyrics": "加载中。。/ 搜索无结果 / 纯音乐",
"term.copyright": "版权所有",
"term.rightsReserved": "保留所有权利。",
"term.sponsor": "赞助",
"term.ciderTeam": "Cider 团队",
"term.developer": "开发者",
"term.socialTeam": "媒体团队",
"term.contributors": "贡献者",
"term.equalizer": "均衡器",
"term.reset": "重置",
"term.tracks": "首歌曲", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "视频",
"term.menu": "菜单",
// Home
"home.title": "主页",
"home.recentlyPlayed": "最近播放",
"home.recentlyAdded": "最近添加",
"home.artistsFeed": "艺人推荐",
"home.artistsFeed.noArtist": "追踪您喜爱的艺人后便可查看他们的最新发行。",
"home.madeForYou": "专属推荐",
"home.friendsListeningTo": "朋友正在听",
"home.followedArtists": "关注的艺人",
// Errors
"error.appleMusicSubRequired": "需要订阅 Apple Music 以使用 Cider",
"error.connectionError": "无法连接到 Apple Music。",
"error.noResults": "没有结果",
"error.noResults.description": "尝试更改搜索条件。",
//Podcasts
"podcast.followOnCider": "在 Cider 中追踪",
"podcast.followedOnCider": "已追踪",
"podcast.subscribeOnItunes": "在 iTunes 上订阅",
"podcast.subscribedOnItunes": "已订阅",
"podcast.itunesStore": "iTunes Store", // Follow brand term
"podcast.episodes": "单集",
"podcast.playEpisode": "播放单集",
"podcast.website": "Podcast 网站",
// Actions
"action.addToLibrary": "加入资料库",
"action.addToLibrary.success": "成功加入资料库",
"action.addToLibrary.error": "加入资料库的过程发生了错误",
"action.removeFromLibrary": "从资料库中移除",
"action.removeFromLibrary.success": "已从资料库中移除",
"action.addToQueue": "加入队列",
"action.addToQueue.success": "成功加入队列",
"action.addToQueue.error": "加入队列的过程发生了错误",
"action.removeFromQueue": "从队列中移除",
"action.removeFromQueue.success": "已从队列中移除",
"action.removeFromQueue.error": "从队列中移除的过程发生了错误",
"action.addToPlaylist": "加入播放列表",
"action.removeFromPlaylist": "从播放列表中移除",
"action.addToFavorites": "加至收藏",
"action.follow": "关注",
"action.follow.success": "已关注",
"action.follow.error": "尝试关注的过程发生了错误",
"action.unfollow": "取消关注",
"action.unfollow.success": "已取消关注",
"action.unfollow.error": "尝试取消关注的过程发生了错误",
"action.playNext": "下一首播放",
"action.playLater": "最后播放",
"action.startRadio": "开始电台",
"action.goToArtist": "前往艺人",
"action.goToAlbum": "前往专辑",
"action.moveToTop": "移到顶部",
"action.share": "分享歌曲",
"action.rename": "重命名",
"action.love": "喜欢",
"action.unlove": "踩",
"action.dislike": "减少此类建议",
"action.undoDislike": "增加此类建议",
"action.showWebRemoteQR": "显示远程控制的二维码",
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
"action.playTracksLater": "最后播放 ${app.selectedMediaItems.length} 首歌曲",
"action.removeTracks": "从队列中移除 ${self.selectedItems.length} 首歌曲",
"action.import": "导入",
"action.export": "导出",
// Settings - Audio
"settings.header.audio": "音频",
"settings.header.audio.description": "调整 Cider 的音频设置",
"settings.option.audio.quality": "音质", // Dropdown
"settings.header.audio.quality.high": "高音质",
"settings.header.audio.quality.low": "高效率",
"settings.header.audio.quality.auto": "自动",
"settings.option.audio.seamlessTransition": "无缝播放", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "进阶功能", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "启用 AudioContext 将解锁例如音量标准化和音频空间化的功能,但可能会在小部分设备上出现音频上的卡顿。",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量标准化", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "使所感知到的音频响度统一",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "音频空间化", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "使所感知到的音频更有立体感 (注: 这不是杜比全景声)",
// Settings - Visual
"settings.header.visual": "外观",
"settings.header.visual.description": "调整 Cider 的外观",
"settings.option.visual.windowBackgroundStyle": "窗口背景样式", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "无",
"settings.header.visual.windowBackgroundStyle.artwork": "专辑封面",
"settings.option.visual.animatedArtwork": "动态专辑封面", // Dropdown
"settings.header.visual.animatedArtwork.always": "总是显示",
"settings.header.visual.animatedArtwork.limited": "只在艺人页面和专辑封面显示",
"settings.header.visual.animatedArtwork.disable": "关闭",
"settings.option.visual.animatedArtworkQuality": "动态专辑封面画质", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "低",
"settings.header.visual.animatedArtworkQuality.medium": "中",
"settings.header.visual.animatedArtworkQuality.high": "高",
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
"settings.header.visual.animatedArtworkQuality.extreme": "极高",
"settings.option.visual.animatedWindowBackground": "动态窗口背景", // Toggle
"settings.option.visual.hardwareAcceleration": "硬件加速", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "需要重启 Cider 才会生效",
"settings.header.visual.hardwareAcceleration.default": "默认",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "显示个人资料", // Toggle
// Settings - General (Reserved)
"settings.header.general": "通用",
"settings.header.general.description": "调整 Cider 的通用设置",
// Settings - Lyrics
"settings.header.lyrics": "歌词",
"settings.header.lyrics.description": "调整 Cider 的歌词设置",
"settings.option.lyrics.enableMusixmatch": "启用 Musixmatch 歌词", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "启用卡拉 OK 模式(仅 Musixmatch", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌词语言偏好", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 时使用 YouTube 歌词", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "外部连接",
"settings.header.connectivity.description": "调整Cider与外部应用的交互设置",
"settings.option.connectivity.discordRPC": "Discord 动态", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "显示正在玩 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "显示正在玩 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "暂停时清除Discord 动态", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 记录", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延迟 (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "启用 LastFM 正在播放",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "从歌名里去除艺人推荐 (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "实验性功能",
"settings.header.experimental.description": "调整Cider的实验性功能",
"settings.option.experimental.compactUI": "紧凑型 UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "点击关闭按钮时",
"settings.option.experimental.closeButtonBehaviour.quit": "退出 Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "最小化到任务栏",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "最小化到系统托盘",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "空间属性",
"spatial.width" : "宽度",
"spatial.height" : "高度",
"spatial.depth" : "深度",
"spatial.gain": "增益",
"spatial.roomMaterials" : "空间材质",
"spatial.roomDimensions" : "空间尺寸",
"spatial.roomPositions" : "空间位置",
"spatial.setDimensions" : "设置尺寸",
"spatial.setPositions" : "设置位置",
"spatial.up" : "上",
"spatial.front" : "前",
"spatial.left" : "左",
"spatial.right" : "右",
"spatial.back" : "后",
"spatial.down" : "下",
"spatial.listener" : "您",
"spatial.audioSource" : "音源",
// Settings - Unfinished
"settings.header.unfinished": "未完成",
// Web Remote
"remote.web.title": "Cider 远程控制",
"remote.web.description": "扫描以下的二维码以控制 Cider",
//About
"about.thanks": "郑重感谢 Cider Collective 以及为这个项目提供支持的贡献者。"
}

297
src/i18n/zh_HK.jsonc Normal file
View file

@ -0,0 +1,297 @@
{
// i18n Info
"i18n.languageName": "繁體中文(香港)", // name of language in native language
"i18n.languageNameEnglish": "Trad. Chinese (Hong Kong)", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@kyw504100 @maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${y}年${m}月${d}日",
// Dialogs
"dialog.cancel": "取消",
"dialog.ok": "確認",
// Notification
"notification.updatingLibrarySongs": "正在更新資料庫的歌曲...",
"notification.updatingLibraryAlbums": "正在更新資料庫的專輯...",
"notification.updatingLibraryArtists": "正在更新資料庫的藝人...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music", // Follows brand term
"term.applePodcasts": "Apple Podcasts", // Follows brand term
"term.itunes": "iTunes", // Follows brand term
"term.github": "GitHub", // Follows brand term
"term.discord": "Discord", // Follows brand term
"term.learnMore": "了解更多",
"term.accountSettings": "帳號設定",
"term.logout": "登出",
"term.login": "登入",
"term.about": "關於",
"term.privateSession": "私人模式",
"term.queue": "待播清單",
"term.search": "搜尋",
"term.library": "資料庫",
"term.listenNow": "立即聆聽",
"term.browse": "瀏覽",
"term.radio": "廣播",
"term.recentlyAdded": "最近加入",
"term.songs": "歌曲",
"term.albums": "專輯",
"term.artists": "藝人",
"term.podcasts": "Podcasts",
"term.playlists": "播放列表",
"term.playlist": "播放列表",
"term.play": "播放",
"term.pause": "暫停",
"term.previous": "上一首",
"term.next": "下一首",
"term.shuffle": "隨機播放",
"term.repeat": "重複播放",
"term.volume": "音量",
"term.mute": "靜音",
"term.unmute": "取消靜音",
"term.share": "分享",
"term.settings": "設定",
"term.seeAll": "顯示全部",
"term.sortBy": "排序",
"term.sortBy.album": "專輯",
"term.sortBy.artist": "藝人",
"term.sortBy.name": "歌名",
"term.sortBy.genre": "音樂風格",
"term.sortBy.releaseDate": "發行日期",
"term.sortBy.duration": "時長",
"term.sortOrder": "字母排序",
"term.sortOrder.ascending": "順序",
"term.sortOrder.descending": "倒序",
"term.viewAs": "顯示模式",
"term.viewAs.coverArt": "專輯封面",
"term.viewAs.list": "列表",
"term.size": "大小",
"term.size.normal": "正常",
"term.size.compact": "緊凑",
"term.enable": "啟用",
"term.disable": "停用",
"term.enabled": "已啟用",
"term.disabled": "已停用",
"term.connect": "連結",
"term.connecting": "連結中",
"term.disconnect": "取消連結",
"term.authed": "已授權",
"term.confirm": "確認?",
"term.more": "更多",
"term.less": "較少",
"term.showMore": "顯示更多",
"term.showLess": "顯示較少",
"term.topSongs" : "熱門歌曲",
"term.latestReleases": "最新發行",
"term.time.added": "加入於",
"term.time.released": "發行於",
"term.time.updated": "更新於",
"term.time.hours": "小時",
"term.time.hour": "小時",
"term.time.minutes": "分鐘",
"term.time.minute": "分鐘",
"term.time.seconds": "秒",
"term.time.second": "秒",
"term.fullscreenView": "全螢幕檢視",
"term.defaultView": "一般檢視",
"term.spacializedAudioSetting": "空間音訊設定",
"term.clearAll": "清空",
"term.recentStations": "最近播放的頻道",
"term.language": "語言",
"term.funLanguages": "惡搞",
"term.noLyrics": "加載中... / 找不到歌詞。/ 純音樂。",
"term.copyright": "Copyright",
"term.rightsReserved": "保留一切權利。",
"term.sponsor": "贊助這個項目",
"term.ciderTeam": "Cider 團隊",
"term.developer": "開發者",
"term.socialTeam": "社交團隊",
"term.contributors": "貢獻者",
"term.equalizer": "均衡器",
"term.reset": "重設",
"term.tracks": "首歌曲", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "影片",
"term.menu": "選項",
// Home
"home.title": "主頁",
"home.recentlyPlayed": "最近播放",
"home.recentlyAdded": "最近加入",
"home.artistsFeed": "藝人動態",
"home.artistsFeed.noArtist": "追蹤一些藝人來獲得他們的最新歌曲資訊。",
"home.madeForYou": "為您推薦",
"home.friendsListeningTo": "朋友正在聆聽",
"home.followedArtists": "追蹤的藝人",
// Errors
"error.appleMusicSubRequired": "需要訂閱Apple Music以使用Cider",
"error.connectionError": "無法連接到 Apple Music。",
"error.noResults": "沒有結果",
"error.noResults.description": "請嘗試新的搜尋內容。",
//Podcasts
"podcast.followOnCider": "在Cider上追蹤",
"podcast.followedOnCider": "已在Cider上追蹤",
"podcast.subscribeOnItunes": "在iTunes上訂閱",
"podcast.subscribedOnItunes": "已在iTunes上訂閱",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "單集",
"podcast.playEpisode": "播放單集",
"podcast.website": "Podcast 網頁",
// Actions
"action.addToLibrary": "加入資料庫",
"action.addToLibrary.success": "成功加入資料庫",
"action.addToLibrary.error": "加入資料庫的過程發生錯誤",
"action.removeFromLibrary": "從資料庫刪除",
"action.removeFromLibrary.success": "已從資料庫刪除",
"action.addToQueue": "加入待播清單",
"action.addToQueue.success": "成功加入待播清單",
"action.addToQueue.error": "加入待播清單的過程發生錯誤",
"action.removeFromQueue": "從待播清單刪除",
"action.removeFromQueue.success": "已從待播清單刪除",
"action.removeFromQueue.error": "從待播清單刪除的過程中發生錯誤",
"action.addToPlaylist": "加至播放列表",
"action.removeFromPlaylist": "從播放列表刪除",
"action.addToFavorites": "加至收藏",
"action.follow": "追蹤",
"action.follow.success": "追蹤中",
"action.follow.error": "追蹤的過程發生錯誤",
"action.unfollow": "取消追蹤",
"action.unfollow.success": "已取消追蹤",
"action.unfollow.error": "取消追蹤的過程發生錯誤",
"action.playNext": "插播",
"action.playLater": "稍後播放",
"action.startRadio": "建立電台",
"action.goToArtist": "前往藝人",
"action.goToAlbum": "前往專輯",
"action.moveToTop": "移動到頂部",
"action.share": "分享歌曲",
"action.rename": "重新命名",
"action.love": "喜愛",
"action.unlove": "取消喜愛",
"action.dislike": "減少此類建議",
"action.undoDislike": "還原減小此類建議",
"action.showWebRemoteQR": "顯示遙距控制二維碼",
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
"action.playTracksLater": "稍後播放 ${app.selectedMediaItems.length} 首歌曲",
"action.removeTracks": "從待播清單刪除 ${self.selectedItems.length} 首歌曲",
"action.import": "匯入",
"action.export": "匯出",
// Settings - General
"settings.header.general": "一般",
"settings.header.general.description": "調整Cider的一般設定",
"settings.option.general.language": "語言",
// Language optgroups
"settings.option.general.language.main": "語言",
"settings.option.general.language.fun": "惡搞語言",
"settings.option.general.language.unsorted": "未分類",
// Settings - Audio
"settings.header.audio": "音訊",
"settings.header.audio.description": "調整Cider的音訊設定",
"settings.option.audio.quality": "音訊音質", // Dropdown
"settings.header.audio.quality.high": "高素質",
"settings.header.audio.quality.low": "高效率",
"settings.header.audio.quality.auto": "自動",
"settings.option.audio.seamlessTransition": "無縫播放", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "進階功能", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "啟用 AudioContext 將解鎖類似音量平衡和均衡器的進階功能。但是會在一些電腦造成音樂卡頓。",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量平衡", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "將平衡輕柔和響亮的歌曲,建立更統一的聆聽體驗。",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "空間音訊", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "將音訊進行空間化處理來製造一個更立體的聆聽體驗(注:此功能不是官方的杜比全景聲)",
// Settings - Visual
"settings.header.visual": "外觀",
"settings.header.visual.description": "調整Cider的外觀",
"settings.option.visual.windowBackgroundStyle": "窗口背景樣式", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "空白",
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
"settings.header.visual.windowBackgroundStyle.image": "圖片",
"settings.option.visual.animatedArtwork": "動態專輯封面", // Dropdown
"settings.header.visual.animatedArtwork.always": "總是顯示",
"settings.header.visual.animatedArtwork.limited": "只在藝人頁面和專輯封面顯示",
"settings.header.visual.animatedArtwork.disable": "關閉",
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "低",
"settings.header.visual.animatedArtworkQuality.medium": "中",
"settings.header.visual.animatedArtworkQuality.high": "高",
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
"settings.header.visual.animatedArtworkQuality.extreme": "極高",
"settings.option.visual.animatedWindowBackground": "動態窗口背景", // Toggle
"settings.option.visual.hardwareAcceleration": "硬體加速", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "需要重啓 Cider 才能生效",
"settings.header.visual.hardwareAcceleration.default": "預設",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "顯示個人檔案", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "歌詞",
"settings.header.lyrics.description": "調整Cider的歌詞設定",
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "啟用卡拉OK模式僅限Musixmatch", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 時使用 YouTube 歌詞", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "外部連結",
"settings.header.connectivity.description": "調整Cider與外部的連結",
"settings.option.connectivity.discordRPC": "Discord 狀態", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "顯示為'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "顯示為'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 記錄", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延遲 (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 LastFM 正在播放",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "實驗性功能",
"settings.header.experimental.description": "調整Cider的實驗性功能",
"settings.option.experimental.compactUI": "緊凑型 UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "關閉按鈕行為",
"settings.option.experimental.closeButtonBehaviour.quit": "結束 Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "縮小至工作列",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "縮小至系統托盤",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "空間音訊屬性",
"spatial.width" : "闊度",
"spatial.height" : "高度",
"spatial.depth" : "深度",
"spatial.gain" : "增益",
"spatial.roomMaterials" : "空間材質",
"spatial.roomDimensions" : "空間大小",
"spatial.roomPositions" : "空間位置",
"spatial.setDimensions" : "大小設定",
"spatial.setPositions" : "位置設定",
"spatial.up" : "上方",
"spatial.front" : "前方",
"spatial.left" : "左方",
"spatial.right" : "右方",
"spatial.back" : "後方",
"spatial.down" : "下方",
"spatial.listener" : "觀眾",
"spatial.audioSource" : "音源",
// Settings - Unfinished
"settings.header.unfinished": "未完成",
// Web Remote
"remote.web.title": "遙距控制 Cider",
"remote.web.description": "掃描以下的二維碼以控制 Cider",
//About
"about.thanks": "感謝 Cider Collective 以及所有貢獻者所作出的貢獻。"
}

287
src/i18n/zh_TW.jsonc Normal file
View file

@ -0,0 +1,287 @@
{
// App info
"app.name": "Cider",
"date.format": "${y}年${m}月${d}日",
// i18n Info
"i18n.languageName": "繁體中文(台灣)", // name of language in native language
"i18n.languageNameEnglish": "Trad. Chinese (Taiwan)", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
// Dialogs
"dialog.cancel": "取消",
"dialog.ok": "OK",
// Notification
"notification.updatingLibrarySongs": "正在更新資料庫的歌曲...",
"notification.updatingLibraryAlbums": "正在更新資料庫的專輯...",
"notification.updatingLibraryArtists": "正在更新資料庫的藝人...",
// Terms
"term.appleMusic": "Apple Music", // Follows brand term
"term.applePodcasts": "Apple Podcasts", // Follows brand term
"term.itunes": "iTunes", // Follows brand term
"term.github": "GitHub", // Follows brand term
"term.discord": "Discord", // Follows brand term
"term.learnMore": "更多内容",
"term.accountSettings": "賬戶設定",
"term.logout": "登出",
"term.login": "登入",
"term.about": "關於",
"term.privateSession": "私人時段",
"term.queue": "待播清單",
"term.search": "搜尋",
"term.library": "資料庫",
"term.listenNow": "立即聆聽",
"term.browse": "瀏覽",
"term.radio": "廣播",
"term.recentlyAdded": "最近加入",
"term.songs": "歌曲",
"term.albums": "專輯",
"term.artists": "藝人",
"term.podcasts": "Podcasts",
"term.playlists": "播放列表",
"term.playlist": "播放列表",
"term.play": "播放",
"term.pause": "暫停",
"term.previous": "上一首",
"term.next": "下一首",
"term.shuffle": "隨機播放",
"term.repeat": "重複播放",
"term.volume": "音量",
"term.mute": "靜音",
"term.unmute": "取消靜音",
"term.share": "分享",
"term.settings": "設定",
"term.seeAll": "顯示全部",
"term.sortBy": "排序",
"term.sortBy.album": "專輯",
"term.sortBy.artist": "藝人",
"term.sortBy.name": "歌名",
"term.sortBy.genre": "音樂風格",
"term.sortBy.releaseDate": "發行日期",
"term.sortBy.duration": "時長",
"term.sortOrder": "字母排序",
"term.sortOrder.ascending": "升序",
"term.sortOrder.descending": "降序",
"term.viewAs": "顯示模式",
"term.viewAs.coverArt": "專輯封面",
"term.viewAs.list": "列表",
"term.size": "大小",
"term.size.normal": "正常",
"term.size.compact": "緊凑",
"term.enable": "啟用",
"term.disable": "停用",
"term.enabled": "已啟用",
"term.disabled": "已停用",
"term.connect": "連接",
"term.connecting": "連接中",
"term.disconnect": "斷開",
"term.authed": "已授權",
"term.confirm": "確定?",
"term.more": "更多",
"term.less": "更少",
"term.showMore": "顯示更多",
"term.showLess": "顯示更少",
"term.topSongs" : "熱門歌曲",
"term.latestReleases": "最新發行",
"term.time.added": "加入于",
"term.time.released": "發行于",
"term.time.updated": "更改于",
"term.time.hours": "小時",
"term.time.hour": "小時",
"term.time.minutes": "分鐘",
"term.time.minute": "分鐘",
"term.time.seconds": "秒",
"term.time.second": "秒",
"term.fullscreenView": "全螢幕顯示",
"term.defaultView": "預設顯示",
"term.spacializedAudioSetting": "音頻空間化設置",
"term.clearAll": "清空",
"term.recentStations": "最近收聽的廣播",
"term.language": "語言",
"term.noLyrics": "正在載入。。/ 無歌詞結果 / 純音樂",
"term.copyright": "版權聲明",
"term.rightsReserved": "保留所有權利。",
"term.sponsor": "贊助",
"term.ciderTeam": "Cider 團隊",
"term.developer": "開發者",
"term.socialTeam": "公關團隊",
"term.contributors": "貢獻者",
"term.equalizer": "等化器",
"term.reset": "重置",
"term.tracks": "首歌曲", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "影片",
"term.menu": "選單",
// Home
"home.title": "主頁",
"home.recentlyPlayed": "最近播放",
"home.recentlyAdded": "最近加入",
"home.artistsFeed": "藝人追蹤",
"home.artistsFeed.noArtist": "追蹤一些藝人來獲得他們的最新歌曲。",
"home.madeForYou": "為您推薦",
"home.friendsListeningTo": "朋友正在聆聽",
"home.followedArtists": "追蹤的藝人",
// Errors
"error.appleMusicSubRequired": "需要訂閱Apple Music以使用Cider",
"error.connectionError": "無法連接到 Apple Music。",
"error.noResults": "沒有結果",
"error.noResults.description": "嘗試新的搜尋項目。",
//Podcasts
"podcast.followOnCider": "在 Cider 上追蹤",
"podcast.followedOnCider": "已追蹤",
"podcast.subscribeOnItunes": "在 iTunes 上訂閱",
"podcast.subscribedOnItunes": "已訂閱",
"podcast.itunesStore": "iTunes Store", // Follows brand term
"podcast.episodes": "單集",
"podcast.playEpisode": "播放單集",
"podcast.website": "Podcast 網站",
// Actions
"action.addToLibrary": "加入資料庫",
"action.addToLibrary.success": "成功加入資料庫",
"action.addToLibrary.error": "加入資料庫的過程發生錯誤",
"action.removeFromLibrary": "從資料庫刪除",
"action.removeFromLibrary.success": "已從資料庫刪除",
"action.addToQueue": "加入待播清單",
"action.addToQueue.success": "成功加入待播清單",
"action.addToQueue.error": "加入待播清單的過程發生錯誤",
"action.removeFromQueue": "從待播清單刪除",
"action.removeFromQueue.success": "已從待播清單刪除",
"action.removeFromQueue.error": "從待播清單刪除的過程發生錯誤",
"action.addToPlaylist": "加入播放列表",
"action.removeFromPlaylist": "從播放列表刪除",
"action.addToFavorites": "加入我的最愛",
"action.follow": "追蹤",
"action.follow.success": "追蹤中",
"action.follow.error": "追蹤的過程發生錯誤",
"action.unfollow": "取消追蹤",
"action.unfollow.success": "已取消追蹤",
"action.unfollow.error": "取消追蹤的過程發生錯誤",
"action.playNext": "插播",
"action.playLater": "最後播放",
"action.startRadio": "建立電台",
"action.goToArtist": "前往藝人",
"action.goToAlbum": "前往專輯",
"action.moveToTop": "移至頂端",
"action.share": "分享歌曲",
"action.rename": "重新命名",
"action.love": "喜愛",
"action.unlove": "取消喜愛",
"action.dislike": "減少此類建議",
"action.undoDislike": "還原減小此類建議",
"action.showWebRemoteQR": "顯示遠程遙控行動條碼",
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
"action.playTracksLater": "最後播放 ${app.selectedMediaItems.length} 首歌曲",
"action.removeTracks": "從待播清單刪除 ${self.selectedItems.length} 首歌曲",
"action.import": "導入",
"action.export": "導出",
// Settings - Audio
"settings.header.audio": "音訊",
"settings.header.audio.description": "調整Cider的音訊設定",
"settings.option.audio.quality": "音訊音質", // Dropdown
"settings.header.audio.quality.high": "高品質",
"settings.header.audio.quality.low": "高效率",
"settings.header.audio.quality.auto": "自動",
"settings.option.audio.seamlessTransition": "無間斷播放", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "進階機能", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "啟用 AudioContext 將解鎖類似音訊標準化和等化器的進階機能。但是會在一些電腦造成音樂卡頓。",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音訊標準化", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "將平衡輕柔和響亮的歌曲,建立更統一的聆聽體驗。",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "音訊空間化", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "將音訊進行空間化處理來製造一個更立體的聆聽體驗(注:此功能不是官方的杜比全景聲)",
// Settings - Visual
"settings.header.visual": "外觀",
"settings.header.visual.description": "調整Cider的外觀",
"settings.option.visual.windowBackgroundStyle": "窗口背景樣式", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "空白",
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
"settings.option.visual.animatedArtwork": "動態專輯封面", // Dropdown
"settings.header.visual.animatedArtwork.always": "總是顯示",
"settings.header.visual.animatedArtwork.limited": "只在藝人頁面和專輯封面顯示",
"settings.header.visual.animatedArtwork.disable": "關閉",
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "低",
"settings.header.visual.animatedArtworkQuality.medium": "中",
"settings.header.visual.animatedArtworkQuality.high": "高",
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
"settings.header.visual.animatedArtworkQuality.extreme": "極高",
"settings.option.visual.animatedWindowBackground": "動態窗口背景", // Toggle
"settings.option.visual.hardwareAcceleration": "硬體加速", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "需要重新啟動 Cider 才會生效",
"settings.header.visual.hardwareAcceleration.default": "默認",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "顯示個人檔案", // Toggle
// Settings - General (Reserved)
"settings.header.general": "一般",
"settings.header.general.description": "調整Cider的一般設定",
// Settings - Lyrics
"settings.header.lyrics": "歌詞",
"settings.header.lyrics.description": "調整 Cider 的歌詞設定",
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "啟用K歌模式僅限Musixmatch", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 時使用 YouTube 歌詞", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "外部連接",
"settings.header.connectivity.description": "調整Cider與外部的連接",
"settings.option.connectivity.discordRPC": "Discord 動態", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "顯示正在玩 'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "顯示正在玩 'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 動態", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 記錄", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延遲 (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 LastFM 目前聆聽", // Toggle
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "實驗性功能",
"settings.header.experimental.description": "調整 Cider 的實驗性功能",
"settings.option.experimental.compactUI": "緊凑型 UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "關閉按鈕行為", // Dropdown
"settings.option.experimental.closeButtonBehaviour.quit": "退出 Cider",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "最小化到工作列",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "最小化到系統匣",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "空間化屬性",
"spatial.width" : "寬度",
"spatial.height" : "高度",
"spatial.depth" : "深度",
"spatial.gain" : "增益",
"spatial.roomMaterials" : "空間材質",
"spatial.roomDimensions" : "空間尺寸",
"spatial.roomPositions" : "空間位置",
"spatial.setDimensions" : "設定尺寸",
"spatial.setPositions" : "設定位置",
"spatial.up" : "上",
"spatial.front" : "前",
"spatial.left" : "左",
"spatial.right" : "右",
"spatial.back" : "後",
"spatial.down" : "下",
"spatial.listener" : "聽衆",
"spatial.audioSource" : "音訊來源",
// Settings - Unfinished
"settings.header.unfinished": "未完成",
// Web Remote
"remote.web.title": "Cider 遠控",
"remote.web.description": "掃描以下的行動條碼以控制 Cider",
//About
"about.thanks": "著重感謝 Cider Collective 的成員以及所有為項目付出的貢獻者。"
}

297
src/i18n/zh_yue.JSONC Normal file
View file

@ -0,0 +1,297 @@
{
// i18n Info
"i18n.languageName": "廣東話(香港﹚", // name of language in native language
"i18n.languageNameEnglish": "Cantonese (Hong Kong)", // name of language in English
"i18n.category": "main", // main = real language, fun = fun community languages
"i18n.authors": "@tszngaiyip @strikesnc", // Authors, if you contribute to this file feel free to add your name seperated with a space
// App info
"app.name": "Cider",
"date.format": "${y}年${m}月${d}日",
// Dialogs
"dialog.cancel": "取消",
"dialog.ok": "確認",
// Notification
"notification.updatingLibrarySongs": "更新緊資料庫嘅歌曲...",
"notification.updatingLibraryAlbums": "更新緊資料庫嘅專輯...",
"notification.updatingLibraryArtists": "更新緊資料庫嘅藝人...",
// Terms
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes",
"term.github": "GitHub",
"term.discord": "Discord",
"term.learnMore": "想知更多",
"term.accountSettings": "帳號設定",
"term.logout": "登出",
"term.login": "登入",
"term.about": "關於",
"term.privateSession": "無痕模式",
"term.queue": "待播清單",
"term.search": "搵野",
"term.library": "資料庫",
"term.listenNow": "即刻聽",
"term.browse": "瀏覽",
"term.radio": "電台",
"term.recentlyAdded": "最近加入",
"term.songs": "歌曲",
"term.albums": "專輯",
"term.artists": "藝人",
"term.podcasts": "Podcasts",
"term.playlists": "播放清單",
"term.playlist": "播放清單",
"term.play": "播放",
"term.pause": "暫停",
"term.previous": "前一首",
"term.next": "下一首",
"term.shuffle": "隨機播放",
"term.repeat": "重複播放",
"term.volume": "音量",
"term.mute": "靜音",
"term.unmute": "取消靜音",
"term.share": "分享",
"term.settings": "設定",
"term.seeAll": "睇哂全部",
"term.sortBy": "排序",
"term.sortBy.album": "專輯",
"term.sortBy.artist": "藝人",
"term.sortBy.name": "歌名",
"term.sortBy.genre": "音樂風格",
"term.sortBy.releaseDate": "幾時出",
"term.sortBy.duration": "幾長",
"term.sortOrder": "點排",
"term.sortOrder.ascending": "順序",
"term.sortOrder.descending": "倒序",
"term.viewAs": "想點樣顯示",
"term.viewAs.coverArt": "專輯封面",
"term.viewAs.list": "列表",
"term.size": "大細",
"term.size.normal": "正常",
"term.size.compact": "迫啲",
"term.enable": "開",
"term.disable": "熄",
"term.enabled": "開左",
"term.disabled": "熄左",
"term.connect": "連結",
"term.connecting": "連緊",
"term.disconnect": "取消連結",
"term.authed": "授權咗",
"term.confirm": "確認?",
"term.more": "多啲",
"term.less": "少啲",
"term.showMore": "顯示多啲",
"term.showLess": "顯示少啲",
"term.topSongs" : "熱門歌曲",
"term.latestReleases": "最新出嘅",
"term.time.added": "加入於",
"term.time.released": "發行於",
"term.time.updated": "更新於",
"term.time.hours": "粒鐘",
"term.time.hour": "粒鐘",
"term.time.minutes": "分鐘",
"term.time.minute": "分鐘",
"term.time.seconds": "秒",
"term.time.second": "秒",
"term.fullscreenView": "用全螢幕睇",
"term.defaultView": "平時咁睇",
"term.spacializedAudioSetting": "空間音訊設定",
"term.clearAll": "清除",
"term.recentStations": "呢排聽緊嘅",
"term.language": "語言",
"term.funLanguages": "惡搞",
"term.noLyrics": "搵緊... / 搵唔到歌詞。 / 純音樂黎。",
"term.copyright": "版權",
"term.rightsReserved": "保留一切權利",
"term.sponsor": "課金俾呢個Project",
"term.ciderTeam": "Cider 團隊",
"term.developer": "開發者",
"term.socialTeam": "PR",
"term.contributors": "合作人",
"term.equalizer": "均衡器 (EQ)",
"term.reset": "重設",
"term.tracks": "首歌", // Assume x amount of tracks. e.g. 50 tracks
"term.videos": "影片",
"term.menu": "選項",
// Home
"home.title": "主頁",
"home.recentlyPlayed": "呢排播左",
"home.recentlyAdded": "呢排加嘅",
"home.artistsFeed": "藝人動態",
"home.artistsFeed.noArtist": "Follow 一啲藝人嚟獲得佢哋嘅最新歌曲資訊。 ",
"home.madeForYou": "為你而整",
"home.friendsListeningTo": "你啲Friend聽緊",
"home.followedArtists": "Follow左嘅藝人",
// Errors
"error.appleMusicSubRequired": "需要訂閱Apple Music先可以用Cider。",
"error.connectionError": "連接唔到Apple Music。",
"error.noResults": "冇結果。",
"error.noResults.description": "重新搵過啦。",
//Podcasts
"podcast.followOnCider": "喺Cider上Follow",
"podcast.followedOnCider": "喺Cider上Follow左",
"podcast.subscribeOnItunes": "喺iTunes上訂閱",
"podcast.subscribedOnItunes": "喺iTunes上訂閱左",
"podcast.itunesStore": "iTunes Store",
"podcast.episodes": "單集",
"podcast.playEpisode": "播呢集",
"podcast.website": "Podcast 網頁",
// Actions
"action.addToLibrary": "加入資料庫",
"action.addToLibrary.success": "加入咗資料庫",
"action.addToLibrary.error": "加入唔到資料庫",
"action.removeFromLibrary": "喺資料庫到刪除",
"action.removeFromLibrary.success": "已經喺資料庫到刪除咗",
"action.addToQueue": "加入待播清單",
"action.addToQueue.success": "加入咗待播清單",
"action.addToQueue.error": "加入唔到待播清單",
"action.removeFromQueue": "喺待播清單刪除",
"action.removeFromQueue.success": "已經喺待播清單到刪除咗",
"action.removeFromQueue.error": "喺待播清單到刪除唔到",
"action.addToPlaylist": "加入播放清單",
"action.removeFromPlaylist": "喺播放清單到刪除",
"action.addToFavorites": "加至收藏",
"action.follow": "Follow",
"action.follow.success": "Follow緊",
"action.follow.error": "Follow唔到",
"action.unfollow": "Unfollow",
"action.unfollow.success": "Unfollow咗",
"action.unfollow.error": "Unfollow唔到",
"action.playNext": "下首即刻播",
"action.playLater": "陣間先再播",
"action.startRadio": "建立電台",
"action.goToArtist": "前往藝人",
"action.goToAlbum": "前往專輯",
"action.moveToTop": "返最頂",
"action.share": "分享歌曲",
"action.rename": "重新命名",
"action.love": "鐘意",
"action.unlove": "唔鐘意",
"action.dislike": "唔想再睇到",
"action.undoDislike": "還原唔想再睇到",
"action.showWebRemoteQR": "顯示遙距控制QR Code",
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
"action.playTracksLater": "陣間播放 ${app.selectedMediaItems.length} 首歌曲",
"action.removeTracks": "喺待播清單到刪除 ${self.selectedItems.length} 首歌曲",
"action.import": "匯入",
"action.export": "匯出",
// Settings - General
"settings.header.general": "一般",
"settings.header.general.description": "調整Cider嘅一般設定",
"settings.option.general.language": "語言",
// Language optgroups
"settings.option.general.language.main": "語言",
"settings.option.general.language.fun": "惡搞語言",
"settings.option.general.language.unsorted": "未分類",
// Settings - Audio
"settings.header.audio": "音訊",
"settings.header.audio.description": "調整Cider嘅音訊設定",
"settings.option.audio.quality": "音質", // Dropdown
"settings.header.audio.quality.high": "質素優先",
"settings.header.audio.quality.low": "流暢度優先",
"settings.header.audio.quality.auto": "自動",
"settings.option.audio.seamlessTransition": "無縫播放", // Toggle
"settings.option.audio.enableAdvancedFunctionality": "進階功能", // Toggle
"settings.option.audio.enableAdvancedFunctionality.description": "啟用AudioContext解鎖類似音量平衡和均衡器嘅進階功能但係會喺部分電腦造成音樂Lag機。",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量平衡", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "平衡輕柔同響亮嘅歌曲,令你有統一嘅聆聽體驗。",
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "空間音訊", // Toggle
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "空間化音訊,製造一個更立體嘅聆聽體驗(注意:呢個功能唔係官方嘅杜比全景聲)",
// Settings - Visual
"settings.header.visual": "外觀",
"settings.header.visual.description": "調整Cider嘅外觀",
"settings.option.visual.windowBackgroundStyle": "視窗背景樣式", // Toggle
"settings.header.visual.windowBackgroundStyle.none": "空白",
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
"settings.header.visual.windowBackgroundStyle.image": "圖片",
"settings.option.visual.animatedArtwork": "動態專輯封面", // Dropdown
"settings.header.visual.animatedArtwork.always": "總是顯示",
"settings.header.visual.animatedArtwork.limited": "淨係喺藝人頁面同專輯封面顯示",
"settings.header.visual.animatedArtwork.disable": "熄左佢",
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質", // Dropdown
"settings.header.visual.animatedArtworkQuality.low": "低",
"settings.header.visual.animatedArtworkQuality.medium": "中",
"settings.header.visual.animatedArtworkQuality.high": "高",
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
"settings.header.visual.animatedArtworkQuality.extreme": "極高",
"settings.option.visual.animatedWindowBackground": "動態視窗背景", // Toggle
"settings.option.visual.hardwareAcceleration": "硬體加速", // Dropdown
"settings.option.visual.hardwareAcceleration.description": "需要重開Cider先會生效",
"settings.header.visual.hardwareAcceleration.default": "預設",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
// Refer to term.disabled for the disabled option
"settings.option.visual.showPersonalInfo": "顯示個人檔案", // Toggle
// Settings - Lyrics
"settings.header.lyrics": "歌詞",
"settings.header.lyrics.description": "調整Cider嘅歌詞設定",
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞", // Toggle
"settings.option.lyrics.enableMusixmatchKaraoke": "開啟唱K模式僅限Musixmatch)", // Toggle
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好", // Dropdown
"settings.option.lyrics.enableYoutubeLyrics": "播放MV嘅時候用YouTube字幕", // Toggle
// Settings - Connectivity
"settings.header.connectivity": "外部連結",
"settings.header.connectivity.description": "調整Cider同外部嘅連結",
"settings.option.connectivity.discordRPC": "Discord 狀態", // Dropdown
// Refer to term.disabled for the disabled option
"settings.header.connectivity.discordRPC.cider": "顯示為'Cider'",
"settings.header.connectivity.discordRPC.appleMusic": "顯示為'Apple Music'",
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態", // Toggle
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 記錄", // Option to Connect
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延遲 (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 LastFM 正在播放",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (LastFM)",
// Refer to term.connect for the connect button
// Settings - Experimental
"settings.header.experimental": "實驗性功能",
"settings.header.experimental.description": "調整Cider嘅實驗性功能",
"settings.option.experimental.compactUI": "逼啲既 UI", // Toggle
"settings.option.experimental.closeButtonBehaviour": "關閉按鈕行為",
"settings.option.experimental.closeButtonBehaviour.quit": "熄左Cider佢",
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "收埋Cider喺工作列",
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "收埋Cider喺系統托盤",
// Refer to term.disabled & term.enabled
// Spatialization Menu
"spatial.spatialProperties" : "空間音訊屬性",
"spatial.width" : "幾闊",
"spatial.height" : "幾高",
"spatial.depth" : "幾深",
"spatial.gain" : "增益",
"spatial.roomMaterials" : "空間材質",
"spatial.roomDimensions" : "空間大小",
"spatial.roomPositions" : "空間位置",
"spatial.setDimensions" : "大小設定",
"spatial.setPositions" : "位置設定",
"spatial.up" : "上面",
"spatial.front" : "前面",
"spatial.left" : "左邊",
"spatial.right" : "右邊",
"spatial.back" : "後面",
"spatial.down" : "下面",
"spatial.listener" : "觀眾",
"spatial.audioSource" : "音源",
// Settings - Unfinished
"settings.header.unfinished": "未搞掂",
// Web Remote
"remote.web.title": "遙距控制 Cider",
"remote.web.description": "Scan 呢個 QR Code 去控制 Cider",
// About
"about.thanks": "多謝 Cider Collective 同埋所有合作人作出嘅貢獻。"
}

201
src/main/base/app.ts Normal file
View file

@ -0,0 +1,201 @@
import * as electron from 'electron';
import * as path from 'path';
export class AppEvents {
private static protocols: any = [
"ame",
"cider",
"itms",
"itmss",
"musics",
"music"
]
private static plugin: any = null;
private static store: any = null;
private static win: any = null;
constructor(store: any) {
AppEvents.store = store
AppEvents.start(store);
}
/**
* Handles all actions that occur for the app on start (Mainly commandline arguments)
* @returns {void}
*/
private static start(store: any): void {
console.info('[AppEvents] App started');
/**********************************************************************************************************************
* Startup arguments handling
**********************************************************************************************************************/
if (electron.app.commandLine.hasSwitch('version') || electron.app.commandLine.hasSwitch('v')) {
console.log(electron.app.getVersion())
electron.app.exit()
}
// Verbose Check
if (electron.app.commandLine.hasSwitch('verbose')) {
console.log("[Cider] User has launched the application with --verbose");
}
// Log File Location
if (electron.app.commandLine.hasSwitch('log') || electron.app.commandLine.hasSwitch('l')) {
console.log(path.join(electron.app.getPath('userData'), 'logs'))
electron.app.exit()
}
// Expose GC
electron.app.commandLine.appendSwitch('js-flags','--expose_gc')
if (process.platform === "win32") {
electron.app.setAppUserModelId("Cider") // For notification name
}
/***********************************************************************************************************************
* Commandline arguments
**********************************************************************************************************************/
switch (store.visual.hw_acceleration) {
default:
case "default":
electron.app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
electron.app.commandLine.appendSwitch('enable-accelerated-video')
electron.app.commandLine.appendSwitch('disable-gpu-driver-bug-workarounds')
electron.app.commandLine.appendSwitch('ignore-gpu-blacklist')
electron.app.commandLine.appendSwitch('enable-native-gpu-memory-buffers')
electron.app.commandLine.appendSwitch('enable-accelerated-video-decode');
electron.app.commandLine.appendSwitch('enable-gpu-rasterization');
electron.app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
electron.app.commandLine.appendSwitch('enable-oop-rasterization');
break;
case "webgpu":
console.info("WebGPU is enabled.");
electron.app.commandLine.appendSwitch('enable-unsafe-webgpu')
break;
case "disabled":
console.info("Hardware acceleration is disabled.");
electron.app.commandLine.appendSwitch('disable-gpu')
break;
}
if (process.platform === "linux") {
electron.app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
}
/***********************************************************************************************************************
* Protocols
**********************************************************************************************************************/
if (process.defaultApp) {
if (process.argv.length >= 2) {
this.protocols.forEach((protocol: string) => {
electron.app.setAsDefaultProtocolClient(protocol, process.execPath, [path.resolve(process.argv[1])])
})
}
} else {
this.protocols.forEach((protocol: string) => {
electron.app.setAsDefaultProtocolClient(protocol)
})
}
}
public quit() {
console.log('App stopped');
}
public ready(plug: any) {
AppEvents.plugin = plug
console.log('[AppEvents] App ready');
}
public bwCreated(win: Electron.BrowserWindow) {
AppEvents.win = win
electron.app.on('open-url', (event, url) => {
event.preventDefault()
if (AppEvents.protocols.some((protocol: string) => url.includes(protocol))) {
AppEvents.LinkHandler(url)
console.log(url)
}
})
AppEvents.InstanceHandler()
}
/***********************************************************************************************************************
* Private methods
**********************************************************************************************************************/
private static LinkHandler(arg: string) {
if (!arg) return;
// LastFM Auth URL
if (arg.includes('auth')) {
let authURI = arg.split('/auth/')[1]
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1];
AppEvents.store.set('lastfm.enabled', true);
AppEvents.store.set('lastfm.auth_token', authKey);
AppEvents.win.webContents.send('LastfmAuthenticated', authKey);
AppEvents.plugin.callPlugin('lastfm', 'authenticate', authKey);
}
}
// Play
else if (arg.includes('/play/')) { //Steer away from protocol:// specific conditionals
const playParam = arg.split('/play/')[1]
const mediaType = {
"s/": "song",
"a/": "album",
"p/": "playlist"
}
for (const [key, value] of Object.entries(mediaType)) {
if (playParam.includes(key)) {
const id = playParam.split(key)[1]
AppEvents.win.webContents.send('play', value, id)
console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`)
}
}
} else if (arg.includes('music.apple.com')) { // URL (used with itms/itmss/music/musics uris)
console.log(arg)
let url = arg.split('//')[1]
console.warn(`[LinkHandler] Attempting to load url: ${url}`);
AppEvents.win.webContents.send('play', 'url', url)
}
}
private static InstanceHandler() {
// Detects of an existing instance is running (So if the lock has been achieved, no existing instance has been found)
const gotTheLock = electron.app.requestSingleInstanceLock()
if (!gotTheLock) { // Runs on the new instance if another instance has been found
console.log('[Cider] Another instance has been found, quitting.')
electron.app.quit()
} else { // Runs on the first instance if no other instance has been found
electron.app.on('second-instance', (_event, startArgs) => {
console.log("[InstanceHandler] (second-instance) Instance started with " + startArgs.toString())
startArgs.forEach(arg => {
console.log(arg)
if (arg.includes("cider://")) {
console.debug('[InstanceHandler] (second-instance) Link detected with ' + arg)
AppEvents.LinkHandler(arg)
} else if (arg.includes("--force-quit")) {
console.warn('[InstanceHandler] (second-instance) Force Quit found. Quitting App.');
electron.app.quit()
} else if (AppEvents.win) {
if (AppEvents.win.isMinimized()) AppEvents.win.restore()
AppEvents.win.focus()
}
})
})
}
}
}

65
src/main/base/plugins.ts Normal file
View file

@ -0,0 +1,65 @@
import * as fs from 'fs';
import * as path from 'path';
import * as electron from 'electron'
export default class PluginHandler {
private basePluginsPath = path.join(__dirname, '../plugins');
private userPluginsPath = path.join(electron.app.getPath('userData'), 'plugins');
private readonly pluginsList: any = {};
private readonly _store: any;
constructor(config: any) {
this._store = config;
this.pluginsList = this.getPlugins();
}
public getPlugins(): any {
let plugins: any = {};
if (fs.existsSync(this.basePluginsPath)) {
fs.readdirSync(this.basePluginsPath).forEach(file => {
if (file.endsWith('.ts') || file.endsWith('.js')) {
const plugin = require(path.join(this.basePluginsPath, file)).default;
if (plugins[file] || plugin.name in plugins) {
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
} else {
plugins[file] = new plugin(electron.app, this._store);
}
}
});
}
if (fs.existsSync(this.userPluginsPath)) {
fs.readdirSync(this.userPluginsPath).forEach(file => {
if (file.endsWith('.ts') || file.endsWith('.js')) {
const plugin = require(path.join(this.userPluginsPath, file)).default;
file = file.replace('.ts', '').replace('.js', '');
if (plugins[file] || plugin in plugins) {
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
} else {
plugins[file] = new plugin(electron.app, this._store);
}
}
});
}
console.log('[PluginHandler] Loaded plugins:', Object.keys(plugins));
return plugins;
}
public callPlugins(event: string, ...args: any[]) {
for (const plugin in this.pluginsList) {
if (this.pluginsList[plugin][event]) {
this.pluginsList[plugin][event](...args);
}
}
}
public callPlugin(plugin: string, event: string, ...args: any[]) {
if (this.pluginsList[plugin][event]) {
this.pluginsList[plugin][event](...args);
}
}
}

167
src/main/base/store.ts Normal file
View file

@ -0,0 +1,167 @@
import * as Store from 'electron-store';
import * as electron from "electron";
export class ConfigStore {
private _store: Store;
private defaults: any = {
"general": {
"close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray
"open_on_startup": false,
"discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music
"discord_rpc_clear_on_pause": true,
"language": "en_US", // electron.app.getLocale().replace('-', '_') this can be used in future
"playbackNotifications": true
},
"home": {
"followedArtists": [],
"favoriteItems": []
},
"libraryPrefs": {
"songs": {
"sort": "name",
"sortOrder": "asc",
"size": "normal"
}
},
"audio": {
"volume": 1,
"lastVolume": 1,
"muted": false,
"quality": "256",
"seamless_audio": true,
"normalization": false,
"spatial": false,
"maxVolume": 1,
"volumePrecision": 0.1,
"volumeRoundMax": 0.9,
"volumeRoundMin": 0.1,
"spatial_properties": {
"presets": [],
"gain": 0.8,
"listener_position": [0, 0, 0],
"audio_position": [0, 0, 0],
"room_dimensions": {
"width": 32,
"height": 12,
"depth": 32
},
"room_materials": {
"left": 'metal',
"right": 'metal',
"front": 'brick-bare',
"back": 'brick-bare',
"down": 'acoustic-ceiling-tiles',
"up": 'acoustic-ceiling-tiles',
}
},
"equalizer": {
'preset': "default",
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
'gain': [0,0,0,0,0,0,0,0,0,0],
'Q' : [1,1,1,1,1,1,1,1,1,1],
'preamp' : 0,
'mix' : 1,
'presets': [],
'userGenerated': false
}
},
"visual": {
"theme": "",
"scrollbars": 0, // 0 = show on hover, 2 = always hide, 3 = always show
"refresh_rate": 0,
"window_background_style": "artwork", // "none", "artwork", "color"
"animated_artwork": "limited", // 0 = always, 1 = limited, 2 = never
"animated_artwork_qualityLevel": 1,
"bg_artwork_rotation": false,
"hw_acceleration": "default", // default, webgpu, disabled
"showuserinfo": true,
"miniplayer_top_toggle": true
},
"lyrics": {
"enable_mxm": false,
"mxm_karaoke": false,
"mxm_language": "en",
"enable_yt": false,
},
"lastfm": {
"enabled": false,
"scrobble_after": 30,
"auth_token": "",
"enabledRemoveFeaturingArtists": true,
"filterLoop": true,
"NowPlaying": "true"
},
"advanced": {
"AudioContext": false,
"experiments": []
}
}
private migrations: any = {}
constructor() {
this._store = new Store({
name: 'cider-config',
defaults: this.defaults,
migrations: this.migrations,
});
this._store.set(this.mergeStore(this.defaults, this._store.store))
this.ipcHandler(this._store);
}
get store() {
return this._store.store;
}
get(key: string) {
return this._store.get(key);
}
set(key: string, value: any) {
this._store.set(key, value);
}
/**
* Merge Configurations
* @param target The target configuration
* @param source The source configuration
*/
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (const key of Object.keys(source)) {
if (key.includes('migrations')) {
continue;
}
if(source[key] instanceof Array) {
continue
}
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
/**
* IPC Handler
*/
private ipcHandler(cfg: Store | any): void {
electron.ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
return (defaultValue ? cfg.get(key, true) : cfg.get(key));
});
electron.ipcMain.handle('setStoreValue', (event, key, value) => {
cfg.set(key, value);
});
electron.ipcMain.on('getStore', (event) => {
event.returnValue = cfg.store
})
electron.ipcMain.on('setStore', (event, store) => {
cfg.store = store
})
}
}

612
src/main/base/win.ts Normal file
View file

@ -0,0 +1,612 @@
// @ts-nocheck
import * as path from "path";
import * as electron from "electron";
import * as windowStateKeeper from "electron-window-state";
import * as express from "express";
import * as getPort from "get-port";
import * as yt from "youtube-search-without-api-key";
import * as fs from "fs";
import { Stream } from "stream";
import * as qrcode from "qrcode-terminal";
import * as os from "os";
import * as mm from 'music-metadata';
import fetch from 'electron-fetch'
import {wsapi} from "./wsapi";
import * as jsonc from "jsonc";
export class Win {
private win: any | undefined = null;
private app: any | undefined = null;
private store: any | undefined = null;
private devMode: boolean = !electron.app.isPackaged;
constructor(app: electron.App, store: any) {
this.app = app;
this.store = store;
}
private paths: any = {
srcPath: path.join(__dirname, "../../src"),
resourcePath: path.join(__dirname, "../../resources"),
ciderCache: path.resolve(electron.app.getPath("userData"), "CiderCache"),
themes: path.resolve(electron.app.getPath("userData"), "Themes"),
plugins: path.resolve(electron.app.getPath("userData"), "Plugins"),
};
private audioStream: any = new Stream.PassThrough();
private clientPort: number = 0;
private remotePort: number = 6942;
private EnvironmentVariables: object = {
env: {
platform: process.platform,
dev: electron.app.isPackaged,
},
};
private options: any = {
icon: path.join(
this.paths.resourcePath,
`icons/icon.` + (process.platform === "win32" ? "ico" : "png")
),
width: 1024,
height: 600,
x: undefined,
y: undefined,
minWidth: 900,
minHeight: 390,
frame: false,
title: "Cider",
vibrancy: "dark",
transparent: process.platform === "darwin",
hasShadow: false,
show: false,
backgroundColor: "#1E1E1E",
webPreferences: {
nodeIntegration: true,
sandbox: true,
allowRunningInsecureContent: true,
contextIsolation: false,
webviewTag: true,
plugins: true,
nodeIntegrationInWorker: false,
webSecurity: false,
preload: path.join(this.paths.srcPath, "./preload/cider-preload.js"),
},
};
/**
* Creates the browser window
*/
async createWindow(): Promise<Electron.BrowserWindow> {
this.clientPort = await getPort({ port: 9000 });
this.verifyFiles();
// Load the previous state with fallback to defaults
const windowState = windowStateKeeper({
defaultWidth: 1024,
defaultHeight: 600,
});
this.options.width = windowState.width;
this.options.height = windowState.height;
// Start the webserver for the browser window to load
this.startWebServer();
this.win = new electron.BrowserWindow(this.options);
const ws = new wsapi(this.win)
ws.InitWebSockets()
// and load the renderer.
this.startSession();
this.startHandlers();
// Register listeners on Window to track size and position of the Window.
windowState.manage(this.win);
return this.win;
}
/**
* Verifies the files for the renderer to use (Cache, library info, etc.)
*/
private verifyFiles(): void {
const expectedDirectories = ["CiderCache"];
const expectedFiles = [
"library-songs.json",
"library-artists.json",
"library-albums.json",
"library-playlists.json",
"library-recentlyAdded.json",
];
for (let i = 0; i < expectedDirectories.length; i++) {
if (
!fs.existsSync(
path.join(electron.app.getPath("userData"), expectedDirectories[i])
)
) {
fs.mkdirSync(
path.join(electron.app.getPath("userData"), expectedDirectories[i])
);
}
}
for (let i = 0; i < expectedFiles.length; i++) {
const file = path.join(this.paths.ciderCache, expectedFiles[i]);
if (!fs.existsSync(file)) {
fs.writeFileSync(file, JSON.stringify([]));
}
}
}
/**
* Starts the webserver for the renderer process.
*/
private startWebServer(): void {
const app = express();
app.use(express.static(path.join(this.paths.srcPath, "./renderer/")));
app.set("views", path.join(this.paths.srcPath, "./renderer/views"));
app.set("view engine", "ejs");
let firstRequest = true;
app.use((req, res, next) => {
// @ts-ignore
if (
req.url.includes("audio.webm") ||
(req.headers.host.includes("localhost") &&
(this.devMode || req.headers["user-agent"].includes("Electron")))
) {
next();
} else {
res.redirect("https://discord.gg/applemusic");
}
});
app.get("/", (req, res) => {
res.render("main", this.EnvironmentVariables);
});
app.get("/audio.webm", (req, res) => {
try {
req.socket.setTimeout(Number.MAX_SAFE_INTEGER);
// CiderBase.requests.push({req: req, res: res});
// var pos = CiderBase.requests.length - 1;
// req.on("close", () => {
// console.info("CLOSED", CiderBase.requests.length);
// requests.splice(pos, 1);
// console.info("CLOSED", CiderBase.requests.length);
// });
this.audioStream.on("data", (data: any) => {
try {
res.write(data);
} catch (ex) {
console.log(ex);
}
});
} catch (ex) {
console.log(ex);
}
});
//app.use(express.static())
app.listen(this.clientPort, () => {
console.log(`Cider client port: ${this.clientPort}`);
});
/*
* Remote Client (I had no idea how to add it to our existing express server, so I just made another one) -@quacksire
* TODO: Broadcast the remote so that /web-remote/ can connect
* https://github.com/ciderapp/Apple-Music-Electron/blob/818ed18940ff600d76eb59d22016723a75885cd5/resources/functions/handler.js#L1173
*/
const remote = express();
remote.use(express.static(path.join(this.paths.srcPath, "./web-remote/")))
remote.set("views", path.join(this.paths.srcPath, "./web-remote/views"));
remote.set("view engine", "ejs");
getPort({port: 6942}).then((port) => {
this.remotePort = port;
// Start Remote Discovery
this.broadcastRemote()
remote.listen(this.remotePort, () => {
console.log(`Cider remote port: ${this.remotePort}`);
if (firstRequest) {
console.log("---- Ignore Me ;) ---");
qrcode.generate(`http://${os.hostname}:${this.remotePort}`);
console.log("---- Ignore Me ;) ---");
/*
*
* USING https://www.npmjs.com/package/qrcode-terminal for terminal
* WE SHOULD USE https://www.npmjs.com/package/qrcode for the remote (or others) for showing to user via an in-app dialog
* -@quacksire
*/
}
firstRequest = false;
})
remote.get("/", (req, res) => {
res.render("index", this.EnvironmentVariables);
});
})
}
/**
* Starts the session for the renderer process.
*/
private startSession(): void {
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.1/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
this.win.webContents.session.webRequest.onBeforeRequest(
{
urls: ["https://*/*.js"],
},
(
details: { url: string | string[] },
callback: (arg0: { redirectURL?: string; cancel?: boolean }) => void
) => {
if (details.url.includes("hls.js")) {
callback({
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`,
});
} else {
callback({
cancel: false,
});
}
}
);
this.win.webContents.session.webRequest.onBeforeSendHeaders(
async (
details: { url: string; requestHeaders: { [x: string]: string } },
callback: (arg0: { requestHeaders: any }) => void
) => {
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
details.requestHeaders["sec-fetch-site"] = "same-site";
details.requestHeaders["DNT"] = "1";
let itspod = await this.win.webContents.executeJavaScript(
`window.localStorage.getItem("music.ampwebplay.itspod")`
);
if (itspod != null)
details.requestHeaders["Cookie"] = `itspod=${itspod}`;
}
callback({ requestHeaders: details.requestHeaders });
}
);
let location = `http://localhost:${this.clientPort}/`;
if (electron.app.isPackaged) {
this.win.loadURL(location);
} else {
this.win.loadURL(location, {
userAgent: "Cider Development Environment",
});
}
}
/**
* Initializes the window handlers
*/
private startHandlers(): void {
/**********************************************************************************************************************
* ipcMain Events
****************************************************************************************************************** */
electron.ipcMain.on("cider-platform", (event) => {
event.returnValue = process.platform;
});
electron.ipcMain.on("get-i18n", (event, key) => {
let i18nBase = fs.readFileSync(path.join(__dirname, "../../src/i18n/en_US.jsonc"), "utf8");
i18nBase = jsonc.parse(i18nBase)
try {
let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${key}.jsonc`), "utf8");
i18n = jsonc.parse(i18n)
Object.assign(i18nBase, i18n)
}catch(e) {
console.error(e);
event.returnValue = e;
}
event.returnValue = i18nBase;
});
electron.ipcMain.on("get-i18n-listing", event => {
let i18nFiles = fs.readdirSync(path.join(__dirname, "../../src/i18n")).filter(file => file.endsWith(".jsonc"));
// read all the files and parse them
let i18nListing = []
for (let i = 0; i < i18nFiles.length; i++) {
let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${i18nFiles[i]}`), "utf8");
i18n = jsonc.parse(i18n)
i18nListing.push({
"code": i18nFiles[i].replace(".jsonc", ""),
"nameNative": i18n["i18n.languageName"] ?? i18nFiles[i].replace(".jsonc", ""),
"nameEnglish": i18n["i18n.languageNameEnglish"] ?? i18nFiles[i].replace(".jsonc", ""),
"category": i18n["i18n.category"] ?? "",
"authors": i18n["i18n.authors"] ?? ""
})
}
event.returnValue = i18nListing;
})
electron.ipcMain.on("get-gpu-mode", (event) => {
event.returnValue = process.platform;
});
electron.ipcMain.on("is-dev", (event) => {
event.returnValue = this.devMode;
});
electron.ipcMain.on("close", () => {
// listen for close event
this.win.close();
});
electron.ipcMain.on("put-library-songs", (event, arg) => {
fs.writeFileSync(
path.join(this.paths.ciderCache, "library-songs.json"),
JSON.stringify(arg)
);
});
electron.ipcMain.on("put-library-artists", (event, arg) => {
fs.writeFileSync(
path.join(this.paths.ciderCache, "library-artists.json"),
JSON.stringify(arg)
);
});
electron.ipcMain.on("put-library-albums", (event, arg) => {
fs.writeFileSync(
path.join(this.paths.ciderCache, "library-albums.json"),
JSON.stringify(arg)
);
});
electron.ipcMain.on("put-library-playlists", (event, arg) => {
fs.writeFileSync(
path.join(this.paths.ciderCache, "library-playlists.json"),
JSON.stringify(arg)
);
});
electron.ipcMain.on("put-library-recentlyAdded", (event, arg) => {
fs.writeFileSync(
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
JSON.stringify(arg)
);
});
electron.ipcMain.on("get-library-songs", (event) => {
let librarySongs = fs.readFileSync(
path.join(this.paths.ciderCache, "library-songs.json"),
"utf8"
);
event.returnValue = JSON.parse(librarySongs);
});
electron.ipcMain.on("get-library-artists", (event) => {
let libraryArtists = fs.readFileSync(
path.join(this.paths.ciderCache, "library-artists.json"),
"utf8"
);
event.returnValue = JSON.parse(libraryArtists);
});
electron.ipcMain.on("get-library-albums", (event) => {
let libraryAlbums = fs.readFileSync(
path.join(this.paths.ciderCache, "library-albums.json"),
"utf8"
);
event.returnValue = JSON.parse(libraryAlbums);
});
electron.ipcMain.on("get-library-playlists", (event) => {
let libraryPlaylists = fs.readFileSync(
path.join(this.paths.ciderCache, "library-playlists.json"),
"utf8"
);
event.returnValue = JSON.parse(libraryPlaylists);
});
electron.ipcMain.on("get-library-recentlyAdded", (event) => {
let libraryRecentlyAdded = fs.readFileSync(
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
"utf8"
);
event.returnValue = JSON.parse(libraryRecentlyAdded);
});
electron.ipcMain.handle("getYTLyrics", async (event, track, artist) => {
const u = track + " " + artist + " official video";
return await yt.search(u);
});
electron.ipcMain.handle("setVibrancy", (event, key, value) => {
this.win.setVibrancy(value);
});
electron.ipcMain.on("maximize", () => {
// listen for maximize event
if (this.win.isMaximized()) {
this.win.unmaximize();
} else {
this.win.maximize();
}
});
electron.ipcMain.on("unmaximize", () => {
// listen for maximize event
this.win.unmaximize();
});
electron.ipcMain.on("minimize", () => {
// listen for minimize event
this.win.minimize();
});
// Set scale
electron.ipcMain.on("setScreenScale", (event, scale) => {
this.win.webContents.setZoomFactor(parseFloat(scale));
});
electron.ipcMain.on("windowmin", (event, width, height) => {
this.win.setMinimumSize(width,height);
})
electron.ipcMain.on("windowontop", (event, ontop) => {
this.win.setAlwaysOnTop(ontop);
});
// Set scale
electron.ipcMain.on("windowresize", (event, width, height, lock = false) => {
this.win.setContentSize(width, height);
this.win.setResizable(!lock);
});
//Fullscreen
electron.ipcMain.on('setFullScreen', (event, flag) => {
this.win.setFullScreen(flag)
})
//Fullscreen
electron.ipcMain.on('detachDT', (event, _) => {
this.win.webContents.openDevTools({ mode: 'detach' });
})
electron.ipcMain.on('play', (event, type, id) => {
this.win.webContents.executeJavaScript(`
MusicKit.getInstance().setQueue({ ${type}: '${id}'}).then(function(queue) {
MusicKit.getInstance().play();
});
`)
})
function getIp() {
let ip = false;
let alias = 0;
let ifaces = os.networkInterfaces();
for (var dev in ifaces) {
ifaces[dev].forEach(details => {
if (details.family === 'IPv4') {
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) {
if (details.address.substring(0, 8) === '192.168.' ||
details.address.substring(0, 7) === '172.16.' ||
details.address.substring(0, 3) === '10.'
) {
ip = details.address;
++alias;
}
}
}
});
}
return ip;
}
//QR Code
electron.ipcMain.handle('showQR', async (event , _) => {
let url = `http://${getIp()}:${this.remotePort}`;
electron.shell.openExternal(`https://cider.sh/pair-remote?url=${btoa(encodeURI(url))}`);
/*
* Doing this because we can give them the link and let them send it via Pocket or another in-browser tool -q
*/
})
// Get previews for normalization
electron.ipcMain.on("getPreviewURL", (_event, url) => {
'get url'
fetch(url)
.then(res => res.buffer())
.then(async(buffer) => {
try {
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a');
let SoundCheckTag = metadata.native.iTunes[1].value
console.log('sc',SoundCheckTag)
this.win.webContents.send('SoundCheckTag', SoundCheckTag)
} catch (error) {
console.error(error.message);
}
})
});
/* *********************************************************************************************
* Window Events
* **********************************************************************************************/
if (process.platform === "win32") {
let WND_STATE = {
MINIMIZED: 0,
NORMAL: 1,
MAXIMIZED: 2,
FULL_SCREEN: 3,
};
let wndState = WND_STATE.NORMAL;
this.win.on("resize", (_: any) => {
const isMaximized = this.win.isMaximized();
const isMinimized = this.win.isMinimized();
const isFullScreen = this.win.isFullScreen();
const state = wndState;
if (isMinimized && state !== WND_STATE.MINIMIZED) {
wndState = WND_STATE.MINIMIZED;
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
wndState = WND_STATE.FULL_SCREEN;
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
wndState = WND_STATE.MAXIMIZED;
this.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
} else if (state !== WND_STATE.NORMAL) {
wndState = WND_STATE.NORMAL;
this.win.webContents.executeJavaScript(
`app.chrome.maximized = false`
);
}
});
}
this.win.on("closed", () => {
this.win = null;
});
// Set window Handler
this.win.webContents.setWindowOpenHandler((x: any) => {
if (x.url.includes("apple") || x.url.includes("localhost")) {
return { action: "allow" };
}
electron.shell.openExternal(x.url).catch(console.error);
return { action: "deny" };
});
}
private async broadcastRemote() {
function getIp() {
let ip :any = false;
let alias = 0;
const ifaces: any = os.networkInterfaces() ;
for (var dev in ifaces) {
ifaces[dev].forEach( (details: any) => {
if (details.family === 'IPv4') {
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) {
if (details.address.substring(0, 8) === '192.168.' ||
details.address.substring(0, 7) === '172.16.' ||
details.address.substring(0, 3) === '10.'
) {
ip = details.address;
++alias;
}
}
}
}) ;
}
return ip;
}
const myString = `http://${getIp()}:${this.remotePort}`;
let mdns = require('mdns-js');
const encoded = new Buffer(myString).toString('base64');
var x = mdns.tcp('cider-remote');
var txt_record = {
"Ver": "131077",
'DvSv': '3689',
'DbId': 'D41D8CD98F00B205',
'DvTy': 'Cider',
'OSsi': '0x212F0',
'txtvers': '1',
"CtlN": "Cider",
"iV": "196623"
}
let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, { name: encoded, txt: txt_record });
server2.start();
console.log('remote broadcasted')
}
}

293
src/main/base/wsapi.ts Normal file
View file

@ -0,0 +1,293 @@
import * as ws from "ws";
import * as http from "http";
import * as https from "https";
import * as url from "url";
import * as fs from "fs";
import * as path from "path";
import * as electron from "electron";
const WebSocket = ws;
const WebSocketServer = ws.Server;
interface standardResponse {
status?: Number,
message?: String,
data?: any,
type?: string,
}
export class wsapi {
static clients: any;
port: any = 26369
wss: any = null
clients: any = []
private _win : any;
constructor(win : any) {
this._win = win;
}
createId() {
// create random guid
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
public async InitWebSockets () {
electron.ipcMain.on('wsapi-updatePlaybackState', (event :any, arg :any) => {
this.updatePlaybackState(arg);
})
electron.ipcMain.on('wsapi-returnQueue', (event :any, arg :any) => {
this.returnQueue(JSON.parse(arg));
});
electron.ipcMain.on('wsapi-returnSearch', (event :any, arg :any) => {
console.log("SEARCH")
this.returnSearch(JSON.parse(arg));
});
electron.ipcMain.on('wsapi-returnSearchLibrary', (event :any, arg :any) => {
this.returnSearchLibrary(JSON.parse(arg));
});
electron.ipcMain.on('wsapi-returnDynamic', (event :any, arg :any, type :any) => {
this.returnDynamic(JSON.parse(arg), type);
});
electron.ipcMain.on('wsapi-returnMusicKitApi', (event :any, arg :any, method :any) => {
this.returnMusicKitApi(JSON.parse(arg), method);
});
electron.ipcMain.on('wsapi-returnLyrics', (event :any, arg :any) => {
this.returnLyrics(JSON.parse(arg));
});
this.wss = new WebSocketServer({
port: this.port,
perMessageDeflate: {
zlibDeflateOptions: {
// See zlib defaults.
chunkSize: 1024,
memLevel: 7,
level: 3
},
zlibInflateOptions: {
chunkSize: 10 * 1024
},
// Other options settable:
clientNoContextTakeover: true, // Defaults to negotiated value.
serverNoContextTakeover: true, // Defaults to negotiated value.
serverMaxWindowBits: 10, // Defaults to negotiated value.
// Below options specified as default values.
concurrencyLimit: 10, // Limits zlib concurrency for perf.
threshold: 1024 // Size (in bytes) below which messages
// should not be compressed if context takeover is disabled.
}
})
console.log(`WebSocketServer started on port: ${this.port}`);
const defaultResponse :standardResponse = {status :0, data:{}, message:"OK", type:"generic"};
this.wss.on('connection', (ws : any) => {
ws.id = this.createId();
console.log(`Client ${ws.id} connected`)
this.clients.push(ws);
ws.on('message', function incoming(message : any) {
});
// ws on message
ws.on('message', (message : any) => {
let data = JSON.parse(message);
let response :standardResponse = {status :0, data:{}, message:"OK", type:"generic"};
if (data.action) {
data.action.toLowerCase();
}
switch (data.action) {
default:
response.message = "Action not found";
break;
case "identify":
response.message = "Thanks for identifying!"
response.data = {
id: ws.id
}
ws.identity = {
name: data.name,
author: data.author,
description: data.description,
version: data.version
}
break;
case "play-next":
this._win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`);
response.message = "Play Next";
break;
case "play-later":
this._win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`);
response.message = "Play Later";
break;
case "quick-play":
this._win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`);
response.message = "Quick Play";
break;
case "get-lyrics":
this._win.webContents.executeJavaScript(`wsapi.getLyrics()`);
break;
case "shuffle":
this._win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
break;
case "set-shuffle":
if(data.shuffle == true) {
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
}else{
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
}
break;
case "repeat":
this._win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
break;
case "seek":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
response.message = "Seek";
break;
case "pause":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
response.message = "Paused";
break;
case "play":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
response.message = "Playing";
break;
case "stop":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
response.message = "Stopped";
break;
case "volume":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
response.message = "Volume";
break;
case "mute":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
response.message = "Muted";
break;
case "unmute":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
response.message = "Unmuted";
break;
case "next":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().skipToNextItem()`);
response.message = "Next";
break;
case "previous":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().skipToPreviousItem()`);
response.message = "Previous";
break;
case "musickit-api":
this._win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)} , ${data.library})`);
break;
case "musickit-library-api":
break;
case "set-autoplay":
this._win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
break;
case "queue-move":
this._win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
break;
case "get-queue":
this._win.webContents.executeJavaScript(`wsapi.getQueue()`);
break;
case "search":
if (!data.limit) {
data.limit = 10;
}
this._win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
break;
case "library-search":
if (!data.limit) {
data.limit = 10;
}
this._win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
break;
case "show-window":
this._win.show()
break;
case "hide-window":
this._win.hide()
break;
case "play-mediaitem":
this._win.webContents.executeJavaScript(`wsapi.playTrackById("${data.id}", \`${data.kind}\`)`);
response.message = "Playing track";
break;
case "get-status":
response.data = {
isAuthorized: true
};
response.message = "Status";
break;
case "get-currentmediaitem":
this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
break;
case "quit":
electron.app.quit();
break;
}
ws.send(JSON.stringify(response));
});
ws.on('close', () => {
// remove client from list
this.clients.splice(wsapi.clients.indexOf(ws), 1);
console.log(`Client ${ws.id} disconnected`);
});
ws.send(JSON.stringify(defaultResponse));
});
}
sendToClient(id : any) {
// replace the clients.forEach with a filter to find the client that requested
}
updatePlaybackState(attr : any) {
const response : standardResponse = {status: 0, data: attr, message: "OK", type:"playbackStateUpdate"};
this.clients.forEach(function each(client: any) {
client.send(JSON.stringify(response));
});
}
returnMusicKitApi(results :any, method :any) {
const response : standardResponse = {status :0, data: results, message:"OK", type:`musickitapi.${method}`};
this.clients.forEach(function each(client :any) {
client.send(JSON.stringify(response));
});
}
returnDynamic(results :any, type :any) {
const response : standardResponse = {status :0, data: results, message: "OK", type: type};
this.clients.forEach(function each(client :any) {
client.send(JSON.stringify(response));
});
}
returnLyrics(results :any) {
const response : standardResponse = {status :0, data: results, message: "OK", type: "lyrics"};
this.clients.forEach(function each(client :any) {
client.send(JSON.stringify(response));
});
}
returnSearch(results :any) {
const response : standardResponse = {status :0, data: results, message: "OK", type: "searchResults"};
this.clients.forEach(function each(client :any) {
client.send(JSON.stringify(response));
});
}
returnSearchLibrary(results :any) {
const response: standardResponse = {status :0, data :results, message:"OK", type:"searchResultsLibrary"};
this.clients.forEach(function each(client :any) {
client.send(JSON.stringify(response));
});
}
returnQueue(queue :any) {
const response : standardResponse = {status :0,data :queue, message:"OK", type:"queue"};
this.clients.forEach(function each(client :any) {
client.send(JSON.stringify(response));
});
}
}

View file

@ -1,446 +0,0 @@
const { BrowserWindow, ipcMain, shell, app, screen } = require("electron")
const { join } = require("path")
const getPort = require("get-port");
const express = require("express");
const path = require("path");
const windowStateKeeper = require("electron-window-state");
const os = require('os');
const yt = require('youtube-search-without-api-key');
const discord = require('./discordrpc');
const lastfm = require('./lastfm');
const { writeFile, writeFileSync, existsSync, mkdirSync } = require('fs');
const fs = require('fs');
const mpris = require('./mpris');
const mm = require('music-metadata');
//const mdns = require('mdns')
const qrcode = require('qrcode-terminal')
const fetch = require('electron-fetch').default;
const { Stream } = require('stream');
// Analytics for debugging.
const ElectronSentry = require("@sentry/electron");
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" });
const CiderBase = {
win: null,
requests: [],
audiostream: new Stream.PassThrough(),
async Start() {
this.clientPort = await getPort({ port: 9000 });
this.win = this.CreateBrowserWindow()
},
clientPort: 0,
CreateBrowserWindow() {
this.VerifyFiles()
// Set default window sizes
const mainWindowState = windowStateKeeper({
defaultWidth: 1024,
defaultHeight: 600
});
let win = null
const options = {
icon: join(__dirname, `../../resources/icons/icon.` + (process.platform === "win32" ? "ico" : "png")),
width: mainWindowState.width,
height: mainWindowState.height,
x: mainWindowState.x,
y: mainWindowState.y,
minWidth: 844,
minHeight: 410,
frame: false,
title: "Cider",
vibrancy: 'dark',
// transparent: true,
hasShadow: false,
webPreferences: {
webviewTag: true,
plugins: true,
nodeIntegration: true,
nodeIntegrationInWorker: false,
webSecurity: false,
allowRunningInsecureContent: true,
enableRemoteModule: true,
sandbox: true,
nativeWindowOpen: true,
contextIsolation: false,
preload: join(__dirname, '../preload/cider-preload.js')
}
}
CiderBase.InitWebServer()
// Create the BrowserWindow
win = new BrowserWindow(options)
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
win.webContents.session.webRequest.onBeforeRequest({
urls: ["https://*/*.js"]
},
(details, callback) => {
if (details.url.includes("hls.js")) {
callback({
redirectURL: `http://localhost:${CiderBase.clientPort}/apple-hls.js`
})
} else {
callback({
cancel: false
})
}
}
)
win.webContents.session.webRequest.onBeforeSendHeaders(async(details, callback) => {
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
details.requestHeaders['sec-fetch-site'] = 'same-site';
details.requestHeaders['DNT'] = '1';
let itspod = await win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`)
if (itspod != null)
details.requestHeaders['Cookie'] = `itspod=${itspod}`
}
callback({ requestHeaders: details.requestHeaders })
})
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
if (details.url.match(/^https:\/\/store-\d{3}\.blobstore\.apple\.com/) || details.url.startsWith("https://store-037.blobstore.apple.com")) {
details.responseHeaders['Access-Control-Allow-Origin'] = '*';
}
callback({ responseHeaders: details.responseHeaders })
})
let location = `http://localhost:${CiderBase.clientPort}/`
win.loadURL(location)
win.on("closed", () => {
win = null
})
// Register listeners on Window to track size and position of the Window.
mainWindowState.manage(win);
// IPC stuff (senders)
ipcMain.on("cider-platform", (event) => {
event.returnValue = process.platform
})
ipcMain.on("get-gpu-mode", (event) => {
event.returnValue = process.platform
})
ipcMain.on("is-dev", (event) => {
event.returnValue = !app.isPackaged
})
// IPC stuff (listeners)
ipcMain.on('close', () => { // listen for close event
win.close();
})
ipcMain.on('put-library-songs', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-songs.json"), JSON.stringify(arg))
})
ipcMain.on('put-library-artists', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-artists.json"), JSON.stringify(arg))
})
ipcMain.on('put-library-albums', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-albums.json"), JSON.stringify(arg))
})
ipcMain.on('put-library-playlists', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-playlists.json"), JSON.stringify(arg))
})
ipcMain.on('put-library-recentlyAdded', (event, arg) => {
fs.writeFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), JSON.stringify(arg))
})
ipcMain.on('get-library-songs', (event) => {
let librarySongs = fs.readFileSync(join(app.paths.ciderCache, "library-songs.json"), "utf8")
event.returnValue = JSON.parse(librarySongs)
})
ipcMain.on('get-library-artists', (event) => {
let libraryArtists = fs.readFileSync(join(app.paths.ciderCache, "library-artists.json"), "utf8")
event.returnValue = JSON.parse(libraryArtists)
})
ipcMain.on('get-library-albums', (event) => {
let libraryAlbums = fs.readFileSync(join(app.paths.ciderCache, "library-albums.json"), "utf8")
event.returnValue = JSON.parse(libraryAlbums)
})
ipcMain.on('get-library-playlists', (event) => {
let libraryPlaylists = fs.readFileSync(join(app.paths.ciderCache, "library-playlists.json"), "utf8")
event.returnValue = JSON.parse(libraryPlaylists)
})
ipcMain.on('get-library-recentlyAdded', (event) => {
let libraryRecentlyAdded = fs.readFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), "utf8")
event.returnValue = JSON.parse(libraryRecentlyAdded)
})
ipcMain.handle('getYTLyrics', async(event, track, artist) => {
var u = track + " " + artist + " official video";
const videos = await yt.search(u);
return videos
})
ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
return (defaultValue ? app.cfg.get(key, true) : app.cfg.get(key));
});
ipcMain.handle('setStoreValue', (event, key, value) => {
app.cfg.set(key, value);
});
ipcMain.on('getStore', (event) => {
event.returnValue = app.cfg.store
})
ipcMain.on('setStore', (event, store) => {
app.cfg.store = store
})
ipcMain.handle('setVibrancy', (event, key, value) => {
win.setVibrancy(value)
});
ipcMain.on('maximize', () => { // listen for maximize event
if (win.isMaximized()) {
win.unmaximize()
} else {
win.maximize()
}
})
ipcMain.on('minimize', () => { // listen for minimize event
win.minimize();
})
ipcMain.on('setFullScreen', (event, flag) => {
win.setFullScreen(flag)
})
if (process.platform === "win32") {
let WND_STATE = {
MINIMIZED: 0,
NORMAL: 1,
MAXIMIZED: 2,
FULL_SCREEN: 3
}
let wndState = WND_STATE.NORMAL
win.on("resize", (_event) => {
const isMaximized = win.isMaximized()
const isMinimized = win.isMinimized()
const isFullScreen = win.isFullScreen()
const state = wndState;
if (isMinimized && state !== WND_STATE.MINIMIZED) {
wndState = WND_STATE.MINIMIZED
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
wndState = WND_STATE.FULL_SCREEN
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
wndState = WND_STATE.MAXIMIZED
win.webContents.executeJavaScript(`app.chrome.maximized = true`)
} else if (state !== WND_STATE.NORMAL) {
wndState = WND_STATE.NORMAL
win.webContents.executeJavaScript(`app.chrome.maximized = false`)
}
})
}
// Set window Handler
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.includes("apple") || url.includes("localhost")) {
return { action: "allow" }
}
shell.openExternal(url).catch(() => {})
return {
action: 'deny'
}
})
// Set scale
ipcMain.on('setScreenScale', (event, scale) => {
win.webContents.setZoomFactor(parseFloat(scale))
})
win.webContents.setZoomFactor(screen.getPrimaryDisplay().scaleFactor)
mpris.connect(win)
mpris.SetButtons(win, false)
lastfm.authenticate()
// Discord
discord.connect((app.cfg.get("general.discord_rpc") == 1) ? '911790844204437504' : '886578863147192350');
ipcMain.on('playbackStateDidChange', (_event, a) => {
app.media = a;
discord.updateActivity(a)
mpris.SetButtons(win, a)
mpris.updateState(a)
lastfm.scrobbleSong(a)
lastfm.updateNowPlayingSong(a)
});
ipcMain.on('nowPlayingItemDidChange', (_event, a) => {
app.media = a;
discord.updateActivity(a)
mpris.SetButtons(win, a)
mpris.updateAttributes(a)
lastfm.scrobbleSong(a)
lastfm.updateNowPlayingSong(a)
});
ipcMain.on("getPreviewURL", (_event, url) => {
fetch(url)
.then(res => res.buffer())
.then(async(buffer) => {
try {
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a');
SoundCheckTag = metadata.native.iTunes[1].value
win.webContents.send('SoundCheckTag', SoundCheckTag)
} catch (error) {
console.error(error.message);
}
})
});
ipcMain.on('writeAudio', function(event, buffer) {
CiderBase.audiostream.write(Buffer.from(buffer));
})
return win
},
VerifyFiles() {
const expectedDirectories = [
"CiderCache"
]
const expectedFiles = [
"library-songs.json",
"library-artists.json",
"library-albums.json",
"library-playlists.json",
"library-recentlyAdded.json",
]
for (let i = 0; i < expectedDirectories.length; i++) {
if (!existsSync(path.join(app.getPath("userData"), expectedDirectories[i]))) {
mkdirSync(path.join(app.getPath("userData"), expectedDirectories[i]))
}
}
for (let i = 0; i < expectedFiles.length; i++) {
const file = path.join(app.paths.ciderCache, expectedFiles[i])
if (!existsSync(file)) {
writeFileSync(file, JSON.stringify([]))
}
}
},
EnvironmentVariables: {
"env": {
platform: os.platform(),
dev: app.isPackaged
}
},
LinkHandler: (startArgs) => {
if (!startArgs) return;
if (String(startArgs).includes('auth')) {
let authURI = String(startArgs).split('/auth/')[1]
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
console.log("lfmtoken", String(startArgs))
const authKey = authURI.split('lastfm?token=')[1];
app.cfg.set('lastfm.enabled', true);
app.cfg.set('lastfm.auth_token', authKey);
CiderBase.win.webContents.send('LastfmAuthenticated', authKey);
lastfm.authenticate()
}
} else {
if (String(startArgs).includes('/play/')) { //Steer away from protocal:// specific conditionals
const playParam = String(startArgs).split('/play/')[1]
if (playParam.includes('s/')) { // setQueue can be done with album, song, url, playlist id
console.log(playParam)
let song = playParam.split('s/')[1]
console.warn(`[LinkHandler] Attempting to load song by id: ${song}`);
this.win.webContents.executeJavaScript(`
MusicKit.getInstance().setQueue({ song: '${song}'}).then(function(queue) {
MusicKit.getInstance().play();
});
`).catch((err) => console.error(err));
}
}
}
},
async InitWebServer() {
const webapp = express();
const webRemotePath = path.join(__dirname, '../renderer/');
webapp.set("views", path.join(webRemotePath, "views"));
webapp.set("view engine", "ejs");
let firstRequest = true
//const webRemoteMDNS = mdns.createAdvertisement(mdns.tcp('https'), 9000, { name: "cider", domain: 'local' })
//webRemoteMDNS.start()
//* Prep for remote -quack
webapp.use(function(req, res, next) {
// if not localhost
if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && req.headers["user-agent"].includes("Cider"))) {
next();
} else {
console.log(req.get('host'))
res.redirect("https://discord.gg/applemusic")
}
});
webapp.use(express.static(webRemotePath));
webapp.get('/', function(req, res) {
//if (!req.headers["user-agent"].includes("Cider"))
//res.sendFile(path.join(webRemotePath, 'index_old.html'));
if (firstRequest) {
console.log("---- Ignore Me ;) ---")
qrcode.generate(`http://${os.hostname}:9000`) //Prep for remote
console.log("---- Ignore Me ;) ---")
/*
*
* USING https://www.npmjs.com/package/qrcode-terminal for terminal
* WE SHOULD USE https://www.npmjs.com/package/qrcode for the remote (or others)
* -quack
*/
}
firstRequest = false
res.render("main", CiderBase.EnvironmentVariables)
});
webapp.get('/audio.webm', function(req, res) {
console.log('hi')
try {
req.connection.setTimeout(Number.MAX_SAFE_INTEGER);
// CiderBase.requests.push({req: req, res: res});
// var pos = CiderBase.requests.length - 1;
// req.on("close", () => {
// console.info("CLOSED", CiderBase.requests.length);
// requests.splice(pos, 1);
// console.info("CLOSED", CiderBase.requests.length);
// });
CiderBase.audiostream.on('data', (data) => {
try {
res.write(data);
} catch (ex) {
console.log(ex)
}
})
} catch (ex) { console.log(ex) }
});
webapp.listen(CiderBase.clientPort, function() {
console.log(`Cider hosted on: ${CiderBase.clientPort}`);
});
},
}
module.exports = CiderBase;

View file

@ -1,142 +0,0 @@
const { app } = require('electron'),
DiscordRPC = require('discord-rpc')
module.exports = {
/**
* Connects to Discord RPC
* @param {string} clientId
*/
connect: function(clientId) {
app.discord = { isConnected: false };
if (app.cfg.get('general.discord_rpc') == 0 || app.discord.isConnected) return;
DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc.
const client = new DiscordRPC.Client({ transport: "ipc" });
app.discord = Object.assign(client, { error: false, activityCache: null, isConnected: false });
// Login to Discord
app.discord.login({ clientId })
.then(() => {
app.discord.isConnected = true;
})
.catch((e) => console.error(`[DiscordRPC][connect] ${e}`));
app.discord.on('ready', () => {
console.log(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${client.user.username} (${client.user.id})`);
})
// Handles Errors
app.discord.on('error', err => {
console.error(`[DiscordRPC] ${err}`);
this.disconnect()
app.discord.isConnected = false;
});
},
/**
* Disconnects from Discord RPC
*/
disconnect: function() {
if (app.cfg.get('general.discord_rpc') == 0 || !app.discord.isConnected) return;
try {
app.discord.destroy().then(() => {
app.discord.isConnected = false;
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
}).catch((e) => console.error(`[DiscordRPC][disconnect] ${e}`));
} catch (err) {
console.error(err)
}
},
/**
* Sets the activity of the client
* @param {object} attributes
*/
updateActivity: function(attributes) {
if (app.cfg.get('general.discord_rpc') == 0) return;
if (!app.discord.isConnected) {
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][updateActivity] ${e}`));
return;
}
// console.log('[DiscordRPC][updateActivity] Updating Discord Activity.')
const listenURL = `https://cider.sh/p?s&id=${attributes.playParams.id}` // cider://play/s/[id] (for song)
//console.log(attributes)
let ActivityObject = {
details: attributes.name,
state: `by ${attributes.artistName}`,
startTimestamp: attributes.startTime,
endTimestamp: attributes.endTime,
largeImageKey: (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider',
largeImageText: attributes.albumName,
smallImageKey: (attributes.status ? 'play' : 'pause'),
smallImageText: (attributes.status ? 'Playing' : 'Paused'),
instance: true,
buttons: [
{ label: "Listen on Cider", url: listenURL },
]
};
if (ActivityObject.largeImageKey == "" || ActivityObject.largeImageKey == null) {
ActivityObject.largeImageKey = (app.cfg.get("general.discord_rpc") == 1) ? "cider" : "logo"
}
// Remove the pause/play icon and test for clear activity on pause
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
delete ActivityObject.smallImageKey
delete ActivityObject.smallImageText
}
// Deletes the timestamp if its not greater than 0
if (!((new Date(attributes.endTime)).getTime() > 0)) {
delete ActivityObject.startTimestamp
delete ActivityObject.endTimestamp
}
// Artist check
if (!attributes.artistName) {
delete ActivityObject.state
}
// Album text check
if (!ActivityObject.largeImageText || ActivityObject.largeImageText.length < 2) {
delete ActivityObject.largeImageText
}
// Checks if the name is greater than 128 because some songs can be that long
if (ActivityObject.details.length > 128) {
ActivityObject.details = ActivityObject.details.substring(0, 125) + '...'
}
// Check if its pausing (false) or playing (true)
if (!attributes.status) {
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][clearActivity] ${e}`));
ActivityObject = null
} else {
delete ActivityObject.startTimestamp
delete ActivityObject.endTimestamp
ActivityObject.smallImageKey = 'pause'
ActivityObject.smallImageText = 'Paused'
}
}
if (ActivityObject && ActivityObject !== app.discord.activityCache && ActivityObject.details && ActivityObject.state) {
try {
// console.log(`[DiscordRPC][setActivity] Setting activity to ${JSON.stringify(ActivityObject)}`);
app.discord.setActivity(ActivityObject)
app.discord.activityCache = ActivityObject
} catch (err) {
console.error(`[DiscordRPC][setActivity] ${err}`)
}
}
},
}

84
src/main/index.ts Normal file
View file

@ -0,0 +1,84 @@
require('v8-compile-cache');
// Analytics for debugging fun yeah.
import * as sentry from '@sentry/electron';
import * as electron from 'electron';
import {Win} from "./base/win";
import {ConfigStore} from "./base/store";
import {AppEvents} from "./base/app";
import PluginHandler from "./base/plugins";
sentry.init({dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214"});
const config = new ConfigStore();
const App = new AppEvents(config.store);
const Cider = new Win(electron.app, config.store)
const plug = new PluginHandler(config.store);
let win: Electron.BrowserWindow;
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* App Event Handlers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
electron.app.on('ready', () => {
App.ready(plug);
console.log('[Cider] Application is Ready. Creating Window.')
if (!electron.app.isPackaged) {
console.info('[Cider] Running in development mode.')
require('vue-devtools').install()
}
electron.components.whenReady().then(async () => {
win = await Cider.createWindow()
App.bwCreated(win);
/// please dont change this for plugins to get proper and fully initialized Win objects
plug.callPlugins('onReady', win);
win.on("ready-to-show", () => {
win.show();
});
});
});
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Renderer Event Handlers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
electron.ipcMain.on('playbackStateDidChange', (event, attributes) => {
plug.callPlugins('onPlaybackStateDidChange', attributes);
});
electron.ipcMain.on('nowPlayingItemDidChange', (event, attributes) => {
plug.callPlugins('onNowPlayingItemDidChange', attributes);
});
electron.app.on('before-quit', () => {
plug.callPlugins('onBeforeQuit');
console.warn(`${electron.app.getName()} exited.`);
});
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Widevine Event Handlers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
// @ts-ignore
electron.app.on('widevine-ready', (version, lastVersion) => {
if (null !== lastVersion) {
console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
} else {
console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
}
})
// @ts-ignore
electron.app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
})
// @ts-ignore
electron.app.on('widevine-error', (error) => {
console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
electron.app.exit()
})

View file

@ -1,153 +0,0 @@
const {app, Notification} = require('electron'),
fs = require('fs'),
{resolve} = require('path'),
sessionPath = resolve(app.getPath('userData'), 'session.json'),
apiCredentials = require('../../resources/lfmApiCredentials.json'),
LastfmAPI = require('lastfmapi');
const lfm = {
authenticateFromFile: function () {
let sessionData = require(sessionPath)
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
app.lastfm.setSessionCredentials(sessionData.name, sessionData.key)
console.log("[LastFM][authenticateFromFile] Logged in.")
},
authenticate: function () {
if (app.cfg.get('lastfm.auth_token')) {
app.cfg.set('lastfm.enabled', true);
}
if (!app.cfg.get('lastfm.enabled') || !app.cfg.get('lastfm.auth_token')) {
app.cfg.set('lastfm.enabled', false);
return
}
const lfmAPI = new LastfmAPI({
'api_key': apiCredentials.key,
'secret': apiCredentials.secret
});
app.lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
fs.stat(sessionPath, function (err) {
if (err) {
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
console.log("[LastFM][Auth] Beginning authentication from configuration")
app.lastfm.authenticate(app.cfg.get('lastfm.auth_token'), function (err, session) {
if (err) {
throw err;
}
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
console.log("[LastFM] Saving session info to disk.")
let tempData = JSON.stringify(session)
fs.writeFile(sessionPath, tempData, (err) => {
if (err)
console.log("[LastFM][fs]", err)
else {
console.log("[LastFM][fs] File was written successfully.")
lfm.authenticateFromFile()
new Notification({
title: app.getName(),
body: "Successfully logged into LastFM using Authentication Key."
}).show()
}
})
});
} else {
lfm.authenticateFromFile()
}
})
},
scrobbleSong: async function (attributes) {
await new Promise(resolve => setTimeout(resolve, Math.round(attributes.durationInMillis * (app.cfg.get('lastfm.scrobble_after') / 100))));
const currentAttributes = app.media;
if (!app.lastfm || app.lastfm.cachedAttributes === attributes ) {
return
}
if (app.lastfm.cachedAttributes) {
if (app.lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
}
if (currentAttributes.status && currentAttributes === attributes) {
if (fs.existsSync(sessionPath)) {
// Scrobble playing song.
if (attributes.status === true) {
app.lastfm.track.scrobble({
'artist': lfm.filterArtistName(attributes.artistName),
'track': attributes.name,
'album': attributes.albumName,
'albumArtist': this.filterArtistName(attributes.artistName),
'timestamp': new Date().getTime() / 1000
}, function (err, scrobbled) {
if (err) {
return console.error('[LastFM] An error occurred while scrobbling', err);
}
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
});
app.lastfm.cachedAttributes = attributes
}
} else {
this.authenticate();
}
} else {
return console.log('[LastFM] Did not add ', attributes.name , '—' , lfm.filterArtistName(attributes.artistName), 'because now playing a other song.');
}
},
filterArtistName: function (artist) {
if (!app.cfg.get('lastfm.enabledRemoveFeaturingArtists')) return artist;
artist = artist.split(' ');
if (artist.includes('&')) {
artist.length = artist.indexOf('&');
}
if (artist.includes('and')) {
artist.length = artist.indexOf('and');
}
artist = artist.join(' ');
if (artist.includes(',')) {
artist = artist.split(',')
artist = artist[0]
}
return artist.charAt(0).toUpperCase() + artist.slice(1);
},
updateNowPlayingSong: function (attributes) {
if (!app.lastfm ||app.lastfm.cachedNowPlayingAttributes === attributes | !app.cfg.get('lastfm.NowPlaying')) {
return
}
if (app.lastfm.cachedNowPlayingAttributes) {
if (app.lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
}
if (fs.existsSync(sessionPath)) {
// update Now Playing
if (attributes.status === true) {
app.lastfm.track.updateNowPlaying({
'artist': lfm.filterArtistName(attributes.artistName),
'track': attributes.name,
'album': attributes.albumName,
'albumArtist': this.filterArtistName(attributes.artistName)
}, function (err, nowPlaying) {
if (err) {
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
}
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
});
app.lastfm.cachedNowPlayingAttributes = attributes
}
} else {
this.authenticate()
}
}
}
module.exports = lfm;

View file

@ -1,149 +0,0 @@
const { nativeImage } = require("electron");
const path = require('path')
let mediaPlayer = null;
module.exports = {
/**
* Connects to the MPRIS interface.
* @param {Object} win - The BrowserWindow.
*/
connect: (win) => {
if (process.platform !== "linux") return;
const Player = require('mpris-service');
mediaPlayer = Player({
name: 'Cider',
identity: 'Cider',
supportedUriSchemes: [],
supportedMimeTypes: [],
supportedInterfaces: ['player']
});
mediaPlayer = Object.assign(mediaPlayer, { canQuit: true, canControl: true, canPause: true, canPlay: true, canGoNext: true })
let pos_atr = {durationInMillis: 0};
mediaPlayer.getPosition = function () {
const durationInMicro = pos_atr.durationInMillis * 1000;
const percentage = parseFloat("0") || 0;
return durationInMicro * percentage;
}
mediaPlayer.active = true
mediaPlayer.on('playpause', async () => {
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
});
mediaPlayer.on('play', async () => {
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
});
mediaPlayer.on('pause', async () => {
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
});
mediaPlayer.on('next', async () => {
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err))
});
mediaPlayer.on('previous', async () => {
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err))
});
},
/**
* Updates the MPRIS interface.
* @param {Object} attributes - The attributes of the track.
*/
updateAttributes: (attributes) => {
if (process.platform !== "linux") return;
const MetaData = {
'mpris:trackid': mediaPlayer.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
'mpris:length': attributes.durationInMillis * 1000, // In microseconds
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
'xesam:title': `${attributes.name}`,
'xesam:album': `${attributes.albumName}`,
'xesam:artist': [`${attributes.artistName}`,],
'xesam:genre': attributes.genreNames
}
if (mediaPlayer.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) {
return
}
mediaPlayer.metadata = MetaData
},
/**
* Updates the playback state of the MPRIS interface.
* @param {Object} attributes - The attributes of the track.
*/
updateState: (attributes) => {
if (process.platform !== "linux") return;
function setPlaybackIfNeeded(status) {
if (mediaPlayer.playbackStatus === status) {
return
}
mediaPlayer.playbackStatus = status;
}
switch (attributes.status) {
case true: // Playing
setPlaybackIfNeeded('Playing');
break;
case false: // Paused
setPlaybackIfNeeded('Paused');
break;
default: // Stopped
setPlaybackIfNeeded('Stopped');
break;
}
},
SetButtons: (win, attributes) => {
if (process.platform === 'win32') { // Set the Windows Thumbnail Toolbar Buttons
win.setThumbarButtons([
{
tooltip: 'Previous',
icon: nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/backwardPng.png')),
click() {
console.log("Clicked the bc taskbar button!")
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err))
}
},
{
tooltip: attributes.status ? 'Pause' : 'Play',
//tooltip: 'Play',
icon: attributes.status ? nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/pausePng.png')) : nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/playPng.png')),
click() {
console.log("Clicked the pl taskbar button!")
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
}
},
{
tooltip: 'Next',
icon: nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/forwardPng.png')),
click() {
console.log("Clicked the fw taskbar button!")
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err))
}
}
]);
}
},
/**
* Closes the MPRIS interface.
*/
clearActivity: () => {
if (process.platform !== "linux") return;
mediaPlayer.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'}
mediaPlayer.playbackStatus = 'Stopped';
},
}

View file

@ -0,0 +1,60 @@
let i = 1, k = 1;
export default class ExamplePlugin {
/**
* Private variables for interaction in plugins
*/
private _win: any;
private _app: any;
private _store: any;
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'examplePlugin';
public description: string = 'Example plugin';
public version: string = '1.0.0';
public author: string = 'Example author';
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(app: any, store: any) {
this._app = app;
this._store = store;
console.debug(`[Plugin][${this.name}] Loading Complete.`);
}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
console.debug(`[Plugin][${this.name}] Ready.`);
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
console.debug(`[Plugin][${this.name}] Stopped.`);
}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.state = current state)
*/
onPlaybackStateDidChange(attributes: object): void {
console.log('onPlaybackStateDidChange has been called ' + i + ' times');
i++
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
console.log('onNowPlayingDidChange has been called ' + k + ' times');
k++
}
}

View file

@ -0,0 +1,37 @@
export default class sendSongToTitlebar {
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'sendSongToTitlebar';
public description: string = 'Sets the app\'s titlebar to the Song title';
public version: string = '0.0.1';
public author: string = 'Cider Collective (credit to 8times9 via #147)';
/**
* Runs on plugin load (Currently run on application start)
*/
private _win: any;
private _app: any;
constructor() {}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.state = current state)
*/
onPlaybackStateDidChange(attributes: any): void {
this._win.setTitle(`${(attributes != null && attributes.name != null && attributes.name.length > 0) ? (attributes.name + " - ") : ''}Cider`)
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {}
}

View file

@ -0,0 +1,195 @@
import * as RPC from 'discord-rpc'
export default class DiscordRichPresence {
/**
* Private variables for interaction in plugins
*/
private static _store: any;
private static _connection: boolean = false;
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'Discord Rich Presence';
public description: string = 'Discord RPC plugin for Cider';
public version: string = '1.0.0';
public author: string = 'vapormusic/Core (Cider Collective)';
/**
* Plugin Initialization
*/
private _client: any = null;
private _activity: RPC.Presence = {
details: '',
state: '',
largeImageKey: '',
largeImageText: '',
smallImageKey: '',
smallImageText: '',
instance: false
};
private _activityCache: RPC.Presence = {
details: '',
state: '',
largeImageKey: '',
largeImageText: '',
smallImageKey: '',
smallImageText: '',
instance: false
};
/*******************************************************************************************
* Private Methods
* ****************************************************************************************/
/**
* Connect to Discord
* @param clientId
* @private
*/
private connect(clientId: any) {
if (DiscordRichPresence._store.general.discord_rpc == 0) {
return
}
// Apparently needed for ask to join, join, spectate etc.
RPC.register(clientId)
// Create the client
this._client = new RPC.Client({transport: "ipc"});
// Runs on Ready
this._client.on('ready', () => {
console.info(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${this._client.user.id}.`);
})
// Handles Errors
this._client.on('error', (err: any) => {
console.error(`[DiscordRichPresence] ${err}`);
this.disconnect()
});
// Login to Discord
this._client.login({clientId})
.then(() => {
DiscordRichPresence._connection = true;
})
.catch((e: any) => console.error(`[DiscordRichPresence][connect] ${e}`));
}
/**
* Disconnects from Discord RPC
*/
private disconnect() {
if (!this._client) return;
this._client.destroy().then(() => {
DiscordRichPresence._connection = false;
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
}).catch((e: any) => console.error(`[DiscordRPC][disconnect] ${e}`));
}
/**
* Sets the activity of the client
* @param {object} attributes
*/
private updateActivity(attributes: any) {
if (!this._client) return;
if (!DiscordRichPresence._connection) {
this._client.clearActivity().catch((e: any) => console.error(`[DiscordRichPresence][clearActivity] ${e}`));
return;
}
this._activity = {
details: attributes.name,
state: `${attributes.artistName ? `by ${attributes.artistName}` : ''}`,
startTimestamp: ((new Date(attributes.endTime).getTime() < 0) ? null : attributes.startTime),
endTimestamp: ((new Date(attributes.endTime).getTime() < 0) ? null : attributes.endTime),
largeImageKey: (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider',
largeImageText: attributes.albumName,
instance: false, // Whether the activity is in a game session
buttons: [
{label: "Listen on Cider", url: attributes.url.cider},
{label: "View on Apple Music", url: attributes.url.appleMusic},
]
};
// Checks if the name is greater than 128 because some songs can be that long
if (this._activity.details && this._activity.details.length > 128) {
this._activity.details = this._activity.details.substring(0, 125) + '...'
}
// Check if its pausing (false) or playing (true)
if (!attributes.status) {
if (DiscordRichPresence._store.general.discord_rpc_clear_on_pause) {
this._client.clearActivity()
.catch((e: any) => console.error(`[DiscordRichPresence][clearActivity] ${e}`));
} else {
this._activity.smallImageKey = 'pause';
this._activity.smallImageText = 'Paused';
delete this._activity.endTimestamp;
delete this._activity.startTimestamp;
this._client.setActivity(this._activity)
.catch((e: any) => console.error(`[DiscordRichPresence][setActivity] ${e}`));
}
} else if (this._activity && this._activityCache !== this._activity && this._activity.details) {
if (!DiscordRichPresence._store.general.discord_rpc_clear_on_pause) {
this._activity.smallImageKey = 'play';
this._activity.smallImageText = 'Playing';
}
this._client.setActivity(this._activity)
.catch((e: any) => console.error(`[DiscordRichPresence][updateActivity] ${e}`));
this._activityCache = this._activity;
}
}
/*******************************************************************************************
* Public Methods
* ****************************************************************************************/
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(_app: any, store: any) {
DiscordRichPresence._store = store
console.debug(`[Plugin][${this.name}] Loading Complete.`);
}
/**
* Runs on app ready
*/
onReady(_win: any): void {
this.connect((DiscordRichPresence._store.general.discord_rpc == 1) ? '911790844204437504' : '886578863147192350');
console.debug(`[Plugin][${this.name}] Ready.`);
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
console.debug(`[Plugin][${this.name}] Stopped.`);
}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.state = current state)
*/
onPlaybackStateDidChange(attributes: object): void {
this.updateActivity(attributes)
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
this.updateActivity(attributes)
}
}

253
src/main/plugins/lastfm.ts Normal file
View file

@ -0,0 +1,253 @@
import * as electron from 'electron';
import * as fs from 'fs';
import {resolve} from 'path';
export default class LastFMPlugin {
private sessionPath = resolve(electron.app.getPath('userData'), 'session.json');
private apiCredentials = {
key: "f9986d12aab5a0fe66193c559435ede3",
secret: "acba3c29bd5973efa38cc2f0b63cc625"
}
/**
* Private variables for interaction in plugins
*/
private _win: any;
private _app: any;
private _lastfm: any;
private _store: any;
private authenticateFromFile() {
let sessionData = require(this.sessionPath)
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
this._lastfm.setSessionCredentials(sessionData.username, sessionData.key)
console.log("[LastFM][authenticateFromFile] Logged in.", sessionData.username, sessionData.key)
}
authenticate() {
try {
if (this._store.lastfm.auth_token) {
this._store.lastfm.enabled = true;
}
if (!this._store.lastfm.enabled || !this._store.lastfm.auth_token) {
this._store.lastfm.enabled = false;
return
}
/// dont move this require to top , app wont load
const LastfmAPI = require('lastfmapi');
const lfmAPI = new LastfmAPI({
'api_key': this.apiCredentials.key,
'secret': this.apiCredentials.secret
});
this._lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
fs.stat(this.sessionPath, (err: any) => {
if (err) {
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
console.log("[LastFM][Auth] Beginning authentication from configuration")
console.log("[LastFM][tk]", this._store.lastfm.auth_token)
this._lastfm.authenticate(this._store.lastfm.auth_token, (err: any, session: any) => {
if (err) {
throw err;
}
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
console.log("[LastFM] Saving session info to disk.")
let tempData = JSON.stringify(session)
fs.writeFile(this.sessionPath, tempData, (err: any) => {
if (err)
console.log("[LastFM][fs]", err)
else {
console.log("[LastFM][fs] File was written successfully.")
this.authenticateFromFile()
new electron.Notification({
title: electron.app.getName(),
body: "Successfully logged into LastFM using Authentication Key."
}).show()
}
})
});
} else {
this.authenticateFromFile()
}
})
} catch (err) {
console.log(err)
}
}
private async scrobbleSong(attributes: any) {
await new Promise(resolve => setTimeout(resolve, Math.round(attributes.durationInMillis * (this._store.lastfm.scrobble_after / 100))));
const currentAttributes = attributes;
if (!this._lastfm || this._lastfm.cachedAttributes === attributes) {
return
}
if (this._lastfm.cachedAttributes) {
if (this._lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
}
if (currentAttributes.status && currentAttributes === attributes) {
if (fs.existsSync(this.sessionPath)) {
// Scrobble playing song.
if (attributes.status === true) {
this._lastfm.track.scrobble({
'artist': this.filterArtistName(attributes.artistName),
'track': attributes.name,
'album': attributes.albumName,
'albumArtist': this.filterArtistName(attributes.artistName),
'timestamp': new Date().getTime() / 1000
}, function (err: any, scrobbled: any) {
if (err) {
return console.error('[LastFM] An error occurred while scrobbling', err);
}
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
});
this._lastfm.cachedAttributes = attributes
}
} else {
this.authenticate();
}
} else {
return console.log('[LastFM] Did not add ', attributes.name, '—', this.filterArtistName(attributes.artistName), 'because now playing a other song.');
}
}
private filterArtistName(artist: any) {
if (!this._store.lastfm.enabledRemoveFeaturingArtists) return artist;
artist = artist.split(' ');
if (artist.includes('&')) {
artist.length = artist.indexOf('&');
}
if (artist.includes('and')) {
artist.length = artist.indexOf('and');
}
artist = artist.join(' ');
if (artist.includes(',')) {
artist = artist.split(',')
artist = artist[0]
}
return artist.charAt(0).toUpperCase() + artist.slice(1);
}
private updateNowPlayingSong(attributes: any) {
if (!this._lastfm || this._lastfm.cachedNowPlayingAttributes === attributes || !this._store.lastfm.NowPlaying) {
return
}
if (this._lastfm.cachedNowPlayingAttributes) {
if (this._lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
}
if (fs.existsSync(this.sessionPath)) {
// update Now Playing
if (attributes.status === true) {
this._lastfm.track.updateNowPlaying({
'artist': this.filterArtistName(attributes.artistName),
'track': attributes.name,
'album': attributes.albumName,
'albumArtist': this.filterArtistName(attributes.artistName)
}, function (err: any, nowPlaying: any) {
if (err) {
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
}
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
});
this._lastfm.cachedNowPlayingAttributes = attributes
}
} else {
this.authenticate()
}
}
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'LastFMPlugin';
public description: string = 'LastFM plugin for Cider';
public version: string = '0.0.1';
public author: string = 'vapormusic / Cider Collective';
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(app: any, store: any) {
this._app = app;
this._store = store
electron.app.on('second-instance', (_e: any, argv: any) => {
// Checks if first instance is authorized and if second instance has protocol args
argv.forEach((value: any) => {
if (value.includes('auth')) {
console.log('[LastFMPlugin ok]')
let authURI = String(argv).split('/auth/')[1];
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1];
this._store.lastfm.enabled = true;
this._store.lastfm.auth_token = authKey;
console.log(authKey);
this._win.webContents.send('LastfmAuthenticated', authKey);
this.authenticate();
}
}
})
})
electron.app.on('open-url', (event: any, arg: any) => {
console.log('[LastFMPlugin] yes')
event.preventDefault();
if (arg.includes('auth')) {
let authURI = String(arg).split('/auth/')[1];
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1];
this._store.lastfm.enabled = true;
this._store.lastfm.auth_token = authKey;
this._win.webContents.send('LastfmAuthenticated', authKey);
console.log(authKey);
this.authenticate();
}
}
})
}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
this.authenticate();
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
console.log('Example plugin stopped');
}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.state = current state)
*/
onPlaybackStateDidChange(attributes: object): void {
this.scrobbleSong(attributes)
this.updateNowPlayingSong(attributes)
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
if (!this._store.lastfm.filterLoop){
this._lastfm.cachedNowPlayingAttributes = false;
this._lastfm.cachedAttributes = false}
this.scrobbleSong(attributes)
this.updateNowPlayingSong(attributes)
}
}

View file

@ -0,0 +1,162 @@
import * as electron from 'electron';
import * as path from 'path';
export default class MinimizeToTray {
/**
* Private variables for interaction in plugins
*/
private _win: any;
private _app: any;
private _store: any;
private _tray: any;
private _forceQuit = false;
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'Minimize to tray';
public description: string = 'Allow Cider to minimize to tray';
public version: string = '1.0.0';
public author: string = 'vapormusic';
constructor(app: any, store: any) {
this._app = app;
this._store = store;
}
private SetContextMenu(visibility : any) {
let self = this
if (visibility) {
this._tray.setContextMenu(electron.Menu.buildFromTemplate([
// {
// label: 'Check for Updates',
// click: function () {
// app.ame.utils.checkForUpdates(true)
// }
// },
{
label: 'Minimize to Tray',
click: function () {
if (typeof self._win.hide === 'function') {
self._win.hide();
self.SetContextMenu(false);
}
}
},
{
label: 'Quit',
click: function () {
self._forceQuit = true; self._app.quit();
}
}
]));
} else {
this._tray.setContextMenu(electron.Menu.buildFromTemplate([
// {
// label: 'Check for Updates',
// click: function () {
// this._app.ame.utils.checkForUpdates(true)
// }
// },
{
label: `Show ${electron.app.getName()}`,
click: function () {
if (typeof self._win.show === 'function') {
self._win.show();
self.SetContextMenu(true);
}
}
},
{
label: 'Quit',
click: function () {
self._forceQuit = true; self._app.quit();
}
}
]));
}
return true
}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
const winTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({
width: 32,
height: 32
})
const macTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
width: 20,
height: 20
})
const linuxTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
width: 32,
height: 32
})
let trayIcon : any ;
if (process.platform === "win32") {
trayIcon = winTray
} else if (process.platform === "linux") {
trayIcon = linuxTray
} else if (process.platform === "darwin") {
trayIcon = macTray
}
this._tray = new electron.Tray(trayIcon)
this._tray.setToolTip(this._app.getName());
this.SetContextMenu(true);
this._tray.on('double-click', () => {
if (typeof this._win.show === 'function') {
if (this._win.isVisible()) {
this._win.focus()
} else {
this._win.show()
}
}
})
electron.ipcMain.on("minimizeTray", (event, value) => {
// listen for close event
this._win.hide();
this.SetContextMenu(false);
});
this._win.on("close", (e :any) => {
if (this._forceQuit || this._store.general["close_behavior"] == '0' ) {
this._app.quit();
} else if (this._store.general["close_behavior"] == '1') {
e.preventDefault();
this._win.minimize();
} else {
e.preventDefault();
this._win.hide();
this.SetContextMenu(false);
}
});
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.state = current state)
*/
onPlaybackStateDidChange(attributes: object): void {
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
}
}

196
src/main/plugins/mpris.ts Normal file
View file

@ -0,0 +1,196 @@
// @ts-ignore
import * as Player from 'mpris-service';
export default class MPRIS {
/**
* Private variables for interaction in plugins
*/
private _win: any;
private _app: any;
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'MPRIS Service';
public description: string = 'Handles MPRIS service calls for Linux systems.';
public version: string = '1.0.0';
public author: string = 'Core';
/**
* MPRIS Service
*/
private mpris: any;
private mprisEvents: Object = {
"playpause": "pausePlay",
"play": "pausePlay",
"pause": "pausePlay",
"next": "nextTrack",
"previous": "previousTrack",
}
/*******************************************************************************************
* Private Methods
* ****************************************************************************************/
/**
* Runs a media event
* @param type - pausePlay, nextTrack, PreviousTrack
* @private
*/
private runMediaEvent(type: string) {
if (this._win) {
this._win.webContents.executeJavaScript(`MusicKitInterop.${type}()`).catch(console.error)
}
}
/**
* Blocks non-linux systems from running this plugin
* @private
* @decorator
*/
private static linuxOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
if (process.platform !== 'linux') {
descriptor.value = function () {
return
}
}
}
/**
* Connects to MPRIS Service
*/
@MPRIS.linuxOnly
private connect() {
this.mpris = Player({
name: 'Cider',
identity: 'Cider',
supportedUriSchemes: [],
supportedMimeTypes: [],
supportedInterfaces: ['player']
});
this.mpris = Object.assign(this.mpris, {
canQuit: true,
canControl: true,
canPause: true,
canPlay: true,
canGoNext: true,
active: true
})
const pos_atr = {durationInMillis: 0};
this.mpris.getPosition = function () {
const durationInMicro = pos_atr.durationInMillis * 1000;
const percentage = parseFloat("0") || 0;
return durationInMicro * percentage;
}
for (const [key, value] of Object.entries(this.mprisEvents)) {
this.mpris.on(key, () => {
this.runMediaEvent(value)
});
}
}
/**
* Update MPRIS Player Attributes
*/
@MPRIS.linuxOnly
private updatePlayer(attributes: any) {
const MetaData = {
'mpris:trackid': this.mpris.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
'mpris:length': attributes.durationInMillis * 1000, // In microseconds
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
'xesam:title': `${attributes.name}`,
'xesam:album': `${attributes.albumName}`,
'xesam:artist': [`${attributes.artistName}`,],
'xesam:genre': attributes.genreNames
}
if (this.mpris.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) {
return
}
this.mpris.metadata = MetaData
}
/**
* Update MPRIS Player State
* @private
* @param attributes
*/
@MPRIS.linuxOnly
private updatePlayerState(attributes: any) {
let status = 'Stopped';
if (attributes.status) {
status = 'Playing';
} else if (attributes.status === false) {
status = 'Paused';
}
if (this.mpris.playbackStatus === status) {
return
}
this.mpris.playbackStatus = status;
}
/**
* Clear state
* @private
*/
private clearState() {
this.mpris.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'}
this.mpris.playbackStatus = 'Stopped';
}
/*******************************************************************************************
* Public Methods
* ****************************************************************************************/
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(app: any, _store: any) {
this._app = app;
console.debug(`[Plugin][${this.name}] Loading Complete.`);
}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
console.debug(`[Plugin][${this.name}] Ready.`);
this.connect()
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
console.debug(`[Plugin][${this.name}] Stopped.`);
this.clearState()
}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.state = current state)
*/
onPlaybackStateDidChange(attributes: object): void {
this.updatePlayerState(attributes)
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
this.updatePlayer(attributes);
}
}

View file

@ -1,102 +1,113 @@
const electron = require('electron') global.ipcRenderer = require('electron').ipcRenderer;
console.log('Loaded Preload') console.log('Loaded Preload')
let cache = {playParams: {id: 0}, status: null, remainingTime: 0}, let cache = {playParams: {id: 0}, status: null, remainingTime: 0},
playbackCache = {status: null, time: Date.now()}; playbackCache = {status: null, time: Date.now()};
const MusicKitInterop = { const MusicKitInterop = {
init: function () { init: function () {
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => { MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => {
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), true, false)) { if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), true, false)) {
console.log("ayy"); global.ipcRenderer.send('playbackStateDidChange', MusicKitInterop.getAttributes())
global.ipcRenderer.send('playbackStateDidChange', MusicKitInterop.getAttributes()) ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
// if (typeof _plugins != "undefined") { // if (typeof _plugins != "undefined") {
// _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()}) // _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()})
// } // }
} }
}); });
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, () => { /** wsapi */
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), false, true)) { MusicKit.getInstance().addEventListener(MusicKit.Events.playbackProgressDidChange, () => {
global.ipcRenderer.send('nowPlayingItemDidChange', MusicKitInterop.getAttributes()); ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
} });
}); /** wsapi */
MusicKit.getInstance().addEventListener(MusicKit.Events.authorizationStatusDidChange, () => { MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, () => {
global.ipcRenderer.send('authorizationStatusDidChange', MusicKit.getInstance().authorizationStatus) if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), false, true) || !app.cfg.lastfm.filterLoop) {
}) global.ipcRenderer.send('nowPlayingItemDidChange', MusicKitInterop.getAttributes());
}
});
MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => { MusicKit.getInstance().addEventListener(MusicKit.Events.authorizationStatusDidChange, () => {
console.warn(`[mediaPlaybackError] ${e}`); global.ipcRenderer.send('authorizationStatusDidChange', MusicKit.getInstance().authorizationStatus)
}) })
},
getAttributes: function () { MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => {
const nowPlayingItem = MusicKit.getInstance().nowPlayingItem; console.warn(`[mediaPlaybackError] ${e}`);
const isPlayingExport = MusicKit.getInstance().isPlaying; })
const remainingTimeExport = MusicKit.getInstance().currentPlaybackTimeRemaining; },
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
attributes.status = isPlayingExport ?? false; getAttributes: function () {
attributes.name = attributes?.name ?? 'No Title Found'; const mk = MusicKit.getInstance()
attributes.artwork = attributes?.artwork ?? { url: '' }; const nowPlayingItem = mk.nowPlayingItem;
attributes.artwork.url = attributes?.artwork?.url ?? ''; const isPlayingExport = mk.isPlaying;
attributes.playParams = attributes?.playParams ?? { id: 'no-id-found' }; const remainingTimeExport = mk.currentPlaybackTimeRemaining;
attributes.playParams.id = attributes?.playParams?.id ?? 'no-id-found'; const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
attributes.albumName = attributes?.albumName ?? '';
attributes.artistName = attributes?.artistName ?? '';
attributes.genreNames = attributes?.genreNames ?? [];
attributes.remainingTime = remainingTimeExport
? remainingTimeExport * 1000
: 0;
attributes.durationInMillis = attributes?.durationInMillis ?? 0;
attributes.startTime = Date.now();
attributes.endTime = Math.round(
attributes?.playParams?.id === cache.playParams.id
? Date.now() + attributes?.remainingTime
: attributes?.startTime + attributes?.durationInMillis
);
return attributes; attributes.status = isPlayingExport ?? false;
}, attributes.name = attributes?.name ?? 'No Title Found';
attributes.artwork = attributes?.artwork ?? {url: ''};
attributes.artwork.url = (attributes?.artwork?.url ?? '').replace(`{f}`, "png");
attributes.playParams = attributes?.playParams ?? {id: 'no-id-found'};
attributes.playParams.id = attributes?.playParams?.id ?? 'no-id-found';
attributes.url = {
cider: "cider://play/s/" + nowPlayingItem?._songId ?? 'no-id-found',
appleMusic: "https://music.apple.com/"+ mk.storefrontId +"/song/" + nowPlayingItem?._songId ?? 'no-id-found'
}
if (attributes.playParams.id === 'no-id-found') {
attributes.playParams.id = nowPlayingItem?.id ?? 'no-id-found';
}
attributes.albumName = attributes?.albumName ?? '';
attributes.artistName = attributes?.artistName ?? '';
attributes.genreNames = attributes?.genreNames ?? [];
attributes.remainingTime = remainingTimeExport
? remainingTimeExport * 1000
: 0;
attributes.durationInMillis = attributes?.durationInMillis ?? 0;
attributes.startTime = Date.now();
attributes.endTime = Math.round(
attributes?.playParams?.id === cache.playParams.id
? Date.now() + attributes?.remainingTime
: attributes?.startTime + attributes?.durationInMillis
);
return attributes;
},
filterTrack: function (a, playbackCheck, mediaCheck) { filterTrack: function (a, playbackCheck, mediaCheck) {
if (a.title === "No Title Found" || a.playParams.id === "no-id-found") { if (a.title === "No Title Found" || a.playParams.id === "no-id-found") {
return; return;
} else if (mediaCheck && a.playParams.id === cache.playParams.id) { } else if (mediaCheck && a.playParams.id === cache.playParams.id) {
return; return;
} else if (playbackCheck && a.status === playbackCache.status) { } else if (playbackCheck && a.status === playbackCache.status) {
return; return;
} else if (playbackCheck && !a.status && a.remainingTime === playbackCache.time) { /* Pretty much have to do this to prevent multiple runs when a song starts playing */ } else if (playbackCheck && !a.status && a.remainingTime === playbackCache.time) { /* Pretty much have to do this to prevent multiple runs when a song starts playing */
return; return;
} }
cache = a; cache = a;
if (playbackCheck) playbackCache = {status: a.status, time: a.remainingTime}; if (playbackCheck) playbackCache = {status: a.status, time: a.remainingTime};
return true; return true;
}, },
pausePlay: function () { pausePlay: function () {
if (MusicKit.getInstance().isPlaying) { if (MusicKit.getInstance().isPlaying) {
MusicKit.getInstance().pause(); MusicKit.getInstance().pause();
} else if (MusicKit.getInstance().nowPlayingItem != null) { } else if (MusicKit.getInstance().nowPlayingItem != null) {
MusicKit.getInstance().play().then(r => console.log(`[MusicKitInterop] Playing ${r}`)); MusicKit.getInstance().play().then(r => console.log(`[MusicKitInterop] Playing ${r}`));
} }
}, },
nextTrack: function () { nextTrack: function () {
MusicKit.getInstance().skipToNextItem().then(r => console.log(`[MusicKitInterop] Skipping to Next ${r}`)); MusicKit.getInstance().skipToNextItem().then(r => console.log(`[MusicKitInterop] Skipping to Next ${r}`));
}, },
previousTrack: function () { previousTrack: function () {
MusicKit.getInstance().skipToPreviousItem().then(r => console.log(`[MusicKitInterop] Skipping to Previous ${r}`)); MusicKit.getInstance().skipToPreviousItem().then(r => console.log(`[MusicKitInterop] Skipping to Previous ${r}`));
} }
} }
process.once('loaded', () => { process.once('loaded', () => {
console.log("Setting ipcRenderer") console.log("Setting ipcRenderer")
global.ipcRenderer = electron.ipcRenderer; global.MusicKitInterop = MusicKitInterop;
global.MusicKitInterop = MusicKitInterop;
}); });

View file

@ -0,0 +1,5 @@
{
"js": {
"beautify.ignore": "src/renderer/index.js"
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -27,6 +27,7 @@
.md-option-segment.md-option-segment_auto { .md-option-segment.md-option-segment_auto {
width: auto; width: auto;
white-space: nowrap;
} }
.md-option-container .md-option-line:not(:last-child) { .md-option-container .md-option-line:not(:last-child) {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mic"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>

After

Width:  |  Height:  |  Size: 418 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 344 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-video"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg>

After

Width:  |  Height:  |  Size: 329 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>

After

Width:  |  Height:  |  Size: 354 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon></svg>

After

Width:  |  Height:  |  Size: 275 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" fill="white"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M471.1 96C405 96 353.3 137.3 320 174.6 286.7 137.3 235 96 168.9 96 75.8 96 0 167.8 0 256s75.8 160 168.9 160c66.1 0 117.8-41.3 151.1-78.6 33.3 37.3 85 78.6 151.1 78.6 93.1 0 168.9-71.8 168.9-160S564.2 96 471.1 96zM168.9 320c-40.2 0-72.9-28.7-72.9-64s32.7-64 72.9-64c38.2 0 73.4 36.1 94 64-20.4 27.6-55.9 64-94 64zm302.2 0c-38.2 0-73.4-36.1-94-64 20.4-27.6 55.9-64 94-64 40.2 0 72.9 28.7 72.9 64s-32.7 64-72.9 64z"/></svg>

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
fill="white" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path id="XMLID_11_" d="M418.5,139.4H232.4v139.8h186.1V139.4z M464.8,46.7H46.3C20.5,46.7,0,68.1,0,93.1v325.9
c0,25.8,21.4,46.3,46.3,46.3h419.4c25.8,0,46.3-20.5,46.3-46.3V93.1C512,67.2,490.6,46.7,464.8,46.7z M464.8,418.9H46.3V92.2h419.4
v326.8H464.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 627 B

View file

@ -0,0 +1,33 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill="white" version="1.1">
<g>
<title>Layer 1</title>
<g id="svg_1">
<g id="svg_2">
<g id="svg_3">
<path id="svg_4" d="m482.197,374.266l-78.717,-45.448c-15.89,-9.174 -35.829,2.308 -35.829,20.686l0,27.587l-216.593,0c-63.597,-0.001 -115.337,-51.74 -115.337,-115.338c0,-9.864 -7.997,-17.86 -17.86,-17.86c-9.864,0 -17.86,7.997 -17.86,17.86c0,83.294 67.765,151.058 151.058,151.058l216.591,0l0,27.587c0,18.347 19.913,29.876 35.829,20.686l78.717,-45.447c15.89,-9.172 15.917,-32.181 0.001,-41.371z"/>
<path id="svg_5" d="m360.942,99.189l-216.593,0l0,-27.588c0,-18.347 -19.913,-29.876 -35.829,-20.686l-78.717,45.447c-15.889,9.173 -15.917,32.182 0,41.372l78.717,45.448c15.89,9.174 35.829,-2.309 35.829,-20.686l0,-27.587l216.593,0c63.598,0 115.337,51.739 115.337,115.337c0,9.864 7.997,17.86 17.86,17.86c9.864,0 17.86,-7.997 17.86,-17.86c0.001,-83.293 -67.764,-151.057 -151.057,-151.057z"/>
</g>
</g>
</g>
<g id="svg_6"/>
<g id="svg_7"/>
<g id="svg_8"/>
<g id="svg_9"/>
<g id="svg_10"/>
<g id="svg_11"/>
<g id="svg_12"/>
<g id="svg_13"/>
<g id="svg_14"/>
<g id="svg_15"/>
<g id="svg_16"/>
<g id="svg_17"/>
<g id="svg_18"/>
<g id="svg_19"/>
<g id="svg_20"/>
<g id="svg_24">
<path id="svg_21" d="m118,511.5c-65.19337,0 -118,-52.80663 -118,-118c0,-65.19337 52.80663,-118 118,-118c65.19337,0 118,52.80663 118,118c0,65.19337 -52.80663,118 -118,118z" opacity="undefined" stroke-width="0" stroke="#000" fill="#fff"/>
<text font-weight="bold" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="250" id="svg_23" y="470" x="54.64063" stroke-width="0" stroke="#000" fill="#000000">1</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -5,6 +5,8 @@ var CiderAudio = {
gainNode : null, gainNode : null,
spatialNode : null, spatialNode : null,
spatialInput: null, spatialInput: null,
audioBands : null,
preampNode : null,
}, },
init: function (cb = function () { }) { init: function (cb = function () { }) {
//AudioOutputs.fInit = true; //AudioOutputs.fInit = true;
@ -21,8 +23,14 @@ var CiderAudio = {
}, },
off: function(){ off: function(){
try{ try{
CiderAudio.audioNodes.gainNode.disconnect(); try{
CiderAudio.audioNodes.spatialNode.disconnect(); CiderAudio.audioNodes.gainNode.disconnect(); } catch(e){}
try{ CiderAudio.audioNodes.spatialNode.disconnect();} catch(e){}
try{
CiderAudio.audioNodes.preampNode.disconnect();
CiderAudio.audioNodes.audioBands[0].disconnect();
CiderAudio.audioNodes.audioBands[9].disconnect();
} catch(e){}
CiderAudio.source.connect(CiderAudio.context.destination);} catch(e){} CiderAudio.source.connect(CiderAudio.context.destination);} catch(e){}
}, },
connectContext: function (mediaElem){ connectContext: function (mediaElem){
@ -42,6 +50,7 @@ var CiderAudio = {
if (app.cfg.audio.spatial){ if (app.cfg.audio.spatial){
CiderAudio.spatialOn() CiderAudio.spatialOn()
} }
CiderAudio.equalizer()
}, },
normalizerOn: function (){}, normalizerOn: function (){},
normalizerOff: function (){ normalizerOff: function (){
@ -49,7 +58,7 @@ var CiderAudio = {
}, },
spatialOn: function (){ spatialOn: function (){
try{ try{
CiderAudio.audioNodes.gainNode.connect(CiderAudio.context.destination);} catch(e){} CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.context.destination);} catch(e){}
CiderAudio.audioNodes.spatialNode = new ResonanceAudio(CiderAudio.context); CiderAudio.audioNodes.spatialNode = new ResonanceAudio(CiderAudio.context);
CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.context.destination); CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.context.destination);
let roomDimensions = { let roomDimensions = {
@ -90,6 +99,42 @@ var CiderAudio = {
} }
); );
} }
},
equalizer: function (){
let BANDS = app.cfg.audio.equalizer.frequencies;
let GAIN = app.cfg.audio.equalizer.gain;
let Q = app.cfg.audio.equalizer.Q;
CiderAudio.audioNodes.audioBands = [];
for (i = 0; i < BANDS.length; i++) {
CiderAudio.audioNodes.audioBands[i] = CiderAudio.context.createBiquadFilter();
CiderAudio.audioNodes.audioBands[i].type = 'peaking'; // 'peaking';
CiderAudio.audioNodes.audioBands[i].frequency.value = BANDS[i];
CiderAudio.audioNodes.audioBands[i].Q.value = Q[i];
CiderAudio.audioNodes.audioBands[i].gain.value = GAIN[i] * app.cfg.audio.equalizer.mix;
}
CiderAudio.audioNodes.preampNode = CiderAudio.context.createBiquadFilter();
CiderAudio.audioNodes.preampNode.type = 'highshelf';
CiderAudio.audioNodes.preampNode.frequency.value = 0; // allow all
CiderAudio.audioNodes.preampNode.gain.value = app.cfg.audio.equalizer.preamp;
if (app.cfg.audio.spatial) {
try{
CiderAudio.audioNodes.spatialNode.output.disconnect(CiderAudio.context.destination); } catch(e){}
CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.audioNodes.preampNode);
} else {
try{
CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.context.destination);} catch(e){}
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.preampNode);
}
CiderAudio.audioNodes.preampNode.connect(CiderAudio.audioNodes.audioBands[0]);
for (i = 1; i < BANDS.length; i ++) {
CiderAudio.audioNodes.audioBands[i-1].connect(CiderAudio.audioNodes.audioBands[i]);
}
CiderAudio.audioNodes.audioBands[BANDS.length-1].connect(CiderAudio.context.destination);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
const wsapi = {
cache: {playParams: {id: 0}, status: null, remainingTime: 0},
playbackCache: {status: null, time: Date.now()},
search(term, limit) {
MusicKit.getInstance().api.search(term, {limit: limit, types: 'songs,artists,albums,playlists'}).then((results)=>{
ipcRenderer.send('wsapi-returnSearch', JSON.stringify(results))
})
},
searchLibrary(term, limit) {
MusicKit.getInstance().api.library.search(term, {limit: limit, types: 'library-songs,library-artists,library-albums,library-playlists'}).then((results)=>{
ipcRenderer.send('wsapi-returnSearchLibrary', JSON.stringify(results))
})
},
getAttributes: function () {
const mk = MusicKit.getInstance();
const nowPlayingItem = mk.nowPlayingItem;
const isPlayingExport = mk.isPlaying;
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
attributes.status = isPlayingExport ? isPlayingExport : false;
attributes.name = attributes.name ? attributes.name : 'No Title Found';
attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''};
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : '';
attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'};
attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : 'no-id-found';
attributes.albumName = attributes.albumName ? attributes.albumName : '';
attributes.artistName = attributes.artistName ? attributes.artistName : '';
attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0;
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
attributes.startTime = Date.now();
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
attributes.volume = mk.volume;
attributes.shuffleMode = mk.shuffleMode;
attributes.repeatMode = mk.repeatMode;
attributes.autoplayEnabled = mk.autoplayEnabled;
return attributes
},
moveQueueItem(oldPosition, newPosition) {
MusicKit.getInstance().queue._queueItems.splice(newPosition,0,MusicKit.getInstance().queue._queueItems.splice(oldPosition,1)[0])
MusicKit.getInstance().queue._reindex()
},
setAutoplay(value) {
MusicKit.getInstance().autoplayEnabled = value
},
returnDynamic(data, type) {
ipcRenderer.send('wsapi-returnDynamic', JSON.stringify(data), type)
},
musickitApi(method, id, params, library = false) {
if (library) {
MusicKit.getInstance().api.library[method](id, params).then((results)=>{
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
})
} else {
MusicKit.getInstance().api[method](id, params).then((results)=>{
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
})
}
},
getPlaybackState () {
ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
},
getLyrics() {
ipcRenderer.send('wsapi-returnLyrics',JSON.stringify(app.lyrics));
},
getQueue() {
ipcRenderer.send('wsapi-returnQueue', JSON.stringify(MusicKit.getInstance().queue))
},
playNext(type, id) {
var request = {}
request[type] = id
MusicKit.getInstance().playNext(request)
},
playLater(type, id) {
var request = {}
request[type] = id
MusicKit.getInstance().playLater(request)
},
love() {
},
playTrackById(id, kind = "song") {
MusicKit.getInstance().setQueue({ [kind]: id }).then(function (queue) {
MusicKit.getInstance().play()
})
},
quickPlay(term) {
// Quick play by song name
MusicKit.getInstance().api.search(term, { limit: 2, types: 'songs' }).then(function (data) {
MusicKit.getInstance().setQueue({ song: data["songs"][0]["id"] }).then(function (queue) {
MusicKit.getInstance().play()
})
})
},
toggleShuffle() {
MusicKit.getInstance().shuffleMode = MusicKit.getInstance().shuffleMode === 0 ? 1 : 0
},
toggleRepeat() {
if(MusicKit.getInstance().repeatMode == 0) {
MusicKit.getInstance().repeatMode = 1
}else if(MusicKit.getInstance().repeatMode == 1){
MusicKit.getInstance().repeatMode = 2
}else{
MusicKit.getInstance().repeatMode = 0
}
}
}

1
src/renderer/js/bootbox.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
src/renderer/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
src/renderer/js/notyf.min.js vendored Normal file

File diff suppressed because one or more lines are too long

5
src/renderer/js/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1107
src/renderer/less/bootstrap.less vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,7 @@
} }
@media (max-width: 951px) { @media (max-width: 951px) {
.content-inner { #app-content {
zoom: 0.8; zoom: 0.8;
} }
} }
@ -53,7 +53,7 @@
// if page width is less than 951px // if page width is less than 951px
@media (max-width: 951px) { @media (max-width: 951px) {
.content-inner { #app-content {
zoom: 0.8; zoom: 0.8;
} }
} }

View file

@ -0,0 +1,370 @@
@-webkit-keyframes notyf-fadeinup {
0% {
opacity: 0;
transform: translateY(25%)
}
to {
opacity: 1;
transform: translateY(0)
}
}
@keyframes notyf-fadeinup {
0% {
opacity: 0;
transform: translateY(25%)
}
to {
opacity: 1;
transform: translateY(0)
}
}
@-webkit-keyframes notyf-fadeinleft {
0% {
opacity: 0;
transform: translateX(25%)
}
to {
opacity: 1;
transform: translateX(0)
}
}
@keyframes notyf-fadeinleft {
0% {
opacity: 0;
transform: translateX(25%)
}
to {
opacity: 1;
transform: translateX(0)
}
}
@-webkit-keyframes notyf-fadeoutright {
0% {
opacity: 1;
transform: translateX(0)
}
to {
opacity: 0;
transform: translateX(25%)
}
}
@keyframes notyf-fadeoutright {
0% {
opacity: 1;
transform: translateX(0)
}
to {
opacity: 0;
transform: translateX(25%)
}
}
@-webkit-keyframes notyf-fadeoutdown {
0% {
opacity: 1;
transform: translateY(0)
}
to {
opacity: 0;
transform: translateY(25%)
}
}
@keyframes notyf-fadeoutdown {
0% {
opacity: 1;
transform: translateY(0)
}
to {
opacity: 0;
transform: translateY(25%)
}
}
@-webkit-keyframes ripple {
0% {
transform: scale(0) translateY(-45%) translateX(13%)
}
to {
transform: scale(1) translateY(-45%) translateX(13%)
}
}
@keyframes ripple {
0% {
transform: scale(0) translateY(-45%) translateX(13%)
}
to {
transform: scale(1) translateY(-45%) translateX(13%)
}
}
.notyf {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
color: #fff;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
pointer-events: none;
box-sizing: border-box;
padding: 20px
}
.notyf__icon--error,
.notyf__icon--success {
height: 21px;
width: 21px;
background: #fff;
border-radius: 50%;
display: block;
margin: 0 auto;
position: relative
}
.notyf__icon--error:after,
.notyf__icon--error:before {
content: "";
background: currentColor;
display: block;
position: absolute;
width: 3px;
border-radius: 3px;
left: 9px;
height: 12px;
top: 5px
}
.notyf__icon--error:after {
transform: rotate(-45deg)
}
.notyf__icon--error:before {
transform: rotate(45deg)
}
.notyf__icon--success:after,
.notyf__icon--success:before {
content: "";
background: currentColor;
display: block;
position: absolute;
width: 3px;
border-radius: 3px
}
.notyf__icon--success:after {
height: 6px;
transform: rotate(-45deg);
top: 9px;
left: 6px
}
.notyf__icon--success:before {
height: 11px;
transform: rotate(45deg);
top: 5px;
left: 10px
}
.notyf__toast {
display: block;
overflow: hidden;
pointer-events: auto;
-webkit-animation: notyf-fadeinup .3s ease-in forwards;
animation: notyf-fadeinup .3s ease-in forwards;
box-shadow: 0 3px 7px 0 rgba(0, 0, 0, .25);
position: relative;
padding: 0 15px;
border-radius: 2px;
max-width: 300px;
transform: translateY(25%);
box-sizing: border-box;
flex-shrink: 0
}
.notyf__toast--disappear {
transform: translateY(0);
-webkit-animation: notyf-fadeoutdown .3s forwards;
animation: notyf-fadeoutdown .3s forwards;
-webkit-animation-delay: .25s;
animation-delay: .25s
}
.notyf__toast--disappear .notyf__icon,
.notyf__toast--disappear .notyf__message {
-webkit-animation: notyf-fadeoutdown .3s forwards;
animation: notyf-fadeoutdown .3s forwards;
opacity: 1;
transform: translateY(0)
}
.notyf__toast--disappear .notyf__dismiss {
-webkit-animation: notyf-fadeoutright .3s forwards;
animation: notyf-fadeoutright .3s forwards;
opacity: 1;
transform: translateX(0)
}
.notyf__toast--disappear .notyf__message {
-webkit-animation-delay: .05s;
animation-delay: .05s
}
.notyf__toast--upper {
margin-bottom: 20px
}
.notyf__toast--lower {
margin-top: 20px
}
.notyf__toast--dismissible .notyf__wrapper {
padding-right: 30px
}
.notyf__ripple {
height: 400px;
width: 400px;
position: absolute;
transform-origin: bottom right;
right: 0;
top: 0;
border-radius: 50%;
transform: scale(0) translateY(-51%) translateX(13%);
z-index: 5;
-webkit-animation: ripple .4s ease-out forwards;
animation: ripple .4s ease-out forwards
}
.notyf__wrapper {
display: flex;
align-items: center;
padding-top: 17px;
padding-bottom: 17px;
padding-right: 15px;
border-radius: 3px;
position: relative;
z-index: 10
}
.notyf__icon {
width: 22px;
text-align: center;
font-size: 1.3em;
opacity: 0;
-webkit-animation: notyf-fadeinup .3s forwards;
animation: notyf-fadeinup .3s forwards;
-webkit-animation-delay: .3s;
animation-delay: .3s;
margin-right: 13px
}
.notyf__dismiss {
position: absolute;
top: 0;
right: 0;
height: 100%;
width: 26px;
margin-right: -15px;
-webkit-animation: notyf-fadeinleft .3s forwards;
animation: notyf-fadeinleft .3s forwards;
-webkit-animation-delay: .35s;
animation-delay: .35s;
opacity: 0
}
.notyf__dismiss-btn {
background-color: rgba(0, 0, 0, .25);
border: none;
cursor: pointer;
transition: opacity .2s ease, background-color .2s ease;
outline: none;
opacity: .35;
height: 100%;
width: 100%
}
.notyf__dismiss-btn:after,
.notyf__dismiss-btn:before {
content: "";
background: #fff;
height: 12px;
width: 2px;
border-radius: 3px;
position: absolute;
left: calc(50% - 1px);
top: calc(50% - 5px)
}
.notyf__dismiss-btn:after {
transform: rotate(-45deg)
}
.notyf__dismiss-btn:before {
transform: rotate(45deg)
}
.notyf__dismiss-btn:hover {
opacity: .7;
background-color: rgba(0, 0, 0, .15)
}
.notyf__dismiss-btn:active {
opacity: .8
}
.notyf__message {
vertical-align: middle;
position: relative;
opacity: 0;
-webkit-animation: notyf-fadeinup .3s forwards;
animation: notyf-fadeinup .3s forwards;
-webkit-animation-delay: .25s;
animation-delay: .25s;
line-height: 1.5em
}
@media only screen and (max-width:480px) {
.notyf {
padding: 0
}
.notyf__ripple {
height: 600px;
width: 600px;
-webkit-animation-duration: .5s;
animation-duration: .5s
}
.notyf__toast {
max-width: none;
border-radius: 0;
box-shadow: 0 -2px 7px 0 rgba(0, 0, 0, .13);
width: 100%
}
.notyf__dismiss {
width: 56px
}
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
<script type="text/x-template" id="artwork-material">
<div class="artworkMaterial">
<img :src="src" v-for="image in images"/>
</div>
</script>
<script>
Vue.component('artwork-material', {
template: '#artwork-material',
data: function () {
return {
src: ""
}
},
mounted() {
this.src = app.getMediaItemArtwork(this.url, this.size)
},
props: {
url: {
type: String,
required: true
},
size: {
type: [String, Number],
required: false,
default: '32'
},
images: {
type: [String, Number],
required: false,
default: '2'
}
},
methods: {
}
});
</script>

View file

@ -0,0 +1,82 @@
<script type="text/x-template" id="add-to-playlist">
<template>
<div class="modal-fullscreen modal-generic" @click.self="app.resetState()" @contextmenu.self="app.resetState()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{app.getLz('action.addToLibrary')}}</div>
<button class="close-btn" @click="app.resetState()"></button>
</div>
<div class="modal-content">
<button class="playlist-item"
:class="{ focused: playlist.id == focused }"
@click="addToPlaylist(playlist.id)" style="width:100%;" v-for="playlist in playlistSorted" v-if="playlist.attributes.canEdit && playlist.type != 'library-playlist-folders'">
<div class="icon"><%- include("../svg/playlist.svg") %></div>
<div class="name">{{ playlist.attributes.name }}</div>
</button>
</div>
<div class="modal-search">
<div class="search-input-container" style="width:100%;margin: 16px 0;">
<div class="search-input--icon"></div>
<input type="search"
ref="searchInput"
style="width:100%;"
spellcheck="false"
:placeholder="app.getLz('term.search') + '...'"
v-model="searchQuery"
@input="search()"
class="search-input">
</div>
</div>
</div>
</div>
</template>
</script>
<script>
Vue.component('add-to-playlist', {
template: '#add-to-playlist',
data: function () {
return {
playlistSorted: [],
searchQuery: "",
focused: "",
app: this.$root,
}
},
props: {
playlists: {
type: Array,
required: true
}
},
mounted() {
this.search()
this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) {
if (this.focused != "") {
this.addToPlaylist(this.focused)
}
}
})
},
methods: {
addToPlaylist(id) {
app.addSelectedToPlaylist(id)
},
search() {
this.focused = ""
if (this.searchQuery == "") {
this.playlistSorted = this.playlists
} else {
this.playlistSorted = this.playlists.filter(playlist => {
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
if (this.playlistSorted.length == 1) {
this.focused = this.playlistSorted[0].id
}
}
},
}
});
</script>

Some files were not shown because too many files have changed in this diff Show more